//=====================================
//
//              Text Editor
//
//=====================================

include "inc/cmdsys.plh"
include "inc/args.plh"
include "inc/fileio.plh"
sysflags nojitc // Keep JITC from compiling and pausing while editing
//
// Hardware constants
//
const csw           = $0036
const pushbttn1     = $C061
const pushbttn2     = $C062
const pushbttn3     = $C063
const keyboard      = $C000
const keystrobe     = $C010
//
// ASCII key values
//
const keyenter      = $8D
const keyspace      = $A0
const keyarrowup    = $8B
const keyarrowdown  = $8A
const keyarrowleft  = $88
const keyarrowright = $95
const keyescape     = $9B
const keyctrla      = $81
const keyctrlb      = $82
const keyctrlc      = $83
const keyctrld      = $84
const keyctrle      = $85
const keyctrlf      = $86
const keyctrlg      = $87
const keyctrli      = $89
const keyctrlk      = $8B
const keyctrll      = $8C
const keyctrln      = $8E
const keyctrlo      = $8F
const keyctrlp      = $90
const keyctrlq      = $91
const keyctrlr      = $92
const keyctrls      = $93
const keyctrlt      = $94
const keyctrlu      = $95
const keyctrlv      = $96
const keyctrlw      = $97
const keyctrlx      = $98
const keyctrly      = $99
const keyctrlz      = $9A
const keytab        = keyctrli
const keydetab      = $9D
const keydelete     = $FF
//
// Data and text buffer constants
//
const MAXLINES      = 999
const MAXLINESSIZE  = MAXLINES+24
const MAXCLIPLINES  = 256
const MAXLNLEN      = 127
const pgjmp         = 16
const changed       = 1
const insmode       = 2
const gutter        = 4
const uppercase     = 8
const selection     = 16
const showcurs      = 32
const shiftlock     = 128
//
// Text screen row address array
//
const scrnheight  = 24
const scrnbottom  = 23
word scrnwidth    = 36
word scrnright    = 35
word txtscrn      = $0400,$0480,$0500,$0580,$0600,$0680,$0700,$0780
word              = $0428,$04A8,$0528,$05A8,$0628,$06A8,$0728,$07A8
word              = $0450,$04D0,$0550,$05D0,$0650,$06D0,$0750,$07D0
//
// Editor variables
//
byte nullstr       = ""
byte[80] findstr   = ""
byte[64] filename  = "UNTITLED"
byte exit          = FALSE
byte flags         = 0
byte flash         = 0
word numlines      = 0
word numcliplines  = 0
word arg
word strpool, strplmapsize, strpoolmap
word txtlinbuf, cliplinbuf
word cursx, cursy, scrnleft, curscol
byte underchr, curschr
word keyin, cursrow, selrow, scrntop, cursptr
byte a3echo         = $80
byte a3noecho       = $00
//
// Predeclared functions
//
predef cmdmode#0
//
// Utility functions
//
// Defines for ASM routines
//
asm equates
    !SOURCE "vmsrc/plvmzp.inc"
end
asm sethibit(strptr)#0
        LDA     ESTKL,X
        STA     SRCL
        LDA     ESTKH,X
        STA     SRCH
        INX
        LDY     #$00
        LDA     (SRC),Y
        BEQ     +
        TAY
STHILP  LDA     (SRC),Y
        ORA     #$80
        STA     (SRC),Y
        DEY
        BNE     STHILP
+       RTS
end
asm lncpy(dststr, srcstr)#0
        LDA     ESTKL,X
        STA     SRCL
        LDA     ESTKH,X
        STA     SRCH
        INX
        LDA     ESTKL,X
        STA     DSTL
        LDA     ESTKH,X
        STA     DSTH
        INX
        LDY     #$00
        LDA     (SRC),Y
        CMP     #80
        BCC     +
        LDA     #79
        CLC
+       ADC     #$01    ; APPEND CR TO END OF STRING
        STA     (DST),Y
        TAY
        LDA     #$0D
        STA     (DST),Y
        DEY
        BEQ     ++
CPLNLP  LDA     (SRC),Y
        CMP     #$20
        BCS     +
        ADC     #$60
+       AND     #$7F
        STA     (DST),Y
        DEY
        BNE     CPLNLP
++      RTS
end
asm lnupcpy(dststr, srcstr)#0
        LDA     ESTKL,X
        STA     SRCL
        LDA     ESTKH,X
        STA     SRCH
        INX
        LDA     ESTKL,X
        STA     DSTL
        LDA     ESTKH,X
        STA     DSTH
        INX
        LDY     #$00
        LDA     (SRC),Y
        CMP     #80
        BCC     +
        LDA     #79
+       STA     (DST),Y
        TAY
        BEQ     +++
CPUPLP  LDA     (SRC),Y
        CMP     #$20
        BCS     +
        ADC     #$60
+       AND     #$7F
        CMP     #$7B
        BCS     ++
        CMP     #$61
        BCC     ++
        SEC
        SBC     #$20
++      STA     (DST),Y
        DEY
        BNE     CPUPLP
+++     RTS
end
//def sizemask(size)
//    if size <= 80
//        return sizetomask[(size - 1) >> 4]
//    fin
//    if size <= 16
//        return $01
//    elsif size <= 32
//        return $03
//    elsif size <= 48
//        return $07
//    elsif size <= 64
//        return $0F
//    elsif size <= 80
//        return $1F
//    fin
//    return 0
//end
asm sizemask(size)#1
        LDA     ESTKL,X
        CMP     #129        ; 81
        BCS     ++
        ADC     #$0F
        LSR
        LSR
        LSR
        LSR
        TAY
        LDA     #$FF
-       ASL
        DEY
        BNE     -
        EOR     #$FF
        STA     ESTKL,X
        LDA     #$00
        STA     ESTKH,X
        RTS
--      DEY                     ; THIS IS NEXT MAP BYTE FROM BELOW
        CPY     #$FF
        BNE     +
        DEC     DSTH
        DEC     ESTKH+1,X
        LDA     ESTKH+1,X
        CMP     #$FF
        BNE     +
        INX
        INX
        INX
