;Programmer's editor.  Assemble with TASM.  (C) David Nye, 1989.

IDEAL
MODEL TINY

MACRO String Name, Text
LOCAL L1
Name    db L1-Name-1, Text
L1:
ENDM

;Constants
LINELENGTH      EQU 80          ;Length of string storage for line
SCREENLENGTH    EQU 24          ;Number of rows in display window
MAXLINES        EQU 5000        ;Size of array of line pointers
BUFFERLENGTH    EQU 1200h       ;Length of file buffer
BELL            EQU 7           ;Some non-printing chars
BS              EQU 8
HT              EQU 9
LF              EQU 10
CR              EQU 13
CRLF            EQU 0A0Dh
CTRL_Z          EQU 26
ESCAPE          EQU 27
DEL             EQU 127

CODESEG
ORG 100h
start:
  mov ax, cs
  mov es, ax
  mov ah, 0Fh                   ;Get display segment
  int 10h
  mov bx, 0B000h                ;B000h for video mode 7 (= MDA or Herc)
  cmp al, 7
  je @@L2
  mov bx, 0B800h                ;B800h for the rest
@@L2:
  mov [cs:videoSegment], bx
  mov si, 80h                   ;Make pointer to command tail
  mov cl, [si]                  ;Get filename length
  sub ch, ch
  push cx                       ;Save a copy
  mov al, ' '                   ;Skip leading blanks
@@L1:
  inc si
  cmp al, [si]
  loope @@L1
  inc cx
  mov di, OFFSET fName          ;Move command tail to FName
  rep movsb
  sub al, al                    ;Make ASCIIZ string
  stosb
  mov ax, 2523h                 ;Redirect Ctrl C handler
  mov dx, cs
  mov ds, dx
  mov dx, OFFSET Cancel
  int 21h
InitFile:
  mov ax, cs                    ;Compute starting segment of heap
  mov ds, ax
  add ax, 1000h
  mov [heapStart], ax
  mov dx, OFFSET fName          ;Open file and set up list of line pointers
  pop ax                        ;If no file name specified on command line,
  or ax, ax
  jne @@L0
  call GetFileName              ;Prompt for it
@@L0:
  call OpenFile

NextKey:
  call Redraw                   ;Redraw screen, status line
NextNoRedraw:
  call DrawCursor               ;Place cursor
  sub ah, ah                    ;Get keypress to AL
  int 16h
  or al, al                     ;Check for control codes
  je IsAux
  cmp al, ' '
  jl IsCtrl
  call Insert                   ;Insert or overwrite if none
  jmp NextKey

IsAux:
  xchg al, ah                   ;Get aux code
  cmp al, 132
  ja NextKey
  mov si, OFFSET auxTable       ;Jump indirect to appropriate routine
DoTableJump:
  shl ax, 1
  add si, ax
  call [WORD si]
  jmp NextKey

IsCtrl:
  mov si, OFFSET ctrlTable      ;Jump to routine through table
  sub ah, ah
  jmp DoTableJump

OtherFile:
;Close current file and open another
  test [changed?], -1           ;If previous file was changed,
  jz @@L0
  call Save                     ;Save file and close it,
  jmp SHORT @@L1
@@L0:
  test [newFile?], -1           ;If unchanged but an old file, just close it
  jnz @@L2
@@L1:
  mov bx, [fHandle]
  mov ah, 3Eh
  int 21h
@@L2:
  mov ax, [blockPtrsLast]       ;If block buffer is empty,
  sub ax, OFFSET blockPtrs
  jne @@L2a
  mov dx, OFFSET fName          ; prompt for new file name
  call GetFileName
  jmp SHORT OpenFile            ; read it in
@@L2a:
  shr ax, 1                     ;Else move lines with pointers in block buffer
  mov cx, ax                    ; to start of heap (load new file above them)
  mov dl, 5
  mul dl
  add ax, [heapStart]           ;Calculate upper limit of target zone
  mov [heapPtr], ax             ; which will also be new value of heap pointer
  mov bx, OFFSET blockPtrs      ;For each pointer in block buffer,
