Metropoli BBS
VIEWER: fsb.asm MODE: TEXT (ASCII)
;------------------------------------------------
; FSB.ASM -- "File Search and Browse" for OS/2
;            (c) 1988, Ziff Communications Co.
;            PC Magazine * Charles Petzold, 3/88
;------------------------------------------------
.286P
;-----------------------------------------------------------
; Macro to call OS/2 functions without declaring them first
;-----------------------------------------------------------
OS2Call		MACRO	fnctname

	IFNDEF	fnctname
		EXTRN	fnctname:FAR
	ENDIF 
		Call	Far Ptr fnctname

		ENDM
;-----------------------------------
; Structures used in OS/2 functions
;-----------------------------------
FileFindBufStr	STRUC
		create_date	dw	?
		create_time	dw	?
		access_date	dw	?
		access_time	dw	?
		write_date	dw	?
		write_time	dw	?
		file_size	dd	?
		falloc_size	dd	?
		attributes	dw	?
		string_len	db	?
		file_name	db	13 dup (?)
FileFindBufStr	ENDS

KeyDataStruc	STRUC
		char_code	db	?
		scan_code	db	?
		status		db	?
		nls_shift	db	?
		shift_state	dw	?
		time		dd	?
KeyDataStruc	ENDS

ModeDataStruc	STRUC
		md_length	dw	?
		md_type		db	?
		color		db	?
		col		dw	?
		row		dw	?
		hres		dw	?
		vres		dw	?
ModeDataStruc	ENDS
;--------------------------------
; Define all segments and DGROUP
;--------------------------------
_TEXT		SEGMENT WORD PUBLIC 'CODE'
_TEXT		ENDS
_DATA		SEGMENT WORD PUBLIC 'DATA'
_DATA		ENDS
_BSS		SEGMENT WORD PUBLIC 'BSS'
_BSS		ENDS
STACK		SEGMENT PARA STACK 'STACK'
		dw	1024 dup (?)
STACK		ENDS
DGROUP		GROUP	_DATA, _BSS, STACK
		ASSUME	CS:_TEXT, DS:DGROUP, SS:DGROUP, ES:DGROUP
;--------------------------
; Initialized Data Segment
;--------------------------
_DATA		SEGMENT

SyntaxMsg	db	13, 10, "Syntax: FSB filespec"
		db	13, 10
		db	13, 10, "File Search and Browse "
		db              "(c) 1988, Ziff Communications Co."
		db	13, 10, "PC Magazine ", 254," Charles Petzold, 3/88"
CRLF		db	13, 10, 0

DriveError	db	"FSB: Invalid disk drive", 0
StartDirError	db	"FSB: Invalid directory", 0
FileFindError	db	"FSB: Invalid file spec", 0
PipeError	db	"FSB: Cannot not create pipe", 0
ThreadError	db	"FSB: Cannot not create second thread", 0
FileOpenError	db	9, "FSB: Cannot open file for reading", 13, 10, 0
VideoModeError	db	9, "FSB: Unsupported video mode", 13, 10, 0
RootDir		db	"\", 0
Delimiters	db	9, ' ,;=', 0
DoBrowse	db	1

_DATA		ENDS
;----------------------------
; Uninitialized Data Segment
;----------------------------
_BSS		SEGMENT

FileSpec	dw	?, ?
LastBackSlash	dw	?
PipeReadHandle	dw	?
PipeWriteHandle	dw	?
ThreadID	dw	?
ThreadStack	dw	1024 dup (?)
FullFileName	db	80 dup (?)
BytesRead	dw	?
BytesWritten	dw	?

_BSS		ENDS
;--------------
; Code Segment
;--------------
_TEXT		SEGMENT
;----------------------------------------------
; Parse command line to get file specification
;----------------------------------------------
Entry:		Push	DS			; Data segment selector
		Pop	ES			; Transfer it to ES
		Mov	DS, AX			; DS = Environment selector
		Mov	SI, BX			; SI = Start of command line

SkipProgName:	Lodsb				; Pull a command line byte
		Or	AL, AL			; Check if it's zero
		Jnz	SkipProgName		; If not, continue

SkipDelim:	Lodsb				; Get command line byte
		Or	AL, AL			; See if it's zero
		Jnz	CheckDelim		; If not, check for delimiter
		Jmp	DoSyntaxMsg		; If so, display message