++      LDA     #$00
        STA     ESTKL,X
        STA     ESTKH,X
        RTS
end
//def strpoolalloc(size)
//    szmask = sizemask(size)
//    for i = strplmapsize - 1 downto 0
//        if  ^(strpoolmap + i) <> $FF
//            mapmask = szmask
//            ofst    = 0
//            repeat // Look for memory hole to fit
//                if ^(strpoolmap + i) & mapmask // Nope, shift over
//                    mapmask = mapmask << 1
//                    ofst++
//                else // Yep, take it
//                    ^(strpoolmap + i) = ^(strpoolmap + i) | mapmask
//                    return (ofst << 4) + (i << 7) + strpool
//                fin
//            until mapmask & $100 // No fit
//        fin
//    next
//    return 0
//end
asm poolalloc(poolbase, mapbase, idx, mask)#1
        LDA     ESTKL+2,X       ; MAPBASEL
        STA     DSTL
        LDA     ESTKH+2,X       ; MAPBASEH
        CLC
        ADC     ESTKH+1,X       ; IDXH
        STA     DSTH
        LDY     ESTKL+1,X       ; IDXL
+       LDA     (DST),Y
        CMP     #$FF
        BEQ     --              ; MAP BYTE FULL, TRY NEXT
        LDA     #$00
        STA     TMP             ; OFST
        LDA     ESTKL,X
        STA     ESTKH,X
-       AND     (DST),Y
        BEQ     ++
        INC     TMP             ; OFST++
        ASL     ESTKH,X         ; MASK <<= 1
        BCS     --              ; NO FIT, TRY NEXT MAP BYTE
        LDA     ESTKH,X
        BCC     -
++      LDA     ESTKH,X         ; UPDATE MAPMASK
        ORA     (DST),Y
        STA     (DST),Y
        LDA     TMP             ; OFST
        ASL
        ASL
        ASL
        ASL
        ;CLC
        ADC     ESTKL+3,X       ; + OFST << 4
        STA     ESTKL+3,X
        BCC     +
        INC     ESTKH+3,X
+       TYA
        LSR     ESTKH+1,X
        ROR
        TAY
        LDA     #$00
        ROR
        ;CLC
        ADC     ESTKL+3,X       ; + IDX << 7
        STA     ESTKL+3,X
        TYA
        ADC     ESTKH+3,X
        STA     ESTKH+3,X
        INX
        INX
        INX
        RTS
end
def bell#0
    putc($07)
end
//
// Memory management routines
//
def striplead(strptr, chr)#0
    byte striplen

    for striplen = 1 to ^strptr
        if ^(strptr + striplen) <> chr
            break
        fin
    next
    if striplen > 1
        ^strptr = ^strptr - (striplen - 1)
        memcpy(strptr + 1, strptr + striplen, ^strptr)
    fin
end
def striptail(strptr, chr)#0
    byte strlen

    for strlen = ^strptr downto 1
        if ^(strptr + strlen) <> $8D and ^(strptr + strlen) <> chr
            break
        fin
    next
    ^strptr = strlen
end
def strstripcpy(dststr, srcstr)#0
    memcpy(dststr, srcstr, ^srcstr + 1)
    striptail(dststr, keyspace)
end
def delstr(strptr)#0
    word ofst, mask

    if strptr <> @nullstr
        ofst = (strptr - strpool) >> 4
        mask = sizemask(^strptr + 1) << (ofst & $07)
        ofst = ofst >> 3
        ^(strpoolmap + ofst) = ^(strpoolmap + ofst) & ~mask
    fin
end
def newstr(strptr)
    byte strlen
    word newptr

    for strlen = ^strptr downto 1
        if ^(strptr + strlen) > keyspace
            break
        fin
    next
    if strlen == 0
        return @nullstr
    fin
    newptr = poolalloc(strpool,          \
                       strpoolmap,       \
                       strplmapsize - 1, \
                       sizemask(strlen + 1))
    if newptr
        memcpy(newptr + 1, strptr + 1, strlen)
        ^newptr = strlen
    else
        puts("Out of memory!\n")
        newptr = @nullstr
    fin
    return newptr
end
def inittxtbuf#0
    word i

    if not strpool
        txtlinbuf    = heapalloc(MAXLINESSIZE*2)
        cliplinbuf   = heapalloc(MAXCLIPLINES*2)
        strplmapsize = (heapavail - 1024) / 128
        strpoolmap   = heapalloc(strplmapsize)
        strpool      = heapalloc(strplmapsize * 128)
        memset(txtlinbuf,  @nullstr, MAXLINESSIZE*2)
        memset(cliplinbuf, @nullstr, MAXCLIPLINES*2)
        memset(strpoolmap, 0, strplmapsize)
    else
        for i = 0 to MAXLINESSIZE-1
            if txtlinbuf=>[i] <> @nullstr
                delstr(txtlinbuf=>[i])
                txtlinbuf=>[i] = @nullstr
            fin
        next
    fin
    numlines   = 1
    cursrow    = 0
    curscol    = 0
    cursx      = 0
    cursy      = 0
    scrnleft   = 0
    scrntop    = 0
    flags      = flags & ~selection
end
//
// Case conversion/printing routines
//
def caseconv(chr)
    if flags & uppercase
        if chr & $E0 == $E0
            chr = chr - $E0
        fin
    fin
    return chr
end
def strupper(strptr)#0
    byte i, chr

    if ^strptr
        for i = ^strptr downto 1
            chr = (strptr).[i]
            if chr & $E0 == $E0
                (strptr).[i] = chr - $E0
            fin
        next
    fin
end
def strlower(strptr)#0
    byte i, chr

    if ^strptr
        for i = ^strptr downto 1
            chr = (strptr).[i]
            if chr & $E0 == $00
                (strptr).[i] = chr + $E0
            fin
        next
    fin
end
def txtupper#0
    word i, strptr

    flags = flags | uppercase
    for i = numlines - 1 downto 0
        strupper(txtlinbuf=>[i])
    next
end
def txtlower#0
    word i, strptr

    flags = flags & ~uppercase
    for i = numlines - 1 downto 0
        strlower(txtlinbuf=>[i])
    next
