
;------------------------------------------------------------------------------
;           CUTPASTE.ASM
;           Must be converted to a .COM file
;------------------------------------------------------------------------------
;
;---------------
;======= Equates
;---------------
;
;------- Window
WNDW_HT     EQU     25
WNDW_WD     EQU     80
;------- Scan codes -- hot keys
CUT_KEY     EQU     7000H              ; Alt-Fn9
PASTE_KEY   EQU     6600H              ; Ctl-Fn9
;------- Scan code -- mark key
MARK_CHAR   EQU     3B00H              ; Fn1
;------- Scan codes -- cursor movement keys
UP_ARROW    EQU     4800H
DOWN_ARROW  EQU     5000H
LEFT_ARROW  EQU     4B00H
RIGHT_ARROW EQU     4D00H
CTL_RGT_ARW EQU     7400H              ; Control-RightArrow
CTL_LFT_ARW EQU     7300H              ; Control-LeftArrow
HOME_KEY    EQU     4700H
END_KEY     EQU     4F00H
TOP_PAGE    EQU     4900H              ; PageUp
BOT_PAGE    EQU     5100H              ; PageDown
;------- Scan codes -- insert/delete keys
CHAR_INS    EQU     5200H
DELETE      EQU     5300H              ; Delete (lower right)
BACKSPACE   EQU     0008H              ; User rubout (^H) key
LINE_INS    EQU     000DH              ; Enter
DEL_TO_EOL  EQU     4000H              ; Fn6
LINE_DEL    EQU     4100H              ; Fn7
;------- Scan codes -- carriage return
CAR_RET1    EQU     000DH              ; Carriage return (one byte)
CAR_RET2    EQU     1C0DH              ; Carriage return (two bytes)
;------- Characters and attributes
BLANK       EQU     0720H              ; Define the blank character
ATTR        EQU     07H                ; Default attribute (white on black)
INV_ATTR    EQU     70H                ; Inverse of ATTR (black on white)
;
;
;======================================= Segment CODESEG
;
COMSEG      SEGMENT PARA PUBLIC 'CODE'
            ASSUME  CS:COMSEG, DS:COMSEG, ES:COMSEG
            ORG     100H

START       LABEL   NEAR
            JMP     INSTALL

;-------------------------
;======= Data storage area
;-------------------------
;
;------- Copyright information
COPYRITE    DB 'Copyright Gerry Boyd, Larry Weiss, Randy Davis 1985  All Rights Reserved'
            DB '(214)238-9545'
;------- Buffer to hold screen contents
SCRNSAVE    DW      ( WNDW_HT * (WNDW_WD+1) ) DUP(BLANK)
;------- Cursor location
CURSOR_POS  DW      0
OLD_CUR_POS DW      0
;------- Old stack location
SAVSTAKSP   DW      0
SAVSTAKSS   DW      0
;------- Request type
REQUEST     LABEL   WORD
 REQUEST0   DB      0
 REQUEST1   DB      0
 REQUEST2   DB      0
;------- Original keyboard request handler address
OLDINT16    LABEL   DWORD
 OLDINT16IP DW      0
 OLDINT16CS DW      0
;------- Define the confines of the window
LEFT_MARG   DB      0
RIGHT_MARG  DB      0
TOP_MARG    DB      0
BOT_MARG    DB      0
;------- Feed chars to application
MARK        DW      -1                 ; Flag used in character feed
FEED_START  DW      0                  ; Address in buffer of begin...
FEED_STOP   DW      0                  ; ...and end of feed
FEED_END1   DW      0                  ; Temp holding spot for beg feed addr
FEED_END2   DW      0                  ; Temp holding spot for end feed addr
FEED_CHAR   DW      0                  ; Char to feed on this call
;------- Display segment
DISPLAY_SEG DW      0
;------- Flag decribing movement of data between screen and buffer
SWAP_SAVE   DB      0


;-------------------
;======= Subroutines
;-------------------

            ASSUME  CS:COMSEG, DS:COMSEG, ES:NOTHING

;======= Proc CALC_WINDOW
;
;        Calculate window extremeties and store them in
;        RIGHT_MARG, LEFT_MARG, TOP_MARG, BOT_MARG
;
CALC_WINDOW PROC    NEAR
;------- Find out video mode for calculating window size
            MOV     AH,0FH
            INT     10H
;------- Save the video segment
            MOV     CX,0B000H          ; Assume monochrome
            CMP     AL,7               ; Look for mode 7
            JZ      CW100              ; Loop around if mono
            MOV     CX,0B800H          ; No, it's graphics
CW100:      MOV     DISPLAY_SEG,CX
;------- Save window margins
            DEC     AH                 ; Number of screen columns
            MOV     RIGHT_MARG,AH
            MOV     LEFT_MARG,0
            MOV     TOP_MARG,0
            MOV     BOT_MARG,WNDW_HT-1
;------- Exit
            RET
CALC_WINDOW ENDP


