TP3-EDITOR-PSEUDOCODE.md 9.6 KB

TP3 editor — structure in pseudo-code

The editor is a tight key-dispatch loop over one big CR-terminated buffer, with the current line edited in a small "line cache" that is flushed back on every move. This mirrors the real TPSRC5/6 structure (entry editor2, edmain, edproc, eflush).


1. Entry — Run(drive, fileName, changed)

Run(drv, fname, changed):
    copy drv, fname into editor globals
    text[txend] = CR; text[txend+1] = LF; txend += 1
    statObsolete = 0            // force status-line repaint
    dislin = 1                   // force full redraw
    sepptr = SEPARATOR_TABLE    // "<>,[].*+-/$:=(){}^#'"  + space
    ClrScr()
    setPosFromArg(inPos)         // inPos=FFFF 'keep'  |  error-line offset
    mainLoop()
    return (changed updated from internal modified flag)

2. Main loop — mainLoop()

loop:
    redisplay()                       // redraw window (skip if key pending!)
    if statera != 0:                  // erase stale status junk
        eraseChars(statera, LowVideo); statera = 0
    statusLine()                      // paint "Line n  Col n  Insert  X:F"
    key = getKey()
    cmd = dispatch(key)               // returns action or NOT_A_COMMAND
    if cmd == NOT_A_COMMAND:
        insertChar(key)               // enter printable char into line buf
    elif cmd == UNKNOWN:
        nothing                        // TP3 ignores; loop again
    else:
        if cmd.markChanged:  modified = true; codeInvalid = true
        save currentPos into posFIFO   // ^Q-P last-position stack (8 bytes)
        cmd.handle()                   // each handler returns to the loop
    if cmd == CMD_QUIT: return

3. Dispatch — table-driven dispatch(key)

dispatch(key):
    if key >= 0x20 and key != DEL:   return NOT_A_COMMAND
    cmdBuf = [ length=1, key ]
    loop:
        found = searchTable(cmdBuf, TABLE_1, exactMatch)   // arrows/ESC seqs
        if not found:
            found = searchTable(cmdBuf, TABLE_2, maskSecond&0x1F) // ^Q + 'd' == ^Q-D
        if found == NEED_MORE_KEYS:
            echo(cmdBuf)                       // show "^K" while typing
            key2 = getKey(); cmdBuf += key2; continue
        if found == COMMAND:
            cmdNo = ...; return cmdTable[cmdNo]   // 45 entries, ejmptab order
        return UNKNOWN

Command numbers in manual order (the MSB of the address = "changed" flag):

CR, Left, Left, Right, ^A wordLeft, ^F wordRight, ^E up, ^X down,
^W scrollUp, ^Z scrollDown, ^R pageUp, ^C pageDown, ^Q-S lineStart,
^Q-D lineEnd, ^Q-E pageTop, ^Q-X pageBottom, ^Q-R textStart, ^Q-C textEnd,
^Q-B blockStart, ^Q-K blockEnd, ^Q-P lastPos, ^V insertToggle,
^N insertLine, ^Y deleteLine, ^Q-Y deleteToEOL, ^T deleteWord,
^G deleteRight, DEL deleteLeft, [2nd DEL], ^K-B markBlockStart,
^K-K markBlockEnd, ^K-T markWord, ^K-H hideBlock, ^K-C copyBlock,
^K-V moveBlock, ^K-Y deleteBlock, ^K-R readBlock, ^K-W writeBlock,
^K-D quit, TAB autoIndent, ^Q-I indentToggle, ^Q-L restoreLine,
^Q-F find, ^Q-A replace, ^L repeatSearch, ^P literalPrefix

4. Text model

const SEPARATORS = "<>,[].*+-/$:=(){}^#'"
text : array[0..62903] of CR-terminated lines, no trailing CR at buffer end
lineBuf : small cache holding the current line under edit
lnpos   : cursor offset inside lineBuf
edpos   : absolute cursor offset inside text
txbeg,txend : used span

flush():                          // eflush - write lineBuf back into text
    end = lineEnd(text, edpos) + 1
    clamp block markers bkbeg/bkend into [edpos..end]  (recompute from line buf)
    delta = oldLineLen - newLineLen
    if delta != 0: resizeText(end, delta)          // echgsize moves the tail
    copy lineBuf -> text[edpos..end-1]
    remap bkbegl/bkendl (lineBuf positions) -> bkbeg/bkend (text positions)

// Every handling that moves the cursor calls flush() first.

forMove(apply):                     // edn, eup, pageUp...: move + auto-scroll
    if rowJustChanged: flush()
    edpos = apply(edpos)
    load lineBuf from text at edpos; adjust disbeg (scrolling) as needed
    if target line out of window: set dislin for full redraw + scroll

5. Editing commands

insertChar(ch):                      // edput
    if linePos == lineEnd0: return   // "Line too long - CR inserted"
    if insertMode: makeRoomInLine()  // einsch (shift tail of lineBuf right)
    else:          (overwrite)
    lineBuf[lnpos] = ch; lnpos++
    redrawLine(); reposition()

