;               DOS-EDIT.ASM -- Resident DOS Command Line Editor
;               ================================================

CSEG            Segment
                Assume  CS:CSEG

                Org     0080h
KeyboardBuffer  Label   Byte

                Org     0100h
Entry:          Jmp     Initialize

;               All Data
;               --------

                db      "(C) Copyright 1985 Ziff-Davis Publishing Co."

OldInterrupt21  dd      ?               ; Original Interrupt 21 vector
OldInterrupt16  dd      ?               ; Original Interrupt 16 vector
DoingBuffKey    db      0               ; Flag for doing Function call 0Ah
BufferPointer   dw      KeyboardBuffer  ; Pointer to Keyboard Buffer
BufferCounter   db      0               ; Number of characters in buffer
MaxCharCol      db      ?               ; Maximum Character Column on screen
OriginalCursor  dw      ?               ; Place to save cursor on full-screen
InsertOn        db      0               ; Insert mode flag

KeyRoutine      dw      Home,Up,PgUp,Dummy,Left,Dummy,Right
                dw      Dummy,End,Down,PgDn,Insert,Delete

;               New Interrupt 21 (DOS Function Calls)
;               -------------------------------------

NewInterrupt21  Proc    Far

                Mov     CS:[DoingBuffKey],0    ; Turn flag off initially

                Cmp     AH,0Ah                 ; Check if doing buffered input
                Jz      BufferedInput            

                Jmp     CS:[OldInterrupt21]    ; If not, do regular interrupt

BufferedInput:  Mov     CS:[DoingBuffKey],-1   ; If so, turn on flag

                PushF                          ; Simulate regular interrupt
                Call    CS:[OldInterrupt21]    

                Mov     CS:[DoingBuffKey],0    ; Turn off flag
                Mov     CS:[BufferCounter],0   ; Re-set character counter

                IRet                           ; Return to user program

NewInterrupt21  EndP

;               New Interrupt 16 (BIOS Keyboard Routine)
;               ----------------------------------------

NewInterrupt16  Proc    Far

                Sti                            ; Re-enable interrupts
                Cmp     CS:[DoingBuffKey],0    ; Check if doing call 0Ah
                Jz      DoNotIntercept         ; If not, do old interrupt

                Cmp     CS:[BufferCounter],0   ; Check if chars in buffer
                Jnz     Substitute             ; If so, get them out

                Cmp     AH,0                   ; See if doing a get key
                Jz      CheckTheKey            ; If so, get the key

DoNotIntercept: Jmp     CS:[OldInterrupt16]    ; Otherwise, do old interrupt

CheckTheKey:    PushF                          ; Save flags
                Call    CS:[OldInterrupt16]    ; Do regular interrupt

                Cmp     AX,4800h               ; Check if up cursor
                Jnz     NotTriggerKey          ; If not, don't bother

                Call    FullScreen             ; Move around the screen

                Cmp     CS:[BufferCounter],0   ; Any chars to deliver?
                Jz      CheckTheKey            ; If not, get another key

ReturnBuffer:   Call    GetBufferChar          ; Otherwise, pull one out

                Inc     CS:[BufferPointer]     ; Kick up the pointer
                Dec     CS:[BufferCounter]     ; And knock down the counter

NotTriggerKey:  IRet                           ; And go back to calling prog

;               Substitute Key from Buffer
;               --------------------------

Substitute:     Cmp     AH,2                   ; See if shift status check
                Jae     DoNotIntercept         ; If so, can't be bothered

                Cmp     AH,0                   ; See if get a key
                Jz      ReturnBuffer           ; If so, get the key above

                Call    GetBufferChar          ; Otherwise get a key
                Cmp     CS:[BufferCounter],0   ; And clear zero flag

                Ret     2                      ; Return with existing flags

NewInterrupt16  EndP

;               Get Buffer Character
;               --------------------

GetBufferChar:  Push    BX
                Mov     BX,CS:[BufferPointer]  ; Get pointer to key buffer
                Mov     AL,CS:[BX]             ; Get the key
                Sub     AH,AH                  ; Blank out scan code
                Pop     BX
                Ret

;               Full Screen Routine
;               -------------------

FullScreen:     Push    AX                     ; Save all these registers
                Push    BX
                Push    CX
                Push    DX
                Push    DI
                Push    DS
                Push    ES

                Mov     AX,CS                  ; Set AX to this segment
                Mov     DS,AX                  ; Do DS is this segment
                Mov     ES,AX                  ; And ES is also

                Assume  DS:CSEG, ES:CSEG       ; Tell the assembler

                Mov     AH,0Fh                 ; Get Video State
                Int     10h                    ;   through BIOS
                Dec     AH                     ; Number of columns on screen
                Mov     [MaxCharCol],AH        ; Save maximum column
                                               ; BH = Page Number throughout
                Mov     AH,03h                 ; Get cursor in DX
                Int     10h                    ;   through BIOS
                Mov     [OriginalCursor],DX    ; And save the cursor position

                Call    Up                     ; Move cursor up