;======= Proc WINDOW_SAVE
WINDOW_SAVE PROC    NEAR
            MOV     SWAP_SAVE,0
            CALL    WIN_SWP_SAV
            RET
WINDOW_SAVE ENDP


;======= Proc WINDOW_SWAP
WINDOW_SWAP PROC    NEAR
            MOV     SWAP_SAVE,0FFH
            CALL    WIN_SWP_SAV
            RET
WINDOW_SWAP ENDP


;======= Proc WIN_SWP_SAV
;
;        When SWAP_SAVE=0, saves screen in buffer SCRNSAVE.
;        When SWAP_SAVE=FF, exchanges contents of screen SCRNSAVE.
;        This procedure does no BIOS calls -- all direct writes
;
WIN_SWP_SAV PROC    NEAR
;
;******* Setup for processing loop
            MOV     CX,WNDW_HT         ; Number of rows in window area
            MOV     ES,DISPLAY_SEG     ; Load up the video segment
            MOV     SI,OFFSET SCRNSAVE ; Point SI at beginning of buffer
            XOR     DI,DI              ; DI indexes rows (lines)
            XOR     BX,BX              ; BX indexes columns (chars)
;
;******* Loop on lines
WS050       LABEL   NEAR
            MOV     BL,LEFT_MARG       ; Start on this line at left margin
;------- Process next character and attribute on line
WS100:      SHL     BX,1               ; Change column number to byte pointer
            MOV     AX,ES:[BX][DI]     ; Get the next char/attr from screen
            XCHG    AX,[SI]            ; Store it and write saved char
            ADD     SI,2               ; Move pointer over a word
;------- Perform swap/save check
            CMP     SWAP_SAVE,0        ; Is this window swap or window save?
            JZ      WS150              ; Loop around if a save
            MOV     ES:[BX][DI],AX     ; For swap, restore that char to screen
;------- Test for end of line
WS150:      SHR     BX,1               ; Put byte offset back to col number
            INC     BX
            CMP     BL,RIGHT_MARG      ; Are we beyond the end of the line?
            JNA     WS100              ; No, branch and process next char
;------- Skip down to next line
            MOV     [SI],CAR_RET2      ; So CRs are fed properly later
            ADD     SI,2               ; Move pointer over a word
            SHL     BX,1               ; Adjust DI by one line so that it...
            ADD     DI,BX              ; ...points to beginning of next line
            LOOP    WS050
;
;******* Exit
            RET
WIN_SWP_SAV ENDP


;======= Proc EDITOR
;
;        Check for edit keys (such as Ins, Del, etc).
;        If not one of those, assume its ASCII and insert it in the screen.
;        Return when `cut' hotkey detected.
;
EDITOR      PROC    NEAR
;
;******* Get a character and prepare for processing
ED100       LABEL   NEAR
;------- Read a character from the keyboard
            CALL    GET_CHAR
;------- If character is ASCII, then null out scan code
            OR      AL,AL
            JZ      ED105
            XOR     AH,AH
;------- Remove previous shading (if any)
ED105:      CALL    UNSHAD_SCRN
;------- Place cursor position in BX (will become new cursor position)
            MOV     BX,CURSOR_POS
;------- Check for and loop around if not `cut' hotkey pressed
            CMP     AX,CUT_KEY
            JNZ     ED120