insertLine():                        // CR / ^N (elinbrk)
    flush(); split text at edpos: move [edpos..endOfLine] down to next line;
    cursor to start of new line

deleteRight():                       // ^G
    if at line end: delete the CR (join with next line), efl2
    else: delete char, collapse, redraw
deleteLeft() (DEL):
    if pos==0: join previous line
    else: delete char at pos-1
deleteLine():                        // ^Y
    flush(); remove chars [lineStart..lineEndInclCarriageReturn]
    cursor to start of next line; redraw
deleteToEOL():                       // ^Q-Y
    from lnpos: [lnpos..lineEnd] = spaces / removed (fill with blanks is
    how TP3 draws it, then tail removed on next flush)
deleteWord():                        // ^T
    move right past word (separator scan), delete scanned chars
wordLeft()/wordRight():              // ^A / ^F
    scan SEPARATORS vs word chars (etstsep)

6. Block commands

markBlockStart(): bkbeg = edpos ('Marked' your position)
markBlockEnd()  : bkend = edpos
markWord()      : mark word under cursor (^K-T)
hideBlock()     : bkhide ^= 1       // invert display or not

testBlock():                         // etstblk
    if bkhide: restore hidden block first; beep; fail
    if bkbeg==bkend or unset: beep; fail

copyBlock():                         // ^K-C
    testBlock(); flush()
    makeGap(bkend, bklen); copy [bkbeg..bkend) into the gap  (echgsize)
    move block markers to the copy; redraw
moveBlock():                         // ^K-V
    testBlock(); flush()
    save block tail [bkend..], then delete [bkbeg..bkend), then reinsert
    tail at new cursor -> effectively moves the block text; restore line
deleteBlock():                       // ^K-Y
    testBlock(); flush()
    delete [bkbeg..bkend); collapse; block markers cleared; modified=true
readBlock(file):                     // ^K-R
    open file; read bytes; insert chunk at cursor; close; redraw
    on 'file too big' (etsterr) -> error path
writeBlock(file):                    // ^K-W
    if bkhide: un-hide first
    open/create file; write [bkbeg..bkend); append ^Z? (TP3 writes raw)
    close; error path on failure
quit():                              // ^K-D (ekd)
    restore screen attributes; return to shell (changed already tracked)

7. Search — find()

find(mode):                            // ^Q-F find  / ^Q-A replace
    status prompt: "Find word:" (or "...and replace with:")
    pattern -> srword buffer, length -> srword1
    if replace: prompt again, summary length -> srrepl1, text -> srrepl2
    ask options: prompt, keys set sropt bitfield:
        B=0x10 backward, G global, n=Nth occurrence, U ignore case,
        W whole word, N no-confirm (replace)
    scan from cursor:
        match candidates by walking CR lines; compare case per U flag;
        test word boundaries per W flag (SEPARATORS)
        remember candidate position + length
        if G: continue after each found position (repeat through whole text)
    if Nth: skip n-1 matches
    if found:
        if replace and not N: confirm "Replace (Y/N):" -> ^Y/^X or 'Y'
        doReplace(candidatePos, oldLen, newText, newLen)
            echgsize by delta; write replacement; modified=true
        cursor to end of match (TP3 leaves cursor after the word)
        if G: keep prompting/looping until text exhausted
    else: beep, "Not found", restore status

repeatSearch():                        // ^L
    rerun last find/replace with stored pattern+options, no prompts

8. Status line & display

statusLine():                          // estat
    if keyPending: return              // never paint while a key waits
    col = horscr + phcol
    if statObsolete:
        clear line;
        if width >= 56: print fileName right-aligned
        write "Line ", write "Col ", "  ", (Insert|Overwrite) "  ", (Indent|"  ")
    always refresh Col number (3-digit, low video); Line only when moved

monitorRedraw():                       // edmalin over rows disbeg..disbeg+22
    for each visible row:
        for chars from current line:
            attr = blockActive(pos in [bkbeg,bkend]) ? HIGH_VIDEO : NORMAL
            if bkhide: no block highlighting
            write char; stop at CR or row width
        pad/blank; handle horizontal scroll (horscr) for long lines
    never repaint if key is pending between rows

scrollUp()/scrollDown():               // ^W/^Z  - move disbeg, full redraw
pageUp()/pageDown()  :                 // ^R/^C
    move disbeg by 23 lines; clamp; full redraw (eredall)
moveTo(row, col):                      // setcpos via BIOS row/col
    repositions cursor physically; keeps phrow/phcol in sync

9. Literal prefix & abort

literalPrefix():                       // ^P
    echo "^"; ch = getKey(); insert ch literally (even control chars)

abort():                               // ^U or ESC
    cancel current find/replace prompt / block operation; restore old state

10. Error/edge handling

"Line too long - CR inserted":
    when insert would exceed lineend0 -> beep, treat as CR (line split)
"File too big":   readBlock when text would overflow txend limit -> etsterr
keyPending():     poll keyboard stat (kbdstat) without blocking
coldStart/editor init and modes: insert default ON, indent depends ^Q-I