Metropoli BBS
VIEWER: ted2.asm MODE: TEXT (ASCII)
NAME    TED
PAGE    58,132
TITLE   TED -- the Tiny EDitor
;=======================================================================
; TED.ASM -- The Tiny EDitor.
; PC Magazine * Tom Kihlken * 11/15/88
;
; See the article "The tiniest editor you'll ever need" by Tom Kihlken,
; in the November 15, 1988 issue of PC Magazine for more detail than is
; presented here.  Portions of this text are excerpted from that article.
; The source listing, itself, was copied directly from that article and
; altered as appropriate to add comments or change or improve the program.
; The use of self-modifying code for "NO_DESNOW" is discussed in the article.
; SEGment register usage have been cleaned up to reduce the size of the code.
;
; TED follows normal text conventions: pressing the <Enter> key actually
; adds two characters, Carriage-return (ASCII 13) and Line-feed (ASCII 10),
; also known as a "hard carriage return".  The <Tab> key inserts the ASCII 9
; tab character, and advances the screen cursor to the next column that is
; an even multiple of eight.  The <Backspace> key deletes the character to
; its immediate left ("destructive" backspace) and combines two lines if it
; is pressed while in the first column.  The Backspace code (Ctrl-H) may be
; entered in the file as a control code.  TED2 allows the ASCII codes for
; Carriage-Return (Ctrl-M) and Line-feed (Ctrl-J) to independently be entered
; into the file as control codes.  Because of this, TED2 rigidly enforces
; a convention that once the <Ctrl-M><Ctrl-J> pair is formed it becomes an
; End-of-line marker, and may not be separated again.
;
; Like DOS, TED lets you enter any character (except <Nul>, value 00) by
; holding down the Alt key, typing the decimal value of its ASCII code on
; the numeric keypad, then releasing Alt.  This gives you access both to
; nonprinting codes below ASCII 32 and to the upper-order (ASCII 128 to 255)
; characters in the extended IBM set.  TED2 allows the <Nul> code to be
; entered by pressing the <Shft-F1> key.
;
; NOTE: Unassigned keyboard codes are treated as no-operations.
;
; The TED Keypad commands are:
; Key                   Description
; Up Arrow              Moves cursor up one row
; Down Arrow            Moves cursor down one row
; Left Arrow            Moves cursor left one column
; Right Arrow           Moves cursor right one column
; PgUp                  Moves text window up one page
; PgDn                  Moves text window down one page
;>Home                  Moves cursor to start of row (if at home, next up)
;>End                   Moves cursor to end of row (if at end, next end down)
; Ins                   Toggles insert/overstrike mode
; Del                   Deletes character (right) at cursor (saved in UnDo)
; Backspace             Deletes character (left) at cursor (not saved in UnDo)
; Ctrl-PgUp             Moves to top of file
;>Ctrl-Home             Moves to top of file (alias)
; Ctrl-PgDn             Moves to bottom of file
;>Ctrl-End              Moves to bottom of file (alias)
; Ctrl-Right Arrow      Moves text window right eight columns
; Ctrl-Left Arrow       Moves text window left eight columns
;
; Alt-S Search          Search for (case insensitive) character string
; Alt-A Search Again    Search again for string
; Alt-D Del EOL         Deletes from cursor to the end of line
;
; The TED Editing Functions are:
; Key   Function        Operation
; F1    Help            Help screen
; ShftF1 <Nul>          Enters the <Nul> code (value 00) in the file
; F2    Exit            Save changes and exit (creates a .BAK file)
; ShftF2 Quit           Exit without saving changes
; F3    Print           Prints the marked text
; F4    Mark            Toggles mark state on/off
; F5    Cut             Moves marked text to paste buffer
; ShftF5 Paste          Inserts contents of paste buffer (64K char's max)
; F6    Search          Search for (case insensitive) character string
; ShftF6 Search Again   Search again for string
; F7    UnDo            Replaces recently DELeted characters (256 char's max)
; F8    Del EOL         Deletes from cursor to the end of line
; ShftF8 Del L          Deletes the current line (MultiEdit)
; F9    Del L           Deletes the current line
; F10   Udel L          Inserts the last deleted line (256 char's max)
;
; In addition to the 64K Text buffer (where the edit file is maintained),
; there are three other important buffers, as indicated above.
;   1.) The paste buffer is a 64K character buffer used for MARK/CUT/PASTE of
;       sections of the file.
;   2.) The line buffer is a 256 character buffer used to save the last
;       deleted line of text (used with F8, F9 and F10).
;   3.) The UnDo buffer is a 256 character buffer used to save the just
;       deleted characters at the current cursor location.  It works with
;       the "DEL" key, but only the last character with the "Backspace" key.
;       It also saves overwritten characters in the Overstrike mode.
;       Deletions are restored with "F7" UnDo.
;
; NOTE: There is a severe subroutine nesting violation in the TED key scan
; routine.  BAD_KEY from within the keyboard scanning loop is a junp to
; read next key.  The BAD_KEY subroutine calls through the DISPATCH_TABLE
; do not have a corresponding return to equalize the stack.  This was
; partially compensated for by making the stack humongous.  TED 1.0 as
; published in PC Magazine could eventually overflow the stack and crash the
; system.  TED2 has corrected this problem by making a stub subroutine for
; the DISPATCH_TABLE BAD_KEY subroutine CALLs.
;
; TED was modified by James E. Galbraith, Dec 18, 1988.
; The changes are of several types.  Compatability with other editors.
; Correction of errors and questionable code.  Reordering of program modules
; to make it easier to follow the listing.  Addition of meaningful comments.
; These modifications include:
;       Change F2 "UnDo" to "Exit" (for BSE compatibility).
;       Change F7 "Exit" to "UnDo".
;       Make CTRL-HOME alias to CTRL-PGUP, to home the cursor (for BSE).
;       Make CTRL-END alias to CTRL-PGDN, cursor to end of file (for BSE).
;       JMP instructions outside modules instead of CALL/RETurn are commentd.
;       Assignment of DS was sometimes ambiguous.
;
; Modified by James E. Galbraith, June 1989.
; The changes are in two groups.  Those taken from TEDPLUS (apparently also
; written by Tom Kihlken), which was acquired from a bulletin board service,
; and my own changes.
;       Add SEARCH and SEARCH AGAIN as F6 and Shft-F6, from TEDPLUS.
;       Delete CPM style Ctrl-Z End-of-file code, from TEDPLUS.
;       "Enter any key code" from TEDPLUS was included, but not like TEDPLUS
;       End-of-line was changed to CR-LF as in TEDPLUS.  CR or LF codes may be
;         entered separately.
; My own changes are:
;       Close the source file if it has been opened.  This was not done by TED.
;       Add a HELP screen as <F1>, move QUIT to <Shft-F2>.
;       Fix the SEARCH inverse video to turn off when any key is pressed.
;       Add DOS version check, needs version 2.0 or higher to run.
;       Add a program checksum test for verification of file integrity.
;       Add <Alt-S> as alias for <F6> SEARCH (usage in BSE editor).
;       Add <Alt-A> as alias for <Shft-F6> SEARCH AGAIN (usage in BSE editor).
;       Add <Alt-D> as alias for <Shft-F8>, Delete to end of line.
;       Add <Shft-F8> as alias for <F9>, Delete Line (for future expansion).
;       If the file has not been altered, don't prompt for save or quit.
;       On QUIT, accept a CR as confirmation of intention to abandon file.
;       Add <Shft-F1> as key to enter the <Nul> character, 00.
;       If a CR and LF code are made to be ajacent, they become an EOL marker,
;         and may not then be separated.  This can occur in several ways that
;         all include entering the codes <Crtl-M> and <Ctrl-J>.
;       A key entry will interrupt a screen update that is in progress.
;

; Assemble the TED2.ASM source file to TED2.COM by using the MASM.EXE assembler,
; the linker LINK.EXE, and the file conversion utility EXE2BIN.EXE, as follows:
; The checksum byte can be obtained with TEST.BAT (included with TED2.ASM).
;
;       MASM TED2;
;       LINK TED2;
;       EXE2BIN TED2 TED2.COM
;-----------------------------------------------------------------------
CSEG            SEGMENT
                ASSUME  CS:CSEG, DS:CSEG, ES:CSEG
                ORG     0100H
START:
                JMP     BEGIN

;-----------------------------------------------------------------------
; Local data area in CSEG.
;-----------------------------------------------------------------------
; ASCII character values used in the program.
TAB             EQU     9
CR              EQU     13
LF              EQU     10
CRLF            EQU     0A0DH

; Character strings and data
COPYRIGHT       DB      "TED 1.0 (C) 1988 Ziff Communications Co.,"
                DB      "PC Magazine, 11/15/88, by Tom Kihlken"
                DB      CR,LF,"TED 2.1, 9/12/89, by James E. Galbraith"
                DB      CR,LF,"$"

                ;(JEG)
HELP_SCREEN     DB      9,9,'TED - the Tiny EDitor (any key to edit)',CR
                DB      LF,LF,9,'F1 - Help',9,9,9,'Shft-F1 - <NUL> char'
                DB      CR,LF,9,'F2 - Exit and save',9,9,'Shft-F2 - Quit'
                DB      CR,LF,9,'F3 - Print marked',9,9,'Shft-F3 - Form-feed'
                DB      CR,LF,9,'F4 - Mark toggle'
                DB      CR,LF,9,'F5 - Cut marked',9,9,9,'Shft-F5 - Paste'
                DB      CR,LF,9,'F6 - String search',9,9,'Shft-F6 - '
                DB      'Search again'
                DB      CR,LF,9,'F7 - Undo <Del> or Ovr'
                DB      CR,LF,9,'F8 - Delete to EOL' ;** ,9,9,'Shft-F8 - '
        ;**     DB      'Delete Line'
                DB      CR,LF,9,'F9 - Delete Line'
                DB      CR,LF,9,'F10 - Un-Delete line',LF
                DB      CR,LF,9,'Ctrl-L/R arrow - Move window'
                DB      CR,LF,9,'Ctrl-Home - to top'
                DB      CR,LF,9,'Ctrl-End - to EOF',LF
                DB      CR,LF,9,'Esc - From Exit'
                DB      CR,LF,'$',7,1AH

                ;from JEG and TEDPLUS
PROMPT_STRING   DB      "1Help",0               ;F1
                DB      "2Exit",0               ;F2
                DB      "3Print",0              ;F3
                DB      "4Mark",0               ;F4
                DB      "5Cut",0                ;F5
                DB      "6Search",0             ;F6
                DB      "7UnDo",0               ;F7
                DB      "8Del EOL",0            ;F8
                DB      "9Del L",0              ;F9
                DB      "10Udel L",0,0          ;F10
PROMPT_LENGTH   =       $ - OFFSET PROMPT_STRING

FILE_TOO_BIG    DB      "File too big$"
READ_ERR_MESS   DB      "Read error$"
MEMORY_ERROR    DB      "Not enough memory$"

                ;(JEG)
CHEK_SUM_MESS   DB      "TED altered$"
DOS_2_MESS      DB      "Needs DOS 2.0$"

VERIFY_MESS     DB      "Lose Changes (Y)?",0
SAVE_MESS       DB      "Save as: ",0
DOT_$$$         DB      ".$$$",0
DOT_BAK         DB      ".BAK",0

                ;from TEDPLUS, string search
SRCH_PROMPT     DB      "SEARCH> ",0
;SRCH_MAX       DB      66              ;(moved to file end to reduce file size)
;SRCH_SIZ       DB      0
;SRCH_STR       DB      66 DUP (0)
SRCH_FLG        DB      0               ;0=normal, 1=search successful (inverse)
SRCH_END        DW      0
SRCH_BASE       DW      0
SRCH_CLR        DB      0F0H            ;244

DIRTY_BITS      DB      03H             ;1=update screen, 2=cursor 4=cur. line
DIRTY_FILE      DB      0               ;0=file not altered, FF=file altered

NORMAL          DB      07H             ;video attribute bits
INVERSE         DB      70H             ;video (inverse)