CheckDelim:	Mov	CX, 5			; Five delimiters
		Mov	DI, Offset Delimiters	; pointer to them
		Repne	Scasb			; scan for delimiter
		Jz	SkipDelim		; if delimiter, loop around

		Cmp	Byte Ptr [SI], ':'	; Check if a drive present
		Jnz	NoDiskDrive		; If not, skip section

		And	AL, 0DFh		; Capitalize it
		Sub	AL, '@'			; Change from A to 1, etc
		Sub	AH, AH			; Zero out top byte

		Push	AX			; Change to that drive
		OS2Call DosSelectDisk		;    by calling OS/2

		Mov	DX, Offset DriveError
		Or	AX, AX			; If AX not zero,
		Jnz	ErrorExit		;    display message and exit

		Add	SI, 2			; Skip past drive
NoDiskDrive:	Dec	SI			; First character of rest
		Mov	ES:[FileSpec], SI	; Save the address
		Mov	ES:[FileSpec + 2], DS
FindEnd:	Lodsb				; Get a byte
		Cmp	AL, '\'			; Check if it's a backslash
		Jnz	NotBackSlash		; If not, continue

		Mov	ES:[LastBackSlash], SI	; If so, save the address
NotBackSlash:	Mov	CX, 6			; now six delimiters
		Mov	DI, Offset Delimiters	; pointer to delimiters
		Repne	Scasb			; scan them
		Jnz	FindEnd			; if not delimiter, loop around

		Mov	Byte Ptr [SI-1], 0	; terminate with zero
DoChangeDir:	Mov	SI, ES:[LastBackSlash]	; Address of last back slash
		Or	SI, SI			; See if any at all
		Jz	NoChangeDir		; If none, skip this section

		Dec	SI			; Points to last backslash now
		Cmp	SI, ES:[FileSpec]	; Check if at beginning
		Jnz	ChangeToDir		; If not, use directory

		Inc	Word Ptr ES:[FileSpec]	; Skip past initial backslash
		Push	DGROUP			; Segment of new dir
		Push	Offset RootDir		; Offset of new dir
		Jmp	Short CallChangeDir	; Change to root directory

ChangeToDir:	Mov	Byte Ptr [SI], 0	; Terinate dir name with zero
		Push	Word Ptr ES:[FileSpec + 2]	; This is segment
		Push	Word Ptr ES:[FileSpec]		; This is offset
		Inc	SI			; Save address of filename
		Mov	ES:[FileSpec], SI	;    in FileSpec

CallChangeDir:	Push	0			; Reserved bytes
		Push	0
		OS2Call	DosChdir		; Change directory

		Mov	DX, Offset StartDirError
		Or	AX, AX			; If non-zero return code,
		Jnz	ErrorExit		;    display message and leave

NoChangeDir:	Push	ES			; Set DS to data segment
		Pop	DS

		Call	MainRoutine		; Go!

		Push	1			; All threads to terminate
		Push	0			; Return code
		OS2Call	DosExit			; End the program
;-------------------------------------
; Error Exit: DX is pointer to string
;-------------------------------------
DoSyntaxMsg:	Mov	DX, Offset SyntaxMsg
ErrorExit:	Mov	AX, DGROUP		; Set DS to data segment
		Mov	DS, AX
		Mov	BX, 2			; Standard Error handle
		Call	StringLen		; Get length of DX string
		Mov	CX, AX			; Move it to CX
		Call	MyDosWrite		; Display error message

		Push	1			; All threads to terminate
		Push	1			; Error return code
		OS2Call	DosExit			; End the program
;----------------------------------------------------------------
; Main Routine, create pipe, start second thread, and do browses
;----------------------------------------------------------------
MainRoutine:	Push	DS			; Address for read handle
		Push	Offset PipeReadHandle
		Push	DS			; Address for write handle
		Push	Offset PipeWriteHandle
		Push	16384			; Size of pipe
		OS2Call	DosMakePipe		; Create it

		Mov	DX, Offset PipeError
		Or	AX, AX			; If non-zero return code,
		Jnz	ErrorExit		;    depart from program

		Push	CS			; Address of second thread
		Push	Offset SecondThread
		Push	DS			; Address for thread ID
		Push	Offset ThreadID
		Push	DS			; Top of thread stack
		Push	Offset ThreadStack + 2048
		OS2Call	DosCreateThread		; Create the thread

		Mov	DX, Offset ThreadError
		Or	AX, AX			; Again, if error code,
		Jnz	ErrorExit		;    get out of this place

StartPipeRead:	Mov	CX, 80			; Read 80 chars from pipe
		Mov	DX, Offset FullFileName	; Store it here
ReadPipe:	Mov	BX, [PipeReadHandle]	; Pipe handle
		Call	MyDosRead		; Read it

		Cmp	[BytesRead], 0		; Check if read no bytes
		Jz	NothingLeft		; If so, pipe is empty

		Add	DX, [BytesRead]		; Increment destination ptr
		Sub	CX, [BytesRead]		; Decrement counter
		Jnz	ReadPipe		; If not 80 bytes, read again

		Mov	DX, Offset FileFindError
		Cmp	[FullFileName], 0	; See if file name present
		Jz	ErrorExit		; If not, thread had problem

