; Warning! if you don't like the taste of spaghetti, don't read this file!

LOCALS @@
SMART
IDEAL
P386N
MODEL TINY

CODESEG
ORG 100h

; Flags
ACTIVE    EQU   01h ; tab-completer active?
PROGRESS  EQU   02h ; tab-completion in progress?
LSHIFT    EQU   04h ; lshift down?
RSHIFT    EQU   08h ; rshift down?
SHIFT     EQU   0Ch ; either shift down?
LCTRL     EQU   10h ; lctrl down?
RCTRL     EQU   20h ; rctrl down?
CTRL      EQU   30h ; either ctrl down?
LALT      EQU   40h ; lalt down?
RALT      EQU   80h ; ralt down?
ALT       EQU  0C0h ; either alt down?
MODS      EQU  0FCh ; any modifiers down?

; Flags2
EXT       EQU   01h ; extended key?

WILDLEN   EQU   84

Start:
jmp   Install

DTA       db 128 dup(?)
Wildcard  db WILDLEN dup(?) ; storage for the wildcard
FileSeg   dw 0              ; file list
FileLen   dw 0

label     Old9h dword
OldOff    dw 0      ; orig int 9h handler
OldSeg    dw 0
OldSP     dw 0      ; saved stack pointer
Index     dw 0      ; current file index
OCX       db 0      ; original cursor X
NCX       db 0      ; cursor to erase to - 1
Flags     db 01h    ; some flags (see above)
Flags2    db 04h

NPOldExitR:
jmp   NPOldExit
OldExitR:
jmp   OldExit

Int9h:
pusha               ; save registers and set up DS
push  ds
push  es
push  cs
pop   ds
mov   [OldSP], sp

@@chkext:
in    al, 60h       ; read scancode
cmp   al, 224       ; regular extended?
jne   @@chkRel
or    [Flags2], EXT


@@chkRel:
test  al, 80h       ; key being released?
jz    Press         ; nope, goto press handler

test  [Flags2], EXT ; extended key?
jz    @@chkNorm

; extended keys
cmp   al, 157
jne   @@chkRA
and   [Flags], NOT RCTRL
jmp   OldExit

@@chkRA:
cmp   al, 184
jne   NPOldExit
and   [Flags], NOT RALT
jmp   OldExit


@@chkNorm:          ; normal key
cmp   al, 143
jne   @@chkLC
jmp   Ret9h

@@chkLC:
cmp   al, 157       ; LCTRL?
jne   @@chkLS
and   [Flags], NOT LCTRL
jmp   OldExit

@@chkLS:
cmp   al, 170       ; LSHIFT?
jne   @@chkRS
and   [Flags], NOT LSHIFT
jmp   OldExit

@@chkRS:
cmp   al, 182       ; RSHIFT?
jne   @@chkLA
and   [Flags], NOT RSHIFT
jmp   OldExit

@@chkLA:
cmp   al, 184       ; LALT?
jne   short NPOldExitR
and   [Flags], NOT LALT
jmp   OldExit


Press:              ; key pressed
test  [Flags2], EXT ; extended?
jz    @@chkNorm

; extended keys
cmp   al, 29        ; RCTRL
jne   @@chkRA
or    [Flags], RCTRL
jmp   OldExit

@@chkRA:            ; RALT
cmp   al, 56
jne   short NPOldExit
or    [Flags], RALT
jmp   OldExit


@@chkNorm:          ; not extended
cmp   al, 15        ; is it TAB?
jne   @@chkLC       ; not tab

test  [Flags],  MODS  ; modifiers?
jz    @@RegTabR       ; regular old tab

test  [Flags], SHIFT  ; shift-tab?
jz    OldExit

test  [Flags], CTRL   ; shift-ctrl-tab?
jz    @@noctrl
xor   [Flags], ACTIVE ; shift-ctrl-tab (toggle active)
jmp   NPOldExit

@@RegTabR:
jmp   RegTab