LEFT_MARGIN     DB      0               ;column number of screen left margin
MARGIN_COUNT    DB      0
INSERT_MODE     DB      01H             ;1=INSert, 0=Overstrike, 2or3=Read Only
INSERT_CODE     DB      'OIRR'          ;indexed code for display screen
MARK_MODE       DB      0               ;toggle, FF=MARK is on.
ROWS            DB      23              ;Rows available on display screen
SAVE_COLUMN     DB      0
SAVE_ROW        DB      0
LINE_FLAG       DB      0

EVEN                                    ;"word align" (for 16-bit bus accesses)
NAME_POINTER    DW      81H             ;offset of command tail in PSP
NAME_END        DW      81H             ;end of tail

VIDEO_STATUS_REG LABEL  DWORD           ;(alias for use by LES instruction)
STATUS_REG      DW      0               ;video status register offset
VIDEO_SEG       DW      0B000H          ;video segment, Mono=0B000, other 0B800

LINE_LENGTH     DW      0               ;number of bytes in current line
UNDO_LENGTH     DW      0               ;number of DELeted bytes in UnDo buffer
CURS_POSN       DW      0               ;Cursor, Hi is row, Low is column

MARK_START      DW      0FFFFH          ;MARK text for CUT/PASTE or Print
MARK_END        DW      0
MARK_HOME       DW      0

TOP_OF_SCREEN   DW      0               ;address in text file of Top-of-screen
CURSOR          DW      0               ;address in text file of cursor
LAST_CHAR       DW      0               ;address of last character in file.
COLUMNSB        LABEL   BYTE            ;alias BYTE definition
COLUMNS         DW      0

PASTE_SEG       DW      0               ;segment address of PASTE buffer
PASTE_SIZE      DW      0               ;size of block in PASTE buffer

PAGE_PROC       DW      0               ;pointer used by PGUP and PGDN

EXIT_CODE       DW      4C00H           ;DOS INT 21H, Func 4CH, exit with code

OLDINT24        DD      0               ;pointer to DOS critical error handler

DISPATCH_BASE   EQU     59        ;initial offset for a PASCAL type CASE list
DISPATCH_TABLE  DW      HELP      ;59;F1, Help screen (common usage)
                DW      EXIT      ;60;F2, Save changes and exit (from BSE)
                DW      PRINT     ;61;F3, Print the marked text
                DW      MARK      ;62;F4, Toggle mark state on/off
                DW      CUT       ;63;F5, Move marked text to buffer
                DW      FIND_STR  ;64;F6, Search for text string
                DW      UNDO      ;65;F7, Replace recently deleted chars
                DW      DEL_EOL   ;66;F8, Delete from cursor to EOL
                DW      DEL_L     ;67;F9, Delete the current line
                DW      UDEL_L    ;68;F10, Insert the last deleted line
                DW      BAD_KEY   ;69;(NumLock)
                DW      BAD_KEY   ;70;(ScrollLock)
                DW      HOME_KEY  ;71;Home/7
                DW      UP        ;72;Up/8
                DW      PGUP      ;73;PgUp/9
                DW      BAD_KEY   ;74;(-)
                DW      LEFT      ;75;Left/4
                DW      BAD_KEY   ;76;(5)
                DW      RIGHT     ;77;Right/6
                DW      BAD_KEY   ;78;(+)
                DW      END_KEY   ;79;End/1
                DW      DOWN      ;80;Down/2
                DW      PGDN      ;81;PgDn/3
                DW      INSERT    ;82;Ins/3
                DW      DEL_CHAR  ;83;Del/.

                DW      NUL_CHAR  ;84;Shft-F1 -- Add NUL character to file
                DW      ABORT     ;85;Shft-F2 -- Quit and abandon changes
                DW      PRINT_FF  ;86;Shft-F3 -- Print a form-feed character
                DW      BAD_KEY   ;87;Shft-F4
                DW      PASTE     ;88;Shft-F5 -- Insert contents of Paste buffer
                DW      FIND_STR  ;89;Shft-F6 -- Search again for string
                DW      BAD_KEY   ;90;Shft-F7
                DW      DEL_L     ;91;Shft-F8 -- Delete line (Multi-Edit)
DISPATCH_END    EQU     92
                ;--------------------------------
                ; gap in keyboard scan code assignments
                ;--------------------------------
DISP_CURS_BASE  EQU     115
                DW      SH_LEFT   ;115;Ctrl-Left arrow
                DW      SH_RIGHT  ;116;Ctrl-Right arrow
                DW      BOTTOM    ;117;Ctrl-End
                DW      CTRL_PGDN ;118;Ctrl-PgDn
                DW      TOP       ;119;Ctrl-Home
DISP_CURS_END   EQU     120

;**             DW      CTRL_PGUP ;132;Ctrl-PgUp

;---------------------------------
; The following constant is a machine instruction that removes the CGA desnow
; delay.  It is inserted into the code for EGA, VGA, and mono displays.
; See the article in PC-Magazine for further discussion.

NO_DESNOW = (OFFSET WRITE_IT - OFFSET HWAIT - 2) * 256 + 0EBH ;opcode JMP SHORT

;=======================================================================
; We start by initialize the display, then allocate memory for the file
; and paste segments.  Parse the command line for the filename, if one was
; input, read in the file.  Finally set the INT 23 and 24 vectors.
;-----------------------------------------------------------------------
BEGIN:
                MOV     AH,30H          ;get DOS version (JEG new)
                INT     21H
                CMP     AL,2
                JAE     DOS_2_UP
                MOV     EXIT_CODE,0     ;is DOS version 1, exit with function 0.
                MOV     DX,OFFSET DOS_2_MESS ;message "Needs DOS 2.0"
;**short        JMP     EXIT_TO_DOS
                JMP     SHORT EXIT_22_DOS ;(saves a byte)
                ;-----------------------
DOS_2_UP:
                ;JEG new -- program code checksum test
                MOV     SI,100H         ;start of program file.
                MOV     SP,SI           ;set stack pointer to top of PSP.
                MOV     CX,OFFSET CHEK_SUM_BYT - 0100H ;size of program file.
                MOV     AL,CHEK_SUM_BYT ;load checksum correction factor byte.
CHKSUM_LOOP:    ADD     AL,[SI]         ;add all program code bytes together.
                INC     SI
                LOOP    CHKSUM_LOOP
                CMP     AL,0            ;is program checksum zero?
                JZ      CHEKSUM_IS_OK   ;yes, continue with program
                NEG     AL              ;make error into correction factor.
                MOV     BYTE PTR EXIT_CODE,AL ;save errorlevel code for exit.
                MOV     DX,OFFSET CHEK_SUM_MESS ;message "TED altered".
;**short        JMP     EXIT_TO_DOS     ;error message and exit to DOS
;EXIT_22_DOS:   JMP     SHORT EXIT_2_DOS ;error message and exit to DOS
EXIT_22_DOS:    JMP     EXIT_2_DOS      ;error message and exit to DOS
                ;-----------------------
CHEKSUM_IS_OK:
                XOR     AX,AX
                MOV     DS,AX           ;zero DS
                ASSUME  DS:NOTHING
                MOV     BL,10H
                MOV     AH,12H          ;get EGA info
                INT     10H
                CMP     BL,10H          ;did BL change?
                JE      NOT_EGA         ;if not, no EGA in system
                TEST    BYTE PTR DS:[0487H],8 ;is EGA active?
                JNZ     NOT_EGA
                MOV     WORD PTR CS:HWAIT,NO_DESNOW ;get rid of desnow
                MOV     AX,DS:[0484H]   ;get number of rows
                DEC     AL              ;last row is for prompt line
                MOV     CS:[ROWS],AL    ;save the number of rows
NOT_EGA:
                MOV     AX,DS:[044AH]   ;get number of columns
                MOV     CS:COLUMNS,AX   ;and store it
                MOV     AX,DS:[0463H]   ;address of display card
                ADD     AX,6            ;add six to get status port
                PUSH    CS
                POP     DS
                ASSUME  DS:CSEG
                MOV     STATUS_REG,AX
                CMP     AX,3BAH         ;is this a MONO display?
                JNE     COLOR           ;if not, must be a CGA
                MOV     WORD PTR HWAIT,NO_DESNOW ;get rid of desnow
                JMP     SHORT MOVE_STACK

COLOR:
                MOV     VIDEO_SEG,0B800H ;segment for color card
                XOR     BH,BH           ;use page zero
                MOV     AH,8            ;get current attribute
                INT     10H
                AND     AH,77H          ;turn off blink and high intensity
                MOV     NORMAL,AH       ;save the normal attribute
                XOR     AH,01110111B    ;flip the color bits
                MOV     INVERSE,AH
                OR      AH,80H
                MOV     SRCH_CLR,AH     ;search is inverse/blink
MOVE_STACK:
                MOV     BX,OFFSET END_BUFFER
;**rounding     ADD     BX,15           ;add offset value for SEG rounding up
                MOV     CL,4            ;convert program size to
                SHR     BX,CL           ; paragraphs
                MOV     AH,4AH          ;deallocate unused memory
                INT     21H             ;DOS call
                MOV     BX,1000H        ;request 64K for file segment
                MOV     AH,48H
                INT     21H             ;DOS call
                MOV     ES,AX
                ASSUME  ES:FILE_SEG
                MOV     AH,48H          ;request 64K for paste buffer
                INT     21H             ;DOS call
                JNC     GOT_ENOUGH      ;if enough memory, continue
NOT_ENOUGH:
                MOV     DX,OFFSET MEMORY_ERROR

EXIT_2_DOS:     JMP     EXIT_TO_DOS     ;jump island to allow SHORT jumps.
                ;-----------------------
GOT_ENOUGH:
                MOV     PASTE_SEG,AX    ;use this for the paste buffer
GET_FILENAME:
                MOV     SI,80H          ;point to command tail in PSP
                MOV     CL,[SI]         ;get number of characters in tail
                XOR     CH,CH           ;make it a word
                INC     SI              ;point to first character
                PUSH    SI
                ADD     SI,CX           ;point to last character
                MOV     BYTE PTR [SI],0 ;make it an ASCIIZ string (clear the CR)
                MOV     NAME_END,SI     ;save pointer to last character
                POP     SI              ;get back pointer to filename
                JCXZ    NO_FILENAME     ;if no params, just exit
                CLD
DEL_SPACES:
                LODSB                   ;get character into AL
                CMP     AL," "          ;is it a space?
                JNE     FOUND_LETTER
                LOOP    DEL_SPACES
FOUND_LETTER:
                DEC     SI              ;backup pointer to first letter
                MOV     NAME_POINTER,SI ;save pointer to filename

                MOV     DX,NAME_POINTER
                MOV     AX,4300H        ;get file attribute byte (in CL)
                INT     21H             ;DOS call
                JC      ATTRIB_ERROR
                AND     CL,1            ;keep bit-0, read-only status
                ADD     CL,CL           ;shift RO bit up
                OR      INSERT_MODE,CL  ;save as index modifier value
ATTRIB_ERROR:
                MOV     DX,SI
                MOV     AX,3D00H        ;setup to open file
                INT     21H             ;DOS call
                ; If no carry, Then the opened file handle is in AX
                ; If no carry, Then the opened file handle is in AX
                JC      NO_FILENAME     ;if we can't open, must be new file
FILE_OPENED:
                PUSH    ES
                POP     DS              ;DS has file segment also
                ASSUME  DS:FILE_SEG
                MOV     BX,AX           ;get the handle into BX
                XOR     DX,DX           ;point to file buffer, DS:DX
                MOV     CX,0FFFEH       ;read almost 64K bytes
                MOV     AH,3FH          ;read from file or device
                ; BX = file handle
                INT     21H             ;DOS call
                ; If no carry, Then AX contains number of bytes read
                MOV     DI,AX           ;number of bytes read in
                JNC     FILE_READ_OK    ;if no error, take jump
                MOV     DX,OFFSET READ_ERR_MESS
                ;-----------------------
                ; the file has been opened, it should now be closed. (JEG)
                ;-----------------------
CLOSE_ERR_EXIT:
                MOV     AH,3EH          ;close the opened file
                ; BX = file handle
                INT     21H
                JMP     SHORT EXIT_2_DOS
                ;-----------------------