NoErrFromThread:Mov	BX, 1			; Standard output handle
		Mov	DX, Offset FullFileName	; Full file name
		Call	StringLen		; Get its length
		Mov	CX, AX			; Store in CX
		Call	MyDosWrite		; Write to standard output

		Mov	CX, 2			; Two bytes
		Mov	DX, Offset CRLF		;    for carriage ret/line feed
		Call	MyDosWrite		; Write that out also

		Cmp	[DoBrowse], 1		; See if still doing browse
		Jnz	StartPipeRead		; If not, skip the call

		Call	Browse			; Browse the file

		Or	AX, AX			; If zero returned, continue
		Jz	StartPipeRead
		Cmp	AX, -1			; If -1, Esc pressed -->
		Jz	NothingLeft		;   terminate gracefully

		Cmp	AX, Offset VideoModeError
		Jnz	ShowBrowseErr		; If video mode error,
		Mov	[DoBrowse], 0		;    don't try browse again
ShowBrowseErr:	Mov	BX, 2			; Standard error output
		Mov	DX, AX			; Address of error message
		Call	StringLen		; Find length 
		Mov	CX, AX			;    and save in CX
		Call	MyDosWrite		; Dispay the string

		Jmp	StartPipeRead		; And get another file name
NothingLeft:	Ret				; Return when all done
;--------------------------------------------------------------------------
; DosRead and DosWrite routines (BX is handle, DX is buffer, CX is length)
;--------------------------------------------------------------------------
MyDosRead:	Push	BX			; Input handle
		Push	DS			; Segment of buffer
		Push	DX			; Offset of buffer
		Push	CX			; Number of bytes to write
		Push	DS			; Segment for bytes read
		Push	Offset BytesRead	; Offset for bytes read
		OS2Call	DosRead			; Read
		Ret

MyDosWrite:	Push	BX			; Output handle
		Push	DS			; Segment of buffer
		Push	DX			; Offset of buffer
		Push	CX			; Number of bytes to write
		Push	DS			; Segment for bytes written
		Push	Offset BytesWritten	; Offset for bytes written
		OS2Call	DosWrite		; Write
		Ret
;------------------------------------------------------------
; StringLen routine (DS:DX points to string, AX is returned)
;------------------------------------------------------------
StringLen:	Push	ES			; Save some registers
		Push	DI
		Push	CX

		Push	DS
		Pop	ES			; Set ES to DS
		Mov	DI, DX			; Set DI to address of string
		Mov	CX, -1			; Initialize CX to big number
		Sub	AL, AL			; Search for zero
		Repnz	Scasb			; Do the scan
		Not	CX			; Invert CX
		Dec	CX			; Take away one
		Mov	AX, CX			; That's the length

		Pop	CX			; Restore saved registers
		Pop	DI
		Pop	ES
		Ret
		
_TEXT		ENDS
;-----------------------------------------------------------------------
;-----------------------------------------------------------------------
; SECOND THREAD section to search for files and write to pipe
;-----------------------------------------------------------------------
;-----------------------------------------------------------------------
_DATA		SEGMENT

BackDir		db	"..", 0
StarDotStar	db	"*.*", 0

_DATA		ENDS

_BSS		SEGMENT

FileFindBuf	FileFindBufStr <>
FileFindBufLen	equ	$ - FileFindBuf
FullPathName	Label	Byte
CurrentDisk	db	?, ?, ?
CurrentDir	db	64 dup (?)
CurrDirLen	dw	?
Zeroes		db	80 dup (?)
DriveMap	dd	?

_BSS		ENDS
;---------------------------------------------------------
; Second Thread -- Calls FindThem, closes pipe, and exits
;---------------------------------------------------------
_TEXT		SEGMENT

SecondThread:	Push	DS				; Variable to receive
		Push	Offset CurrentDisk		;   current drive
		Push	DS				; Variable to receive
		Push	Offset DriveMap			;   drive map
		OS2Call	DosQCurDisk

		Add	[CurrentDisk], '@'		; Convert to letter
		Mov	[CurrentDisk + 1], ':'		; Follow by colon
		Mov	[CurrentDisk + 2], '\'		;   and backslash

		Call	FindThem		; Do the finds

TerminateThread:Push	[PipeWriteHandle]	; Close the pipe for writing
		OS2Call	DosClose

		Push	0			; Terminate this thread only
		Push	0			; Result code (ignored)
		OS2Call	DosExit