;------- `Cut' hotkey pressed -- set termination conditions
            MOV     MARK,-1
            MOV     FEED_END1,0
            MOV     FEED_END2,0
;------- Branch to end of routine
            JMP     ED800
;
;******* Simple cursor movement keys
;------- Left arrow
ED120:      CMP     AX,LEFT_ARROW
            JNZ     ED140
            DEC     BL
            JMP     ED500
;------- Right arrow
ED140:      CMP     AX,RIGHT_ARROW
            JNZ     ED160
            INC     BL
            JMP     ED500
;------- Up arrow
ED160:      CMP     AX,UP_ARROW
            JNZ     ED180
            DEC     BH
            JMP     ED500
;------- Down arrow
ED180:      CMP     AX,DOWN_ARROW
            JNZ     ED185
            INC     BH
            JMP     ED500
;------- Home
ED185:      CMP     AX,HOME_KEY
            JNZ     ED190
            MOV     BL,LEFT_MARG
            JMP     ED500
;------- End
ED190:      CMP     AX,END_KEY
            JNZ     ED195
            MOV     BL,RIGHT_MARG
            JMP     ED500
;------- PageUp
ED195:      CMP     AX,TOP_PAGE
            JNZ     ED200
            MOV     BH,TOP_MARG
            JMP     ED500
;------- PageDown
ED200:      CMP     AX,BOT_PAGE
            JNZ     ED205
            MOV     BH,BOT_MARG
            JMP     ED500
;
;******* Control-RightArrow and Control-LeftArrow
;------- Check for Control-RightArrow and set increment
ED205:      CMP     AX,CTL_RGT_ARW
            JNZ     ED210
            MOV     CL,1               ; Set increment to go forward
            JMP     ED213
;------- Check for Control-LeftArrow and set increment
ED210:      CMP     AX,CTL_LFT_ARW
            JNZ     ED220
            CMP     BL,LEFT_MARG
            JLE     ED219
            MOV     CL,-1              ; Set increment to go backward
            DEC     BL                 ; Begin one char to left
;------- Get and save character at current position
ED213:      MOV     DX,BX              ; Use DX for cursor for READ_CHAR
            CALL    READ_CHAR          ; Get char at current position
            MOV     CH,AL              ; Save current char in CH
;------- Move one character location and check if margin violated
ED215:      ADD     DL,CL              ; Move over one character
            CMP     DL,RIGHT_MARG      ; Stop at left/right margins
            JGE     ED218
            CMP     DL,LEFT_MARG
            JLE     ED218
;------- Test for change from space to non-space
            CALL    READ_CHAR          ; Read current character
            CMP     CH,' '             ; Branch if original...
            JNZ     ED216              ; ...was not a space
            CMP     AL,' '             ; Is this a space?
            JZ      ED215              ; Yes, branch and repeat
            JMP     ED217              ; No, stop
;------- Test for change from non-space to space
ED216:      CMP     AL,' '             ; Is this a space?
            JNZ     ED215              ; No, branch and repeat
;------- If we were going towards left then move to the right by one char
ED217:      CMP     CL,-1
            JNZ     ED218
            ADD     DL,1
;------- Place new cursor position in BX
ED218:      MOV     BX,DX
;------- Branch to end of key processing loop
ED219:      JMP     ED500
;
;******* Erase remainder of line
;------- Test for, and go to next case if not, DEL_TO_EOL key
ED220:      CMP     AX,DEL_TO_EOL
            JNZ     ED240
;------- Use ERASE_LINE proc to erase rest of line
            MOV     DX,BX
            CALL    ERASE_LINE
;------- Branch to end of key processing loop
            JMP     ED500
;
;******* Backspace
;------- Test for, and go to next case if not, BACKSPACE key
ED240:      CMP     AX,BACKSPACE
            JNZ     ED260
;------- Move to left one space
            DEC     BL
;------- Test and branch if we didn't move off screen
            CMP     BL,LEFT_MARG
            JGE     ED250
;------- Put cursor at left margin and go to bottom of processing loop
            MOV     BL,LEFT_MARG
            JMP     ED500
;------- Jump to Delete key processing to the actual delete
ED250:      JMP     ED270
;
;******* Delete key
ED260       LABEL   NEAR
;------- Test for and branch if not Del key
            CMP     AX,DELETE
            JNZ     ED280
;------- Set CX to number of spaces to right of current position
ED270:      MOV     CL,RIGHT_MARG
            SUB     CL,BL
            XOR     CH,CH
;------- Set DX to current cursor position
            MOV     DX,BX
;------- Branch if CX is zero
            JCXZ    ED275
;------- Loop which moves CX characters one space to left
ED272:      INC     DL
            CALL    READ_CHAR
            DEC     DL
            CALL    WRITE_CHAR
            INC     DL
            LOOP    ED272
;------- Place BLANK at current cursor position
ED275:      MOV     AX,BLANK
            CALL    WRITE_CHAR
;------- Branch to end of key processing loop
            JMP     ED500
;
;******* Insert key
ED280       LABEL   NEAR
;------- Test for, and go to next case if not, Insert key
            CMP     AX,CHAR_INS
            JNZ     ED300
;------- Set DX to right margin of current row
            MOV     DH,BH
            MOV     DL,RIGHT_MARG
;------- Set CX to number of spaces to right of current position
            MOV     CL,DL
            SUB     CL,BL
            XOR     CH,CH
;------- Branch if CX is zero
            JCXZ    ED290
;------- Loop which moves CX characters one space to right
ED285:      DEC     DL
            CALL    READ_CHAR
            INC     DL
            CALL    WRITE_CHAR
            DEC     DL
            LOOP    ED285
;------- Place BLANK at current cursor position
ED290:      MOV     AX,BLANK
            CALL    WRITE_CHAR
;------- Branch to end of key processing loop
            JMP     ED500
;
;******* LINE_INS key
ED300       LABEL   NEAR
;------- Test for, and go to next case if not, LINE_INS key
            CMP     AX,LINE_INS
            JNZ     ED320
;------- Start at the bottom margin
            MOV     DH,BOT_MARG
;------- Start at the left margin
ED305:      MOV     DL,LEFT_MARG
;------- Test for and branch if we're on the current line
            CMP     DH,BH
            JZ      ED315
;------- Assign loop counter
            XOR     CX,CX
            MOV     CL,RIGHT_MARG
            INC     CX
;------- Loop to move row of char/attr down
ED310:      DEC     DH
            CALL    READ_CHAR          ; Get the character
            INC     DH
            CALL    WRITE_CHAR         ; And put it back one line higher
            INC     DL                 ; Move right one character
            LOOP    ED310
;------- Now move up a line and do it again
            DEC     DH
            JMP     ED305
;------- Erase the current line
ED315:      CALL    ERASE_LINE
;------- Branch to end of key processing loop
            JMP     ED500
;
;******* LINE_DEL key
ED320       LABEL   NEAR
;------- Test for, and go to next case if not, LINE_DEL key
            CMP     AX,LINE_DEL
            JNZ     ED340
;------- Start at the left margin
ED325:      MOV     DL,LEFT_MARG
;------- Test for and branch if we're on the last line
            CMP     DH,BOT_MARG
            JZ      ED335
;------- Assign loop counter
            XOR     CX,CX
            MOV     CL,RIGHT_MARG
            INC     CX
;------- Loop to move row of char/attr up
ED330:      INC     DH
            CALL    READ_CHAR          ; Get the character
            DEC     DH
            CALL    WRITE_CHAR         ; And put it back one line lower
            INC     DL                 ; Move right one character
            LOOP    ED330
;------- Now move down a line and do it again
            INC     DH
            JMP     ED325
;------- Erase bottom line on display
ED335:      CALL    ERASE_LINE         ; And wipe out the bottom line
;------- Branch to end of key processing loop
            JMP     ED500
;
;******* First MARK_CHAR
ED340       LABEL   NEAR
;------- Test for, and go to next case if not, `mark' hotkey
            CMP     AX,MARK_CHAR
            JNZ     ED400