end
def strtonum(strptr)
    word num, i
    byte c

    num = 0
    for i = 1 to ^strptr
        c = ^(strptr + i) & $7F
        if c < '0' and c > '9'
            break
        fin
        num = num * 10 + c - '0'
    next
    return num
end
def nametostr(namestr, len, strptr)#0
    ^strptr = len
    memcpy(strptr + 1, namestr, len)
end
//
// File routines
//
def readtxt(filename, startline)#0
    byte refnum, i, j, txtbuf[MAXLNLEN+2]

    refnum = fileio:open(filename)
    if refnum
        fileio:newline(refnum, $7F, $0D)
        numlines = startline
        repeat
            txtbuf = fileio:read(refnum, @txtbuf + 1, MAXLNLEN)
            if txtbuf
                sethibit(@txtbuf)
                if flags & uppercase; strupper(@txtbuf); fin
                txtlinbuf=>[numlines] = newstr(@txtbuf)
                numlines++
            fin
            if !(numlines & $0F); putc('.'); fin
        until txtbuf == 0 or numlines == MAXLINES
        fileio:close(refnum)
        //
        // Make sure there is a blank line at the end of the buffer
        //
        if numlines < MAXLINES and txtlinbuf=>[numlines - 1] <> @nullstr
            txtlinbuf=>[numlines] = @nullstr
            numlines++
        fin
    fin
    putln
end
def writetxt(filename)#0
    word i
    byte refnum, txtbuf[MAXLNLEN+2]

    //
    // Remove blank lines at end of text.
    //
    while numlines > 1 and txtlinbuf=>[numlines - 1] == @nullstr; numlines--; loop
    //
    // Delete old file and re-create
    //
    fileio:destroy(filename)
    fileio:create(filename, $04, $00) // full access, TXT file
    refnum = fileio:open(filename)
    if refnum == 0
        puts("\nError $"); puth(perr); puts(" opening: "); puts(filename); putln
        return
    fin
    //
    // Write all the text lines to the file.
    //
    for i = 0 to numlines - 1
        lncpy(@txtbuf, txtlinbuf=>[i])
        if fileio:write(refnum, @txtbuf + 1, txtbuf) <> txtbuf
            puts("\nError $"); puth(perr); puts(" writing: "); puts(filename); putln
            fileio:close(refnum)
            return
        fin
        if !(i & $0F); putc('.'); fin
    next
    if fileio:close(refnum) <> FILE_ERR_OK
        puts("\nError $"); puth(perr); puts(" closing: "); puts(filename)
    fin
    putln
end
//
// Screen routines
//
def clrscrn#0
    if MACHID == $F2 // Apple 3
        putc(28)
    else
        call($FC58, 0, 0, 0, 0)
    fin
end
def drawrow(row, ofst, strptr)#0
    byte numchars
    word scrnptr

    scrnptr = txtscrn[row] + (flags & gutter)
    if ofst >= ^strptr
        numchars = 0
    else
        numchars = ^strptr - ofst
    fin
    if numchars >= scrnwidth
        numchars = scrnwidth
    else
        memset(scrnptr + numchars, $A0A0, scrnwidth - numchars)
    fin
    memcpy(scrnptr, strptr + ofst + 1, numchars)
end
def drawgutter(scrnrow, ofst)#0
    byte row, hilite, ofstch, huns, tens, ones
    word scrnptr

    //
    // Draw line numbers and gutter hilites
    //
    ofstch = ofst ?? '<' :: ' '
    huns, tens = divmod(scrnrow + 1, 100)
    tens, ones = divmod(tens, 10)
    for row = 0 to 23
        scrnptr = txtscrn[row]
        if scrnrow < numlines
            if flags & selection and (scrnrow >= selrow and scrnrow <= cursrow) or (scrnrow >= cursrow and scrnrow <= selrow)
                hilite = $00
            elsif scrnrow == cursrow
                hilite = $00
            else
                hilite = $80
            fin
            if huns
                ^scrnptr     = hilite + '0' + huns
                ^(scrnptr+1) = hilite + '0' + tens
                ^(scrnptr+2) = hilite + '0' + ones
                ones++
                if ones > 9
                    ones = 0
                    tens++
                    if tens > 9
                        tens = 0
                        huns++
                    fin
                fin
            elsif tens
                ^scrnptr     = hilite + ' '
                ^(scrnptr+1) = hilite + '0' + tens
                ^(scrnptr+2) = hilite + '0' + ones
                ones++
                if ones > 9
                    ones = 0
                    tens++
                    if tens > 9
                        tens = 0
                        huns = 1
                    fin
                fin
            elsif ones
                ^scrnptr     = hilite + ' '
                ^(scrnptr+1) = hilite + ' '
                ^(scrnptr+2) = hilite + '0' + ones
                ones++
                if ones > 9
                    ones = 0
                    tens = 1
                fin
            fin
            ^(scrnptr+3) = ofstch
        else
            *scrnptr     = $A0A0
            *(scrnptr+2) = $A0A0
        fin
        scrnrow++
    next
end
def drawscrn(toprow, ofst)#0
    byte row, numchars, lofst
    word strptr, scrnptr

    lofst = flags & gutter
    if lofst
        drawgutter(toprow, ofst)
    fin
    //
    // Draw text
    //
    if ofst
        for row = 0 to 23
            strptr   = txtlinbuf=>[toprow + row]
            scrnptr  = txtscrn[row] + lofst
            if ofst >= ^strptr
                numchars = 0
            else
                numchars = ^strptr - ofst
            fin
            if numchars >= scrnwidth
                numchars = scrnwidth
            else
                memset(scrnptr + numchars, $A0A0, scrnwidth - numchars)
            fin
            memcpy(scrnptr, strptr + ofst + 1, numchars)
        next
    else
        for row = 0 to 23
            strptr   = txtlinbuf=>[toprow + row]
            scrnptr  = txtscrn[row] + lofst
            numchars = ^strptr
            if numchars >= scrnwidth
                numchars = scrnwidth
            else
                memset(scrnptr + numchars, $A0A0, scrnwidth - numchars)
            fin
            memcpy(scrnptr, strptr + 1, numchars)
        next
    fin