SearchError:	Mov	BX, [PipeWriteHandle]		; If error, 
		Mov	DX, Offset Zeroes		;   write zeroes to 
		Mov	CX, 80				;   pipe
		Call	MyDosWrite

		Jmp	TerminateThread			;   and terminate
;----------------------------------------------------------------
; Find Them -- Recursive routine to find files fitting file spec
;----------------------------------------------------------------
FindThem:	Enter	4, 0		; Dir Handle is   [BP - 2]
					; Search Count is [BP - 4]
;------------------------------------
; Get current directory for printing
;------------------------------------
		Mov	[CurrDirLen], 64	; Len for current dir
		Push	0			; Current disk drive
		Push	DS
		Push	Offset CurrentDir	; Space for current dir
		Push	DS
		Push	Offset CurrDirLen
		OS2Call	DosQCurDir		; Get current dir

		Mov	DX, Offset FullPathName	; Find length of it
		Call	StringLen
		Cmp	AX, 3			; See if root
		Jz	NoMoreSlashes		; If so, skip next part

		Mov	SI, DX			; Add a slash
		Add	SI, AX			;   at end of directory
		Mov	Byte Ptr [SI], '\'
		Mov	Byte Ptr [SI + 1], 0
		Inc	AX
NoMoreSlashes:	Mov	[CurrDirLen], AX	; Save total length
;----------------------
; Search for the files
;----------------------
		Mov	Word Ptr [BP - 2], -1	; Initial directory Handle
		Mov	Word Ptr [BP - 4], 1	; Search for one file

		Push	[FileSpec + 2]		; segment of find file name
		Push	[FileSpec]		; offset of find file name
		Push	SS			; segment of directory handle
		Lea	AX, [BP - 2]		; offset of directory handle
		Push	AX
		Push	07h			; attribute
		Push	DS			; segment of buffer
		Push	Offset FileFindBuf	; offset of buffer
		Push	FileFindBufLen		; length of buffer
		Push	DS			; segment of search count
		Lea	AX, [BP - 4]		; offset of search count
		Push	AX
		Push	0			; Reserved
		Push	0
		OS2Call   DosFindFirst		; Find first file

FindResult1:	Or	AX, AX			; See if error
		Jz	NoFindError1		; If not skip next
		Cmp	AX, 18			; See if no more files
		Jz	FindAllDone1		; If so, done with search
		Jmp	SearchError		; Process error 

NoFindError1:	Cmp	Word Ptr [BP - 4], 0	; See if zero files found
		Jz	FindAllDone1		; If so, done with search

		Mov	BX, [PipeWriteHandle]	; Write drive and
		Mov	DX, Offset FullPathName	;   directory path
		Mov	CX, [CurrDirLen]	;   to pipe
		Mov	SI, CX
		Call	MyDosWrite

		Mov	DX, Offset FileFindBuf.file_name
		Mov	CL, [FileFindBuf.string_len]	; Write file name
		Sub	CH, CH				;   to pipe
		Add	SI, CX
		Call	MyDosWrite

		Mov	DX, Offset Zeroes	; Pad with zeroes
		Mov	CX, 80			;   written to pipe
		Sub	CX, SI
		Call	MyDosWrite

		Push	[BP - 2]		; Directory Handle
		Push	DS			; segment of buffer
		Push	Offset FileFindBuf	; offset of buffer
		Push	FileFindBufLen		; length of buffer
		Push	DS			; segment of count
		Lea	AX, [BP - 4]		; offset of count
		Push	AX
		OS2Call	DosFindNext		; Find next file

		Jmp	FindResult1		; Loop around

FindAllDone1:	Push	[BP - 2]		; directory handle
		OS2Call	DosFindClose		; close it
;-------------------------------
; Now search for subdirectories
;-------------------------------
		Mov	Word Ptr [BP - 2], -1	; Initial directory handle
		Mov	Word Ptr [BP - 4], 1	; Search for one directory

		Push	DS			; segment of spec
		Push	Offset StarDotStar	; offset of spec
		Push	SS			; segment of handle
		Lea	AX, [BP - 2]		; offset of handle
		Push	AX
		Push	10h			; attribute (dirs only)
		Push	DS			; segment of buffer
		Push	Offset FileFindBuf	; offset of buffer
		Push	FileFindBufLen		; length of buffer
		Push	DS			; segment of count
		Lea	AX, [BP - 4]		; offset of count
		Push	AX
		Push	0			; reserved
		Push	0
		OS2Call	DosFindFirst		; Find first dir

FindResult2:	Or	AX, AX			; Check if error
		Jz	NoFindError2
		Cmp	AX, 18			; Check if all finished
		Jz	FindAllDone2
		Jmp	SearchError