;------- Put cursor position in DX
            MOV     DX,BX
;------- Test for and loop around if already marking
            CMP     MARK,-1
            JNZ     ED350
;------- Save cursor location in MARK
            MOV     MARK,BX
;------- Branch to end of key processing loop
            JMP     ED500
;
;******* Second MARK_CHAR
;------- Store current location as FEED_END1 address in SCRNSAVE buffer
ED350:      CALL    CONVERT_LOC
            MOV     FEED_END1,AX
;------- Store previous mark location as feed end address SCRNSAVE buffer
            MOV     DX,MARK
            CALL    CONVERT_LOC
            MOV     FEED_END2,AX
;------- Ensure that FEED_END1 is less than or equal to FEED_END2
            MOV     CX,FEED_END1
            CMP     CX,AX
            JNA     ED360
            MOV     FEED_END1,AX
            MOV     FEED_END2,CX
;------- Increase FEED_END2 by one cell
ED360:      MOV     AX,FEED_END2
            ADD     AX,2
            MOV     FEED_END2,AX
;------- Trim unmarked chars from screen
            CALL    TRIM_SCREEN
;------- Branch to exit routine
            JMP     ED800
;
;******* ASCII char -- write at current position
ED400:      MOV     AH,ATTR
            CALL    W_CHAR
            INC     BL                 ; Move over one position
;
;******* Adjust cursor position
;------- Right margin
ED500:      CMP     BL,RIGHT_MARG
            JLE     ED520
            MOV     BL,RIGHT_MARG
;------- Left margin
ED520:      CMP     BL,LEFT_MARG
            JGE     ED540
            MOV     BL,LEFT_MARG
;------- Top margin
ED540:      CMP     BH,TOP_MARG
            JGE     ED560
            MOV     BH,TOP_MARG
;------- Bottom margin
ED560:      CMP     BH,BOT_MARG
            JLE     ED600
            MOV     BH,BOT_MARG
;------- Save new cursor position in CURSOR_POS
ED600:      MOV     CURSOR_POS,BX
;------- Move cursor to the new position
            MOV     DX,BX
            CALL    PUT_CURSOR
;------- Jump to beginning of processing loop
            JMP     ED100
;
;******* End routine
ED800:      RET
;
EDITOR      ENDP


;======= Proc PUT_CURSOR
;
;        Place cursor at location in DX
;
PUT_CURSOR  PROC    NEAR
;------- If we are in mark mode, set the screen square to inverse video
            CALL    SHADE_SCRN
            MOV     AH,02H             ; SET_CUR_POS service
            PUSH    BX
            XOR     BX,BX              ; Page 0
            INT     10H
            POP     BX
            RET
PUT_CURSOR  ENDP

UP_LEFT     LABEL   WORD
 ULHC_C     DB      0
 ULHC_R     DB      0
LW_RIGHT    LABEL   WORD
 LRHC_C     DB      0
 LRHC_R     DB      0
ATTRIB      DB      0
SAVE_AX     DW      0
SAVE_DX     DW      0

;======= Proc SHADE_SCRN
;
;        Assigns INV_ATTR to characters in box defined by
;        MARK and CURSOR_POS.  Called by PUT_CURSOR.
;
SHADE_SCRN  PROC    NEAR
;
;******* Initialization
;------- Test for, and branch to routine end, if not marking
            CMP     MARK,-1            ; Is a mark set?
            JZ      SS450
;------- Save AX and DX registers
            MOV     SAVE_AX,AX
            MOV     SAVE_DX,DX
;------- First set the attribute to inverse
            MOV     ATTRIB,INV_ATTR
;
;******* Place upper left-hand corner of shaded box in UP_LEFT
;------- Place current position in BX and marked position in AX
            MOV     BX,CURSOR_POS
            MOV     AX,MARK