end
def cursoff#0
    word scrnptr

    if flags & showcurs
        ^cursptr     = underchr
        if flags & gutter
            scrnptr      = txtscrn[cursy]
            *scrnptr     = *scrnptr     | $8080
            ^(scrnptr+2) = ^(scrnptr+2) | $80
        fin
        flags = flags & ~showcurs

    fin
end
def curson#0
    byte lofst
    word scrnptr

    if !(flags & showcurs)
        lofst = flags & gutter
        if lofst
            scrnptr = txtscrn[cursy]
            *scrnptr     = *scrnptr     & $7F7F
            ^(scrnptr+2) = ^(scrnptr+2) & $7F
        fin
        cursptr  = txtscrn[cursy] + lofst + cursx
        underchr = ^cursptr
        ^cursptr = curschr
        flags = flags | showcurs
    fin
end
def cursflash#0
    if flags & showcurs
        if flash == 0
            ^cursptr = curschr
        elsif flash == 128
            ^cursptr = underchr
        fin
        flash++
    fin
end
def redraw#0
    cursoff
    drawscrn(scrntop, scrnleft)
    curson
end
def curshpos(hpos)#1
    byte needredraw

    needredraw = TRUE
    if hpos < 0; hpos = 0; fin
    if hpos > MAXLNLEN; hpos = MAXLNLEN; fin
    curscol = hpos
    cursx   = curscol - scrnleft
    if cursx > scrnright
        cursx      = scrnright
        scrnleft   = curscol - scrnright
    elsif cursx < 0
        cursx      = 0
        scrnleft   = curscol
    else
        needredraw = FALSE
    fin
    return needredraw
end
def cursvpos(vpos)#1
    byte needredraw

    needredraw = TRUE
    if vpos < 0; vpos = 0; fin
    if vpos > numlines - 1; vpos = numlines - 1; fin
    cursrow = vpos
    cursy   = cursrow - scrntop
    if cursy > scrnbottom
        cursy = scrnbottom
        scrntop = cursrow - scrnbottom
    elsif cursy < 0
        cursy    = 0
        scrntop  = cursrow
    else
        needredraw = FALSE
    fin
    return needredraw
end
def curshome#0
    cursoff
    cursrow  = 0
    curscol  = 0
    cursx    = 0
    cursy    = 0
    scrnleft = 0
    scrntop  = 0
    drawscrn(scrntop, scrnleft)
    curson
end
def cursend#0
    cursoff
    if numlines > 23
        cursrow  = numlines - 1
        cursy    = 23
        scrntop  = cursrow - 23
    else
        cursrow  = numlines - 1
        cursy    = numlines - 1
        scrntop  = 0
    fin
    curscol  = 0
    cursx    = 0
    scrnleft = 0
    drawscrn(scrntop, scrnleft)
    curson
end
def cursup#0
    if cursrow > 0
        cursoff
        cursrow--
        if cursy > 0
            cursy--
            if flags & selection
                drawgutter(scrntop, scrnleft)
            fin
        else
            scrntop = cursrow
            drawscrn(scrntop, scrnleft)
        fin
        curson
    fin
end
def pgup#0
    cursoff
    if cursvpos(cursrow - pgjmp)
        drawscrn(scrntop, scrnleft)
    else
        if flags & selection
            drawgutter(scrntop, scrnleft)
        fin
    fin
    curson
end
def cursdown#0
    if cursrow < numlines - 1
        cursoff
        cursrow++
        if cursy < 23
            cursy++
            if flags & selection
                drawgutter(scrntop, scrnleft)
            fin
        else
            scrntop = cursrow - 23
            drawscrn(scrntop, scrnleft)
        fin
        curson
    fin
end
def pgdown#0
    cursoff
    if cursvpos(cursrow + pgjmp)
        drawscrn(scrntop, scrnleft)
    else
        if flags & selection
            drawgutter(scrntop, scrnleft)
        fin
    fin
    curson
end
def cursleft#0
    if curscol > 0
        cursoff
        curscol--
        if cursx > 0
            cursx--
        else
            scrnleft = curscol
            drawscrn(scrntop, scrnleft)
        fin
        curson
    fin
end
def pgleft#0
    word i, strptr

    strptr = txtlinbuf=>[cursrow]
    if curscol > ^strptr
        i = ^strptr
    else
        for i = 1 to ^strptr
            if ^(strptr + i) <> keyspace
                break
            fin
        next
        i--
        if i >= curscol
            i = 0
        fin
    fin
    cursoff
    if curshpos(i)
    //if curshpos(curscol - 8)
        drawscrn(scrntop, scrnleft)
    else
        if flags & selection
            drawgutter(scrntop, scrnleft)
        fin
    fin
    curson
end
def cursright#0
    if curscol < MAXLNLEN
        cursoff
        curscol++
        if cursx < scrnright
            cursx++
        else
            scrnleft = curscol - scrnright
            drawscrn(scrntop, scrnleft)
        fin
        curson
    fin
end
def pgright#0
    word i, strptr

    strptr = txtlinbuf=>[cursrow]
    if curscol >= ^strptr
        i = curscol + 8
    else
        for i = 1 to ^strptr
            if ^(strptr + i) <> keyspace
                break
            fin
        next
        i--
        if curscol >= i
            i = ^strptr
        fin
    fin
    cursoff
    if curshpos(i)
    //if curshpos(curscol + 8)
        drawscrn(scrntop, scrnleft)
    else
        if flags & selection
            drawgutter(scrntop, scrnleft)
        fin
    fin
    curson
end
//
// Find string in text
//
def findline(strptr, start)#1
    byte scan, i, upstr[MAXLNLEN+1]

    if ^strptr >= findstr
        lnupcpy(@upstr, strptr)
        for scan = start to upstr - findstr + 1
            if upstr[scan] == findstr[1]
                for i = 2 to findstr
                    if upstr[scan + i - 1] <> findstr[i]
                        break
                    fin
                next
                if i > findstr
                    curshpos(scan - 1)
                    return TRUE
                fin
            fin
        next
    fin
    return FALSE