FILE_READ_OK:
                MOV     LAST_CHAR,DI    ;save the file size
                CMP     CX,AX           ;did the buffer fill?
                MOV     DX,OFFSET FILE_TOO_BIG
                JE      CLOSE_ERR_EXIT  ;if yes, it is too big
                MOV     AH,3EH          ;close the file
                ; BX = file handle
                INT     21H
;**dirty        MOV     BYTE PTR CS:DIRTY_FILE,0 ;file opened but not altered.
NO_FILENAME:
                PUSH    ES
                PUSH    ES              ;save the file segment
                PUSH    CS
                POP     DS
                ASSUME  DS:CSEG
                ;-----------------------
                ;INT 24 is the critical error handler
                ;-----------------------
                MOV     AX,3524H        ;get INT 24 vector
                INT     21H             ;DOS call
                MOV     WORD PTR OLDINT24,BX   ;store the offset
                MOV     WORD PTR OLDINT24+2,ES ;and the segment
                MOV     DX,OFFSET NEWINT24 ;point to new vector
                MOV     AX,2524H        ;now change INT 24 vector
                INT     21H             ;DOS call
                ;-----------------------
                ;INT 23 is the CTRL-Break or CTRL-C handler
                ;-----------------------
                MOV     DX,OFFSET NEWINT23
                MOV     AX,2523H        ;set the INT 23 vector
                INT     21H             ;DOS call

                POP     ES              ;get back file segment
                POP     DS
                ASSUME  DS:FILE_SEG
                CALL    REDO_PROMPT     ;draw the prompt line

;-----------------------------------------------------------------------
; Here's the main loop.  It updates the screen, then reads a keystroke.
;-----------------------------------------------------------------------
READ_A_KEY:
                CMP     MARK_MODE,0     ;is the mark state on?
                JZ      MARK_OFF        ;if not, skip this
                OR      DIRTY_BITS,4    ;refresh the current row
                MOV     DX,CURS_POSN
                CMP     SAVE_ROW,DH     ;are we on the save row?
                JE      SAME_ROW        ;if yes, then redo the row only
                OR      DIRTY_BITS,1    ;refresh the whole screen
SAME_ROW:
                MOV     AX,CURSOR       ;get cursor location
                MOV     BX,MARK_HOME    ;get the anchor mark position
                CMP     AX,BX           ;moving backward in file?
                JAE     SAME1
                MOV     MARK_START,AX   ;switch start and end positions
                MOV     MARK_END,BX
                JMP     SHORT MARK_OFF
SAME1:
                MOV     MARK_END,AX     ;store start and end marks
                MOV     MARK_START,BX
MARK_OFF:
                CALL    DISPLAY_SCREEN  ;redraw the screen

                XOR     AH,AH           ;read the next key (wait if none ready)
                INT     16H             ;BIOS call
                ; AL = ASCII code or 0 (for function keys)
                ; AH = scan code
                TEST    SRCH_FLG,0FFH
                JZ      CHECK_KEY       ;jump if inverse not on
                MOV     SRCH_FLG,0      ;turn off highlight
                OR      DIRTY_BITS,1    ;redraw screen (next time)
CHECK_KEY:
                OR      AL,AL           ;is this an extended code?
                JZ      IS_EXTENDED_CODE ;(jump if not an ASCII character)
                CALL    INSERT_KEY      ;put this ASCII character in the file
;**short        JMP     READ_A_KEY      ;get another key
                JMP     SHORT RD_NEXT_KEY ;get another key
;---------------------------------------

IS_EXTENDED_CODE:
                ;-----------------------
                ; The following code is for "orphan" key codes and alias keys
                ;-----------------------
                CMP     AH,132          ;is it Ctrl-PgUp? (an orphan code)
                JNE     NOT_CT_PGUP
                CALL    CTRL_PGUP
;**short        JMP     READ_A_KEY
                JMP     SHORT RD_NEXT_KEY ;get another key
                ;-----------------------
NOT_CT_PGUP:
                CMP     AH,32           ;is it Alt-D (MultiEdit Del-EOL)?
                JNE     NOT_ALT_D
                MOV     AH,66           ;substitute scan code for F8
NOT_ALT_D:
                CMP     AH,31           ;is it Alt-S (BSE string search)?
                JNE     NOT_ALT_S
                MOV     AH,64           ;substitute scan code for F6
NOT_ALT_S:
                CMP     AH,30           ;is it Alt-A (BSE search again)?
                JNE     NOT_ALT_A
                MOV     AH,89           ;substitute scan code for Shft-F6
NOT_ALT_A:
                ;-----------------------
                ; The following code sets up a PASCAL style CASE statement.
                ;-----------------------
                CMP     AH,DISPATCH_END ;split the dispatch table
                JB      DO_DISPATCH
                ;-----------------------
                ; offset cursor group of keys to join the regular dispatch table.
                ;-----------------------
                CMP     AH,DISP_CURS_BASE
                JB      RD_NEXT_KEY
                CMP     AH,DISP_CURS_END
                JAE     RD_NEXT_KEY
                SUB     AH,LOW (DISP_CURS_BASE - DISPATCH_END) ;close table gap
DO_DISPATCH:
                ;-----------------------
                ; This is a PASCAL style CASE statement.
                ;-----------------------
                MOV     BX,AX           ;put AH offset value in BX, AL=0
                XCHG    BL,BH           ;make into a proper word
                SUB     BX,DISPATCH_BASE ;zero offset for dispatch table jump
                JC      RD_NEXT_KEY     ;too low, not in table
                ADD     BX,BX           ;make into word
                CALL    CS:DISPATCH_TABLE[BX] ;call the key procedure
RD_NEXT_KEY:    JMP     READ_A_KEY      ;then read another key


;=======================================================================
; KEYBOARD and CURSOR services
;=======================================================================
; These two routines shift the display right or left to allow editing
; files which contain lines longer than 80 columns.  Starting with TED2,
; they are proper subroutines.
;-----------------------------------------------------------------------
SH_LEFT         PROC    NEAR
                CMP     LEFT_MARGIN,0   ;at start of line already?
                JE      NO_SHIFT        ;if yes,then don't shift
                SUB     LEFT_MARGIN,8   ;move the window over
                JMP     SHORT SH_RETURN
SH_LEFT         ENDP
;-----------------------------------------------------------------------
SH_RIGHT        PROC    NEAR
                CMP     LEFT_MARGIN,255 - 8 ;past max allowable margin?
                JAE     NO_SHIFT        ;then can't move any more
                ADD     LEFT_MARGIN,8   ;this moves the margin over
SH_RETURN:
                CALL    CURSOR_COL      ;compute column for cursor
                MOV     SAVE_COLUMN,DL  ;save the current column
                OR      DIRTY_BITS,1    ;redraw the screen
NO_SHIFT:
;**dispatch     JMP     READ_A_KEY
;**bad_key      RET
SH_RIGHT        ENDP

;-----------------------------------------------------------------------
; DISPATCH_TABLE calls to BAD_KEY now go here so that the stack can be
; kept equallized.
;-----------------------------------------------------------------------
BAD_KEY         PROC    NEAR
                RET
BAD_KEY         ENDP

;=======================================================================
; This routine moves the cursor to the top of the file.
;-----------------------------------------------------------------------
TOP             PROC    NEAR
                XOR     AX,AX           ;get a zero into AX
                MOV     CURSOR,AX       ;cursor to start of file
                MOV     TOP_OF_SCREEN,AX
                MOV     LEFT_MARGIN,AL  ;move to far left margin
                MOV     CURS_POSN,AX    ;home the cursor
                MOV     SAVE_COLUMN,AL  ;save the cursor column
                MOV     DIRTY_BITS,3    ;redraw the screen and cursor
                RET
TOP             ENDP

;=======================================================================
; This routine moves the cursor to the bottom of the file.
;-----------------------------------------------------------------------
BOTTOM          PROC    NEAR
                MOV     DH,ROWS         ;get screen size
                MOV     SI,LAST_CHAR    ;point to last character
                ;-----------------------
                ; from TEDPLUS
                DEC     SI
                ;-----------------------
                MOV     LEFT_MARGIN,0   ;set window to start of line
                CALL    LOCATE          ;adjust the cursor screen position
                CALL    HOME            ;move cursor to start of line
                MOV     DIRTY_BITS,1    ;this will redraw the screen
                RET
BOTTOM          ENDP

;=======================================================================
; This routine moves the cursor to the start of the current line.
; If <HOME> key and already at start of line, move to start of previous line.
;-----------------------------------------------------------------------
HOME_KEY        PROC    NEAR
                CALL    LEFT            ;back up one space, in case at home
HOME_KEY        ENDP

HOME            PROC    NEAR
                CALL    FIND_START      ;find start of line
                MOV     CURSOR,SI       ;save the new cursor
                MOV     SAVE_COLUMN,0   ;save the cursor column

                MOV     BYTE PTR CURS_POSN,0 ;store the column number
                RET
HOME            ENDP

;=======================================================================
; These routines move the cursor right and left
;-----------------------------------------------------------------------
RIGHT           PROC    NEAR
                MOV     SI,CURSOR
                CMP     SI,LAST_CHAR    ;at end of file?
                JE      LR_NO_CHANGE    ;if yes, then can't move
                ;-----------------------
                ; from TEDPLUS
                INC     SI
                CMP     SI,LAST_CHAR    ;at end of file?
                DEC     SI
                JAE     INC_RIGHT       ;if yes, then increment
                CMP     WORD PTR [SI],CRLF ;is it CRLF?
                JE      MOVE_DOWN       ;if yes, then move down to next line
INC_RIGHT:
                ;-----------------------
                INC     CURSOR          ;advance the cursor
                JMP     SHORT LR_RETURN
MOVE_DOWN:
                CALL    HOME            ;move to start of line
                JMP     SHORT DOWN      ;and move down one row
                ;(CALL/RETurn)
RIGHT           ENDP
;-----------------------------------------------------------------------
LEFT            PROC    NEAR
                CMP     CURSOR,0        ;at start of file?
                JZ      LR_NO_CHANGE    ;then can't move left
                MOV     DX,CURS_POSN
                OR      DL,DL           ;at first column?
                JZ      MOVE_UP         ;if yes, then move up one
                DEC     CURSOR          ;shift the cursor offset
LR_RETURN:
                CALL    CURSOR_COL      ;compute column for cursor
                MOV     SAVE_COLUMN,DL  ;save the cursor column
LR_NO_CHANGE:
                MOV     UNDO_LENGTH,0
                RET
                ;-----------------------
MOVE_UP:
                CALL    UP              ;move up to next row
                JMP     SHORT ENDD      ;and move to end of line
                ;(CALL/RETurn)
LEFT            ENDP

;-----------------------------------------------------------------------
; This routine moves the cursor to the end of the current line.
; If END key and already at end of line, move to end of next line.
;-----------------------------------------------------------------------
END_KEY         PROC    NEAR
                CALL    RIGHT           ;move one space right, if at EOL
END_KEY         ENDP

ENDD            PROC    NEAR
                MOV     SI,CURSOR
                CALL    FIND_EOL        ;find end of this line
                MOV     CURSOR,SI       ;store the new cursor
                CALL    CURSOR_COL      ;compute the correct column
                MOV     SAVE_COLUMN,DL  ;save the cursor column
                RET
ENDD            ENDP

;-----------------------------------------------------------------------
; This routine moves the cursor down one row.  When the last row is reached,
; the screen is shifted up one row.
;-----------------------------------------------------------------------
DOWN            PROC    NEAR
                MOV     UNDO_LENGTH,0
                MOV     DX,CURS_POSN
                CMP     DH,ROWS         ;at bottom row already?
                MOV     SI,CURSOR       ;get position in file
                JE      SCREEN_UP       ;if at bottom, then scroll up
                CALL    FIND_NEXT       ;find the start of next line
                JC      DOWN_RET        ;if no more lines, then return
                MOV     CURSOR,SI
                INC     DH              ;advance cursor to next row
                CALL    SHIFT_RIGHT     ;move cursor to current column
DOWN_RET:
                RET
                ;-----------------------