;------- Place upper left-hand corner column value in AL
            CMP     BL,AL
            JA      SS100
            MOV     AL,BL
;------- Place upper left-hand corner row value in AH
SS100:      CMP     BH,AH
            JA      SS200
            MOV     AH,BH
;------- Store upper left-hand corner in UP_LEFT
SS200:      MOV     UP_LEFT,AX
;
;******* Place lower right-hand corner of shaded box in LW_RIGHT
;------- Place marked position in AX
            MOV     AX,MARK
;------- Place column value in AL
            CMP     BL,AL
            JBE     SS300
            MOV     AL,BL
;------- Place row value in AH
SS300:      CMP     BH,AH
            JBE     SS400
            MOV     AH,BH
;------- Store LRH corner in LW_RIGHT
SS400:      MOV     LW_RIGHT,AX
;
;******* Call SS_COMMON to do the shading
            CALL    SS_COMMON
;
;******* Return to caller
SS450:      RET
SHADE_SCRN  ENDP


;======= Proc UNSHAD_SCRN
;
;        Assign ATTR to attributes of characters in the box defined
;        by UP_LEFT and LW_RIGHT.  Called by EDITOR, at the beginning
;        of key processing.
;
UNSHAD_SCRN PROC    NEAR
;
;------- Test for, and branch to routine end, if not marking
            CMP     MARK,-1            ; Here we unshade the shaded area
            JZ      SS475
;------- Save AX and DX registers
            MOV     SAVE_AX,AX
            MOV     SAVE_DX,DX
;------- Set the attribute to normal
            MOV     ATTRIB,ATTR        ; Change the box back to normal
;------- Call SS_COMMON to do the shading
            CALL    SS_COMMON
;------- Return to caller
SS475:      RET
;
UNSHAD_SCRN ENDP


;======= Proc SS_COMMON
;
;        Assign ATTRIB to attributes of characters in the box defined
;        by UP_LEFT and LW_RIGHT.
;
SS_COMMON   PROC    NEAR
;
;******* Initialization
;------- Put box corners in BX and DX registers
            MOV     DX,UP_LEFT         ; DX is variable -- loc to change attr
            MOV     BX,LW_RIGHT        ; BX is constant
;
;******* Change attributes of row in DH
;------- Test and branch if past last column
SS500:      CMP     DL,BL
            JA      SS600
;------- Change the attribute
            MOV     AH,ATTRIB
            CALL    WRITE_ATTR
;------- Point to next char on row and branch to top
            INC     DL
            JMP     SS500
;
;******* Prepare to do next row
;------- Point to next row
SS600:      INC     DH
;------- Branch if past last row
            CMP     DH,BH
            JA      SS700
;------- Put first column in DL and branch
            MOV     DL,ULHC_C
            JMP     SS500
;
;******* End program
;------- Restore AX and DX
SS700:      MOV     AX,SAVE_AX
            MOV     DX,SAVE_DX
;------- Return
            RET
;
SS_COMMON   ENDP


;======= Proc TRIM_SCREEN
;
;        Trims the right margin of screen after the second mark
;
TRIM_SCREEN PROC    NEAR
;
;******* Initialization
;------- First clear mark
            MOV     MARK,-1
;------- Test and branch to return if at right-hand edge of screen
            MOV     DL,LRHC_C
            CMP     DL,RIGHT_MARG
            JZ      TS200
;------- Put space to right of upper right-hand corner in BX
            MOV     BH,ULHC_R
            MOV     BL,LRHC_C
            INC     BL

;******* Loop to erase to right of box
;------- Place lower right-hand corner in DX
TS100:      MOV     DX,LW_RIGHT
;------- Branch to end if BH equals DH
            CMP     BH,DH
            JZ      TS200
;------- Erase end of current line
            MOV     DX,BX
            CALL    ERASE_LINE
;------- Increment current line and repeat
            INC     BH
            JMP     TS100
;
;******* End routine
TS200:      RET
TRIM_SCREEN ENDP


;======= Proc TRIM_BUF
;
;        Trims blanks from first and last row in the feed area
;
TRIM_BUF    PROC    NEAR
;
;******* Exit unless buffer holds data
            MOV     BX,FEED_END2
            CMP     BX,FEED_END1
            JZ      TB070
;
;******* Trim trailing blanks off last row
;------- Set CX to number of chars in a row
            XOR     CX,CX
            MOV     CL,LRHC_C
            SUB     CL,ULHC_C
            INC     CX
;------- Move backwards until first non-blank is found
TB010:      MOV     AX,[BX-2]
            CMP     AL,' '
            JNZ     TB020
            SUB     BX,2
            LOOP    TB010
;------- Reassign FEED_END2
TB020:      MOV     FEED_END2,BX
;
;******* Check for, and skip over, a blank first line
;------- Branch and end if feed area corresponds to one row
            MOV     AX,UP_LEFT
            CMP     AH,LRHC_R
            JZ      TB070