end
def findtxt#0
    word f

    //
    // Search from current pos to end
    //
    if findline(txtlinbuf=>[cursrow], curscol + 2)
        return
    fin
    for f = cursrow + 1 to numlines - 1
        if findline(txtlinbuf=>[f], 1)
            cursvpos(f)
            return
        fin
    next
    //
    // Search from beginning to current pos
    //
    for f = 0 to cursrow
        if findline(txtlinbuf=>[f], 1)
            cursvpos(f)
            return
        fin
    next
    bell
end
//
// Keyboard routines
//
def dev_control(devnum, code, list)#1
    byte params[5]

    params.0 = 3
    params.1 = devnum
    params.2 = code
    params:3 = list
    perr     = syscall($83, @params)
    return perr
end
def cons_keyavail
    byte params[5]
    byte count

    params.0 = 3
    params.1 = cmdsys.devcons
    params.2 = 5
    params:3 = @count
    return syscall($82, @params) ?? 0 :: count
end
def cons_keyread
    byte params[8]
    byte key

    params.0 = 4
    params.1 = cmdsys.refcons
    params:2 = @key
    params:4 = 1
    params:6 = 0
    syscall($CA, @params)
    return params:6 ?? key :: 0
end
def keyin3
    byte key

    repeat
        cursflash
    until cons_keyavail
    key = cons_keyread
    if key & $80 // Open Apple modifier
        when key
            is keyarrowleft
                key = keyctrla; break
            is keyarrowright
                key = keyctrls; break
            is keyarrowup
                key = keyctrlw; break
            is keyarrowdown
                key = keyctrlz; break
            is keyenter
                key = keyctrlf; break
             is keytab
                key = keydetab; break
           is $80 | '\\'
                key = keydelete; break // Delete
            //
            // Map OA+keypad
            //
            is $80 | '4'
                key = keyarrowleft; break
            is $80 | '6'
                key = keyarrowright; break
            is $80 | '8'
                key = keyarrowup; break
            is $80 | '2'
                key = keyarrowdown; break
            is $80 | '7'
                key = keyctrlq; break // Top
            is $80 | '1'
                key = keyctrle; break // Bottom
            is $80 | '9'
                key = keyctrlw; break // Pg Up
            is $80 | '3'
                key = keyctrlz; break // Pg Dn
            is $80 | '5'
                key = keyctrlb; break // Selection start/end
            is $80 | '0'
                key = keyctrld; break // Del
            is $80 | '.'
                key = keyctrlv; break // Paste
            is $80 | '-'
                key = keyctrlx; break // Cut
        wend
    fin
    return key | $80
end
def keyin2e
    byte key, vbl

    vbl  = ^$C019
    repeat
        if flags & showcurs
            if flash == 0
                ^cursptr = curschr
            elsif flash == 128
                ^cursptr = underchr
            fin
            if vbl ^ ^$C019
                flash = flash + 8
                vbl = ^$C019
                ^$C079 = 0 // Clear VBL int on //c
            fin
        fin
        key = ^keyboard
    until key >= 128
    ^keystrobe
    if ^pushbttn2 & 128 // Closed Apple (option) pressed
        when key
            is keyarrowleft
                key = keyctrla; break
            is keyarrowright
                key = keyctrls; break
            is keyarrowup
                key = keyctrlw; break
            is keyarrowdown
                key = keyctrlz; break
            is keyenter
                key = keyctrlf; break
            is keytab
                key = keydetab; break
            //
            // Map option+keypad on Platinum //e
            //
            is $80 | '4'
                key = keyarrowleft; break
            is $80 | '6'
                key = keyarrowright; break
            is $80 | '8'
                key = keyarrowup; break
            is $80 | '2'
                key = keyarrowdown; break
            is $80 | '7'
                key = keyctrlq; break // Top
            is $80 | '1'
                key = keyctrle; break // Bottom
            is $80 | '9'
                key = keyctrlw; break // Pg Up
            is $80 | '3'
                key = keyctrlz; break // Pg Dn
            is $80 | '5'
                key = keyctrlb; break // Selection start/end
            is $80 | '0'
                key = keyctrld; break // Del
            is $80 | '.'
                key = keyctrlv; break // Paste
            is $80 | '-'
                key = keyctrlx; break // Cut
            is $80 | '+'
                key = keyctrlc; break // Copy
            is $80 | '*'
                key = keyctrlo; break // Open line
            is $80 | '/'
                key = keyctrlt; break // Join lines
            is $80 | '='
                key = keyctrly; break // Insert/Overwrite
        wend
    fin
    return key
end
def keyin2c
    byte key

    ^$C079 = 0 // IOU enable and clear VBL int on //c
    ^$C05B = 0 // Enable VBL Ints on //c
    key = keyin2e
    ^$C05A = 0 // Disable VBL Ints on //c
    ^$C078 = 0 // IOU disable on //c
    return key
end
def keyin2
    byte key

    repeat
        cursflash
        key = ^keyboard
        if key == keyctrll
            ^keystrobe
            flags = flags ^ shiftlock
            key   = 0
        fin
    until key >= 128
    ^keystrobe
    when key
        is keyctrln
            key = $DB // '['
            break
        is $9E // SHIFT+CTRL+N
            key = $FE // '~'
            break
        is keyctrlp
            key = $DC // '\'
            break
        is $80 // SHIFT+CTRL+P -> CTRL+@
            key = $FC // '|'
            break
        is keyctrlg
            key = $DF // '_'
            break
        is keyarrowleft
            if ^pushbttn3 < 128
                key = keydelete
            fin
            break
        is keyarrowright
            if ^pushbttn3 < 128
                key = keytab
            fin
            break
        otherwise
            if key >= $C0 and flags < shiftlock
                if ^pushbttn3 < 128
                    when key
                        is $C0
                            key = $D0 // P
                            break
                        is $DD
                            key = $CD // M
                            break
                        is $DE
                            key = $CE // N
                    wend
                else
                   key = key | $E0
                fin
            fin
    wend
    return key
end
def setkeyin#0
    when MACHID & MACHID_MODEL
        is MACHID_IIE
            keyin = @keyin2e
            break
        is MACHID_IIC
            keyin = @keyin2c
            break
        is MACHID_III
            keyin = @keyin3
            break
        otherwise // ][ or ][+
            keyin = @keyin2
    wend
