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).
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)
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
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
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
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)
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)
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
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
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
"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