MainLoop:       Cmp     DH,Byte Ptr [OriginalCursor + 1]    ; If at line
                Jz      TermFullScreen         ; stated from, terminate

                Mov     AH,02h                 ; Set cursor from DX
                Int     10h                    ;   through BIOS

GetKeyboard:    Mov     AH,0                   ; Get the next key
                PushF                          ; By simulating Interrupt 16h
                Call    CS:[OldInterrupt16]    ;   which goes to BIOS

                Cmp     AL,1Bh                 ; See if Escape key
                Jz      TermFullScreen         ; If so, terminate full screen

;               Back Space
;               ----------

                Cmp     AL,08h                 ; See if back space
                Jnz     NotBackSpace           ; If not, continue test

                Or      DL,DL                  ; Check if cursor at left
                Jz      MainLoop               ; If so, do nothing

                Dec     DL                     ; Otherwise, move cursor back
                Call    ShiftLeft              ; And shift line to the left

                Jmp     MainLoop               ; And continue for next key

;               Carriage Return
;               ---------------

NotBackSpace:   Cmp     AL,0Dh                 ; See if Carriage Return
                Jnz     NotCarrRet             ; If not, continue test

                Call    End                    ; Move line into buffer

                Mov     AL,0Dh                 ; Tack on a Carriage Return
                Stosb                          ; By writing to buffer
                Inc     [BufferCounter]        ; One more character in buffer

                Jmp     MainLoop               ; And continue

;               Normal Character
;               ----------------

NotCarrRet:     Cmp     AL,' '                 ; See if normal character
                Jb      NotNormalChar          ; If not, continue test

                Cmp     [InsertOn],0           ; Check for Insert mode
                Jz      OverWrite              ; If not, overwrite

                Call    ShiftRight             ; Shift line right for insert
                Jmp     Short NormalCharEnd    ; And get ready to print

OverWrite:      Mov     CX,1                   ; Write one character
                Mov     AH,0Ah                 ;   by calling BIOS
                Int     10h

NormalCharEnd:  Call    Right                  ; Cursor to right and print

                Jmp     MainLoop               ; Back for another key

;               Cursor Key, Insert, or Delete Subroutine
;               ----------------------------------------

NotNormalChar:  Xchg    AL,AH                  ; Put extended code in AL
                Sub     AX,71                  ; See if it's a cursor key
                Jc      GetKeyboard            ; If not, no good

                Cmp     AX,12                  ; Another check for cursor
                Ja      GetKeyBoard            ; If not, skip it

                Add     AX,AX                  ; Double for index
                Mov     DI,AX                  ;   into vector table

                Call    [KeyRoutine + DI]      ; Do the routine

                Jmp     MainLoop               ; Back for another key

;               Terminate Full Screen Movement
;               ------------------------------

TermFullScreen: Mov     DX,[OriginalCursor]    ; Set cursor to original
                Mov     AH,2                   ; And set it
                Int     10h                    ;   through BIOS

                Pop     ES                     ; Restore all registers
                Pop     DS
                Pop     DI
                Pop     DX
                Pop     CX
                Pop     BX
                Pop     AX

                Ret                            ; And return to New Int. 16h

;               Cursor Movement
;               ---------------

Home:           Mov     DL,Byte Ptr [OriginalCursor]     ; Move cursor to
                Ret                            ; to original column

Up:             Or      DH,DH                  ; Check if at top row
                Jz      UpEnd                  ; If so, do nothing
                Dec     DH                     ; If not, decrement row
UpEnd:          Ret

PgUp:           Sub     DL,DL                  ; Move cursor to far left
                Ret

Left:           Or      DL,DL                  ; Check if cursor at far left
                Jnz     GoWest                 ; If not, move it left
                Mov     DL,[MaxCharCol]        ; Move cursor to right
                Jmp     Up                     ; And go up one line
GoWest:         Dec     DL                     ; Otherwise, decrement column
                Ret

Right:          Cmp     DL,[MaxCharCol]        ; Check if cursor at far right
                Jb      GoEast                 ; If not, move it right
                Sub     DL,DL                  ; Set cursor to left of screen
                Jmp     Down                   ; And go down one line
GoEast:         Inc     DL                     ; Otherwise, increment column
                Ret

End:            Call    TransferLine           ; Move line to buffer
                Mov     DX,[OriginalCursor]    ; Set cursor to original
                Ret

Down:           Inc     DH                     ; Move cursor down one row
                Ret