end
//
// Printer routines
//
def printtxt(slot)#0
    byte txtbuf[MAXLNLEN+1]
    word i, scrncsw

    scrncsw = *csw
    *csw  = $C000 | (slot << 8)
    for i = 0 to numlines - 1
        lncpy(@txtbuf, txtlinbuf=>[i])
        puts(@txtbuf)
        putln
    next
    *csw = scrncsw
end
def freesel#0
    word i

    if numcliplines
        for i = 0 to numcliplines - 1
            if cliplinbuf=>[i] <> @nullstr
                delstr(cliplinbuf=>[i])
                cliplinbuf=>[i] = @nullstr
            fin
        next
        numcliplines = 0
    fin
end
def selrange#2
    word first, last

    if flags & selection
        if cursrow > selrow
            first, last = selrow, cursrow
        else
            first, last = cursrow, selrow
        fin
    else
        first = cursrow
        last = first
    fin
    return first, last
end
def beginsel#0
    flags  = flags ^ selection
    selrow = cursrow
    drawgutter(scrntop, scrnleft)
end
def copysel#0
    word firstsel, lastsel

    freesel
    firstsel, lastsel = selrange
    if lastsel - firstsel < MAXCLIPLINES
        for numcliplines = 0 to lastsel - firstsel
            cliplinbuf=>[numcliplines] = newstr(txtlinbuf=>[firstsel + numcliplines])
        next
        flags = flags & ~selection
        drawgutter(scrntop, scrnleft)
    else
        bell
    fin
end
def cutsel#0
    word firstsel, lastsel

    freesel
    firstsel, lastsel = selrange
    if lastsel - firstsel < MAXCLIPLINES
        for numcliplines = 0 to lastsel - firstsel
            cliplinbuf=>[numcliplines] = txtlinbuf=>[firstsel + numcliplines]
        next
        memcpy(@txtlinbuf=>[firstsel], \
               @txtlinbuf=>[lastsel + 1], \
               (numlines - lastsel + 1) * 2)
        numlines = numlines - numcliplines
        for lastsel = numlines to numlines + numcliplines
            txtlinbuf=>[lastsel] = @nullstr
        next
        cursrow  = firstsel
        if cursrow >= numlines
            cursrow = numlines - 1
        fin
        if cursrow < scrntop
            scrntop = cursrow
        fin
        cursy = cursrow - scrntop
        flags = flags | changed
        flags = flags & ~selection
        redraw
    else
        bell
    fin
end
def pastesel#0
    word p

    if numcliplines and numcliplines + numlines < MAXLINES
        memcpy(@txtlinbuf=>[cursrow + numcliplines], \
               @txtlinbuf=>[cursrow], \
               (numlines - cursrow) * 2)
        for p = 0 to numcliplines - 1
            txtlinbuf=>[cursrow + p] = newstr(cliplinbuf=>[p])
        next
        numlines = numlines + numcliplines
        flags = flags | changed
        redraw
    else
        bell
    fin
end
def indentsel#0
    byte indentstr[MAXLNLEN+1], j, l
    word firstsel, lastsel, i

    freesel
    firstsel, lastsel = selrange
    for i = firstsel to lastsel
        l = ^(txtlinbuf=>[i])
        if l and l < MAXLNLEN - 2
            memcpy(@indentstr + 3, txtlinbuf=>[i] + 1, l)
            indentstr[0] = l + 2
            indentstr[1] = keyspace
            indentstr[2] = keyspace
            //
            // Align indent
            //
            for j = 3 to indentstr[0]
                if indentstr[j] <> keyspace
                    if j & 1 == 0
                        indentstr[0]--
                        memcpy(@indentstr + 1, @indentstr + 2, indentstr[0])
                    fin
                    break
                fin
            next
            delstr(txtlinbuf=>[i])
            txtlinbuf=>[i] = newstr(@indentstr)
            flags = flags | changed
        fin
    next
    redraw
end
def undentsel#0
    byte undentstr[MAXLNLEN+1], l
    word firstsel, lastsel, i

    freesel
    firstsel, lastsel = selrange
    for i = firstsel to lastsel
        l = ^(txtlinbuf=>[i])
        if l
            memcpy(@undentstr + 1, txtlinbuf=>[i] + 1, l)
            if undentstr[1] == keyspace
                memcpy(@undentstr + 1, @undentstr + 2, l - 1)
                l--
                if l and undentstr[1] == keyspace
                    memcpy(@undentstr + 1, @undentstr + 2, l - 1)
                    l--
                fin
                undentstr[0] = l
                delstr(txtlinbuf=>[i])
                txtlinbuf=>[i] = newstr(@undentstr)
                flags = flags | changed
            fin
        fin
    next
    redraw
end
def autoindent(strptr)#0
    byte i

    for i = 1 to ^strptr
        if ^(strptr + i) <> keyspace
            break
        fin
    next
    curshpos(i - 1)
end
def openline(row)
    if numlines < MAXLINES
        memcpy(@txtlinbuf=>[row + 1], @txtlinbuf=>[row], (numlines - row) * 2)
        txtlinbuf=>[row] = @nullstr
        numlines++
        flags = flags | changed
        return TRUE
    fin
    bell
    return FALSE
end
def joinline#0
    word  joinlen, joinstr, stripjoin

    if cursrow < numlines - 1
        joinstr   = heapalloc(MAXLNLEN+1)
        stripjoin = heapalloc(MAXLNLEN+1)
        strstripcpy(joinstr, txtlinbuf=>[cursrow])
        memcpy(stripjoin, txtlinbuf=>[cursrow + 1], ^(txtlinbuf=>[cursrow + 1]) + 1)
        striplead(stripjoin, keyspace);
        joinlen = ^joinstr + ^stripjoin
        if joinlen <= MAXLNLEN
            curshpos(^joinstr)
            memcpy(joinstr + ^joinstr + 1, stripjoin + 1, ^stripjoin)
            ^joinstr = joinlen
            delstr(txtlinbuf=>[cursrow])
            txtlinbuf=>[cursrow] = newstr(joinstr)
            delstr(txtlinbuf=>[cursrow + 1])
            numlines--
            memcpy(@txtlinbuf=>[cursrow + 1], @txtlinbuf=>[cursrow + 2], (numlines - cursrow) * 2)
            flags = flags | changed
            heaprelease(joinstr)
            return
        fin
        heaprelease(joinstr)
    fin
    bell