;------- Set CX to number of chars in a row
            XOR     CX,CX
            MOV     CL,LRHC_C
            SUB     CL,ULHC_C
            INC     CX
;------- Point BX to start of feed area
            MOV     BX,FEED_END1
;------- Check for, and branch to end, if a non-blank char is found
TB030:      MOV     AX,[BX]
            CMP     AL,' '
            JNZ     TB070
            ADD     BX,2
            LOOP    TB030
;------- Set AX to number of chars from left side to carriage return
            XOR     AX,AX
            MOV     AL,RIGHT_MARG
            INC     AL
            SUB     AL,ULHC_C
;------- Convert to words and add to FEED_END1
            SHL     AX,1
            ADD     FEED_END1,AX

;******* End routine
TB070:      RET
;
TRIM_BUF ENDP


;======= Proc READ_CURSOR
;
;        Find the current cursor location and place in DX
;
READ_CURSOR PROC    NEAR
            MOV     AH,03H
            PUSH    BX
            XOR     BX,BX
            INT     10H
            POP     BX
            RET
READ_CURSOR ENDP


;======= Proc READ_CHAR
;
;        Read character on screen at location in DX and place in AX
;
READ_CHAR   PROC    NEAR
;------- Covert the row and column into location
            CALL    CAL_VID_LOC
;------- Get the character and attribute at that location
            MOV     AX,ES:[DI]
            RET
READ_CHAR   ENDP


;======= Proc R_CHAR
;
;        Read character on screen at current cursor location and place in AX
;
R_CHAR      PROC    NEAR
            MOV     AH,08H
            PUSH    BX
            XOR     BH,BH
            INT     10H
            POP     BX
            RET
R_CHAR      ENDP

;======= Proc WRITE_CHAR
;
;        Write character and attribute in AX to screen at location in DX
;
WRITE_CHAR  PROC    NEAR
            PUSH    AX                 ; Save the attrib from destruction
            CALL    CAL_VID_LOC        ; Convert the row and col into location
            POP     AX
            MOV     ES:[DI],AX
            RET
WRITE_CHAR  ENDP


;======= Proc WRITE_ATTR
;
;        Write attrib in AX to screen location in DX
;
WRITE_ATTR  PROC    NEAR
            PUSH    AX
            CALL    CAL_VID_LOC
            POP     AX
            MOV     ES:[DI+1],AH
            RET
WRITE_ATTR  ENDP


;======= Proc W_CHAR
;
;        Write character and attribute in AX at current cursor location
;
W_CHAR      PROC    NEAR
;------- Save registers
            PUSH    BX
            PUSH    CX
;------- Do the write using INT 10H
            MOV     BL,AH              ; Place attribute in BL
            XOR     BH,BH              ; Page 0
            MOV     CX,1               ; Count = 1
            MOV     AH,09H             ; WRITE_CHAR_ATTR service
            INT     10H
;------- Restore registers
            POP     CX
            POP     BX
;------- Exit
            RET
W_CHAR      ENDP

;======= Proc GET_CHAR
;
;        Get character from keyboard into AX
;
GET_CHAR    PROC    NEAR
            MOV     AH,0
            PUSHF
            CALL    OLDINT16
            RET
GET_CHAR    ENDP

;======= Proc ERASE_LINE
;
;        Erase the current line on the screen from DX to RIGHT_MARG, inclusive
;
ERASE_LINE  PROC    NEAR
;------- Set CX to number of columns to erase
            XOR     CX,CX
            MOV     CL,RIGHT_MARG
            SUB     CL,DL
            INC     CL
;------- Loop to blank row
ER100:      MOV     AX,BLANK
            CALL    WRITE_CHAR
            INC     DL
            LOOP    ER100
;------- Exit routine
            RET
ERASE_LINE  ENDP


;======= Proc CONVERT_LOC
;
;        Convert a location on the screen (in DX) into an offset in
;        the SCRNSAVE buffer (in AX).
;
CONVERT_LOC PROC    NEAR
;
;******* Initialize
;------- Place line length (in bytes) in BX
            XOR     BX,BX
            MOV     BL,RIGHT_MARG
            INC     BX
            INC     BX
            SHL     BX,1
;------- Place location in CX
            MOV     CX,DX
;------- Point AX at beginning of SCRNSAVE buffer
            MOV     AX,OFFSET SCRNSAVE
;
;******* Loop to find row
;------- Test and branch if CH equals top row of window
CL100:      CMP     CH,TOP_MARG
            JZ      CL200
;------- Add a row to AX and repeat test
            ADD     AX,BX
            DEC     CH
            JMP     CL100
;
;******* Loop to find column
;------- Test and branch if CL equals left margin of window
CL200:      CMP     CL,LEFT_MARG
            JZ      CL300
;------- Add a column and repeat
            ADD     AX,2
            DEC     CL
            JMP     CL200
;
;******* Exit
CL300:      RET
CONVERT_LOC ENDP