@@noctrl:             ; shift-tab (prev. file)
test  [Flags], ACTIVE ; quit if inactive
jz    OldExit
push  ds              ; go to prev. file
mov   ax, [FileSeg]
mov   bx, [Index]
mov   ds, ax
call  strlen
add   bx, ax
mov   bx, [bx+1]
pop   ds
mov   [Index], bx
jmp   ShowFile      ; now show it

; non-tab
@@chkLC:
cmp   al, 29        ; LCTRL?
jne   @@chkLS
or    [Flags], LCTRL
jmp   OldExit

@@chkLS:
cmp   al, 42        ; LSHIFT?
jne   @@chkRS
or    [Flags], LSHIFT
jmp   short OldExit

@@chkRS:
cmp   al, 54        ; RSHIFT?
jne   @@chkLA
or    [Flags], RSHIFT
jmp   OldExit

@@chkLA:
cmp   al, 56        ; LALT?
jne   NPOldExit
or    [Flags], LALT
jmp   OldExit


; disables PROGRESS, falls into OldExit, writes string if necessary
NPOldExit:
test  [Flags], PROGRESS ; if TC not in progress, just exit
jz    @@nowrite

call  Erase             ; finished with tab completion, so write chosen string
mov   bx, OFFSET Wildcard
call  strlen            ; remove *.* from wildcard
sub   ax, 3
add   bx, ax
mov   [byte bx], 0
sub   bx, ax

pusha                   ; print wildcard
push  bx
push  ax
mov   ah, 0Fh
int   10h
mov   ah, 03h
int   10h
mov   ax, 1301h
mov   bl, 7
pop   cx
push  ds
pop   es
pop   bp
int   10h
popa

add   bx, ax            ; find last '\', if any
@@loop:
mov   cl, [bx]
cmp   cl, '\'
je    @@found
dec   bx
cmp   bx, OFFSET Wildcard
jae   @@loop
jmp   @@writefile
@@found:
sub   bx, OFFSET Wildcard
inc   bx
sub   ax, bx

@@writefile:
mov   bx, [Index]       ; write the file minus the wildcard
add   bx, ax
push  ds
mov   ax, [FileSeg]
mov   ds, ax
call  PushStr
pop   ds

@@nowrite:
and   [Flags], NOT PROGRESS   ; not in progress anymore

; Calls old handler, then exits
OldExit:
pushf
call  [Old9h]
mov   sp, [OldSP]
pop   es
pop   ds
popa
iret

; indicates that an error occured, then falls into Ret9h
DoError:
and   [Flags], NOT PROGRESS

; Resets keyboard, restores regs and then irets
Ret9h:
mov   al,  20h      ; reset keyboard
out   20h, al
mov   sp, [OldSP]
pop   es
pop   ds
popa
iret


RegTab:               ; regular tab(file complete)
test  [Flags], ACTIVE ; quit if inactive
jz    OldExit
test  [Flags], PROGRESS
jnz   NextFile

call  ReadWild        ; fetch Wildcard from screen
jc    DoError         ; an error occured

mov   bx, OFFSET Wildcard ; append a '*.*' onto the wildcard
call  strlen
add   bx, ax
mov   [word bx], '.'*256 + '*'
mov   [word bx+2], '*'

call  SearchFiles
jc    DoError

or    [Flags], PROGRESS
NextFile:
push  ds              ; advance to next file
mov   ax, [FileSeg]
mov   bx, [Index]
mov   ds, ax
mov   bx, [bx-2]
pop   ds
mov   [Index], bx

ShowFile:
call  Erase           ; erase old file from screen
call  WriteTemp       ; write the new one
jmp   Ret9h           ; and return


; searches for files that match Wildcard and stores them
SearchFiles:
mov   bp, sp
sub   sp, 2
mov   dx, OFFSET DTA    ; tell dos where our DTA is
mov   ah, 1ah
int   21h

cmp   [FileLen], 0      ; if(!FileLen) Filearr=malloc(FileLen=256);
jne   @@nomalloc
mov   cx, 256
call  malloc
mov   [FileLen], cx
jc    @@return
mov   [FileSeg], ax