end
def splitline#0
    byte splitstr[MAXLNLEN+1], splitlen, i

    if openline(cursrow + 1)
        if curscol
            splitlen = ^(txtlinbuf=>[cursrow])
            if curscol < splitlen - 1
                splitstr = splitlen - curscol
                memcpy(@splitstr + 1, txtlinbuf=>[cursrow] + curscol + 1, splitstr)
                striplead(@splitstr, keyspace)
                for i = 1 to curscol
                    if ^(txtlinbuf=>[cursrow] + i) <> keyspace
                        break
                    fin
                    memcpy(@splitstr + 2, @splitstr + 1, splitstr)
                    splitstr[1] = keyspace
                    splitstr++
                next
                txtlinbuf=>[cursrow + 1] = newstr(@splitstr)
                splitstr = curscol
                memcpy(@splitstr + 1, txtlinbuf=>[cursrow] + 1, splitstr)
                delstr(txtlinbuf=>[cursrow])
                txtlinbuf=>[cursrow] = newstr(@splitstr)
                curshpos(i - 1)
            else
                if splitlen > 0
                    for curscol = 1 to splitlen - 1
                        if ^(txtlinbuf=>[cursrow] + curscol) <> keyspace
                            break
                        fin
                    next
                    curshpos(curscol - 1)
                fin
            fin
         else
            txtlinbuf=>[cursrow + 1] = txtlinbuf=>[cursrow]
            txtlinbuf=>[cursrow]     = @nullstr
        fin
    fin
end
def editkey(key)
    if key >= keyspace or key == keydelete or key == keyctrld or key == keyctrlr
        return TRUE
    fin
    return FALSE
end
def editline(key)
    word undoline
    byte undopos, localchange, editstr[], editlen, editchars[MAXLNLEN+1]

    if (editkey(key))
        undopos  = curscol
        undoline = txtlinbuf=>[cursrow]
        editlen = MAXLNLEN
        memset(@editchars, $A0A0, MAXLNLEN)
        memcpy(@editchars, undoline + 1, ^undoline)
        txtlinbuf=>[cursrow] = @editstr
        localchange = FALSE
        repeat
            when key
                is keyctrld
                    memcpy(@editchars[curscol], @editchars[curscol + 1], MAXLNLEN - 1 - curscol)
                    editchars[MAXLNLEN - 1] = keyspace
                    cursoff
                    drawrow(cursy, scrnleft, @editstr)
                    curson
                    localchange = TRUE
                    break
                is keyctrlr
                    memset(@editchars, $A0A0, MAXLNLEN)
                    memcpy(@editchars, undoline + 1, ^undoline)
                    cursoff
                    if curshpos(undopos)
                        drawscrn(scrntop, scrnleft)
                    else
                        drawrow(cursy, scrnleft, @editstr)
                    fin
                    curson
                    localchange = FALSE
                    break
                is keydelete
                    if curscol > 0
                        memcpy(@editchars[curscol - 1], @editchars[curscol], MAXLNLEN - curscol)
                        editchars[MAXLNLEN - 1] = keyspace
                        cursoff
                        if curshpos(curscol - 1)
                            drawscrn(scrntop, scrnleft)
                        else
                            drawrow(cursy, scrnleft, @editstr)
                        fin
                        curson
                        localchange = TRUE
                    fin
                    break
                otherwise
                    if flags & insmode
                        if editchars[MAXLNLEN - 1] == keyspace
                            memcpy(@editchars[curscol+1], @editchars[curscol], MAXLNLEN - 1 - curscol)
                        else
                            bell
                            break
                        fin
                    fin
                    editchars[curscol] = caseconv(key)
                    cursoff
                    if curshpos(curscol + 1)
                        drawscrn(scrntop, scrnleft)
                    else
                        drawrow(cursy, scrnleft, @editstr)
                    fin
                    curson
                    localchange = TRUE
                    break
            wend
            key = keyin()
        until not editkey(key)
        if localchange
            flags = flags | changed
            delstr(undoline)
            txtlinbuf=>[cursrow] = newstr(@editstr)
        else
            txtlinbuf=>[cursrow] = undoline
        fin
    fin
    return key
end
def editmode#0
    if MACHID == $F2 // Apple 3
        dev_control(cmdsys.devcons, 11, @a3noecho)
    fin
    repeat
        when editline(keyin())
            is keyarrowup
                cursup; break
            is keyarrowdown
                cursdown; break
            is keyarrowleft
                cursleft; break
            is keyarrowright
                cursright; break
            is keyctrlw
                pgup; break
            is keyctrlz
                pgdown; break
            is keyctrla
                pgleft; break
            is keyctrls
                pgright; break
            is keyctrlq
                curshome; break
            is keyctrle
                cursend; break
            is keyctrlb
                beginsel; break
            is keyctrlc
                copysel; break
            is keyctrlx
                cutsel; break
            is keyctrlv
                pastesel; break
            is keyctrlf
                if cursrow == (numlines - 1)
                    if numlines < MAXLINES
                        numlines++
                        cursdown
                        flags = flags | changed
                        drawgutter(scrntop, scrnleft)
                    else
                        bell
                    fin
                    break
                fin
                cursdown
            is keyctrlo
                openline(cursrow)
                if cursrow
                    autoindent(txtlinbuf=>[cursrow - 1])
                else
                    curshpos(0)
                fin
                redraw
                break
            is keyenter
                if flags & insmode
                    splitline
                else
                    openline(cursrow + 1)
                fin
                autoindent(txtlinbuf=>[cursrow])
                cursvpos(cursrow + 1)
                redraw
                break
            is keyctrlt
                joinline
                redraw
                break
            is keytab
                if flags & insmode
                    indentsel
                    cursoff
                    if not ^(txtlinbuf=>[cursrow])
                        curshpos((curscol + 2) & $FE)
                    else
                        autoindent(txtlinbuf=>[cursrow])
                    fin
                    curson
                    break
                fin
           is keydetab
                undentsel
                cursoff
                if not ^(txtlinbuf=>[cursrow])
                    curshpos((curscol - 2) & $FFFE)
                else
                    autoindent(txtlinbuf=>[cursrow])
                fin
                curson
                break
            is keyctrly
                flags   = flags ^ insmode
                curschr = flags & insmode ?? '+' :: ' '
                break
            is keyescape
                if MACHID == $F2 // Apple 3
                    dev_control(cmdsys.devcons, 11, @a3echo)
                fin
                cursoff
                cmdmode
                if not exit
                    if MACHID == $F2 // Apple 3
                        dev_control(cmdsys.devcons, 11, @a3noecho)
                    fin
                    redraw
                fin
        wend
    until exit