;======= Proc CAL_VID_LOC
;
;        Convert the screen location (row and column) in DX into an
;        offset into the video display buffer in DI
;
CAL_VID_LOC PROC    NEAR
;------- Initialize AX, CX, DI to zero
            PUSH    CX                 ; Save CX -- some callers need it
            XOR     AX,AX              ; AX holds line length
            XOR     CX,CX              ; CX is temp for location
            XOR     DI,DI              ; DI holds offset
;------- Set AX to line length in chars
            MOV     AL,RIGHT_MARG
            INC     AX
;------- Set CX to row number and branch if zero
            MOV     CL,DH              ; Put the number of rows into cx
            JCXZ    CVL200
;------- Increment DI by the line length times the row number
CVL100:     ADD     DI,AX
            LOOP    CVL100
;------- Increment DI by the column number
CVL200:     MOV     AL,DL
            ADD     DI,AX
;------- End routine
            POP     CX                 ; Restore CX
            SHL     DI,1               ; Now convert this into byte offset
            MOV     ES,DISPLAY_SEG
            RET
CAL_VID_LOC ENDP


;-------------------------------
;======= Replacement for INT 16H
;-------------------------------
;
            ASSUME  CS:COMSEG, DS:NOTHING, ES:NOTHING
BEGIN       PROC    FAR
;
;======= Initial processing -- figure out what to do
;
;******* Check if in middle of feeding characters
MAINLOOP    LABEL   NEAR
;------- Save caller's AX register
            MOV     REQUEST,AX
            AND     AH,0EFH
            MOV     REQUEST2,AH
;------- Branch if we're in the middle of feeding characters
            MOV     AX,FEED_START      ; Are we in the middle of feeding...
            CMP     AX,FEED_STOP       ; ...chars to the application?
            JZ      NOFEED             ; No, loop around
            JMP     FEED               ; Yes, branch
;
;******* Test for type-0 request and return if not
NOFEED      LABEL   NEAR
;------- Restore caller's AX
            MOV     AX,REQUEST
;------- Check for type-0 request -- only interested in char requests
            CMP     REQUEST2,0
            JZ      CONTINU
;------- Jump to old INT 16 routine, which will return to application
            JMP     OLDINT16
;
;******* Now check for a hot key -- return if not
CONTINU     LABEL   NEAR
;------- Call old INT 16H (emulate interrupt)
            PUSHF
            CALL    OLDINT16
;------- Check for Alt-Fn9
            CMP     AX,CUT_KEY
            JZ      GOT_CUT_KEY
;------- Check for Ctl-Fn9
            CMP     AX,PASTE_KEY
            JZ      PASTE_CHAR
;------- Not a hotkey -- provide keystroke to caller
            IRET
;
;======= `Cut' hotkey processing
;
;******* Setup for subroutine calls
GOT_CUT_KEY LABEL   NEAR
;------- Save stack and point SS:SP to top of PSP
            MOV     SAVSTAKSS,SS
            MOV     SAVSTAKSP,SP
            MOV     AX,CS
            MOV     SS,AX
            MOV     SP,100H
;------- Enable interrupts while processing characters
            STI
;------- Set up a stack frame
            PUSH    BP
            MOV     BP,SP
;------- Save registers
            SUB     SP,0EH
            CALL    SAVEREG
;------- Set DS to COMSEG
            MOV     DS,AX
            ASSUME  DS:COMSEG
;
;******* Call EDITOR and other subroutines
;------- Calculate window extremeties
            CALL    CALC_WINDOW
;------- Save the cursor for later restoring
            CALL    READ_CURSOR
            MOV     OLD_CUR_POS,DX
;------- Save screen in buffer SCRNSAVE
            CALL    WINDOW_SAVE
;------- Restore cursor in edit window
            MOV     DX,CURSOR_POS
            CALL    PUT_CURSOR
;------- Edit in the window
            CALL    EDITOR
;------- Save cursor in edit window for next edit
            CALL    READ_CURSOR
            MOV     CURSOR_POS,DX
;------- Put back whatever was originally there
            CALL    WINDOW_SWAP
;------- Trim blanks from end of buffer
            CALL    TRIM_BUF
;------- Restore the cursor position
            MOV     DX,OLD_CUR_POS
            CALL    PUT_CURSOR
;
;******* End `cut-key' processing
;------- Restore registers
            CALL    RESTREG
            ADD     SP,0EH
            ASSUME  DS:NOTHING
;------- Restore stack
            POP     BP
            MOV     SS,SAVSTAKSS
            MOV     SP,SAVSTAKSP
;------- End of loop -- jump to beginning
            MOV     AX,REQUEST         ; Restore request
            JMP     MAINLOOP           ; Get another char to return to caller