SCREEN_UP:
                CMP     SI,LAST_CHAR    ;get cursor offset
                JE      DOWN_RET
                CALL    FIND_START      ;find the start of this line
                MOV     CURSOR,SI       ;this is the new cursor
                CALL    FIND_NEXT       ;find the offset of next line
                JC      SHIFT_RET       ;if no more lines, then return
                MOV     CURSOR,SI       ;this is the new cursor
                MOV     SI,TOP_OF_SCREEN ;get the start of the top row
                CALL    FIND_NEXT       ;and find the next line
                MOV     TOP_OF_SCREEN,SI ;store the new top of screen
                JMP     SHORT SHIFT_RET
                ;(CALL/RETurn)
DOWN            ENDP

;-----------------------------------------------------------------------
; This routine moves the cursor up one row.  If the cursor is at the first row,
; the screen is scrolled down.
;-----------------------------------------------------------------------
UP              PROC    NEAR
                MOV     UNDO_LENGTH,0
                MOV     DX,CURS_POSN
                MOV     SI,CURSOR
                OR      DH,DH           ;at top row already?
                JZ      SCREEN_DN       ;if yes, then scroll down
                DEC     DH              ;move the cursor up one row
                CALL    FIND_CR         ;find the beginning of this row
                MOV     CURSOR,SI
                CALL    FIND_START      ;find start of this row
                MOV     CURSOR,SI
                CALL    SHIFT_RIGHT     ;skip over to current column
AT_TOP:
                RET
                ;-----------------------
SCREEN_DN:
                MOV     SI,TOP_OF_SCREEN
                OR      SI,SI           ;at start of file?
                JZ      AT_TOP          ;if at top, then do nothing
                CALL    FIND_PREVIOUS   ;find the preceding line
                MOV     TOP_OF_SCREEN,SI ;save new top of screen
                MOV     SI,CURSOR
                CALL    FIND_PREVIOUS   ;find the preceding line
                MOV     CURSOR,SI       ;this is the new cursor
SHIFT_RET:
                OR      DIRTY_BITS,1    ;need to redraw the screen
                MOV     SI,CURSOR
                MOV     DX,CURS_POSN
                JMP     SHIFT_RIGHT     ;move cursor to current column
                ;(CALL/RETurn)
UP              ENDP

;=======================================================================
; These four routines move the screen one page at a time by calling the
; UP and DOWN procedures.
;-----------------------------------------------------------------------
CTRL_PGDN       PROC    NEAR    ;do PgDn then PgUp
                CALL    PGDN
                JMP     SHORT PGUP
                ;(CALL/RETurn)
CTRL_PGDN       ENDP
;-----------------------------------------------------------------------
PGUP            PROC    NEAR
                MOV     CS:PAGE_PROC,OFFSET UP
                JMP     SHORT PAGE_UP_DN
PGUP            ENDP
;-----------------------------------------------------------------------
CTRL_PGUP       PROC    NEAR    ;do PgUp then PgDn
                CALL    PGUP
;**             JMP     SHORT PGDN
                ;(CALL/RETurn)
CTRL_PGUP       ENDP
;-----------------------------------------------------------------------
PGDN            PROC    NEAR
                MOV     CS:PAGE_PROC,OFFSET DOWN
PAGE_UP_DN:
                MOV     CL,CS:ROWS      ;get vertical length of screen
                SUB     CL,5            ;don't page a full screen
                XOR     CH,CH           ;make it a word
PAGE_LOOP:
                PUSH    CX
                CALL    PAGE_PROC       ;move the cursor down
                POP     CX
                LOOP    PAGE_LOOP       ;loop for one page length
                RET
PGDN            ENDP

;=======================================================================
; This routine toggles the Insert/Overstrike mode.
;-----------------------------------------------------------------------
INSERT          PROC    NEAR
        ;       ASSUME  DS:NOTHING
;**RO mode      NOT     CS:INSERT_MODE  ;toggle the switch
                XOR     CS:INSERT_MODE,1 ;toggle the "I/O" switch
                CALL    REDO_PROMPT ;redraw the insert/overstrike status
                RET
INSERT          ENDP

;=======================================================================
; FILE MANIPULATION services
;=======================================================================
; This routine forces the insertion of the NUL character, 00H, in the file.
;-----------------------------------------------------------------------
NUL_CHAR        PROC    NEAR
                XOR     AX,AX
NUL_CHAR        ENDP

;-----------------------------------------------------------------------
; This routine adds a character into the file.  In insert mode, remaining
; characters are pushed forward.  If the scan code for <Enter> is detected,
; a <CR><LF> pair is added as the EOL marker.  If the scan code for
; <Backspace> is detected, the character to the left of the cursor is
; deleted (unless the cursor is already at the start of the file).
;-----------------------------------------------------------------------
INSERT_KEY      PROC    NEAR
                ASSUME  DS:FILE_SEG
                MOV     SI,CS:CURSOR

                CMP     CS:INSERT_MODE,1 ;in insert mode? (0=ovr,1=ins,2 or 3=RO)
                JA      FILE_FULL       ;jump if read-only file

                CMP     AH,28           ;was this the <Enter> key scan code?
                JE      NEW_LINE        ;if yes, make new line: <CR><LF>.

                CMP     AH,14           ;was it the <Backspace> key scan code?
                JE      BACK_SPACE      ;if yes, delete character left.

                CMP     CS:INSERT_MODE,1 ;in insert mode? (0=ovr,1=ins,2 or 3=RO)
                JE      INSERT_CHAR     ;jump if Insert
                ;-----------------------
                ; test for overstrike character at end of line or file.
                ; if EOL or EOF, then insert rather than overstrike.
                ;-----------------------
                CMP     SI,CS:LAST_CHAR ;at end of file?
                JAE     INSERT_CHAR
                INC     SI
                CMP     SI,CS:LAST_CHAR ;at end of file?
                DEC     SI
                JAE     INSERT_CHAR
                CMP     WORD PTR [SI],CRLF ;at end of line?
                JE      INSERT_CHAR

OVERSTRIKE_NOT_AT_CRLF:
                MOV     DI,SI
                PUSH    AX              ;save new character
                XCHG    DS:[SI],AL      ;switch new character for old one
                CALL    SAVE_CHAR       ;store the old character
                POP     AX              ;get back new character
                JMP     SHORT ADVANCE
;---------------------------------------
INSERT_CHAR:
                PUSH    SI
                PUSH    AX              ;save the new character
                MOV     AX,1
                CALL    OPEN_SPACE      ;make room for it at SI
                POP     AX              ;get back the new character
                POP     DI
                JC      FILE_FULL
                STOSB                   ;insert character in file buffer, ES:DI
ADVANCE:
                OR      CS:DIRTY_BITS,4 ;current line is dirty

                ;-----------------------
                ; see if we made a CR-LF pair by adding a Ctrl-M with a Ctrl-J
                ;-----------------------
                CALL    C_IF_NEW_CRLF   ;see if in middle of new CR-LF
                JC      FILE_FULL       ;jump if new line was done
                ;------------------------
                CMP     AL,CR           ;was key <Ctrl-M> - CR?
                JNE     INS_NOT_CR
                MOV     CS:DIRTY_BITS,1 ;redraw the screen (just in case EOL)
INS_NOT_CR:
                ;-----------------------
                PUSH    UNDO_LENGTH
                CALL    RIGHT           ;move cursor to next letter
                POP     UNDO_LENGTH
FILE_FULL:
                RET

CR_AT_EOF:
                INC     CS:CURSOR
                RET

;---------------------------------------
; process the <Enter> key, make new line
;---------------------------------------
NEW_LINE:
                PUSH    SI
                MOV     AX,2
                CALL    OPEN_SPACE      ;make space for CR and LF
                POP     DI              ;get back old cursor location
                JC      FILE_FULL
                MOV     AX,CRLF         ;LF*256+CR
                STOSW                   ;store the CR and LF
                JMP     SHORT ADVANCE_NEW_LINE ;repaint the screen
                ;(CALL/RETurn)
INSERT_KEY      ENDP

;---------------------------------------
; process the <Backspace> key, delete character left of cursor.
;---------------------------------------
BACK_SPACE      PROC    NEAR
                OR      SI,SI           ;is cursor at start of file?
                JZ      FILE_FULL       ;if so, no delete right
                CALL    LEFT            ;move left one space (flush UnDo buffer)
;**follows      JMP     DEL_CHAR        ;and delete the character (into buffer)
                ;(CALL/RETurn)
BACK_SPACE      ENDP

;=======================================================================
; This routine deletes the character at the cursor by shifting the remaining
; characters forward.  <Del> key
;-----------------------------------------------------------------------
DEL_CHAR        PROC    NEAR
                CMP     CS:INSERT_MODE,1 ;in insert mode? (0=ovr,1=ins,2 or 3=RO)
                JA      NO_DEL          ;jump if read-only file
                MOV     CX,LAST_CHAR
                MOV     SI,CURSOR
                MOV     DI,SI
                CMP     SI,CX           ;are we at end of file?
                JAE     NO_DEL          ;if yes, then don't delete
                LODSB                   ;loads char in AL, DS:SI
                CALL    SAVE_CHAR       ;save it for UNDO function
                MOV     AH,[SI]         ;look at the next character also
                PUSH    AX              ;save character we're deleting
                DEC     LAST_CHAR       ;shorten the file by one
                SUB     CX,SI
                REP     MOVSB           ;move file down one, DS:SI to ES:DI

                POP     AX              ;get back character we deleted
                CMP     AX,CRLF         ;did we delete a CR?
                JE      COMBINE
                OR      DIRTY_BITS,4    ;current line is dirty
                ;-----------------------
                ; see if we made a CRLF by deleting a character.
                ; if we are in the middle of a CRLF, move the cursor back one.
                ;-----------------------
                JMP     SHORT C_IF_NEW_CRLF
                ;(CALL/RETurn) - saves a byte
;---------------------------------------
COMBINE:                                ;deleted an EOL marker, CR-LF
                CALL    DEL_CHAR        ;now delete the line feed (recursive)
                MOV     DIRTY_BITS,3
                MOV     DX,CURS_POSN
                MOV     SAVE_COLUMN,DL  ;save the cursor column
NO_DEL:
                RET
DEL_CHAR        ENDP

;=======================================================================
; see if we made a CR-LF pair with the cursor located on the LF character
; by joining the character (Ctrl-M) with a (Ctrl-J).  If so, back up the
; cursor and open a new line and return with carry set.  if not, return with
; carry clear.
;-----------------------------------------------------------------------
C_IF_NEW_CRLF   PROC    NEAR
                MOV     SI,CS:CURSOR    ;present cursor location
                OR      SI,SI           ;is it at start of file
                JZ      DONE_CRLF
                CMP     WORD PTR [SI-1],CRLF ;did we make CR-LF?
                JNE     DONE_CRLF
                DEC     CS:CURSOR       ;move cursor back to point at CR code
C_IF_NEW_CRLF   ENDP

;---------------------------------------
; This routine opens a new line.
;---------------------------------------
ADVANCE_NEW_LINE PROC   NEAR            ;split line on screen for CR-LF
                CALL    HOME            ;cursor to start of line
                CALL    DOWN            ;move down to the new line
                MOV     DIRTY_BITS,3    ;repaint the screen and cursor
                STC                     ;C=1 if new line was done
                RET
DONE_CRLF:
                CLC                     ;C=0 if not new line
                RET
ADVANCE_NEW_LINE ENDP

;=======================================================================
; This routine restores any characters which have recently been deleted.
;-----------------------------------------------------------------------
UNDO            PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                XOR     AX,AX
                XCHG    AX,CS:UNDO_LENGTH ;get buffer length
                MOV     SI,OFFSET UNDO_BUFFER
                JMP     INSERT_STRING
                ;(CALL/RETurn)
UNDO            ENDP

;=======================================================================
; This routine inserts spaces into the file buffer.  On entry, AX
; contains the number of spaces to be inserted.  On return, CF=1 if
; there was not enough space in the file buffer.
;-----------------------------------------------------------------------
OPEN_SPACE      PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                MOV     CX,LAST_CHAR    ;last character in the file
                MOV     SI,CX
                MOV     DI,CX
                ADD     DI,AX           ;offset for new end of file
                JC      NO_ROOM         ;if no more room, return error
                MOV     LAST_CHAR,DI    ;save offset of end of file
                SUB     CX,CURSOR       ;number of characters to shift
                DEC     DI
                DEC     SI
                STD                     ;string moves goes forward
                REP     MOVSB           ;shift the file upward
                CLD
                MOV     BYTE PTR DIRTY_FILE,0FFH ;file has been altered
                CLC