@@L3:
  cmp [bx], ax                  ;If its line is already within target zone,
  jae @@L4
  mov es, [bx]                  ;Set high bit of first char in line to mark it
  or [byte es:0], 80h           ; (we won't need to move these lines)
@@L4:
  inc bx                        ;Next pointer
  inc bx
  loop @@L3
  push ds
  mov bx, OFFSET blockPtrs      ;For each pointer in block buffer:
  mov es, [heapStart]
@@L4a:
  test [byte es:0], 80h         ;If high bit set in target line,
  jne @@L7                      ; line already in use, try next target line
  mov ds, [cs:bx]               ;If high bit set in source line,
  test [byte 0], 80h
  je @@L5                       ; don't need to move it (already there)
  inc bx                        ; Next source line, same target line
  inc bx
  jmp SHORT @@L8
@@L5:
  mov cx, 40                    ;Else move one line
  sub si, si
  sub di, di
  rep movsw
  mov [cs:bx], es               ;Update pointer
@@L6:
  inc bx                        ;Next block pointer
  inc bx
@@L7:
  mov ax, es                    ;Next target line
  add ax, 5
  mov es, ax
@@L8:
  cmp bx, [cs:blockPtrsLast]    ;Loop until all lines moved
  jb @@L4a
  pop ds
  mov bx, OFFSET blockPtrs      ;Reset high bits of all first chars set high
@@L9:
  mov es, [bx]
  and [byte es:0], 7Fh
  inc bx
  inc bx
  cmp bx, [blockPtrsLast]
  jb @@L9
  mov dx, OFFSET fName          ;Prompt for new file name
  call GetFileName
  jmp SHORT OpenFile1           ;Read in new file above block buffer lines

OpenFile:
;Open file, load if found.  Call with dx -> ASCIIZ file name.
  mov ax, OFFSET blockPtrs      ;Reset block buffer pointer
  mov [blockPtrsLast], ax
  mov ax, [heapStart]           ;Begin loading at start of heap
  mov [heapPtr], ax
OpenFile1:
  mov [newFile?], 0
  mov [changed?], 0
  mov ax, 3D02h                 ;Try to open file
  int 21h
  jnc OldFile
  mov [newFile?], -1            ;If no such file, create a new one
  call NewFile
  jmp XOpenFile
OldFile:
  mov [fHandle], ax             ;Else save file handle
  mov bx, OFFSET linePtrs       ;Read file in
  mov dx, [fHandle]
  call ReadFile
  dec bx
  dec bx
  mov [lastLine], bx            ;Save index of last line
XOpenFile:
  mov bx, OFFSET linePtrs       ;Reset row, screen pointers
  mov [top], bx
  mov es, [bx]
  sub di, di
Ret0:
  ret

GetFileName:
;Prompt for file name.  Abort if null name.  Call with buffer address in DX.
  push si
  push dx
  mov si, OFFSET newFileMsg     ;Print prompt
  call Prompt
  pop dx
  call GetString                ;Get file name
  mov si, dx                    ;Convert to ASCIIZ
  add si, ax
  mov [BYTE si], 0
@@Lx:
  pop si
Ret5:
  ret

GetString:
;Get string to [DX], return count (minus CR/LF) in AX.  Abort if null string.
  push bx
  push cx
  push si
  push di
  push es
  push dx
  mov dx, OFFSET pad            ;Get string
  mov ah, 3Fh
  sub bx, bx
  mov cx, 20
  int 21h
  dec ax                        ;Strip CR/LF
  dec ax
  jz @@La                       ;Abort if null string
  mov cx, ax                    ;Copy temporary copy of string to [DX]
  pop dx
  push ax
  mov ax, ds
  mov es, ax
  mov si, OFFSET pad
  mov di, dx
  rep movsb
  pop ax
  pop es
  pop di
  pop si
  pop cx
  pop bx
  ret
@@La:
  mov si, OFFSET cancelledMsg   ;Abort if null string
  jmp Abort

ReadFile:
;Load file with handle in DX into memory, assigning segment pointers from BX
  push es                                        ÿ
  mov ax, [heapPtr]                            ÿ
  mov es, ax
  sub cx, cx
  sub di, di
FillBuffer:                                      ÿ
  push bx                       ; Fill buffer
  push cx
  push dx
  mov bx, dx
  mov ah, 3Fh
  mov cx, BUFFERLENGTH
  mov dx, OFFSET buffer
  int 21h
  jnc @@L1                      ; Check for read error
  jmp ReadError
@@L1:
  pop dx
  pop cx
  pop bx
  mov si, OFFSET buffer         ; Set pointers
  add ax, si
  mov [bufferPtr], ax
  cmp ax, OFFSET buffer         ;Exit if empty buffer
  je EndOfFile
  cmp [byte si], LF             ;Skip LF if first char in buffer
  jne SHORT NextLine
  inc si
NextLine:
  mov al, [si]                  ;Get next char
  cmp al, CR                    ;If char is CR, end of line
  jne @@L2
  inc si                        ; move past CR
  cmp [byte si], LF             ; and LF if present
  jne @@L1
  inc si
@@L1:
  call EndOfLine                ; pad out line with spaces and save it
  jmp SHORT @@L3
@@L2:
  cmp al, HT                    ;Else if a tab, expand it
  jne @@L2a
  push cx
  mov al, ' '
  mov cl, [tabSize]
  sub ch, ch
  rep stosb
  pop cx
  sub cl, [tabSize]
  sbb ch, 0
  inc si
  jmp SHORT @@L3
@@L2a:
  movsb                         ;Else add char to line
  dec cx
@@L3:
  cmp si, [bufferPtr]           ;Loop until end of buffer
  jb NextLine
  cmp si, OFFSET bufferEnd      ;If buffer less than full, indicates end of file
  jae FillBuffer
EndOfFile:
  cmp bx, OFFSET linePtrs       ;If an empty file,
  jne @@L0
  call NewFile                  ; set up as a new file (single blank line)
  jmp SHORT @@L1
@@L0:
  or di, di                     ;Finish up present line if anything is on it
  je @@L1
  call EndOfLine
@@L1:
  mov [heapPtr], es             ;Update pointer to start of free heap space
  pop es
  ret

EndOfLine:
  add cx, LINELENGTH            ;Pad to end with spaces
  jle @@L1                      ;Truncate lines longer than 80 chars
  mov al, ' '
  rep stosb
@@L1:
  mov [bx], es                  ;Store segment of this line
  mov ax, es                    ;Next line
  add ax, 5
  cmp ax, 0A000h                ;Out of room?
  jae SHORT TooBig
  mov es, ax
  inc bx
  inc bx
  sub di, di
  sub cx, cx
Ret3:
  ret
TooBig:
  mov si, OFFSET noRoomMsg
  jmp Abort

Redraw:
;Redraw screen and status line
  mov [here], bx
  mov [hereCol], di
  push bx
  push di
  push ds
  push es
  push di
  mov es, [videoSegment]        ;Get segment for display
  mov si, OFFSET editingMsg     ;Refresh status line:  "Editing ..."
  call Prompt
  mov di, 34                    ;Tab to column 17
  mov si, OFFSET fName          ; <file name>
  mov ah, [attribInv]
@@S1:
  lodsb
  or al, al
  je @@S2
  stosw
  jmp @@S1
@@S2:
  add di, 6                     ;3 spaces
  mov al, 'L'                   ;"L" <line #>
  stosw
  inc di
  inc di
  mov ax, bx
  sub ax, OFFSET linePtrs
  shr ax, 1
  inc ax
  call PrintInt
  add di, 4                     ;2 spaces
  mov al, 'C'                   ;"C" <column number>
  mov ah, [attribInv]
  stosw
  inc di
  inc di
  pop ax                        ;Get copy of DI as cursor row
  inc ax
  call PrintInt
  mov di, 152                   ;Tab to column 76
  mov al, 'I'                   ;Insert/Overwrite status
  mov ah, [attribInv]
  test [inserting?], -1
  jne @@S3
  mov al, 'O'
@@S3:
  stosw
  mov al, 'C'                   ;Changed status
  test [changed?], -1
  jne @@S5
  mov al, ' '
@@S5:
  stosw
  mov al, 'A'
  test [autoIndent?], -1
  jne @@S6
  mov al, ' '
@@S6:
  stosw
  mov di, 160                   ;Move to next display line
  mov ax, [top]                 ;Compute bottom of screen
  mov bx, ax
  add ax, (SCREENLENGTH - 1) * 2
  cmp ax, [lastLine]            ;If at end of file,
  jle @@L0
  mov ax, [lastLine]            ; stop at lastLine
@@L0:
  mov [bottom], ax
@@L1:                           ;For each row
  mov cx, 80                    ;Count of chars per row
  mov ds, [cs:bx]               ;Get pointer to screen line
  sub si, si                    ;Initialize column counter
  mov ah, [cs:attribNl]         ;Attribute = inverse video if Marked
  test [cs:marking?], -1
  je @@L2
  cmp bx, [cs:mark]
  je @@L1b
  jb @@L1a
  cmp bx, [cs:here]
  jbe @@L1b
  jmp SHORT @@L2
@@L1a:
  cmp bx, [cs:here]
  jb @@L2
@@L1b:
  mov ah, [cs:attribInv]
@@L2:                           ;For each char, write char and attribute
  lodsb
  stosw
  loop @@L2                     ;Next char
  inc bx                        ;Next row
  inc bx
  cmp bx, [cs:bottom]           ;Stop if screen full
  jle @@L1
  mov cx, 160*(SCREENLENGTH+1)  ;Fill out screen with blanks
  sub cx, di
  shr cx, 1
  mov ah, [cs:attribNl]
  mov al, ' '
  rep stosw
@@L3:
  pop es
  pop ds
  pop di
  pop bx
  ret

DrawCursor:
;Set cursor shape and place it on screen
  push bx
  mov cl, 13                    ;Set cursor shape depending on Inserting?
  mov ch, 12                    ;Line for insert mode,
  test [Inserting?], -1
  jne @@L1
  sub ch, ch                    ;Block for overwrite
@@L1:
  mov ah, 1
  int 10h
  sub bx, [top]                 ;Show cursor at current row, column
  shr bx, 1
  inc bx
  mov dh, bl
  mov ax, di
  mov dl, al
  mov ah, 2
  mov bh, 0
  int 10h
  pop bx
  ret

Print0:
  call ClearStatus              ;Blank status line
  sub di, di                    ;Starting at beginning of line ...

Print:
;Print string pointed to by SI on status line in inverse video, starting at DI
  push es
  mov es, [videoSegment]
  lodsb                         ;Get count of string to be printed
  mov cl, al
  sub ch, ch
  mov ah, [attribInv]            ;Attribute = inverse video
@@L1:
  lodsb
  stosw
  loop @@L1
  pop es
  ret

ClearStatus:
;Inverse-blank status line
  push di
  push es
  mov es, [videoSegment]
  mov ah, [attribInv]
  mov al, ' '
  mov cx, 80
  sub di, di
  rep stosw
  pop es
  pop di
  ret

Cancel:
;Ctrl C routine
  mov si, OFFSET ctrlCMsg       ;Abort with message ...

Abort:
;Print counted string pointed to by SI on status line and abort
  call Print0                   ;Print error message ...

na:
;Unassigned key or other error.  Beep and abort.
  call Beep                     ;Beep
  mov ax, -2                    ;Reset stack pointer to top
  mov sp, ax
  mov bx, [here]                ;Retrieve cursor position in case it was trashed
  mov di, [hereCol]
  call DrawCursor
  jmp NextNoRedraw              ;Restart main editing loop

Beep:
  mov ah, 2                     ;Output a BELL
  mov dl, BELL
  int 21h
  ret

Prompt:
  push di
  call Print0                   ;Print string at start of line
  mov dx, di                    ;Set cursor to end of printed string ...
  shr dl, 1
  sub dh, dh
  pop di

GotoXY:
;Position cursor at row, column given by DL, DH
  push bx
  mov ah, 2
  sub bx, bx
  int 10h
  pop bx
  ret

PrintInt:
;Print to ES:DI in inverse video the unsigned decimal integer in AX
  sub dx, dx                    ;Start stack with a null
  push dx
  or ax, ax                     ;If integer = 0,
  jne @@L0
  push dx                       ; push another 0 and skip divisions
  jmp SHORT @@L2
@@L0:
  mov cx, 10                    ;Get remainders of successive divisions by 10
@@L1:
  div cx
  add dl, '0'                   ;Convert to ASCII
  mov dh, [attribInv]            ;Attribute is reverse video
  push dx
  sub dx, dx
  or ax, ax
  jne @@L1
@@L2:
  pop ax                        ;Pop and print remainders in reversed order
@@L3:
  stosw
  pop ax
  or ax, ax
  jne @@L3
  ret

NewLine:
;Jump here if cursor row is changed by a command
  mov [justFound?], 0
NewLine0:
  cmp bx, OFFSET linePtrs       ;Check bounds, adjust if necessary
  jge @@L1
  mov bx, OFFSET linePtrs
@@L1:
  cmp bx, [lastLine]
  jle @@L2
  mov bx, [lastLine]
@@L2:
  mov ax, [top]   ÿ
  cmp bx, ax
  jge @@L3
  mov [top], bx
@@L3:
  add ax, (SCREENLENGTH-1)*2
  cmp bx, ax
  jle @@L4
  mov ax, bx
  sub ax, (SCREENLENGTH-1)*2
  mov [top], ax
@@L4:
  mov es, [bx]                  ;Adjust ES to point to new line
  ret

Left:
  or di, di                     ;If at start of line,
  jne @@L1
  call Up                       ; move to end of line above
  jmp EndLine
@@L1:
  dec di                        ; else just decrement cursor
CursorMoved:
  mov [justFound?], 0
  ret

Right:
  cmp di, LINELENGTH - 1        ;If at end of line,
  jne @@L1
  sub di, di                    ; move to start of line below
  jmp Down
@@L1:
  inc di                        ; else just increment cursor
  jmp SHORT CursorMoved

LScanE:
;Scan left past first non-space or start of line
  mov al, ' '     ÿ
  mov cx, di
  inc cx
  std
  repe scasb
  cld
  ret

LScanNE:
;Scan left past first space or start of line
  mov al, ' '
  mov cx, di
  inc cx
  std
  repne scasb
  cld
  ret

RScanE:
;Scan right past first non-space or end of line
  mov al, ' '
  mov cx, LINELENGTH
  sub cx, di
  repe scasb
  ret

RScanNE:
;Scan right past first space or end of line
  mov al, ' '
  mov cx, LINELENGTH
  sub cx, di
  repne scasb
  ret

WordLeft:
;Move left one word
  or di, di                     ;Do nothing if at start of line
  je @@Lx
  mov [justFound?], 0
  dec di                        ;Else starting at char to left,
  call LScanE                   ; skip spaces until non-space
  inc di
  je @@Lx                       ; or start of line,
  call LScanNE                  ; then scan to next space or start of line
  jne @@L1
  inc di
@@L1:
  inc di
@@Lx:
  ret

WordRight:
;Move right one word
  cmp di, LINELENGTH - 1        ;Do nothing if at end of line
  je @@Lx
  mov [justFound?], 0
  call RScanNE                  ;Skip non-spaces until space
  jne @@L1                      ; or end of line,
  dec di
  call RScanE                   ; then scan to next non-space or end of line
@@L1:
  dec di
@@Lx:
  ret

HomeLine:
;Move cursor to column zero
  sub di, di
  mov [justFound?], 0
  ret

EndLine:
;Move cursor to end of line
  push cx
  mov [justFound?], 0
  mov di, LINELENGTH - 1        ;Start at end of line
  call LScanE                   ;Skip all spaces until non-space
  je @@L1                       ; or beginning of line
  inc di
  cmp di, LINELENGTH - 1
  je @@L2
@@L1:
  inc di
@@L2:
  pop cx
Ret2:
  ret

Up:
;Move cursor up one line
  cmp bx, OFFSET linePtrs       ;If at top of file already, do nothing
  je Ret2
  dec bx
  dec bx
  jmp NewLine

Down:
;Move cursor down one line
  cmp bx, [lastLine]            ;If at last line already, do nothing
  je Ret2
  inc bx
  inc bx
  jmp NewLine

PageUp:
;Move cursor up one page
  sub bx, (SCREENLENGTH-1)*2
  jmp NewLine

PageDown:
;Move cursor down one page
  add bx, (SCREENLENGTH-1)*2
  jmp NewLine

TopFile:
;Move cursor to top of file
  mov bx, OFFSET linePtrs
  mov [top], bx
  call HomeLine
  jmp NewLine

BottomFile:
;Move cursor to bottom of file
  mov bx, [lastLine]
  mov es, [bx]
  mov ax, bx
  sub ax, (SCREENLENGTH-1)*2
  cmp ax, OFFSET linePtrs
  ja @@L1
  mov ax, OFFSET linePtrs
@@L1:
  mov [top], ax
  call EndLine
  jmp NewLine

Tab:
;Tab right
  mov [justFound?], 0
  mov ax, di                    ;Advance di to next tab stop
  mov cl, [tabSize]
  div cl
  sub cl, ah
  sub ch, ch
  add di, cx
  cmp di, LINELENGTH            ;If past end of line,
  jl @@L1
  mov di, LINELENGTH - 1        ;Set cursor at end of line
@@L1:
  ret

ReverseTab:
;Tab left
  mov [justFound?], 0
  mov ax, di                    ;Decrement di to nearest tab stop
  dec al
  div [tabSize]
  mov al, ah
  sub ah, ah
  inc al
  sub di, ax
  jnc @@L1                      ;Set to start of line if past start
  sub di, di
@@L1:
  ret

CRet:
;Split line at cursor
  push ds
  push es
  push di
  push es
  call InsertLine               ;Start a new line below current one, ->ES:DI
  pop ds                        ;DS:SI := current cursor position
  pop si
  push di
  mov cx, LINELENGTH            ;CX := # chars left on line
  sub cx, si
  je @@L2
@@L1:
  movsb                         ;Split line,
  mov [byte si - 1], ' '        ; blank original to end from cursor
  loop @@L1
@@L2:
  pop di
  pop es
  pop ds
  mov [changed?], -1            ;Mark file as changed
  jmp NewLine

InsertLine:
;Insert a new blank line below current one
  mov cx, 1                     ;Make room for new entry in linePtr
  call OpenRow
  inc bx
  inc bx
  mov ax, [heapPtr]
  jmp SHORT BlankLine

NewFile:
;Set up initial blank line of a new file
  mov ax, [heapStart]           ;Set ES and [bx] to available heap
  mov bx, OFFSET linePtrs
  mov [lastLine], bx
BlankLine:
  mov [bx], ax
  mov es, ax
  add ax, 5
  mov [heapPtr], ax             ;Update heap pointer (segment value only)
  sub di, di                    ;Blank new line
  mov cx, LINELENGTH
  mov al, ' '
  rep stosb
  sub di, di                    ;Home cursor on new line
  test [autoIndent?], -1        ; or if in autoindent mode,
  je @@Lx
  cmp bx, OFFSET linePtrs       ; and this is not first line in file,
  je @@Lx
  mov es, [bx - 2]              ; line up with first char of line above
  call RScanE
  mov es, [bx]
  dec di
  cmp di, LINELENGTH - 1        ; unless above line is blank
  jb @@Lx
  sub di, di
@@Lx:
  ret

OpenRow:
;Open CX lines at BX in linePtrs
  push cx
  push di
  push es
  mov ax, ds                    ;DS, ES -> data segment (for linePtr)
  mov es, ax
  mov si, [lastLine]            ;SI points to last line's segment pointer
  mov di, si                    ;DI points CX lines beyond that
  add di, cx
  add di, cx
  mov [lastLine], di            ;Point lastLine to new last line
  mov cx, si                    ;Count = # lines from here to end
  sub cx, bx
  shr cx, 1
  inc cx
  std                                                               ÿ
  rep movsw                     ;Move array elements up
  cld
  pop es
  pop di
  pop cx
  ret

Backspace:
;Delete char to left of cursor
  mov ax, di                    ;Unless at first character of file,
  add ax, bx
  sub ax, OFFSET linePtrs
  jz Ret1                       ; do Left then Delete
  mov [justFound?], 0
  push di
  call Left
  pop ax                        ;Don't do Join if already at end of line ...
  or ax, ax
  jne Delete0

Delete:
;Delete char at cursor
  mov dx, di                    ;Save cursor column
  cmp [byte ptr es:LINELENGTH-1], ' ' ;If deleting a space at end of line,
  jne Delete0
  call EndLine
  xchg di, dx
  cmp di, dx
  jge Join                      ; join to line below
Delete0:
  push di                       ; else slide text left
  push cx
  push ds
  mov cx, LINELENGTH - 1
  sub cx, di
  mov si, di
  inc si
  mov ax, es
  mov ds, ax
  rep movsb
  mov [BYTE di], ' '            ;Blank last character on line
  pop ds
  pop cx
  pop di
  mov [changed?], -1
Ret1:
  ret

UndeleteLine:
  mov bp, [delLinePtrsLast]     ;Abort if no lines are in buffer
  cmp bp, OFFSET delLinePtrs
  ja @@L0
  jmp Beep
@@L0:
  dec bp                        ;Else move pointer to top line of delete buffer
  dec bp
  mov [delLinePtrsLast], bp
  or di, di                     ;If cursor is at start of line,
  jne @@L1
  mov cx, 1
  call OpenRow                  ;Start new row below current one
  mov [bx+2], es                ;Swap rows to insert undeleted above current
  mov ax, [bp]                  ;Retrieve and store pointer to undeleted line
  mov [bx], ax
  jmp NewLine
@@L1:
  mov cx, LINELENGTH            ;Cursor not at start of line
  sub cx, di                    ;Copy popped line over current one
  push di
  push ds
  mov ds, [bp]
  sub si, si
  rep movsb
  pop ds
  pop di
  ret

Join:
;Join lower line to current line at cursor
  cmp bx, [lastLine]            ;Abort if this is the last line of the file
  je @@Lx
  push di                       ;Save registers
  push ds
  push di
  push es
  mov es, [bx + 2]              ;Get next line's segment
  push es                       ;Save a copy
  mov dx, di                    ;Get length of lower line:
  call EndLine                  ;Find first non-space char from end
  add dx, di                    ;If concatenated line is too long, abort.
  cmp dx, LINELENGTH
  jbe @@L0
  call Beep
  pop ax
  pop es
  pop di
  pop ds
  pop ax
  jmp Ret1
@@L0:
  mov cx, di                    ;Count = lower line length
  sub si, si                    ;Source = start of lower line
  pop ds
  pop es                        ;Destination = present cursor location
  pop di
  rep movsb                     ;Concatenate lines
  pop ds
  inc bx                        ;Delete lower line
  inc bx
  call DeleteLineNS
  cmp bx, [lastLine]
  je @@L1
  dec bx
  dec bx
@@L1:
  pop di                        ;Restore pointers and return
@@Lx:
  jmp NewLine

Insert:
;Insert or overwrite at cursor
  mov [justFound?], 0
  test [inserting?], -1         ;If inserting, open up space for new character
  jz Insert1
  cmp [BYTE es:LINELENGTH - 1], ' ' ;If line is full, split it
  je Insert0
  push ax
  push bx
  push di
  call CRet
  pop di
  pop bx
  call NewLine
  pop ax
  jmp SHORT Insert1
Insert0:
  push ax
  push cx
  push ds
  mov ax, es
  mov ds, ax
  mov si, LINELENGTH - 1
  mov cx, si
  sub cx, di
  mov di, si
  dec si
  std
  rep movsb
  cld
  pop ds
  pop cx
  pop ax
Insert1:
  stosb                         ;Add character
  mov [changed?], -1
  cmp di, LINELENGTH            ;Don't advance DI if at end of line
  jb @@L1
  dec di
@@L1:
  ret

DeleteToEOL:
;Delete from cursor to end of line
  mov [justFound?], 0
  push bx                       ;Save regs to return to current cursor position
  push di
  push es
  push [word autoIndent?]       ;Turn autoIndent off
  mov [autoIndent?], 0
  call Cret                     ;Do Enter then delete lower line
  call DeleteLine
  pop [word autoIndent?]
  pop es
  pop di
  pop bx
  jmp NewLine

DeleteLine:
;Delete cursor line and append to buffer
  mov bp, [delLinePtrsLast]     ;Save segment of current line in delete buffer
  mov [bp], es
  inc bp
  inc bp
  mov [delLinePtrsLast], bp
DeleteLineNS:                   ;Enter here if we don't want to save line
  mov di, bx                    ;Delete line:  destination = this line
  mov si, di                    ;Source = next line
  inc si
  inc si
  mov cx, [lastLine]            ;Count = number of lines from here to end
  mov ax, cx
  dec ax
  dec ax
  mov [lastLine], ax
  sub cx, bx
  shr cx, 1
  mov ax, ds                    ;Move line segment values above cursor down
  mov es, ax    ÿ
  rep movsw
  mov [changed?], -1
  sub di, di                    ;Home cursor on new line
  jmp NewLine

DeleteWordL:
;Delete left to space or column zero
  mov si, di                    ;Save cursor column
  call WordLeft                 ;Tab left one word
  push di
CloseGap:
  mov cx, LINELENGTH            ;Close gap between di and si cursor positions
  sub cx, si
  push ds
  mov ax, es
  mov ds, ax
  rep movsb
  mov cx, LINELENGTH            ;Pad end of line with spaces
  sub cx, di
  mov al, ' '
  rep stosb
  mov [changed?], -1
  pop ds
  pop di
  ret

DeleteWordR:
;Delete right to space or end of line
  mov si, di                    ;Save cursor
  push di
  call WordRight                ;Tab right one word
  xchg si, di                   ;Close up space between si and di
  jmp CloseGap

ToggleIns:
  not [inserting?]
  ret

Jump:
;Jump to line number n
  mov si, OFFSET gotoMsg
  call Prompt
  call GetInt
  dec ax
  shl ax, 1
  mov bx, ax
  add bx, OFFSET linePtrs
  mov [justFound?], 0
  jmp JL1                       ;Jump to address

GetInt:
;Get a decimal integer from keyboard to AX.  Carry set on improper input.
;Abort if null input.
  push bx
  push cx
  push dx
  push si
  mov dx, OFFSET buffer
  call GetString                ;Input a string
  mov cx, ax                    ;Construct integer a digit at a time
  mov si, OFFSET buffer
  sub ax, ax
  mov bh, 10
@@L1:
  mov bl, [si]                  ;Get next char
  inc si
  sub bl, '0'
  jc @@Lx                       ;Exit with carry set if not a digit
  cmp bl, '9'
  cmc
  jc @@Lx
  mul bh                        ;AX := AX + (new digit - '0')
  add al, bl
  adc ah, 0
  jc @@Lx                       ;Check for overflow
  loop @@L1                     ;Next char
@@Lx:
  pop si                        ;Return with int in AX, carry set if error
  pop dx
  pop cx
  pop bx
  ret
@@La:
  mov si, OFFSET cancelledMsg
  jmp Abort

SetLabel:
;Set label 0-9 at current line
  call GetLabel
  mov [si], bx
  ret

GotoLabel:
;Goto label 0-9 previously set by SetLabel
  call GetLabel
  cmp [WORD si], 0
  je JLx
  mov bx, [si]                  ;Retrieve address
  mov [justFound?], 0
JL1:
  mov ax, bx
  sub ax, 8                     ;Make cursor line fifth from top
  cmp ax, OFFSET linePtrs
  jge @@L1
  mov ax, OFFSET linePtrs
@@L1:
  mov [top], ax
JLx:
  jmp NewLine0

GetLabel:
  mov si, OFFSET setLabelMsg
  call Prompt
  mov ah, 8                     ;Get char from keyboard
  int 21h
  mov dl, al                    ;Save copy to echo
  sub al, '0'                   ;Don't accept input if not a digit
  jl GetLabel
  cmp al, 9
  jg GetLabel
  mov ah, 2
  mov cl, al
  int 21h
  mov si, OFFSET LabelTable     ;Form index into LabelTable
  shl cl, 1
  sub ch, ch
  add si, cx                    ;Return address of label storage in SI
  ret

SetTabs:
;Set tab width
  mov si, OFFSET setTabsMsg
  call Prompt
  call GetInt
  mov [tabSize], al
  ret

AutoIndent:
;Toggle autoindent mode
  not [autoIndent?]
  ret

Kill:
;Clear changed? flag so file changes will be discarded on exit
  mov [changed?], 0
  ret

Save:
;Write lines to file, renaming old version with .BAK extension
  push dx
  push di
  push es
  push bx
  mov al, [changed?]            ;If no changes, done.
  or al, al
  jnz @@L0
  jmp XSave
@@L0:
  mov al, [newFile?]            ;If a new file, create it first
  or al, [isBAKed?]             ;If already BAKed up, no .BAK needed
  jnz DoSave
  mov ah, 3Eh                   ;Else close file
  mov bx, [fHandle]
  int 21h
  mov ax, ds
  mov es, ax
  mov si, OFFSET fName          ;Make new ASCIIZ string with .BAK extension
  mov di, OFFSET fNameBAK
@@L1:
  lodsb
  cmp al, '.'
  je @@L2
  or al, al
  je @@L2
  stosb
  jmp SHORT @@L1
@@L2:
  mov cx, 4
  mov si, OFFSET BAK
  rep movsb
  mov ah, 41h                   ;Delete old back-up copy
  mov dx, OFFSET fNameBAK
  int 21h
  mov dx, OFFSET fName          ;Rename current file to file.BAK
  mov di, OFFSET fNameBAK
  mov ah, 56h
  int 21h
DoSave:
  mov ah, 3Ch                   ;CREATe new file with old name
  sub cx, cx
  mov dx, OFFSET fName
  int 21h
  jc CantOpen
  mov [fHandle], ax
  mov [isBAKed?], -1            ;Set flag so we only make .BAK file once
  mov bx, OFFSET linePtrs       ;Write file
  call WriteFile
XSave:
  pop bx
  pop es
  pop di
  pop dx
  ret

WriteFile:
;Write lines out to file starting at BX and ending at [lastLine]
  push es
  push di
  mov di, OFFSET buffer
@@L1:
  mov si, di                    ;Preserve file buffer pointer
  mov es, [bx]                  ;Strip trailing blanks
  mov cx, LINELENGTH
  mov di, LINELENGTH - 1
  mov al, ' '
  std
  repe scasb
  cld
  je @@L1a
  inc cx
@@L1a:
  mov ax, ds                    ;Copy line to file buffer
  mov dx, es
  mov es, ax
  mov ds, dx
  mov di, si
  sub si, si
  rep movsb
  mov ax, CRLF                  ;Stick a CRLF on the end
  stosw
  mov ax, ds
  mov dx, es
  mov es, ax
  mov ds, dx
  cmp di, OFFSET Buffer + BUFFERLENGTH - 80  ;If buffer is almost full,
  jl @@L2
  call WriteBuffer              ; write it
@@L2:
  inc bx                        ;Next line, loop until all lines are written
  inc bx
  cmp bx, [lastLine]
  jle @@L1
  call WriteBuffer              ;Write final partial buffer to file and exit
  pop di
  pop es
  ret

FileError:
  mov si, OFFSET fileErrorMsg
  jmp Abort
CantOpen:
  mov si, OFFSET cantOpenMsg
  jmp Abort
NoRoom:
  mov si, OFFSET noRoomMsg
  jmp Abort
ReadError:
  mov si, OFFSET rdErrorMsg
  jmp Abort

WriteBuffer:
;Write text in buffer to disk
  push bx
  mov ah, 40h
  mov bx, [fHandle]
  mov cx, di
  mov dx, OFFSET Buffer
  sub cx, dx
  jz @@L1
  int 21h
  jc FileError
  mov di, OFFSET Buffer
@@L1:
  pop bx
  ret

Exit:
  call Save                     ;Save file if changed
  mov cx, 0C0Dh                 ;Restore standard cursor size
  mov ah, 1
  int 10h
  mov dx, 1800h                 ;Put cursor at bottom of screen
  sub bh, bh
  mov ah, 2
  int 10h
  mov ax, 4C00h                 ;Bye!
  int 21h

BeginBlock:
;Start marking block for block operation
  mov [marking?], -1
  mov [mark], bx
  ret

Unmark:
;Clear marking
  mov [marking?], 0
  ret

InsertBlock:
;Insert from buffer or named file
  push bx
  call FileOrBuffer?            ;From file or buffer?
  je InsertBuffer
  mov dx, OFFSET fNameBlock     ;If file, open it
  mov ax, 3D00h
  int 21h
  jnc @@L1
  jmp CantOpen
@@L1:
  mov dx, ax                    ;Load file
  mov bx, OFFSET blockPtrs                                ÿ
  mov di, bx
  call ReadFile
  mov cx, bx
  mov bx, dx                    ;Close file
  mov ah, 3Eh
  int 21h
  jmp SHORT DoInsert
InsertBuffer:                   ;Insert from buffer
  mov bp, [blockPtrsLast]       ;Abort if empty
  cmp bp, OFFSET blockPtrs
  jne @@L0
  pop bx
  jmp na
@@L0:
  test [needCopies?], -1        ;If not just moving lines, need to duplicate
  jz @@L2
  push bx
  push es
  push ds
  mov bx, bp
  mov dx, [heapPtr]
@@L1:
  dec bx                        ;Copy contents of buffered lines to new ones
  dec bx
  mov ds, [cs:bx]
  sub si, si
  mov es, dx
  sub di, di
  mov [cs:bx], es
  mov cx, LINELENGTH
  rep movsb
  add dx, 5
  cmp dx, 0A000h
  jb @@L1a
  jmp TooBig
@@L1a:
  cmp bx, OFFSET blockPtrs
  ja @@L1
  pop ds
  pop es
  pop bx
  mov [heapPtr], dx
@@L2:
  mov [needCopies?], -1
  mov cx, bp
DoInsert:
  pop bx
  sub cx, OFFSET blockPtrs      ;Get count of lines to move
  shr cx, 1
  call OpenRow                  ;Open that much room in array of seg pointers
  mov si, OFFSET blockPtrs      ;Copy new lines into opening
  mov di, bx
  mov ax, ds
  mov es, ax
  rep movsw
  mov [changed?], -1
  sub di, di
  jmp NewLine

FileOrBuffer?:
;Prompt for file name if Shift is down, put it in fNameBlock.
;If return with Z set, Shift was up, indicating buffer is to be used.
  call Shifted?                 ;If shift is down, prompt for file name
  jz Ret4
  mov dx, OFFSET fNameBlock
  call GetFileName
  jnz Ret4                      ;If null string returned, abort
  mov si, OFFSET cancelledMsg
  jmp Abort

Shifted?:
  mov ah, 2                     ;Get shift key status
  int 16h
  and al, 3
Ret4:
  ret

EmptyBuffer:
;If Shifted, write block buffer to file, else discard
  mov bp, [blockPtrsLast]       ;Abort if buffer is empty
  cmp bp, OFFSET blockPtrs
  jne @@L0
  jmp Beep
@@L0:
  call Shifted?                 ;If shifted, write to file
  je @@L1
  push bx
  mov dx, OFFSET fNameBlock
  call GetFileName
  mov si, OFFSET blockPtrs
  mov bx, bp
  dec bx
  dec bx
  call WriteBlock
  pop bx
@@L1:
  mov ax, OFFSET blockPtrs      ;Else just reset buffer pointer
  mov [blockPtrsLast], ax
@@Lx:
  ret

WriteBlock:
;Write block to file.  SI -> starting seg pointer, BX -> ending seg pointer.
  push [lastLine]               ;Copy block buffered lines to file:
  push [fHandle]
  mov ah, 3Ch                   ;CREATe file
  sub cx, cx
  mov dx, OFFSET fNameBlock
  int 21h
  jnc @@L1
  jmp CantOpen
@@L1:
  mov [fHandle], ax             ;Write it
  mov [lastLine], bx
  mov bx, si
  call WriteFile
  mov bx, [fHandle]             ;Close it
  mov ah, 3Eh
  int 21h
  pop [fHandle]
  pop [lastLine]
  ret

Copy:
;Copy marked lines, to file if shift is down, otherwise to buffer.
  test [marking?], -1          ;Abort with a beep if not already marking,
  jnz @@L1
  mov si, OFFSET notMarkingMsg
  jmp Abort
@@L1:
  push bx
  push di
  push es
  mov si, [mark]
  cmp bx, si                    ;If mark comes after here, exchange
  jae @@L2
  xchg bx, si
@@L2:
  mov [mark], si                ; save in this order for possible delete
  call FileOrBuffer?            ;If Shift key was down when command entered,
  je @@L4
  call WriteBlock
  mov di, OFFSET blockPtrs
  jmp @@Lx
@@L4:
  mov cx, bx                    ;If no Shift, move marked lines to buffer
  sub cx, si
  shr cx, 1
  inc cx
  mov di, OFFSET blockPtrs
  mov ax, ds
  mov es, ax
  rep movsw
@@Lx:
  mov [blockPtrsLast], di       ;Save pointer to last line
  pop es
  pop di
  pop bx
  mov [marking?], 0
  ret

DeleteBlock:
;Do a Copy, then delete copied lines
  call Copy                     ;Copy block to file or buffer
  mov si, bx                    ;Close up copied lines:
  inc si                        ;SI = cursor line + 2
  inc si
  mov di, [mark]                ;DI = start of marking
  mov cx, [lastLine]            ;CX = number of lines from here to end
  sub cx, bx
  push es
  mov ax, ds
  mov es, ax
  rep movsb
  pop es
  dec di
  dec di
  mov [lastLine], di            ;Store index of new last line
  sub di, di                    ;Point cursor to start of old marked line
  mov bx, [mark]
  mov [needCopies?], 0
  mov [changed?], -1
  jmp NewLine

Find:
;Prompt for and find next <string>.  If Shifted, reuse last <string>.
  push bx
  push es
  push di
  call Right
  test [autoReplace?], -1       ;If doing replace all, bypass shift check
  js @@L0
  jnz @@L1
  call Shifted?                 ;If Shifted,
  jnz @@L1
@@L0:
  mov si, OFFSET findMsg        ;Get search string
  mov dx, OFFSET findString
  call GetCountedString
@@L1:
  mov si, OFFSET findString
  lodsw
  dec al
  mov dl, al
  mov al, ah
  mov cx, LINELENGTH
  sub cx, di
FindLoop:
  repne scasb                   ;Scan for first char of Find string
  jne FindNextLine
  push di                       ;Once found, compare rest of string
  mov dh, cl
  mov cl, dl
  mov si, OFFSET findString + 2
  repe cmpsb
  je Found
  pop di                        ;Match failed.  Scan again for 1st char.
  mov cl, dh
  jmp FindLoop                                            ÿ
FindNextLine:
  inc bx                        ;Search next line (until EOF)
  inc bx
  mov es, [bx]
  sub di, di
  mov cl, LINELENGTH
  cmp bx, [lastLine]
  jbe FindLoop
  mov si, OFFSET notFoundMsg    ;Not found
  test [autoReplace?], -1       ;If doing auto-replace, return
  jz @@L1
  mov [autoReplace?], 0
  pop di
  pop es
  pop bx
  ret
@@L1:
  call Print0                   ;Else restore cursor, abort with error message
  pop di
  pop es
  pop bx
  jmp na
Found:
  pop di
  dec di
  add sp, 6
  mov [justFound?], -1
  mov ax, bx                    ;Show found line 5 below top of screen
  jmp JL1

GetCountedString:
;Print prompt string at [SI], read counted string into [DX].
;Returns count (minus CR/LF) in AL.  DX is advanced one to start of string.
  push di
  push dx
  call Prompt                   ;Display prompt
  pop dx
  mov di, dx
  inc dx
  call GetString                ;Get input string
  mov [di], al                  ;Store count in front of string
  pop di
  ret

Replace:
  push di
  test [autoReplace?], -1       ;If using auto-replace command,
  jz @@L0
  jns @@L2                      ;Skip shift check
  mov [autoReplace?], 1
  jmp SHORT @@L1a               ;Get replace string if shifted and first pass
@@L0:
  test [justFound?], -1         ;Beep if not immediately preceded by a Find
  jnz @@L1
@@LE:
  pop di
  jmp na
@@L1:
  call Shifted?                 ;If not Shifted, prompt for replace string
  jnz @@L2
@@L1a:
  mov si, OFFSET replaceMsg
  mov dx, OFFSET replaceString
  call GetCountedString
@@L2:
  mov si, OFFSET replaceString
  lodsb
  sub ah, ah
  mov dx, si
  push ax
  push dx
  sub ch, ch                    ;Compare lengths of find and replace strings
  mov cl, al
  sub cl, [byte findString]     ;If replace string is longer,
  je @@L6
  jb @@L4
  xchg dx, di
  call EndLine                  ; make sure there will be enough room on line
  xchg dx, di
  add dl, cl
  cmp dl, LINELENGTH
  ja @@LE
@@L3:
  call Insert0                  ; then insert extra characters
  loop @@L3
  jmp SHORT @@L6
@@L4:
  neg cl                        ;If shorter, delete difference
@@L5:
  call Delete0
  loop @@L5
@@L6:
  pop si                        ;Now copy new string over old
  pop cx
  sub ch, ch
  pop di
  rep movsb
  mov [changed?], -1
  jmp NewLine

AutoReplace:
;Find and replace all.  If shift not down, prompt for strings.
  push [top]
  push es
  push di
  push bx
  mov [autoReplace?], 1         ;Set flag: 1 = no shift, -1 = shift
  call Shifted?
  jne @@L1
  mov [autoReplace?], -1
@@L1:
  call Find                     ;Find/replace until flag reset by not found
  test [autoReplace?], -1
  jz @@Lx
  call Replace
  jmp @@L1
@@Lx:
  pop bx                        ;Restore original position
  pop di
  pop es
  pop [top]
  push si
  call ReDraw                   ;Refresh screen with any changes
  pop si
  push di
  call Print0                   ;Show notFoundMsg and exit
  pop di
  jmp NextNoRedraw

Help:
  push es
  push di
  mov es, [videoSegment]
  mov di, 160
  mov si, OFFSET helpMsg
  mov cx, 80 * 24
  mov ah, [attribNl]
@@L1:
  lodsb
  stosw
  loop @@L1
  mov si, OFFSET endHelpMsg
  call Print0
  sub ah, ah
  int 16h
  pop di
  pop es
  ret

;******************************************************************************

;Strings
String cantOpenMsg,     "Can't open file."
String rdErrorMsg,      'Error reading file.'
String fileErrorMsg,    'File error.'
String noRoomMsg,       'Out of memory.'
String notMarkingMsg,   'Not marking.'
String setLabelMsg,     'Label (0-9): '
String setTabsMsg,      'Tab width: '
String newFileMsg,      'File name: '
String gotoMsg          'Jump to what line? '
String editingMsg       <'Help F1',186,'Editing: '>
String findMsg          'Find: '
String replaceMsg       'Replace with: '
String notFoundMsg      'No more matching strings found.'
String endHelpMsg       'Press any key to continue.'
String ctrlCMsg         '*Break*'
String cancelledMsg     'Cancelled.'
BAK                     db  '.BAK', 0
tempFile                db  'e.tmp', 0
helpMsg                 db 'left                left arrow, ^s      '
                        db '          begin block            @b     '
                        db 'right               right arrow, ^d     '
                        db '          copy block to buffer   @c *   '
                        db 'word left           ^left arrow, ^a     '
                        db '          delete block to buffer @d *   '
                        db 'word right          ^right arrow, ^f    '
                        db '          insert block from buf  @i *   '
                        db 'start of line       Home                '
                        db '          empty block buffer     @e *   '
                        db 'end of line         End                 '
                        db '          unmark                 @u     '
                        db 'up                  up arrow, ^e        '
                        db 40 dup ( )
                        db 'down                down arrow, ^x      '
                        db '          find                   @f +   '
                        db 'page up             PgUp, ^r            '
                        db '          replace                @r +   '
                        db 'page down           PgDn, ^c            '
                        db '          find/replace all       @= +   '
                        db 'start of file       ^PgUp               '
                        db 40 dup ( )
                        db 'end of file         ^PgDn               '
                        db '          save and continue      @s     '
                        db 40 dup ( )
                        db '          save and exit          @x     '
                        db 'backspace           Backspace           '
                        db '          kill save on exit      @k     '
                        db 'delete              Del                 '
                        db 40 dup ( )
                        db 'delete word left    ^[                  '
                        db '          label (0-9)            @l     '
                        db 'delete word right   ^], ^t              '
                        db '          go to label (0-9)      @g     '
                        db 'delete to EOL       ^\                  '
                        db '          jump to line #         @j     '
                        db 'delete line         ^-, ^y              '
                        db '          set tab width          @t     '
                        db 'undelete line       ^^                  '
                        db '          toggle autoindent      @a     '
                        db 'toggle insert mode  Ins                 '
                        db '          open another file      @o     '
                        db 80 dup ( )
                        db '@ = Alt, ^ = Ctrl, * = to/from file if s'
                        db 'hifted, + = use last string if shifted. '
                        db 'Status line flags:  Insert  Overwrite  C'
                        db 'hanged  AutoInsert                      '


;Variables
attribNl        db 7            ;Default video attributes
attribInv       db 70h
tabSize         db 4            ;Tab increment
inserting?      db -1           ;True if in insert mode
newFile?        db ?            ;True if new file
autoIndent?     db -1           ;True if in autoindent mode
marking?        db 0            ;True if marking text
changed?        db 0            ;True if original file has been changed
isBAKed?        db 0            ;True if .BAK file already written
justFound?      db ?            ;True if no other commands since last Find
needCopies?     db -1           ;True unless lines in buffer were just deleted
autoReplace?    db 0            ;-1 if auto-replace with shift, 1 without shift
fName?          db -1           ;True if file name given on command line
labelTable      dw 10 dup (0)   ;Table of line pointers assigned to labels
blockPtrsLast   dw OFFSET blockPtrs
delLinePtrsLast dw OFFSET delLinePtrs

;Jump tables:   ^ = Ctrl, @ = Alt, # = Shift.
ctrlTable       dw na           ;^@
                dw WordLeft     ;^A
                dw na           ;^B
                dw PageDown     ;^C
                dw Right        ;^D
                dw Up           ;^E
                dw WordRight    ;^F
                dw na           ;^G or BEL
                dw BackSpace    ;^H or BS
                dw Tab          ;^I or HT
                dw na           ;^J or LF
                dw na           ;^K or VT
                dw na           ;^L or FF
                dw CRet         ;^M or CR
                dw na           ;^N or SO
                dw na           ;^O or SI
                dw na           ;^P
                dw na           ;^Q or DC1
                dw PageUp       ;^R or DC2
                dw Left         ;^S or DC3
                dw DeleteWordR  ;^T or DC4
                dw na           ;^U
                dw na           ;^V
                dw na           ;^W
                dw Down         ;^X or CAN
                dw DeleteLine   ;^Y
                dw na           ;^Z
                dw DeleteWordL  ;^[ or ESC
                dw DeleteToEOL  ;^\
                dw DeleteWordR  ;^]
                dw UndeleteLine ;^^
                dw DeleteLine   ;^-

auxTable        dw 15 DUP (na)  ;Undefined (except for NULL = 3)
                dw ReverseTab   ;#Tab
                dw na           ;@Q
                dw na           ;@W
                dw EmptyBuffer  ;@E
                dw Replace      ;@R
                dw SetTabs      ;@T
                dw na           ;@Y
                dw Unmark       ;@U
                dw InsertBlock  ;@I
                dw OtherFile    ;@O
                dw na           ;@P
                dw 4 DUP (na)   ;Undefined
                dw AutoIndent   ;@A
                dw Save         ;@S
                dw DeleteBlock  ;@D
                dw Find         ;@F
                dw GotoLabel    ;@G
                dw Help         ;@H
                dw Jump         ;@J
                dw Kill         ;@K
                dw SetLabel     ;@L
                dw 5 DUP (na)   ;Undefined
                dw na           ;@Z
                dw Exit         ;@X
                dw Copy         ;@C
                dw na           ;@V
                dw BeginBlock   ;@B
                dw na           ;@N
                dw na           ;@M
                dw 8 DUP (na)   ;Undefined
                dw Help         ;F1
                dw na           ;F2
                dw na           ;F3
                dw na           ;F4
                dw na           ;F5
                dw na           ;F6
                dw na           ;F7
                dw na           ;F8
                dw na           ;F9
                dw na           ;F10
                dw 2 DUP (na)   ;Undefined
                dw HomeLine     ;Home
                dw Up           ;Up arrow
                dw PageUp       ;PgUp
                dw na           ;Undefined
                dw Left         ;Left arrow
                dw na           ;Undefined
                dw Right        ;Right arrow
                dw na           ;Undefined
                dw EndLine      ;End
                dw Down         ;Down arrow
                dw PageDown     ;PgDn
                dw ToggleIns    ;Ins
                dw Delete       ;Del
                dw 30 DUP (na)  ;[#Fn, ^Fn, @Fn]
                dw na           ;^PrtSc
                dw WordLeft     ;^Left arrow
                dw WordRight    ;^Right arrow
                dw na           ;^End
                dw BottomFile   ;^PgDn
                dw na           ;^Home
                dw 10 DUP (na)  ;[Alt numbers]
                dw na           ;@-
                dw AutoReplace  ;@=
                dw TopFile      ;^PgUp

;Uninitialized variables
row             db ?            ;Current row
column          db ?            ;Current column
fHandle         dw ?            ;File handle
lastLine        dw ?            ;Index of last line
top             dw ?            ;Index of first line on screen
bottom          dw ?            ;Index of last line on screen
bufferPtr       dw ?            ;Multipurpose buffer pointer
mark            dw ?            ;Start of marking for block command
here            dw ?            ;Temporary
hereCol         dw ?            ;Temporary
videoSegment    dw ?            ;Segment of system's video memory
fName           db 20 dup (?)   ;File name in ASCIIZ format
fNameBAK        db 20 dup (?)   ;Current file with .BAK extension added
fNameBlock      db 20 dup (?)   ;File to be used in block read/writes
pad             db 20 dup (?)   ;Scratch buffer
heapStart       dw ?            ;Segment of start of heap
heapPtr         dw ?            ;Segment pointer to next free paragraph in heap
findString      dw LINELENGTH dup (?)  ;Search string for Find command
replaceString   dw LINELENGTH dup (?)  ;New string for Replace command
linePtrs        dw MAXLINES dup (?) ;List of line pointers
blockPtrs       dw MAXLINES dup (?) ;Line pointers for block commands
delLinePtrs     dw MAXLINES dup (?) ;Line pointers for del/undel lines
buffer          db BUFFERLENGTH dup (?)  ;File and delete buffer (dual purpose)
bufferEnd:

END start