@@nomalloc:
mov   ah, 4eh           ; find first file   dta = findfirst(wild);
mov   cx, 37h           ; all files and dirs
mov   dx, OFFSET Wildcard
int   21h
jc    @@return

sub   ax, ax            ; len =
sub   di, di            ; fpos = 0;

@@loop:                 ; do {
mov   bx, OFFSET DTA + 30 ; len = strlen(dta.fn+5)
mov   dx, di              ; dx  = fpos
call  strlen              
add   ax, 5               ; ax  = len

add   dx, ax              ; if(fpos+len >= filelen) dx = fpos+len
cmp   dx, [FileLen]       ; { // resize FileSeg memory block
jb    @@nogrow
pusha
push  ds
mov   bx, [FileLen]   ; try to resize the current block
shl   bx, 1
mov   [FileLen], bx
shr   bx, 4
mov   cx, [FileSeg]
mov   ah, 4ah
mov   es, cx
int   21h
jnc   @@goodalloc     ; fall back on an alloc/copy/free if that fails
mov   ah, 48h         ; alloc
mov   bx, [FileLen]
shr   bx, 4
int   21h
jnc   @@copy          ; error?
pop   ds
jmp   @@return
@@copy:               ; copy
sub   si, si
sub   di, di
mov   cx, [FileLen]
mov   dx, [FileSeg]
mov   es, ax
shr   cx, 1
mov   ds, dx
mov   [FileSeg], ax
rep   movsb
mov   es, dx          ; free
mov   ah, 49h
int   21h
@@goodalloc:
pop   ds
popa

@@nogrow:
push  di                ; save fpos(1)
add   dx, 2             ; dx = fpos+len+2
mov   si, bx            ; si = &dta.fn
mov   bx, [FileSeg]     ; filearr[fpos] = fpos+len+2;   bx = FileSeg
mov   es, bx
mov   [es:di], dx

add   di, 2             ; strcpy(filearr+fpos+2, dta.fn);  di = fpos+2
call  strcpy

mov   di, dx            ; filearr[fpos+len-2] = oldfpos+2;    di=fpos+len+2
sub   di, 4             ; di = fpos+len-2
mov   bx, [bp-2]        ; bx = old fpos
add   bx, 2             ; bx = old fpos+2
mov   [es:di], bx

pop   bx                ; bx = fpos(0)
mov   di, bx            ; fpos+=len;  di = fpos
mov   [bp-2], di        ; save old fpos
add   di, ax            ; di = fpos+len   bx = fpos-len

mov   ah, 4fh           ; dta = nextfile();
int   21h
jnc   @@loop

sub   si, si            ; filearr[filearr[0]-4] = fpos-len+2;  si = 0
add   bx, 2             ; bx = fpos-len+2
mov   si, [es:si]       ; si = filearr[0]
sub   si, 4             ; si = filearr[0]-4
mov   [es:si], bx
mov   [Index], bx       ; Index = fpos-len+2;
sub   bx, 2             ; filearr[fpos-len] = 2; bx = fpos-len
mov   [word es:bx], 2

@@return:
mov   sp, bp
ret


; takes the string in DS:BX and returns length in AX
strlen:
push  bx
dec   bx
mov   ax, -1
@@loop:
inc   bx
inc   ax
cmp   [byte bx], 0
jne   @@loop
pop   bx
ret

; reverses the string in DS:SI
reverse:
push  ax
push  bx
push  dx
mov   si, bx
call  strlen
cmp   ax, 1
jle   @@done

add   si, ax
clc
rcr   ax, 1
adc   ax, 0

@@loop:
dec   si
mov   dl, [bx]
mov   dh, [si]
mov   [bx], dh
inc   bx
mov   [si], dl
dec   ax
jnz   @@loop

@@done:
pop   dx
pop   bx
pop   ax
ret


; reads the wildcard from the screen into Wildcard and sets carry on error
ReadWild:
mov   si, OFFSET Wildcard
push  si
mov   ah, 0Fh     ; get display page
int   10h
mov   ah, 03h     ; get cursor pos
int   10h
mov   [NCX], dl   ; save end cursor

@@loop:
mov   ah, 03h     ; get cursor pos
int   10h
dec   dl          ; back a space
js    @@abort     ; off left edge? if so, abort
dec   ah          ; set cursor pos (02h)
int   10h
mov   ah, 08h     ; read character
int   10h
call  ValidChar
jc    @@done
mov   [si], al
inc   si
jmp   @@loop

@@done:
inc   dl
mov   [OCX], dl     ; save cursor
mov   [byte si], 0  ; null terminate
pop   bx
call  reverse
clc
ret
@@abort:
mov   dl, [OCX]
mov   ah, 02h
int   10h
pop   bx
stc
ret

; sets carry if char in al is invalid
ValidChar:
cmp   al, 32  ; control characters and ' '
jbe   @@bad
;cmp   al, '\'
;je    @@bad
cmp   al, '/'
je    @@bad
cmp   al, ':'
je    @@bad
cmp   al, '<'
je    @@bad
cmp   al, '>'
je    @@bad
cmp   al, '|'
je    @@bad
cmp   al, '"'
je    @@bad
clc
ret
@@bad:
stc
ret


; allocates CX bytes of mem, returns segment in AX, carry set on error
malloc:
push  bx
mov   bx, cx
mov   ah, 48h
add   bx, 15
shr   bx, 4
int   21h
pop   bx
ret


; copies the string from ds:si to es:di
strcpy:
pusha
cld
@@loop:
movsb
cmp [byte si-1], 0
jne @@loop
popa
ret


; erases OCX to NCX and places cursor at OCX
Erase:
pusha
mov   ah, 0Fh     ; get display page
int   10h
mov   ah, 03h     ; get cursor pos
int   10h
mov   dl, [OCX]   ; set cursor to OCX
dec   ah
int   10h
mov   ax, 0E20h   ; write space and advance cursor
@@loop:
cmp   dl, [NCX]
jae   @@done
int   10h
inc   dl
jmp   @@loop
@@done:
mov   ah, 2       ; set cursor to OCX
mov   dl, [OCX]
int   10h
popa
ret

; writes the current string and sets NCX
WriteTemp:
pusha
push  ds
mov   cx, [FileSeg]
mov   ah, 0Eh     ; write and advance cursor
mov   dl, [OCX]
mov   bx, [Index]
mov   ds, cx
@@loop:
mov   al, [bx]
inc   bx
inc   dl
cmp   al, 0
je    @@done
int   10h
jmp   @@loop
@@done:
dec   dl
pop   ds
mov   [NCX], dl
popa
ret


; Pushes a string (ds:bx) into the keyboard buffer
PushStr:
pusha
;mov   ch, 30            ; A's scancode
mov   ch, 0
@@loop:
mov   ah, 5h            ; int 16h, func 5h - push keystroke
mov   cl, [bx]          ; read char
cmp   cl, 0             ; we done?
je    @@done
int   16h               ; nope, push it
inc   bx                ; next char
cmp   al, 0             ; success?
je    @@loop            ; yep.. continue on
@@done:
popa
ret

Install:
mov   ax, 3509h     ; install int 9h handler
int   21h
mov   dx, es
mov   [OldOff], bx
mov   [OldSeg], dx
mov   ah, 25h
mov   dx, OFFSET Int9h
int   21h

mov   bx, 2Ch       ; free environment block
mov   bx, [bx]
mov   ah, 49h
mov   es, bx
int   21h

mov   bx, OFFSET ProgEnd  ; resize program's memory block
add   bx, 15
shr   bx, 4
mov   ah, 4ah
push  cs
pop   es
int   21h

mov   dx, OFFSET Install   ; TSR!
inc   dx
int   27h
ProgEnd:
end Start