NO_ROOM:                                ;carry is set if no room in file
                RET
OPEN_SPACE      ENDP

;=======================================================================
; This routine adds a character to the UNDO buffer.
;-----------------------------------------------------------------------
SAVE_CHAR       PROC    NEAR
                MOV     BX,UNDO_LENGTH
                OR      BH,BH           ;is buffer filled?
                JNZ     NO_SAVE         ;buffer overflowed, no insert
                INC     UNDO_LENGTH
                MOV     BYTE PTR CS:UNDO_BUFFER[BX],AL
                MOV     BYTE PTR CS:DIRTY_FILE,0FFH ;file altered
NO_SAVE:
                RET
SAVE_CHAR       ENDP

;=======================================================================
; This routine deletes from the cursor position to the end of the line.
;-----------------------------------------------------------------------
DEL_EOL         PROC    NEAR
                ;-----------------------
                ; from TEDPLUS,  modified by JEG, delete line if at column 1.
;**Shft-F8      MOV     CX,CURSOR_POSN
;**Shft-F8      JCXZ    DEL_L
                ;-----------------------
                CMP     CS:INSERT_MODE,1 ;in insert mode? (0=ovr,1=ins,2 or 3=RO)
                JA      NO_DEL_L        ;jump if read-only file
                MOV     LINE_FLAG,0
                PUSH    CURSOR          ;save starting cursor location
                CALL    ENDD            ;move to the end of the line
                POP     SI              ;get back starting cursor
                MOV     CX,CURSOR       ;offset of end of line
                MOV     CURSOR,SI       ;restore starting cursor
                JMP     SHORT DEL_END   ;delete characters to end of line
                ;(CALL/RETurn)
DEL_EOL         ENDP

;-----------------------------------------------------------------------
; This routine deletes a line, placing it in the line buffer. <F9>, <Shft-F8>
;-----------------------------------------------------------------------
DEL_L           PROC    NEAR
                CMP     CS:INSERT_MODE,1 ;in insert mode? (0=ovr,1=ins,2 or 3=RO)
                JA      NO_DEL_L        ;jump if read-only file
                MOV     LINE_FLAG,1
                CALL    FIND_START      ;find start of this line
                MOV     CURSOR,SI       ;this will be the new cursor
                PUSH    SI              ;save the cursor position
                CALL    FIND_NEXT       ;find the next line
                MOV     CX,SI           ;CX will hold line length
                POP     SI              ;get back new cursor location
DEL_END:
                MOV     BYTE PTR DIRTY_FILE,0FFH ;file has been altered

                SUB     CX,SI           ;number of bytes on line
                OR      CH,CH           ;is line too long to fit?
                JZ      NOT_TOO_LONG
                MOV     CX,100H         ;save only 256 characters
NOT_TOO_LONG:
                MOV     LINE_LENGTH,CX  ;store length of deleted line
                JCXZ    NO_DEL_L
                MOV     DI,OFFSET LINE_BUFFER ;buffer for deleted line

                PUSH    CX
                PUSH    ES
                PUSH    CS
                POP     ES              ;line buffer is in CSEG
                REP     MOVSB           ;put deleted line in buffer
                POP     ES              ;get back file segment
                POP     AX

                MOV     CX,LAST_CHAR    ;get the file size
                SUB     LAST_CHAR,AX    ;subtract the deleted line
                MOV     SI,CURSOR       ;get new cursor location
                MOV     DI,SI
                ADD     SI,AX           ;SI points to end of file
                SUB     CX,SI           ;length of remaining file
                JCXZ    NO_DEL_L
                REP     MOVSB           ;shift remainder of file up
NO_DEL_L:
                MOV     DIRTY_BITS,3    ;redraw the screen and adjust cursor
                RET
DEL_L           ENDP

;=======================================================================
; This routine undeletes a line by copying it from the line buffer into
; the file.
;-----------------------------------------------------------------------
UDEL_L          PROC    NEAR
                CMP     LINE_FLAG,0     ;is this an end of line only?
                JE      UDEL_EOL        ;if yes, don't home the cursor
                CALL    HOME            ;move cursor to home
UDEL_EOL:
                MOV     AX,LINE_LENGTH  ;length of deleted line
                MOV     SI,OFFSET LINE_BUFFER
;**insert_str   JMP     INSERT_STRING   ;(follows immediately)
                ;(CALL/RETurn)
UDEL_L          ENDP

;-----------------------------------------------------------------------
; This routine inserts AX characters from CS:SI into the file.
; (This routine follows "UDEL_L")
;-----------------------------------------------------------------------
INSERT_STRING   PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                PUSH    SI              ;save string buffer
                MOV     SI,CS:CURSOR    ;get cursor offset
                PUSH    AX              ;save length of string
                PUSH    SI
                CALL    OPEN_SPACE      ;make space to insert string
                POP     DI              ;get back cursor position
                POP     CX              ;get back string length
                POP     SI              ;get back string buffer
                JC      NO_SPACE        ;if no space available, exit

                PUSH    DS
                PUSH    CS
                POP     DS
                ASSUME  DS:CSEG
                REP     MOVSB           ;copy characters DS:SI to ES:DI
                MOV     DIRTY_BITS,3    ;redraw the screen and adjust cursor
                POP     DS
                ASSUME  DS:NOTHING
                CALL    C_IF_NEW_CRLF
NO_SPACE:
                RET
INSERT_STRING   ENDP

;=======================================================================
; VIDEO services
;=======================================================================
; This routine displays a character by writing directly to
; the screen buffer.  To avoid screen noise (snow) on the color
; card (CGA), the horizontal retrace has to be monitored.
;-----------------------------------------------------------------------
WRITE_INVERSE   PROC    NEAR
                ASSUME  DS:FILE_SEG, ES:FILE_SEG
                MOV     BH,CS:INVERSE   ;attribute for inverse video
                JMP     SHORT WRITE_SCREEN
;-----------------------------------------------------------------------
WRITE_FIND:
                MOV     BH,CS:SRCH_CLR  ;attribute for find string
;               MOV     BH,CS:INVERSE   ;attribute for find string
                JMP     SHORT WRITE_SCREEN
;-----------------------------------------------------------------------
WRITE_NORMAL:
                MOV     BH,CS:NORMAL    ;attribute for normal video
WRITE_SCREEN:
                MOV     BL,AL           ;save the character
                PUSH    ES
                LES     DX,CS:VIDEO_STATUS_REG ;video status register
                ;-------------------------------------------------------
                ; HWAIT is used to "desnow" the display for the CGA.
                ; If not CGA, the following instruction is modified to a
                ; JMP   SHORT WRITE_IT
                ;-------------------------------------------------------
HWAIT:
                IN      AL,DX           ;get video status
                ROR     AL,1            ;look at horizontal retrace
                JNC     HWAIT           ;wait for retrace
WRITE_IT:
                MOV     AX,BX           ;get the character/attributes
                STOSW                   ;write the character
                POP     ES
                RET
WRITE_INVERSE   ENDP

;=======================================================================
; This routine moves the cursor to the row/column in DX.
;-----------------------------------------------------------------------
SET_CURSOR      PROC    NEAR
                XOR     BH,BH           ;we're using page zero
                MOV     AH,2            ;BIOS set cursor function
                INT     10H
                RET
SET_CURSOR      ENDP

;=======================================================================
; This routine computes the video buffer offset for the row/column in DX.
;-----------------------------------------------------------------------
POSITION        PROC    NEAR
                MOV     AX,CS:COLUMNS   ;take columns per row
                MUL     DH              ;times row number
                XOR     DH,DH
                ADD     AX,DX           ;add the column number
                SHL     AX,1            ;times 2 for offset
                MOV     DI,AX           ;return result in DI
                RET
POSITION        ENDP

;=======================================================================
; This routine erases from the location in DX to the right edge of the screen.
;-----------------------------------------------------------------------
ERASE_EOL       PROC    NEAR
                ASSUME  DS:NOTHING
                CALL    POSITION        ;find screen offset
                MOV     CX,CS:COLUMNS   ;get screen size
                SUB     CL,DL           ;subtract current position
                JCXZ    NO_CLEAR
ERASE_LOOP:
                MOV     AL," "          ;write blanks to erase
                CALL    WRITE_NORMAL    ;display it
                LOOP    ERASE_LOOP
NO_CLEAR:
                RET
ERASE_EOL       ENDP

;=======================================================================
; This routine displays the function key prompt and insert/overstrike mode
; state.  It sets DIRTY_BITS := 3 to force redraw of the entire screen
; and locate cursor.
;-----------------------------------------------------------------------
REDO_PROMPT     PROC    NEAR
                ASSUME  DS:NOTHING, ES:NOTHING
                PUSH    DS
                PUSH    CS
                POP     DS
                ASSUME  DS:CSEG
                MOV     DH,ROWS         ;put prompt at last row
                INC     DH
                XOR     DL,DL           ;and column 0
                CALL    POSITION        ;convert to screen offset
                MOV     SI,OFFSET PROMPT_STRING
KEY_LOOP:
                MOV     AL,"F"          ;display an "F"
                CALL    WRITE_NORMAL
                LODSB
                OR      AL,AL           ;last key in prompt?
                JZ      PROMPT_DONE
                CALL    WRITE_NORMAL

                CMP     BYTE PTR CS:[SI],"0" ;is it F10?
                JNE     TEXT_LOOP
                LODSB
                CALL    WRITE_NORMAL
TEXT_LOOP:
                LODSB
                OR      AL,AL           ;last letter in word?
                JNZ     WRITE_CHAR

                MOV     AL," "          ;display a space
                CALL    WRITE_NORMAL
                JMP     KEY_LOOP
WRITE_CHAR:
                CALL    WRITE_INVERSE   ;display the letter
                JMP     TEXT_LOOP       ;do the next letter
PROMPT_DONE:
                MOV     DH,ROWS
                INC     DH              ;get the last row on the screen
                MOV     DL,PROMPT_LENGTH + 9
                CALL    ERASE_EOL       ;erase to the end of this row
                MOV     BL,INSERT_MODE
                XOR     BH,BH
                MOV     AL,CS:[BX + OFFSET INSERT_CODE]
        ;       MOV     AL,"O"          ;write an "O" (for "Overstrike")
        ;       CMP     INSERT_MODE,1   ;in insert mode?
        ;       JB      OVERSTRIKE
        ;       MOV     AL,"I"          ;write an "I" (for "Insert")
        ;       JE      OVERSTRIKE
        ;       MOV     AL,"R"          ;write an "R" (for "read-only")
OVERSTRIKE:
                DEC     DI              ;backup one character position
                DEC     DI
                CALL    WRITE_NORMAL
                MOV     DIRTY_BITS,3    ;now redraw the entire screen
                POP     DS
                ASSUME  DS:NOTHING
                RET
REDO_PROMPT     ENDP

;=======================================================================
; This routine displays the file buffer on the screen.
; DISPLAY_BOTTOM has been deleted to reduce the program code size.
;-----------------------------------------------------------------------
DISPLAY_SCREEN  PROC    NEAR
                ASSUME  DS:FILE_SEG, ES:FILE_SEG

                TEST    DIRTY_BITS,2    ;see if we should adjust the cursor
                JZ      DISP_CURS_OK
                MOV     SI,CURSOR       ;get the new cursor offset
                MOV     DX,CURS_POSN    ;also get the current row
                CALL    LOCATE          ;adjust the cursor screen position
DISP_CURS_OK:

                MOV     DX,CURS_POSN
                MOV     SAVE_ROW,DH
                CALL    SET_CURSOR      ;position the cursor

                TEST    DIRTY_BITS,1    ;see if we should update the screen
                JNZ     DO_UPDATE_SCREEN

                TEST    DIRTY_BITS,4    ;is the current line dirty?
                JZ      DISP_DONE       ;if not, take jump
                CALL    DISPLAY_CURRENT ;redraw the current line
                JMP     SHORT DISP_DONE

DO_UPDATE_SCREEN:
                MOV     SI,TOP_OF_SCREEN ;point to first char on screen
                XOR     DH,DH           ;start at first row