end
//
// Command mode
//
def prfiles(optpath)
    byte path[64]
    byte refnum
    byte firstblk
    byte entrylen, entriesblk
    byte i, type, len
    word databuff, entry

    if ^optpath
        strstripcpy(@path, optpath)
    else
        fileio:getpfx(@path)
        puts(@path)
        putln
    fin
    databuff = heapalloc(512)
    refnum = fileio:open(@path)
    if perr
        return perr
    fin
    firstblk = 1
    while fileio:read(refnum, databuff, 512) == 512
        entry = databuff + 4
        if firstblk
            entrylen   = databuff->$23
            entriesblk = databuff->$24
            entry      = entry + entrylen
        fin
        for i = firstblk to entriesblk
            type = ^entry
            if type <> 0
                len = type & $0F
                ^entry = len
                puts(entry)
                if type & $F0 == $D0 // Is it a directory?
                    putc('/')
                    len++
                fin
                for len = 20 - len downto 1
                    putc(' ')
                next
            fin
            entry = entry + entrylen
        next
        firstblk = 0
    loop
    fileio:close(refnum)
    heaprelease(databuff)
    putln
    return 0
end
def parsecmd(strptr)
    byte cmd

    cmd = 0
    striplead(strptr, ' ')
    if ^strptr
        cmd = ^(strptr + 1)
        memcpy(strptr + 1, strptr + 2, ^strptr)
        ^strptr--
    fin
    if ^strptr
        striplead(strptr, ' ')
    fin
    return cmd
end
def chkchng
    if flags & changed
        puts("LOSE CHANGES TO FILE (Y/N)?")
        if toupper(keyin()) == 'N'
            putln
            return FALSE
        fin
        putln
    fin
    return TRUE
end
def cmdmode#0
    byte slot, b
    word cmdptr, line
    var i, freepool

    clrscrn
    puts("PLASMA Editor, Version 2.20\n")
    while not exit
        puts(@filename)
        cmdptr = gets($BA)
        when toupper(parsecmd(cmdptr))
            is 'F' // Find string
                if ^cmdptr
                    lnupcpy(@findstr, cmdptr)
                fin
                findtxt
                return
            is 'T' // Toggle upper/lower case display
                if ^cmdptr
                    when toupper(^(cmdptr + 1))
                        is 'G' // Gutter view
                            flags = flags ^ gutter
                            if flags & gutter
                                scrnwidth    = 36
                                scrnright    = 35
                            else
                                scrnwidth    = 40
                                scrnright    = 39
                            fin
                            break
                        is 'C' // Lower case chip (Apple ][/][+ only)
                            if flags & uppercase
                                txtlower
                            else
                                txtupper
                            fin
                            break
                    wend
                fin
                return
            is 'G' // Goto line #
                line = strtonum(cmdptr)
                if line
                    curshpos(0)
                    cursvpos(line - 1)
               fin
            is 'E' // Edit mode
            is 0
                return
            is 'A' // Append file
                readtxt(cmdptr, numlines)
                flags = flags | changed
                break
            is 'R' // Read file
                if chkchng
                    inittxtbuf
                    strstripcpy(@filename, cmdptr)
                    readtxt(@filename, 0)
                    flags = flags & ~changed
                fin
                break
            is 'W' // Write file
                if ^cmdptr
                    strstripcpy(@filename, cmdptr)
                fin
                writetxt(@filename)
                //if flags & changed; fin
                flags = flags & ~changed
                break
            is 'C' // Catalog
                prfiles(cmdptr); break
            is 'P' // Prefix
                fileio:setpfx(cmdptr); break
            is 'H' // Hardcopy
                if ^cmdptr
                    slot = cmdptr.1 - '0'
                else
                    slot = 1
                fin
                printtxt(slot)
                break
            is 'Q' // Quit
                exit = chkchng
                if not exit
                    return
                fin
                break
           is 'N'
                if chkchng
                    inittxtbuf
                    strstripcpy(@filename, "UNTITLED")
                fin
                break
            is 'D' // Dump stats
                freepool = 0
                for i = strplmapsize - 1 downto 0
                    slot = ^(strpoolmap + i)
                    if slot <> $FF
                        for b = 0 to 7
                            if not ((1 << b) & slot)
                                freepool++
                            fin
                        next
                    fin
                next
                puts("Pool size: "); puti(strplmapsize*128); putln
                puts("Pool free: "); puti(freepool*16); putln
                puts("Num lines: "); puti(numlines); putln
                puts("Clip lines: "); puti(numcliplines); putln
                break
            otherwise
                bell
                putc('?')
                putln
        wend
        if perr
            puts("ERROR: $")
            putb(perr)
        putln
        fin
    loop
end
//
// Init editor
//
setkeyin
if not (MACHID & $80) // ][ or ][+
    flags = uppercase | shiftlock
fin
inittxtbuf
arg = argNext(argFirst)
if ^arg
    strcpy(@filename, arg)
    puts(@filename)
    readtxt(@filename, 0)
    arg = argNext(arg)
    if ^arg
        cursrow = strtonum(arg)
        if cursrow
            curshpos(0)
            cursvpos(cursrow - 1)
        fin
    fin
fin
curschr  = '+'
flags    = flags | insmode | gutter
drawscrn(scrntop, scrnleft)
curson
editmode
done