NoFindError2:	Cmp	Word Ptr [BP - 4], 0	; Check if no dirs found
		Jz	FindAllDone2
		Test	[FileFindBuf.attributes], 10h	; See if directory
		Jz	FindTheNext
		Cmp	[FileFindBuf.file_name], '.'	; Exclude '.' and '..'
		Jz	FindTheNext

		Push	DS			; Change the directory
		Push	Offset FileFindBuf.file_name
		Push	0
		Push	0
		OS2Call	DosChdir

		Call	FindThem		; Recursive call

		Push	DS			; Change to '..' directory
		Push	Offset BackDir
		Push	0
		Push	0
		OS2Call	DosChdir

FindTheNext:	Push	[BP - 2]		; search handle
		Push	DS			; segment of buffer
		Push	Offset FileFindBuf	; offset of buffer
		Push	FileFindBufLen		; length of buffer
		Push	DS			; segment of count
		Lea	AX, [BP - 4]		; offset of count
		Push	AX
		OS2Call	DosFindNext		; Find next directory

		Jmp	FindResult2

FindAllDone2:	Push	[BP - 2]		; directory handle
		OS2Call	DosFindClose		; close it

		Leave
		Ret               
_TEXT		ENDS
;-----------------------------------------------------------------------
;-----------------------------------------------------------------------
; BROWSE section to display files after names are read from pipe
;-----------------------------------------------------------------------
;-----------------------------------------------------------------------
_DATA		SEGMENT
               
		db	'ATTR1='
Attribute1	db	1Eh		; Attribute for file text
		db	'ATTR2='
Attribute2	db	4Fh		; Attribute for file name
		db	'SHIFT='
ShiftHoriz	db	8		; Horizontal shift screen default
FileOffset	dw	-1, -1
Dispatch	dw	Home,  Up,    PgUp,  Dummy,  Left
		dw	Dummy, Right, Dummy, EndKey, Down, PgDn

_DATA		ENDS

_BSS		SEGMENT

ModeData	ModeDataStruc <>
KeyData		KeyDataStruc <>
Buffer		db	16384 dup (?)
BufferMid	db	16384 dup (?)
BufferEnd	Label	Byte
ScreenSize	dw	?
ScreenStart	dw	?
EndOfFile	dw	?
HorizOffset	dw	?
RightMargin	dw	?
ScreenAddr	Label	DWord
ScreenOff	dw	?
ScreenSeg	dw	?
FileHandle	dw	?
OpenAction	dw	?
NewPointer	dd	?
ScreenSaveSel	dw	?

_BSS		ENDS
;---------------------------
; Open the File for reading
;---------------------------
_TEXT		SEGMENT

Browse:		Mov	[FileOffset], -1	; Initialize these pointers
		Mov	[FileOffset + 2], -1

		Push	DS			; segment of name
		Push	Offset FullFileName	; offset of name
		Push	DS			; segment for handle
		Push	Offset FileHandle	; offset for handle
		Push	DS			; segment for 'action'
		Push	Offset OpenAction	; offset for 'acton'
		Sub	AX, AX
		Push	AX			; high size (ignored)
		Push	AX			; low size (ignored)
		Push	AX			; attribute (ignored)
		Push	1			; open if file exists
		Push	0A0h			; read only / deny write
		Push	AX			; reserved
		Push	AX			; reserved

		OS2Call	DosOpen			; Open File

		Or	AX, AX			; Check if error
		Jz	GotTheFile		; If not, continue

		Mov	AX, Offset FileOpenError
		Ret		
GotTheFile:
;-----------------------------
; Get Screen Mode Information
;-----------------------------
		Mov	[ModeData.md_length], 12	; length of structure

		Push	DS			; segment of structure
		Push	Offset ModeData		; offset of structure
		Push	0			; reserved
		OS2Call	VioGetMode		; get video mode

		Or	AX, AX			; See if error (only if
		Jz	NotDetached		;   program is detached)
VideoError:	Mov	AX, Offset VideoModeError
		Ret

NotDetached:	Test	[ModeData.md_type], 2	; See if graphics mode
		Jnz	VideoError		; If so, do not continue
		Mov	AX, [ModeData.col]	; character columns
		Mul	[ModeData.row]		; character rows
		Jc	VideoError		; Leave if exceeds 64K
		Add	AX, AX			; Ditto here
		Jc	VideoError
		Mov	[ScreenSize], AX	; Save screen size in bytes

		Push	DS			; Segment for address
		Push	Offset ScreenAddr	; Offset for address
		Push	DS			; Segment for size
		Push	Offset ScreenSize	; Offset for size
		Push	0			; Reserved
		OS2Call	VioGetBuf		; Get logical video buffer

		Or	AX, AX			; Leave if an error
		Jnz	VideoError