;               JMP     SHORT NEXT_ROW
;               ;-----------------------
;DISPLAY_BOTTOM:                                ;this redraws the bottom only
;               CALL    FIND_START      ;find first character on this row
;               MOV     DX,CURS_POSN    ;get current cursor row
NEXT_ROW:
                PUSH    DX
                CALL    DISPLAY_LINE    ;display a line
                POP     DX
                ;-----------------------
                ; the keyboard is tested here to see if a key is waiting
                ;-----------------------
                MOV     AH,1
                INT     16H
                MOV     AL,1            ;for dirty_bits, update not completed
                JNZ     DISP_NOT_DONE   ;jump if there is a key is waiting

                INC     DH              ;move to the next row
                CMP     DH,ROWS         ;at end of screen yet?
                JBE     NEXT_ROW        ;do all the rows
DISP_DONE:
                MOV     AL,0
DISP_NOT_DONE:
                MOV     DIRTY_BITS,AL   ;screen is completely redone
                RET
DISPLAY_SCREEN  ENDP

;=======================================================================
; This routine displays a single line to the screen.  DH holds the
; row number, SI has the offset into the file buffer.  Tabs are expanded.
; Adjustment is made for side shift.
;-----------------------------------------------------------------------
DISPLAY_CURRENT PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                CALL    FIND_START
                MOV     DX,CS:CURS_POSN
DISPLAY_CURRENT ENDP
                ;-----------------------
DISPLAY_LINE    PROC    NEAR            ;external entry point
                XOR     DL,DL           ;start at column zero
                MOV     CS:MARGIN_COUNT,DL
                MOV     CX,DX           ;use CL to count the columns
                CALL    POSITION        ;compute offset into video
NEXT_CHAR:
                CMP     SI,LAST_CHAR    ;at end of file?
                JAE     LINE_DONE
                LODSB                   ;get next character, DS:SI
                CMP     AL,TAB          ;is this a Tab character?
                JE      EXPAND_TAB      ;if yes, expand to spaces

                CMP     SI,LAST_CHAR    ;at end of file now?
                JAE     DO_PUT          ;jump, can't be CR-LF pair

                CMP     WORD PTR [SI-1],CRLF ;EOL marker
                JE      FOUND_CR        ;quit when a CR-LF is found
DO_PUT:
                CALL    PUT_CHAR        ;put character onto screen
TAB_DONE:
                CMP     CL,CS:COLUMNSB  ;at right edge of screen?
                JB      NEXT_CHAR
                ;-----------------------
                ; from TEDPLUS
                INC     SI
                CMP     SI,LAST_CHAR    ;at end of file?
                DEC     SI
                JAE     NOT_BEYOND
                CMP     WORD PTR [SI],CRLF ;at end of line?
                ;----------------------
                JE      NOT_BEYOND
DO_DIAMOND:
                DEC     DI              ;backup one character
                DEC     DI
                MOV     AL,4            ;show a diamond
                CALL    WRITE_INVERSE   ;in inverse video
NOT_BEYOND:
                JMP     FIND_NEXT       ;find start of next line
                ;(CALL/RETurn)
;---------------------------------------
EXPAND_TAB:
                MOV     AL," "          ;convert tab to spaces
                CALL    PUT_CHAR
                MOV     AL,MARGIN_COUNT
                ADD     AL,CL           ;CL is column count
                TEST    AL,00000111B    ;at even multiples of eight?
                JNZ     EXPAND_TAB      ;if not, keep adding spaces
                JMP     TAB_DONE
;---------------------------------------
FOUND_CR:
                INC     SI              ;past the CR-LF
LINE_DONE:
                MOV     DX,CX
                JMP     ERASE_EOL       ;erase the end of the line
                ;(CALL/RETurn)
                ;-----------------------
DISPLAY_LINE    ENDP

;=======================================================================
; This routine displays a single character to the screen.  If the character
; is marked, it is shown in inverse video.  Characters outside the current
; margin are not displayed.  Characters left of the margin are skipped.
;-----------------------------------------------------------------------
PUT_CHAR        PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                MOV     BL,MARGIN_COUNT ;get distance to left margin
                CMP     BL,LEFT_MARGIN  ;are we inside left margin?
                JAE     IN_WINDOW       ;yes, show the character
                INC     BL
                MOV     MARGIN_COUNT,BL
                RET
                ;-----------------------
                ; from TEDPLUS
IN_WINDOW:
                CMP     SRCH_FLG,0
                JE      CKM
                CMP     SI,SRCH_BASE
                JBE     CKM
                CMP     SI,SRCH_END
                JA      CKM
                CALL    WRITE_FIND
;**inverse      CALL    WRITE_INVERSE   ;found string highlighted, inverse video
                JMP     SHORT NEXT_COL
CKM:
                ;-----------------------
                CMP     SI,MARK_START   ;is this character marked?
                JBE     NOT_MARKED
                CMP     SI,MARK_END
                JA      NOT_MARKED
                CALL    WRITE_INVERSE   ;marked characters shown inverse
                JMP     SHORT NEXT_COL
                ;-----------------------
NOT_MARKED:
                CALL    WRITE_NORMAL
NEXT_COL:
                INC     CL              ;increment the column count
                RET
PUT_CHAR        ENDP

;=======================================================================
; This routine adjusts the cursor position ahead to the saved cursor column.
; On entry DH has the cursor row.
;-----------------------------------------------------------------------
SHIFT_RIGHT     PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                MOV     CL,SAVE_COLUMN  ;keep the saved cursor offset
                XOR     CH,CH
                MOV     BP,CX           ;keep the saved cursor position
                ADD     CL,LEFT_MARGIN  ;shift into visible window
                ADC     CH,0
                XOR     DL,DL
                MOV     CURS_POSN,DX    ;get cursor row/column
                JCXZ    NO_CHANGE
RIGHT_AGAIN:
                PUSH    CX
                ;-----------------------
                ; (JEG)
                INC     SI
                CMP     SI,LAST_CHAR    ;at end of file?
                DEC     SI
                JAE     DO_MOVE
                CMP     WORD PTR [SI],CRLF ;at end of line?
                ;-----------------------
                JE      DONT_MOVE       ;if at end, stop moving
DO_MOVE:
                CALL    RIGHT           ;move right one character
DONT_MOVE:
                POP     CX

                MOV     AL,SAVE_COLUMN
                XOR     AH,AH
                CMP     AX,CX           ;is cursor still in margin?
                JL      IN_MARGIN       ;if yes, keep moving

                MOV     DX,CURS_POSN    ;get cursor column again
                XOR     DH,DH
                CMP     DX,BP           ;at saved cursor position?
                JE      RIGHT_DONE      ;if yes, we're done
                JA      RIGHT_TOO_FAR   ;did we go too far?
IN_MARGIN:
                LOOP    RIGHT_AGAIN
RIGHT_DONE:
                MOV     CX,BP
                MOV     SAVE_COLUMN,CL  ;get back saved cursor position
NO_CHANGE:
                RET
                ;-----------------------
RIGHT_TOO_FAR:
                CALL    LEFT            ;move back left one place
                MOV     CX,BP
                MOV     SAVE_COLUMN,CL  ;get back saved cursor position
                RET
SHIFT_RIGHT     ENDP

;=======================================================================
; This routine finds the beginning of the previous line.
;-----------------------------------------------------------------------
FIND_PREVIOUS   PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                PUSH    CURSOR          ;save the cursor location
                CALL    FIND_CR         ;find the start of this line
                MOV     CURSOR,SI       ;save the new cursor
                CALL    FIND_START      ;find the start of this line
                POP     CURSOR          ;get back starting cursor
                RET
FIND_PREVIOUS   ENDP

;=======================================================================
; This routine searches for the previous carriage return.
; Search starts at DS:SI.
;-----------------------------------------------------------------------
FIND_CR         PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                PUSH    CX
                MOV     AL,LF           ;look for a line feed character
                MOV     DI,SI
                MOV     CX,SI
                JCXZ    AT_BEGINNING
                DEC     DI
                STD                     ;search backwards
LF_PREV:
                REPNE   SCASB           ;scan for the character
                ;-----------------------
                ; from TEDPLUS
                JCXZ    LF_END
                CMP     BYTE PTR [DI],CR
                JNE     LF_PREV
                DEC     DI
LF_END:
                ;-----------------------
                CLD                     ;restore the direction flag
                INC     DI
                MOV     SI,DI
AT_BEGINNING:
                POP     CX
                RET
FIND_CR         ENDP

;=======================================================================
; This routine skips past the CR and LF at SI.  SI returns new offset.
;-----------------------------------------------------------------------
SKIP_CR_LF      PROC    NEAR
        ;       ASSUME  CS:FILE_SEG
                CMP     SI,LAST_CHAR    ;at last char in file?
                JAE     NO_SKIP         ;if yes, don't skip anything
                CMP     BYTE PTR [SI],CR ;is first character a CR?
                JNE     NO_SKIP
                INC     SI              ;look at next character
                CMP     SI,LAST_CHAR    ;is it at the end of file?
                JAE     NO_SKIP         ;if yes, don't skip anymore
                CMP     BYTE PTR [SI],LF ;is next character a line feed?
                JNE     NO_SKIP         ;skip any line feeds also
                INC     SI
NO_SKIP:
                RET
SKIP_CR_LF      ENDP

;=======================================================================
; This routine computes the location of the start of current line.
; Returns SI pointing to the first character of the current line.
;-----------------------------------------------------------------------
FIND_START      PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                MOV     SI,CURSOR       ;get the current cursor
                OR      SI,SI           ;at start of file?
                JZ      AT_START        ;if yse, we're done
                CALL    FIND_CR         ;find the CR
                CALL    SKIP_CR_LF
AT_START:
                RET
FIND_START      ENDP

;=======================================================================
; This routine finds the offset of the start of the next line.  The search
; is started at location of ES:SI.  On return, CF=1 if no CR was found.
;-----------------------------------------------------------------------
FIND_NEXT       PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                PUSH    CX
                CALL    FIND_EOL        ;find the end of this line
                JC      AT_NEXT         ;if at end of file, return
                CALL    SKIP_CR_LF      ;skip past CR and LF
                CLC                     ;indicate end of line found
AT_NEXT:
                POP     CX
                RET
FIND_NEXT       ENDP

;=======================================================================
; This routine searches for the next carriage return in the file.
; The search starts at the offset in register SI.
;-----------------------------------------------------------------------
FIND_EOL        PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                MOV     AL,CR           ;look for a carriage return
CR_SCAN:
                MOV     CX,LAST_CHAR    ;last letter in the file
                SUB     CX,SI           ;count for the search
                MOV     DI,SI
                JCXZ    AT_END          ;if nothing to search, return
                REPNE   SCASB           ;scan for the character
                MOV     SI,DI           ;return the location of the CR
                JCXZ    AT_END          ;if not found, return
                ;-----------------------
                ; from TEDPLUS
                CMP     BYTE PTR [SI],LF
                JNE     CR_SCAN
                ;-----------------------
                DEC     SI              ;back up one, to CR character
                CLC                     ;indicate the CR was found
                RET
AT_END:
                STC                     ;indicate CR was not found
                RET
FIND_EOL        ENDP

;=======================================================================
; This routine positions the screen with the cursor at the row
; selected in register DH.  On entry, SI holds the cursor offset.
;-----------------------------------------------------------------------
LOCATE          PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                MOV     CL,DH
                XOR     CH,CH
                MOV     CURSOR,SI
                XOR     DX,DX           ;start at top of screen
                OR      SI,SI           ;at start of buffer?
                JZ      LOCATE_FIRST

                CALL    FIND_START      ;get start of this row
                XOR     DX,DX           ;start at top of screen
                OR      SI,SI           ;is cursor at top of file?
                JZ      LOCATE_FIRST
                JCXZ    LOCATE_FIRST    ;if location to top row were done
FIND_TOP:
                PUSH    SI
                PUSH    CX
                CALL    FIND_CR         ;find previous row
                POP     CX
                POP     AX
                CMP     WORD PTR [SI],CRLF
                JNE     LOCATE_FIRST
                CMP     SI,AX           ;did it change?
                JE      LOCATE_DONE     ;if not, quit moving
                INC     DH              ;cursor moves to next row
                LOOP    FIND_TOP