PgDn:           Mov     CL,[MaxCharCol]        ; Get last column on screen
                Inc     CL                     ; Kick it up by one
                Sub     CL,DL                  ; Subtract current column
                Sub     CH,CH                  ; Set top byte to zero
                Mov     AL,' '                 ; Character to write
                Mov     AH,0Ah                 ; Write blanks to screen
                Int     10h                    ;   through BIOS
Dummy:          Ret

;               Insert and Delete
;               -----------------

Insert:         Xor     [InsertOn],-1          ; Toggle the InsertOn flag
                Ret                            ;   and return

Delete:         Call    ShiftLeft              ; Shift cursor line left
                Ret                            ;   and return

;               Transfer Line on Screen to Keyboard Buffer
;               ------------------------------------------

TransferLine:   Sub     CX,CX                  ; Count characters in line
                Mov     DI,Offset KeyboardBuffer        ; Place to store 'em
                Mov     [BufferPointer],DI     ; Save that address
                Cld                            ; String direction forward

GetCharLoop:    Mov     AH,02h                 ; Set Cursor at DX
                Int     10h                    ;    through BIOS

                Mov     AH,08h                 ; Read Character & Attribute
                Int     10h                    ;    through BIOS

                Stosb                          ; Save the character

                Inc     CX                     ; Increment the counter
                Inc     DL                     ; Increment the cursor column
                Cmp     DL,[MaxCharCol]        ; See if at end of line yet
                Jbe     GetCharLoop            ; If not, continue

                Dec     DI                     ; Points to end of string
                Mov     AL,' '                 ; Character to search through
                Std                            ; Searching backwards
                Repz    Scasb                  ; Search for first non-blank
                Cld                            ; Forward direction again
                Jz      SetBufferCount         ; If all blanks, skip down

                Inc     CL                     ; Number of non-blanks
                Inc     DI                     ; At last character
SetBufferCount: Inc     DI                     ; After last character
                Mov     [BufferCounter],CL     ; Save the character count

                Ret                            ; Return from routine

;               Shift Line One Space Right (For Insert)
;               ---------------------------------------

ShiftRight:     Push    DX                     ; Save original cursor
                Mov     DI,AX                  ; Character to insert
ShiftRightLoop: Call    ReadAndWrite           ; Read character and write

                Inc     DL                     ; Kick up cursor column
                Cmp     DL,[MaxCharCol]        ; Check if it's rightmost
                Jbe     ShiftRightLoop         ; If not, keep going

                Pop     DX                     ; Get back original cursor
                Ret                            ; And return from routine

;               Shift Line One Space Left (For Delete)
;               --------------------------------------

ShiftLeft:      Mov     DI,0020h               ; Blank at end
                Mov     BL,DL                  ; Save cursor column
                Mov     DL,[MaxCharCol]        ; Set cursor to end of line

ShiftLeftLoop:  Call    ReadAndWrite           ; Read character and write

                Dec     DL                     ; Kick down cursor column
                Cmp     DL,BL                  ; See if at original yet
                Jge     ShiftLeftLoop          ; If still higher, keep going

                Inc     DL                     ; Put cursor back to original
                Ret                            ; And return from routine

;               Read and Write Character for Line Shifts
;               ----------------------------------------

ReadAndWrite:   Mov     AH,2                   ; Set Cursor from DX
                Int     10h                    ;    through BIOS

                Mov     AH,08h                 ; Read Character and Attribute
                Int     10h                    ;    through BIOS

                Xchg    AX,DI                  ; Switch with previous char

                Mov     CX,1                   ; One character to write
                Mov     AH,0Ah                 ; Write charater only
                Int     10h                    ;    through BIOS

                Ret                            ; Return from routine

;               Initialization on Entry
;               -----------------------

Initialize:     Sub     AX,AX                  ; Make AX equal zero
                Mov     DS,AX                  ; To point to vector segment

                Les     BX,dword ptr DS:[21h * 4]   ; Get and save Int. 21h
                Mov     Word Ptr CS:[OldInterrupt21],BX
                Mov     Word Ptr CS:[OldInterrupt21 + 2],ES

                Les     BX,dword ptr DS:[16h * 4]    ; Get and save Int. 16h
                Mov     Word Ptr CS:[OldInterrupt16],BX
                Mov     Word Ptr CS:[OldInterrupt16 + 2],ES

                Push    CS                     ; Restore DS register
                Pop     DS                     ;    by setting to CS

                Mov     DX,Offset NewInterrupt21
                Mov     AX,2521h               ; Set new Interrupt 21h
                Int     21h                    ;    through DOS

                Mov     DX,Offset NewInterrupt16
                Mov     AX,2516h               ; Set new Interrupt 16h
                Int     21h                    ;    through DOS

                Mov     DX,Offset Initialize   ; Number of bytes to stay
                Int     27h                    ; Terminate & Remain Resident

CSEG            EndS
                End     Entry