;---------------------------------------
; Allocate memory and save screen in it
;---------------------------------------
		Push	[ScreenSize]		; Size of segment
		Push	DS			; Segment for selector
		Push	Offset ScreenSaveSel	; Offset for selector
		Push	0			; No sharing
		OS2Call	DosAllocSeg		; Allocate segment

		Or	AX, AX			; If error, leave
		Jnz	VideoError

		Push	[ScreenSaveSel]		; Segment for destination
		Sub	AX, AX
		Push	AX			; Offset for destination
		Push	DS			; Segment of screen size
		Push	Offset ScreenSize	; Offset of screen size
		Push	AX			; Starting row
		Push	AX			; Starting column
		Push	AX			; Reserved
		OS2Call	VioReadCellStr		; Save the screen

		Call	SetExitList		; Will restore on exit
;---------------------------------------
; Get keyboard key and decide on action
;---------------------------------------
		Call	Home			; Read file in
		Mov	[ScreenStart],SI	; Set buffer address
KeyLoop:	Push	ThreadID		; Don't let the search
		OS2Call	DosSuspendThread 	;    slow down the update

		Call	UpDateScreen		; Update the screen

		Push	ThreadID
		OS2Call	DosResumeThread 	; Let the search resume

GetKey:		Push	DS			; Segment of structure
		Push	Offset KeyData		; Offset of structure
		Push	0			; Wait for key
		Push	0			; Reserved
		OS2Call	KbdCharIn		; Read key

		Mov	AL, [KeyData.char_code]	; Get character code
		Cmp	AL, ' '			; A space means do next
		Je	SpaceLeave		;    file
		Cmp	AL,27			; Check if ESC
		Je	EscLeave		; If so, terminate 
		Cmp	AL, 0E0h		; E0 for enhanced keyboard
		Jz	ScanCode		;   extended keys
		Or	AL, AL			; Check if extended
		Jnz	GetKey			; If not, try again
ScanCode:       Mov	AL, [KeyData.scan_code]	; Get scan code
		Sub	AL,71			; Subtract Home key value
		Jb	GetKey			; If below that, not valid
		Cmp	AL,(81 - 71)		; Check if above PgDn
		Ja	GetKey			; If so, ignore it
		Sub	AH,AH			; Zero out top byte
		Add	AX,AX			; Double for word access
		Mov	BX,AX			; Offset in dispatch table
		Mov	SI,[ScreenStart]	; Set current buffer pointer
		Call	[Dispatch + BX]		; Do the call
		Mov	[ScreenStart],SI	; Set new buffer pointer
		Jmp	KeyLoop			; And update the screen
;--------------------------------------------
; Terminate -- Restore screen and close file
;--------------------------------------------
SpaceLeave:	Call	ExitRoutine		; Restore screen
		Call	UnsetExitList		; Take away exit list
		Sub	AX, AX			; 0 means continue with files
		Ret				; Return

EscLeave:	Call	ExitRoutine		; Restore screen
		Call	UnsetExitList		; Take away exit list
		Mov	AX, -1			; -1 means stop program
		Ret				; Return
;-------------------------------------
; Exit List Processing for Ctrl-Break
;-------------------------------------
SetExitList:	Push	1			; Set an exit list
		Push	CS			; Segment of routine
		Push	Offset ExitList		; Offset of routine
		OS2Call	DosExitList
		Ret

UnsetExitList:	Push	2			; Unset an exit list
		Push	CS			; Segment of routine
		Push	Offset ExitList		; Offset of routine
		OS2Call	DosExitList
		Ret

ExitRoutine:	Push	[ScreenSaveSel]		; Segment of saved screen
		Push	0			; Offset of saved screen
		Push	[ScreenSize]		; Length of saved screen
		Push	0			; Starting row
		Push	0			; Starting column
		Push	0			; Reserved
		OS2Call	VioWrtCellStr		; Restore screen

		Push	[FileHandle]		; File handle
		OS2Call	DosClose		; Close it
		Ret

ExitList:	Call	ExitRoutine		; Do exit routine
		Push	3			; Then continue exiting
		Push	0
		Push	0
		OS2Call	DosExitList
;---------------------
; Cursor Key Routines
;---------------------
Home:		Sub	BX,BX			; For zeroing out values
		Mov	AX,[FileOffset]		; Check if read in file
		Or	AX,[FileOffset + 2]
		Mov	[FileOffset],BX		; Zero out file address
		Mov	[FileOffset + 2],BX
		Mov	[HorizOffset],BX	; Zero out horizontal offset	
		Mov	SI,Offset Buffer	; Reset buffer pointer	
		Jz	Dummy			; Skip file read if in already
		Mov	DX,Offset Buffer	; Area to read file in
		Mov	CX,32768		; Number of bytes to read
		Call	FileRead		; Read in file