LOCATE_DONE:
                PUSH    CURSOR
                MOV     CURSOR,SI
                CALL    FIND_START      ;find start at top of screen
                POP     CURSOR
LOCATE_FIRST:
                MOV     TOP_OF_SCREEN,SI
                MOV     CURS_POSN,DX
                CALL    CURSOR_COL
                MOV     SAVE_COLUMN,DL
                RET
LOCATE          ENDP

;=======================================================================
; This routine computes the correct column for the cursor.  No inputs.
; On exit, CURS_POSN is set and DX has row/column.
;-----------------------------------------------------------------------
CURSOR_COL      PROC    NEAR
        ;       ASSUME  DS:FILE_SEG
                MOV     SI,CURSOR       ;get cursor offset
                CALL    FIND_START      ;find start of this line
                MOV     CX,CURSOR       ;cursor location in file
                SUB     CX,SI
                MOV     DX,CURS_POSN    ;get current row
                XOR     DL,DL           ;start at column zero
                MOV     MARGIN_COUNT,DL ;count past the left margin
                JCXZ    COL_DONE
CURSOR_LOOP:
                LODSB                   ;get the next character
                INC     SI
                CMP     SI,LAST_CHAR
                DEC     SI
                JAE     NOT_EOL         ;is <CR> without <LF>, at end of file

                CMP     WORD PTR [SI-1],CRLF ;(JEG)
                JE      COL_DONE        ;if at end, we're done
NOT_EOL:
                CMP     AL,TAB          ;is it a tab?
                JNE     NOT_A_TAB

                MOV     BL,MARGIN_COUNT
                OR      BL,00000111B
                MOV     MARGIN_COUNT,BL
                CMP     BL,LEFT_MARGIN  ;inside visible window yet?
                JB      NOT_A_TAB       ;if not, don't advance cursor
                OR      DL,00000111B    ;move to multiple of eight
NOT_A_TAB:
                MOV     BL,MARGIN_COUNT
                INC     BL
                MOV     MARGIN_COUNT,BL
                CMP     BL,LEFT_MARGIN
                JBE     OUT_OF_WINDOW
                INC     DL              ;we're at next column now
OUT_OF_WINDOW:
                LOOP    CURSOR_LOOP
COL_DONE:
                CMP     DL,COLUMNSB     ;past end of display?
                JB      COLUMN_OK       ;if not, we're OK
                MOV     DL,COLUMNSB
                DEC     DL              ;leave cursor at last column
COLUMN_OK:
                MOV     CURS_POSN,DX    ;store at last column
                RET
CURSOR_COL      ENDP

;=======================================================================
; MARK, CUT, PASTE, PRINT services
;=======================================================================
; This routine toggles the mark state and resets the paste buffer pointers.
;-----------------------------------------------------------------------
MARK            PROC    NEAR
                XOR     AX,AX
                NOT     CS:MARK_MODE    ;toggle the mode flag
                CMP     CS:MARK_MODE,AL ;turning mode ON?
                JNE     MARK_ON
                OR      CS:DIRTY_BITS,1 ;need to redraw the screen
                MOV     CS:MARK_START,0FFFFH
                JMP     SHORT MARK_RET
                ;-----------------------
MARK_ON:
                MOV     AX,CS:CURSOR    ;get the cursor offset
                MOV     CS:MARK_START,AX        ;start of marked range
MARK_RET:
                MOV     CS:MARK_END,AX  ;end of marked range
                MOV     CS:MARK_HOME,AX ;center of marked range
                RET
MARK            ENDP

;=======================================================================
; This routine removes the marked text and places it in the paste buffer.
;-----------------------------------------------------------------------
CUT             PROC    NEAR
                CMP     CS:INSERT_MODE,1 ;in insert mode? (0=ovr,1=ins,2 or 3=RO)
                JA      NO_MARK         ;jump if read-only file
                CMP     CS:MARK_MODE,0  ;is the mark mode on?
                JE      NO_MARK         ;if not, then do nothing
                MOV     CX,CS:MARK_END  ;get end of mark region
                MOV     SI,CS:MARK_START ;get start of mark region
                SUB     CX,SI           ;number of bytes selected
                MOV     PASTE_SIZE,CX
                XOR     DI,DI           ;point to paste buffer

                PUSH    CX
                PUSH    ES
                MOV     ES,CS:PASTE_SEG ;set the paste buffer segment to ES
                REP     MOVSB           ;deleted text to buffer, DS:SI to ES:DI
                POP     ES
                POP     AX

                MOV     CX,CS:LAST_CHAR
                SUB     CS:LAST_CHAR,AX ;shorten the file this much
                MOV     DI,CS:MARK_START
                MOV     SI,CS:MARK_END
                SUB     CX,SI
                JCXZ    NO_DELETE
                REP     MOVSB           ;shorten the file, DS:SI to ES:DI
NO_DELETE:
                MOV     DX,CS:CURS_POSN
                MOV     SI,CS:MARK_START
                CALL    LOCATE          ;adjust the screen position
                CALL    MARK            ;this turns off select

                ;-----------------------
                ; see if we made a CR-LF pair by cutting the character(s) from
                ; between a Ctrl-M and a Ctrl-J.  If so, open a new line.
                ;-----------------------
                CALL    C_IF_NEW_CRLF

NO_MARK:
                RET
CUT             ENDP

;=======================================================================
; This routine copies the paste buffer into the file at the cursor location.
;-----------------------------------------------------------------------
PASTE           PROC    NEAR
                MOV     AX,PASTE_SIZE   ;number of characters in buffer
                OR      AX,AX           ;any there?
                JZ      NO_PASTE        ;if not, nothing to paste

                MOV     SI,CURSOR       ;get cursor location
                PUSH    AX
                PUSH    SI
                CALL    OPEN_SPACE      ;make room for new characters
                POP     DI
                POP     CX
                JC      NO_PASTE        ;if no room, just exit
                XOR     SI,SI           ;point to start of paste buffer
                PUSH    DS
                MOV     DS,PASTE_SEG    ;segment of paste buffer
                REP     MOVSB           ;copy in the new characters
                POP     DS
                OR      DIRTY_BITS,1    ;redraw the screen
                CALL    C_IF_NEW_CRLF

NO_PASTE:
                RET
PASTE           ENDP

;=======================================================================
; This routine prints an ASCII Form-Feed character.
;-----------------------------------------------------------------------
PRINT_FF        PROC    NEAR
                CALL    MARK            ;toggle MARK "on"
                PUSH    DS
                PUSH    CS
                POP     DS
                MOV     CX,1            ;one character to print
                MOV     SI,OFFSET FF    ;offset of last byte in paste buffer
                CALL    DO_FF_CHAR
                POP     DS
                RET

FF              DB      12              ;the ASCII Form-Feed character
PRINT_FF        ENDP
;-----------------------------------------------------------------------
; This routine prints the marked text.  If printer fails, it is cancelled.
;-----------------------------------------------------------------------
PRINT           PROC    NEAR
                CMP     MARK_MODE,0     ;is mark mode on?
                JE      PRINT_RET       ;if not, nothing to print
                MOV     CX,MARK_END     ;end of marked region
                MOV     SI,MARK_START   ;start of marked region
                SUB     CX,SI           ;number of bytes selected
                JCXZ    PRINT_DONE      ;if nothing to print, return
DO_FF_CHAR:
                XOR     DX,DX           ;select printer 0
                MOV     AH,2
                INT     17H             ;get printer status
                TEST    AH,10000000B    ;is busy bit set?
                JZ      PRINT_DONE
                TEST    AH,00100000B    ;is printer out of paper?
                JNZ     PRINT_DONE
PRINT_LOOP:
                LODSB
                XOR     AH,AH
                INT     17H             ;print the character
                ROR     AH,1            ;check the time out bit
                JC      PRINT_DONE      ;if set, quit printing
                LOOP    PRINT_LOOP
                MOV     AL,CR
;------ [typographical error in article]
;**nop!         XOR     AH,0            ;[would be No-Operation]
                XOR     AH,AH
;------
                INT     17H             ;finish with a CR
PRINT_DONE:
                CALL    MARK            ;turn off the mark status
PRINT_RET:
                RET
PRINT           ENDP

;=======================================================================
; CHARACTER STRING SEARCH and SEARCH AGAIN services; HELP screen
;=======================================================================
; from TEDPLUS
; This routine is used to search or search again for a character string.
;-----------------------------------------------------------------------
FIND_STR        PROC    NEAR
                PUSH    DS
                MOV     BX,CS
                MOV     DS,BX
                ASSUME  DS:CSEG
;---------------------------------------
;** now ShftF6  CMP     AH,66H          ;Is it shift F9
                CMP     AH,89           ;Is it shift-F6 (JEG)
;---------------------------------------
                JE      RPT_FIND
                MOV     DH,ROWS
                INC     DH              ;Last row on the screen
                XOR     DL,DL           ;First column
                MOV     SI,OFFSET SRCH_PROMPT
                CALL    TTY_STRING      ;Display search prompt
                MOV     DX, OFFSET SRCH_MAX
                MOV     AH,0AH
                INT     21H             ;Read input string
RPT_FIND:
                XOR     DX,DX
                MOV     DL,BYTE PTR SRCH_SIZ
                ADD     DX,OFFSET SRCH_STR
                MOV     DI,DX
                DEC     DI
                MOV     SRCH_END,DI
                XOR     DX,DX
                MOV     SI,CURSOR
                INC     SI
                MOV     SRCH_BASE,SI
S_REDO:
                MOV     DI,OFFSET SRCH_STR
                MOV     BX,SRCH_BASE
S_CYCLE:        MOV     AL,[DI]
                MOV     AH,AL           ;CONVERT AL TO OPPOSITE AND PUT IN AH
                CMP     AL,'A'
                JB      S_CMP
                CMP     AL,'Z'
                JA      TSTLO
                XOR     AH,20H          ;toggle letter case
                JMP     SHORT S_CMP
TSTLO:          CMP     AL,'a'
                JB      S_CMP
                CMP     AL,'z'
                JA      S_CMP
                XOR     AH,20H          ;toggle letter case
S_CMP:          CMP     BX,LAST_CHAR
                JA      END_MCH
                CMP     AL,ES:[BX]
                JE      S_MCH
                CMP     AH,ES:[BX]
                JE      S_MCH
                CMP     DI,OFFSET SRCH_STR
                JNE     S_REDO
                INC     BX
                CMP     WORD PTR ES:[BX-1],CRLF ;test for EOL marker
                JNE     S_BX1
                INC     DL
S_BX1:          JMP     SHORT S_CMP

S_MCH:          INC     BX
                CMP     DI,OFFSET SRCH_STR
                JNE     NO_BSE
                MOV     SRCH_BASE,BX
NO_BSE:         ADD     DH,DL
                XOR     DL,DL
                CMP     DI,SRCH_END
                JE      YEA_MCH
                INC     DI
                JMP     SHORT S_CYCLE
YEA_MCH:
                MOV     SRCH_FLG,1
                MOV     SI,SRCH_BASE
                DEC     SI
                MOV     SRCH_BASE,SI
                MOV     CURSOR,SI
                XOR     BX,BX
                MOV     BL,BYTE PTR SRCH_SIZ
                ADD     BX,SI
                MOV     SRCH_END,BX
                XOR     DL,DL
                ADD     DX,CURS_POSN
                CMP     DH,ROWS
                JBE     NEW_S
                XOR     DX,DX
NEW_S:
                POP     DS              ;ASSUME is no longer valid
                CALL    LOCATE
                JMP     SHORT END_MCH2
END_MCH:
                POP     DS
END_MCH2:
                CALL    REDO_PROMPT     ;redraw the prompt and the screen
                RET
FIND_STR        ENDP

;=======================================================================
; This routine displays a user help screen
;-----------------------------------------------------------------------
HELP            PROC    NEAR
                PUSH    DS
                PUSH    CS
                POP     DS
                ASSUME  DS:CSEG         ;for func-9, display message
                CALL    CLR_SCREEN

                MOV     DX,OFFSET HELP_SCREEN   ;message is at DS:DX in CSEG
                MOV     AH,9            ;display message
                INT     21H