;
;======= `Paste' hotkey processing
;
;******* Paste characters into application
PASTE_CHAR  LABEL   NEAR
            MOV     AX,FEED_END1
            MOV     FEED_START,AX
            MOV     AX,FEED_END2
            MOV     FEED_STOP,AX
            MOV     AX,REQUEST         ; Restore request
            JMP     MAINLOOP           ; Go get another character to return
;
;******* Feed characters to application from the buffer SCRNSAVE
FEED        LABEL   NEAR
;------- Test and branch for return if a type-2 request
            CMP     REQUEST2,1
            JA      KSTAT
;------- Get next char from SCRNSAVE buffer into AX
            PUSH    BX
            MOV     BX,AX
            MOV     AX,CS:[BX]
            POP     BX
;------- Loop around if end of screen line
            CMP     AX,CAR_RET2
            JZ      FEED10
;------- Null attribute
            XOR     AH,AH
;------- Save in FEED_CHAR
FEED10:     MOV     FEED_CHAR,AX
;------- Test and branch if type-1 request
            CMP     REQUEST2,0
            JNZ     FEED_STAT
;------- Point to next character
            ADD     FEED_START,2
;
;******* Handle beginning and end of line
;------- Test for and loop around if not carriage return
            CMP     AL,CAR_RET1
            JNZ     FEED20
;------- Skip to beginning of the feed area
            PUSH    DX
            XOR     DX,DX
            MOV     DL,ULHC_C
            SHL     DX,1               ; In words
            ADD     FEED_START,DX
            POP     DX
;------- Put address of next buffer location in BX
FEED20:     PUSH    BX
            MOV     BX,FEED_START
;------- Test, and branch to return, if at end of feed area
FEED30:     CMP     BX,FEED_STOP
            JZ      FEED50
;------- Test and loop around if next char is space
            MOV     AX,CS:[BX]
            CMP     AL,' '
            JZ      FEED40
;------- Test and branch if a non-space char follows on this line
            CMP     AX,CAR_RET2
            JNZ     FEED50
;------- Point to carriage return, skipping over trailing blanks
            MOV     FEED_START,BX
            JMP     FEED50
;------- Point to next char on line and branch to top of loop
FEED40:     INC     BX
            INC     BX
            JMP     FEED30
;
;******* Exit to caller
;------- Exit for type-0 request -- returning character to application
FEED50      LABEL   NEAR
            POP     BX                 ; Restore stack
            MOV     AX,FEED_CHAR       ; Place char to feed in AX
            IRET                       ; Return char to application
;------- Exit for type-1 request: enable interrupts, feed ZF = NZ and char
FEED_STAT   LABEL   NEAR
            STI
            RET     02
;------- Exit for type-2 request -- invoke INT 16H to do what he wants
KSTAT:      MOV     AX,REQUEST
            JMP     OLDINT16

BEGIN       ENDP


;======= Proc SAVEREG
;------- Save registers on stack frame
SAVEREG     PROC    NEAR
            MOV     [BP-2],BX
            MOV     [BP-4],CX
            MOV     [BP-6],DX
            MOV     [BP-8],SI
            MOV     [BP-0AH],DI
            MOV     [BP-0CH],DS
            MOV     [BP-0EH],ES
            RET
SAVEREG     ENDP

;======= Proc RESTREG
;------- Now put the registers back
RESTREG     PROC    NEAR
            MOV     BX,[BP-2]
            MOV     CX,[BP-4]
            MOV     DX,[BP-6]
            MOV     SI,[BP-8]
            MOV     DI,[BP-0AH]
            MOV     DS,[BP-0CH]
            MOV     ES,[BP-0EH]
            RET
RESTREG     ENDP

;------- Upper limit resident program (paras)
RESPARA     EQU     ($-START+100H+15)/16


;-------------------------
;======= Installation code
;-------------------------

INSTALL     LABEL   NEAR
            ASSUME  CS:COMSEG, DS:COMSEG, ES:COMSEG

;------- Output 'OK' message
            MOV     DX,OFFSET MESSAGE
            MOV     AH,09H
            INT     21H
;------- Get existing 16H vector using ES:BX
            MOV     AX,3516H
            INT     21H
            MOV     OLDINT16IP,BX
            MOV     OLDINT16CS,ES
            ASSUME  ES:NOTHING
;------- Now put our routine there using DS:DX
            MOV     AX,2516H
            MOV     DX,OFFSET BEGIN
            INT     21H
;------- Terminate and stay resident
            MOV     DX,RESPARA
            MOV     AX,3100H
            INT     21H

MESSAGE     DB      10,13,'CUTPASTE installed',10,13
            DB      '   Alt-Fn9 to enable cut ',10,13
            DB      '      Start/stop marking: Fn1',10,13
            DB      '      Move 1 space (4 ways): Cursor keys', 10,13
            DB      '      Move 1 word (2 ways): Ctl-LeftArw, Ctl-RgtArw', 10,13
            DB      '      Move to screen edge (4 ways): Home, End, PgUp, PgDn', 10,13
            DB      '      Erase to End of Line: Fn6', 10,13
            DB      '      Erase Line: Fn7', 10,13
            DB      '      Add Line: Enter', 10,13
            DB      '      Move text to right/left: Ins, Del, Bksp', 10,13
            DB      '   Ctl-Fn9 to paste',10,13,'$'

COMSEG      ENDS
            END     START