Dummy:		Ret

Up:		Call	GetPrevChar		; Get previous char in buffer
		Jc	UpDone			; If none available, finish
UpLoop:		Call	GetPrevChar		; Get previous char again
		Jc	UpDone			; if none, we're done
		Cmp	AL,10			; Check if line feed
		Jnz	UpLoop			; If not, try again 
		Call	GetNextChar		; Get char after line feed
UpDone:		Ret

PgUp:		Mov	CX,Word Ptr [ModeData.row]	; Number of lines
		Dec	CX				;   less title line

PgUpLoop:	Call	Up			; Do UP that many times
		Loop	PgUpLoop
		Ret

Left:		Mov	[HorizOffset],0		; Reset Horizontal Offset
		Ret

Right:		Mov	AL,[ShiftHoriz]		; Get places to shift
		Sub	AH,AH
		Add	[HorizOffset],AX	; Move that many right
		Ret

EndKey:		Mov	BX,SI			; Save buffer pointer
		Call	PgDn			; Go page down
		Cmp	BX,SI			; Check if we did so
		Jnz	EndKey			; If so, do it again
		Ret

Down:		Call	GetNextChar		; Get next character
		Jc	NoMoreDown		; If no more, we're done
DownLoop:	Call	GetNextChar		; Get one again
		Jc	UpLoop			; If no more, find prev LF
		Cmp	AL,10			; See if line feed
		Jnz	DownLoop		; If not, continue
NoMoreDown:	Ret

PgDn:		Mov	CX,Word Ptr [ModeData.row]	; Number of lines
		Dec	CX				;   less title line

PgDnLoop:	Call	Down			; Do DOWN that many times
		Loop	PgDnLoop
		Ret
;---------------
; Update Screen
;---------------
UpdateScreen:	Push	ES

		Les	DI,[ScreenAddr]		; Address of display
		Mov	CX,ScreenSize		; Number of bytes in screen
		Shr	CX,1			; Half for number of chars
		Mov	AL,' '			; Will blank screen
		Mov	AH,[Attribute1]		; With screen attribute
		Rep	Stosw			; Blank it
		Mov	CX, [ModeData.col]	; Number of character cols
		Mov	DI, [ScreenOff]		; Offset of screen
		Mov	SI, Offset FullFileName	; File name

DisplayName:	Lodsb				; Get character
		Or	AL, AL			; See if end
		Jz	EndOfName
		Mov	AH, [Attribute2]	; Use second attribute
		Stosw				; Display it
		Loop	DisplayName

EndOfName:	Mov	SI,[ScreenStart]	; Address of data in buffer
		Mov	DL, 1			; Start with second line
		Mov	AL, Byte Ptr [ModeData.col]	; Length of row
		Sub	AH,AH
		Add	AX,[HorizOffset]	; Add Horizontal Offset
		Mov	[RightMargin],AX	; That's right display margin

LineLoop:	Sub	BX,BX			; Column Number
		Mov	AL, Byte Ptr [ModeData.col]	; Use Line Length
		Mul	DL			;   and Line Number
		Add	AX,AX			;     to recalculate
		Mov	DI,AX			;       display destination
		Add	DI,[ScreenOff]		; Add beginning address	

CharLoop:	Call	GetNextChar		; Get next character
		Jc	EndOfScreen		; If no more, we're done
		Cmp	AL,13			; Check for carriage return
		Je	CharLoop		; Do nothing if so
		Cmp	AL,10			; Check for line feed
		Je	LineFeed		; Do routine if so
		Cmp	AL,9			; Check for tab
		Je	Tab			; Do routine if so
		Mov	CX,1			; Just 1 char to display

PrintChar:	Cmp	BX,[HorizOffset]	; See if we can print it
		Jb	NoPrint		
		Cmp	BX,[RightMargin]	; See if within margin
		Jae	NoPrint
		Mov	AH,[Attribute1]		; Attribute for display

WriteIt:	Stosw				; Write without retrace wait
NoPrint:	Inc	BX			; Bump up line counter
		Loop	PrintChar		; Do it CX times
		Jmp	CharLoop		; Then go back to top

Tab:		Mov	AX,BX			; Current column number
		And	AX,07h			; Take lower three bits
		Mov	CX,8
		Sub	CX,AX			; Subtract from 8
		Mov	AL,' '			; Will print CX blanks
		Jmp	PrintChar