HELP_WAIT_KEY:
                MOV     AH,1            ;wait for any key pressed
                INT     16H
                JZ      HELP_WAIT_KEY
                MOV     AH,0
                INT     16H

                POP     DS
                ASSUME  DS:NOTHING
                JMP     REDO_PROMPT     ;redraw the prompt and the screen
                ;(CALL/RETurn)
HELP            ENDP

;=======================================================================
; This routine prompts for a filename then writes the file.  The original
; file is renamed to filename.BAK.  If an invalid filename is entered, the
; speaker is beeped.  If the file has not been altered, the edit file is
; abandoned and control is immediately returned to DOS.
;-----------------------------------------------------------------------
EXIT            PROC    NEAR
                PUSH    DS              ;save in case ESC key
                PUSH    ES
                MOV     AX,CS
                MOV     DS,AX
                MOV     ES,AX
                ASSUME  DS:CSEG, ES:CSEG
                TEST    BYTE PTR DIRTY_FILE,0FFH ;is the file altered?
                JNZ     NEXT_LETTER     ;(jump was out of range)
                JMP     FINISHED        ;file has not been altered, no save.
                ;-----------------------
IS_BACKSPACE:
                CMP     DI,NAME_POINTER ;at first letter?
                JLE     NEXT_LETTER     ;if yes, don't erase it
                MOV     BYTE PTR [DI-1],0
                DEC     NAME_END

NEXT_LETTER:
                MOV     DH,ROWS
                INC     DH              ;last row on the screen
                XOR     DL,DL           ;first column
                MOV     SI,OFFSET SAVE_MESS
                PUSH    DX
                CALL    TTY_STRING      ;display a prompt
                POP     DX
                ADD     DL,9            ;move right 9 spaces
                MOV     SI,NAME_POINTER
                CALL    TTY_STRING      ;display the filename

                XOR     AH,AH           ;read the next key
                INT     16H
                MOV     DI,NAME_END     ;this points to the last letter
                OR      AL,AL           ;is it a real character?
;               JZ      NEXT_LETTER     ;ignore special keys
                JZ      IS_ESCAPE       ;special keys return to edit mode
                CMP     AL,1BH          ;is it ESCape?
                JE      IS_ESCAPE       ;continue with exit procedure
                CMP     AL,CR           ;is it CR?
                JE      GOT_NAME
                CMP     AL,08H          ;is it a backspace?
                JE      IS_BACKSPACE
                CMP     DI,81H + 65     ;too many letters?
                JG      NEXT_LETTER     ;if yes, ignore them
                XOR     AH,AH           ;for new terminal 0 to string
                STOSW                   ;store the new letter and 0
                INC     NAME_END        ;name is one character longer
                JMP     NEXT_LETTER     ;read another keystroke
                ;-----------------------
IS_ESCAPE:
                POP     ES              ;get back file segment
                POP     DS
                JMP     REDO_PROMPT     ;redraw the prompt and the screen
                ;(CALL/RETurn)          ;return to edit mode
                ;-----------------------
GOT_NAME:
                MOV     DX,NAME_POINTER ;point to the filename
                MOV     AX,4300H        ;get the file attribute
                INT     21H             ;DOS call
                JNC     NAME_OK         ;if no error, filename is OK
                CMP     AX,3            ;was it "path not found" error?
                JE      BAD_NAME        ;if yes, filename was bad
NAME_OK:
                MOV     SI,OFFSET DOT_$$$       ;point to the ".$$$"
                MOV     DI,OFFSET NAME_DOT_$$$
                CALL    CHG_EXTENSION           ;add the new extension

                MOV     DX,OFFSET NAME_DOT_$$$  ;point to temp filename
                MOV     CX,0020H                ;attribute for new file
                MOV     AH,3CH                  ;function to create file
                INT     21H                     ;DOS call
                JNC     NAME_WAS_OK             ;continue if name was OK
BAD_NAME:
                MOV     AX,0E07H        ;write a bell character
                INT     10H             ;BIOS TTY service
                JMP     NEXT_LETTER     ;get another letter
                ;-----------------------
WRITE_ERROR:
                MOV     AH,3EH          ;close the file
                INT     21H             ;DOS call
                JMP     BAD_NAME        ;filename must be bad
                ;-----------------------
NAME_WAS_OK:
                XOR     DX,DX           ;this is the file buffer
                MOV     CX,LAST_CHAR    ;number of chars in file
                MOV     BX,AX           ;this is the handle
                MOV     AH,40H          ;write to the file
                POP     DS              ;recover the buffer segment
                INT     21H             ;write the buffer contents
                POP     DS
                JC      WRITE_ERROR     ;exit on a write error
                CMP     AX,CX           ;was entire file written?
                JNE     WRITE_ERROR     ;if not, exit

                PUSH    CS
                POP     DS                      ;get the code segment
                MOV     AH,3EH                  ;close the temp file
                INT     21H                     ;DOS call
                MOV     SI,OFFSET DOT_BAK       ;point to the ".BAK"
                MOV     DI,OFFSET NAME_DOT_BAK
                CALL    CHG_EXTENSION           ;make the backup filename

                MOV     DX,OFFSET NAME_DOT_BAK  ;point to the backup name
                MOV     AH,41H                  ;delete existing backup file
                INT     21H                     ;DOS call
                MOV     DI,OFFSET NAME_DOT_BAK
                MOV     DX,NAME_POINTER
                MOV     AH,56H
                INT     21H                     ;DOS call

                MOV     DI,NAME_POINTER ;point to new filename
                MOV     DX,OFFSET NAME_DOT_$$$ ;point to temporary file
                MOV     AH,56H          ;rename temp to new file
                INT     21H             ;DOS call, rename
                POP     AX              ;restore the stack
                POP     AX
                JMP     SHORT FINISHED
                ;(To return to DOS)
EXIT            ENDP

;-----------------------------------------------------------------------
; This routine prompts for a verify keystroke then exits without saving
; the file.  If the file has not been altered, the edit file is abandoned
; and control is immediately returned to DOS.
;-----------------------------------------------------------------------
ABORT           PROC    NEAR
                PUSH    CS
                POP     DS
                ASSUME  DS:CSEG
                TEST    BYTE PTR DIRTY_FILE,0FFH ;is the file altered?
                JZ      FINISHED        ;file has not been altered.

                MOV     DH,ROWS         ;last row on display
                INC     DH              ;bottom row on screen
                XOR     DL,DL           ;first column
                MOV     SI,OFFSET VERIFY_MESS
                CALL    TTY_STRING      ;display verify message

                XOR     AH,AH           ;read the next key
                INT     16H             ;BIOS read key routine
                CMP     AL,CR           ;is it CR?
                JE      FINISHED
                OR      AL,20H          ;convert to lower case
                CMP     AL,"y"          ;was answer Yes?
                JE      FINISHED        ;if yes, then we're finished
                CALL    REDO_PROMPT     ;redraw the prompt and the screen
                PUSH    ES
                POP     DS              ;set DS back to file segment
                RET                     ;return to edit mode
                ;-----------------------
FINISHED:
                MOV     DX,OFFSET COPYRIGHT
                ;-----------------------
EXIT_TO_DOS:                            ;External entry point (DX, message)
                PUSH    CS
                POP     DS              ;point to code segment
                ASSUME  DS:CSEG
                CALL    CLR_SCREEN      ;clear the screen (passes DX through)
                MOV     AH,9            ;display error/exit message DS:DX
                INT     21H             ;DOS call
                MOV     AX,EXIT_CODE    ;Exit to DOS with exit code
                INT     21H             ;DOS call

ABORT           ENDP

;=======================================================================
; This routine copies the input filename to CS:DI and changes the extension.
;-----------------------------------------------------------------------
CHG_EXTENSION   PROC    NEAR
                ASSUME  DS:CSEG, ES:CSEG
                PUSH    SI
                MOV     SI,NAME_POINTER
CHG_LOOP:
                LODSB
                CMP     AL,"."          ;look for the extension
                JE      FOUND_DOT
                OR      AL,AL
                JZ      FOUND_DOT
                STOSB                   ;copy a character
                JMP     CHG_LOOP
                ;-----------------------
FOUND_DOT:
                MOV     CX,5            ;five characters in extension
                POP     SI
                REP     MOVSB           ;move new extension in
                RET
CHG_EXTENSION   ENDP

;=======================================================================
; This routine displays the string at CS:SI at the location in DX.  The
; remainder of the row is erased.  Cursor is put at the end of the line.
;-----------------------------------------------------------------------
TTY_STRING      PROC    NEAR
                ASSUME  DS:CSEG
                PUSH    DX
                CALL    POSITION        ;compute offset into video
                POP     DX
TTY_LOOP:
                LODSB                   ;load DS:SI to AL
                OR      AL,AL           ;at end of string yet?
                JZ      TTY_DONE
                INC     DL
                PUSH    DX
                CALL    WRITE_INVERSE   ;write in inverse video
                POP     DX
                JMP     TTY_LOOP
                ;-----------------------
TTY_DONE:
                CALL    SET_CURSOR      ;move cursor to end of string
                JMP     ERASE_EOL       ;erase the rest of the line
                ;(CALL/RETurn)
TTY_STRING      ENDP

;=======================================================================
; This routine clears the screen, leaves registers unaltered.
;-----------------------------------------------------------------------
CLR_SCREEN      PROC    NEAR
                ASSUME  DS:NOTHING
                PUSH    AX              ;save registers (for INT-24)
                PUSH    BX
                PUSH    CX
                PUSH    DX              ;save DX for message address
                MOV     DL,79           ;DL = right column
                MOV     DH,CS:ROWS      ;DH = lower row
                INC     DH              ;to clear the last line on the screen
                MOV     CX,0            ;CH = upper row, CL = left column
                MOV     BH,CS:NORMAL    ;BH is blank line attribute, 07 = normal video
                MOV     AX,0600H        ;scroll the cursor up (blank line)
                INT     10H
                XOR     DX,DX           ;set cursor to top left of screen
                CALL    SET_CURSOR
                POP     DX
                POP     CX
                POP     BX
                POP     AX
                RET
CLR_SCREEN      ENDP

;=======================================================================
; This is the control break handler for MS-DOS.  It ignores the break.
; TED then responds by entering a <Ctrl-C> in the file.
;-----------------------------------------------------------------------
NEWINT23        PROC    FAR
                ASSUME  DS:NOTHING, ES:NOTHING
                MOV     CS:DIRTY_BITS,1 ;to redraw the screen
                CLC                     ;tell DOS to ignore break
                IRET
NEWINT23        ENDP

;=======================================================================
; This is the severe error handler.  It homes the cursor before
; processing the error.
;-----------------------------------------------------------------------
NEWINT24        PROC    FAR
                ASSUME  DS:NOTHING, ES:NOTHING
                PUSHF
                CALL    CLR_SCREEN      ;clear the screen and home the cursor
                POPF
                JMP     CS:OLDINT24
                ;(chain to old INT 24 handler)
NEWINT24        ENDP

;=======================================================================
; This is the location of the character string search buffer.  It occupies
; the last three bytes in the program file.  The following 64-bytes are used
; for the buffer area.  The third byte is also used as the program code
; check-sum compensation byte.
;-----------------------------------------------------------------------
                EVEN
SRCH_MAX        DB      66              ;42H, maximum size of buffer
SRCH_SIZ        DB      0               ;number of char's actually in buffer
SRCH_STR        DB      0               ;start of buffer block

                ;This is the correction byte for the check-sum test
CHEK_SUM_BYT    DB      0103

;=======================================================================

NAME_DOT_$$$    EQU     $ +64           ;128 bytes
NAME_DOT_BAK    EQU     $+80H +64       ;128 bytes
UNDO_BUFFER     EQU     $+100H +64      ;256 bytes, Del key buffer
LINE_BUFFER     EQU     $+200H +64      ;256 bytes, Delete line, EOL

END_BUFFER      EQU     $+300H +64 +15  ;end of buffers, file segment follows
                                        ; +15 is for paragraph rounding


CSEG    ENDS

;-----------------------------------------------------------------------

FILE_SEG        SEGMENT
FILE_SEG        ENDS

END             START
[ RETURN TO DIRECTORY ]