LineFeed:	Inc	DL			; Next line
		Cmp	DL,Byte Ptr [ModeData.row]	; See if down at bottom
		Jb	LineLoop		; If not, continue

EndOfScreen:	Push	0			; Offset of buffer
		Push	[ScreenSize]		; Size of buffer
		Push	0			; Reserved
		OS2Call	VioShowBuf		; Update the screen

		Pop	ES			; All done -- leave
		Ret
;--------------------------------
; Get Next Character from buffer
;--------------------------------
;		(Input is SI pointing to buffer, Returns AL, CY if no more)

GetNextChar:	Cmp	SI, [EndOfFile]		; See if at end of file
		Jae	NoMoreNext		; If so, no more chars
		Cmp	SI, Offset BufferEnd	; See if at end of buffer
		Jb	CanGetNext		; If not, just get character

		Push	CX			; Otherwise save registers
		Push	DX
		Push	DI
		Push	ES
		Push	DS			; Set ES to DS
		Pop	ES			;   (could be different)

		Mov	SI,Offset BufferMid	; Move 2nd buffer half
		Mov	DI,Offset Buffer	;   to 1st buffer half	
		Mov	CX,16384	   
		Sub	[ScreenStart],CX	; New buffer pointer
		Rep	Movsb			; Move them
		Mov	SI,DI			; SI also buffer pointer
		Add	[FileOffset],32768	; Adjust file addr to read
		Adc	[FileOffset + 2],0 
		Mov	DX,Offset BufferMid	; Place to read file
		Mov	CX,16384		; Number of bytes
		Call	FileRead		; Read the file
		Sub	[FileOffset],16384	; Now adjust so reflects
		Sbb	[FileOffset + 2],0	;   1st half of buffer

		Pop	ES			; Get back registers
		Pop	DI
		Pop	DX
		Pop	CX

		Jmp	GetNextChar		; And try again to get char
CanGetNext:	Lodsb				; Get the character
		And	AL, 7Fh
		Stc
NoMoreNext:	Cmc				; So CY set if no more
		Ret				
;------------------------------------
; Get Previous Character from buffer
;------------------------------------
GetPrevChar:	Cmp	SI,Offset Buffer	; See if at top of buffer
		Ja	CanGetPrev		; If not, just get character
		Mov	AX,[FileOffset]		; See if at top of file
		Or	AX,[FileOffset + 2]
		Jz	AtTopAlready		; If so, can't get anymore

		Push	CX			; Save some registers
		Push	DX
		Push	ES
		Push	DS
		Pop	ES

		Mov	SI,Offset Buffer	; Move 1st half of buffer
		Mov	DI,Offset BufferMid	;   to 2nd half of buffer
		Mov	CX, 16384
		Add	[ScreenStart], CX	; New buffer pointer
		Rep	Movsb			; Do the move
		Sub	[FileOffset], 16384	; Adjust file addr for read
		Sbb	[FileOffset + 2], 0
		Mov	DX, Offset Buffer	; Area to read file into
		Mov	CX, 16384		; Number of bytes
		Call	FileRead		; Read the file

		Pop	ES
		Pop	DX			; Get back registers
		Pop	CX

CanGetPrev:	Dec	SI			; Move pointer back
		Mov	AL,[SI]			; Get the character
		And	AL, 7Fh			; Wipe out high byte
		Stc				; CY flag reset for success
AtTopAlready:	Cmc				; CY flag set for no more
		Ret
;--------------------------------------------
; Read CX bytes from the file into DX buffer
;--------------------------------------------
FileRead:	Push	AX			; Save some registers
		Push	BX
		Mov	[EndOfFile], -1		; Initialize this

		Push	[FileHandle]		; File handle
		Push	[FileOffset + 2]	; New pointer (high)
		Push	[FileOffset]		; New pointer (low)
		Push	0			; Action
		Push	DS			; Segment for new pointer
		Push	Offset NewPointer	; Offset for new pointer
		OS2Call	DosChgFilePtr

		Mov	BX, [FileHandle]	; Read from the file
		Call	MyDosRead

		Or	AX, AX			; See if error
		Mov	AX, [BytesRead]
		Jz	NoReadError		; If no error, continue
		Sub	AX, AX			; Otherwise read zero bytes
NoReadError:	Cmp	AX,CX			; See if 32K has been read
		Je	GotItAll		; If so, we're home free
		Add	AX,DX			; Otherwise add to buffer addr
		Mov	[EndOfFile],AX		; And save as end of file

GotItAll:	Pop	BX
		Pop	AX
		Ret

_TEXT		ENDS
		END	Entry

[ RETURN TO DIRECTORY ]