; INSTALL.ASM
; (c) 1989, 1990 Ashok P. Nadkarni
; Addition of /c,l,w,o,t & z options by David Abbott (dfa),  Nov-Dec, 1990.
; Improvements to locate_cmdedit by dfa,  Jan 1991.
; also /k option added May 1991 and /n added July 1991.
; /a and /y options added by dfa and wd,  and /x by wd June 1992.
; NODIRS added by Jason Hood (jmh), 9 March, 1997.
; Switcher and improvement to locate_cmdedit by jmh, 11 March, 1997.
; Moved config file code from cmdedit.asm, jmh, 13 March.
; Moved getargs from utl.asm, jmh, 15 March.
; Used DOSKey interface, jmh, 15 December.
; Requires DOS5+, jmh, 4 May, 1998.
; Setup all the interrupt vectors here, jmh, 5 May, 1998.
; Removed the /k and /y options;
;  modified the option processing code;
;  allowed /x and /f options to be changed, jmh, 5 May, 1998.
; Moved all the display stack code into here and changed the options
;  accordingly, jmh, 7 May, 1998.
; Changed options, jmh, 8 May:
;   /b -> /s
;   /d -> /h
;   /s -> /d
;   /f -> removed (just use a filename)
; Removed the obsolete /e option, jmh, 21 May, 1999.
; Added /f disable LFN completion, sorted list of options, jmh, 26 May, 1999.

; Added /s? and /m? to display used sized (installed only), jmh, 2 April, 2001.
; Added /k keep buffers and modified signatures, jmh, 3 April, 2001.
; Added /? help screen, /a application buffer size (no backslash append is
;  now /b) and "-@" application file history, jmh, 5 April, 2001.
; Added /q quiet option and /e empty/end option, jmh, 6 April, 2001.
; Use /tt to enable/toggle application macro translation and its status is
;  now also displayed, jmh, 18 December, 2001.
; Added self-load high capability (/j to keep low), jmh, 20 December, 2001.
; Have separate macro and symbol buffers for applications. Use /a to indicate
;  following /tzhms options are for applications. jmh, 21 December, 2001.
; May as well display history and directory sizes, too, jmh, 22 December, 2001.
; Explicitly set/clear toggles by prefixing with '+' or '-', ditto.
; Removed alternate warning, /w has no effect, jmh, 23 December, 2001.
;
; This code is the installation code for CMDEDIT. This file must be
; linked last !

SIGNATURE1 equ <"Ashok P. Nadkarni, D. Abbott and W. Davison.">
SIGNATURE2 equ <"Jason Hood. http://cmdedit.adoxa.cjb.net/">
SIGNATURE3 equ <"CMDEDIT 2.0 (c) 1990 Ziff Communications Co.">

	INCLUDE common.inc
	INCLUDE ascii.inc
	INCLUDE dos.inc
	INCLUDE general.inc
	INCLUDE buffers.inc		;added by jmh 980507

	PUBLIC	install_begin
	PUBLIC	install
	PUBLIC	file_error
	PUBLIC	abort_install
	PUBLIC	cmdedit_seg		;added by jmh 011220
	PUBLIC	cmdedit_low		;ditto
	PUBLIC	process_args
	PUBLIC	install_vectors 	;added by jmh
	PUBLIC	init_sis		;added by jmh 010405
	PUBLIC	read_cmdfile
	PUBLIC	init_buffers		;added by jmh 011222


CSEG	SEGMENT	BYTE PUBLIC 'CODE'
	EXTRN	cmdlen:BYTE
	EXTRN	default_imode:BYTE
	EXTRN	auto_recall:BYTE
	EXTRN	silent:BYTE
	EXTRN	macro_ignore_char:BYTE
	EXTRN	mfilename:BYTE
	EXTRN	mfile_handle:WORD
	EXTRN	linebuf:BYTE
	EXTRN	lastchar:WORD
	EXTRN	source:WORD
	EXTRN	set_type:PROC		;added by jmh 980506
	EXTRN	cmdedit_cmd:PROC
	EXTRN	remember:PROC		;added by jmh 980506
	EXTRN	get_kbd_line:PROC	;added by jmh 980506
	EXTRN	skip_whitespace:PROC
	EXTRN	isspace:PROC
	EXTRN	prev_isr1b:WORD
	EXTRN	prev_isr2f:WORD
	EXTRN	dos_version_major:BYTE
	EXTRN	dos_version_minor:BYTE
	EXTRN	old_int21vec:WORD
	EXTRN	cmdedit:PROC
	EXTRN	our_break_handler:PROC
	EXTRN	our_mpx_handler:PROC	;added by jmh
	EXTRN	init_over:PROC
	EXTRN	cmdedit_isr:PROC
	EXTRN	getkey_funcnum:BYTE
;dfa added the following
	EXTRN	cursor_type:BYTE
	EXTRN	disable_macro:BYTE
	EXTRN	appl_macro:BYTE 	;added by jmh 011218
	EXTRN	no_append_slash:BYTE
	EXTRN	min_length:WORD
	;EXTRN	msg_flag:BYTE		;removed by jmh 011223
	EXTRN	cmdedit_disable:BYTE
	EXTRN	appl_disable:BYTE	;added by jmh 011221
	EXTRN	disable_lfn:BYTE	;added by jmh 990526
	EXTRN	empty_hist:BYTE 	;added by jmh 010406
IFNDEF NODIRS
	EXTRN	dir_stk:WORD
ENDIF
	EXTRN	history:WORD
	EXTRN	mac_stk:WORD
	EXTRN	sym_stk:WORD
	EXTRN	hist_ptr:WORD
	EXTRN	mac_ptr:WORD
	EXTRN	sym_ptr:WORD
	EXTRN	endm_cmd:BYTE
	EXTRN	defm:BYTE
	EXTRN	defs:BYTE
	EXTRN	check_separator:PROC
	EXTRN	output_newline:PROC
	EXTRN	linebuf:BYTE
	EXTRN	separate:PROC		;added by jmh 980509
	EXTRN	temp1:WORD		;added by jmh 980514
	EXTRN	temp2:BYTE		;added by jmh 011222
	EXTRN	get_line_len:PROC	;added by jmh 980517
	EXTRN	strlen:PROC		;added by jmh 990523
;following added by jmh 010405
	EXTRN	sisInstData:WORD
	EXTRN	DataBlockPtr1:WORD
	EXTRN	DataBlockSize1:WORD
	EXTRN	DataBlockPtr2:WORD
	EXTRN	DataBlockSize2:WORD
	EXTRN	DataBlockPtr3:WORD
	EXTRN	DataBlockSize3:WORD
	EXTRN	DataBlockPtr4:WORD
	EXTRN	DataBlockSize4:WORD
	EXTRN	DataBlockPtr5:WORD
	EXTRN	DataBlockSize5:WORD
IFNDEF NODIRS
	EXTRN	DataBlockPtr6:WORD
	EXTRN	DataBlockSize6:WORD
ENDIF
	EXTRN	StackBlockPtr:WORD
	EXTRN	StackBlockSize:WORD

DGROUP	GROUP	CSEG


	ASSUME	CS:DGROUP,DS:DGROUP,ES:DGROUP,SS:DGROUP

NL	equ	<CR,LF> 		;these two equ's added by jmh 980507
EOL	equ	<NL,DOLLAR>		;(NL is newline)

; install_begin MUST be start of installation code.
; (jmh - alternatively, it's the end of resident code.)
install_begin	LABEL BYTE
copyrite     db VERSION_STR,NL
	     db SIGNATURE1,NL
	     db SIGNATURE2,NL
	     db SIGNATURE3,NL,EOL

	     include help.inc			;added by jmh 010405
;help_msg    db 'A long help message'

msg_nomem    db '* Insufficient memory - reduce buffer sizes. *',EOL
option_error db '* Unknown or illegal option: '
option_let   db ?, ' *',EOL                     ;added by jmh 980506
option_error2 db '* Missing option. *',EOL      ;added by jmh 980508
file_error   db '* Error processing command file. *',EOL
inv_dosver   db '* Unsupported DOS version. *',EOL
;jmh 980508: removed next line
;msg_dup     db '* CMDEDIT already installed in memory. *',EOL
msg_notdup   db '* No copy of CMDEDIT found in memory. *',EOL
msg_unload   db '* Unable to unload CMDEDIT.        *',NL
	     db '* Uninstall TSRs in reverse order. *',EOL
msg_memerr   db '* DOS memory allocation error! *',NL,BEL
	     db '* Recommend you reboot!        *',NL,BEL,DOLLAR
msg_uninstalled db NL,'* CMDEDIT uninstalled successfully. *',EOL
msg_notinst  db '* /o specified but CMDEDIT not previously installed. *',EOL
;jmh 980519: added following line
msg_resize   db '* Buffers cannot be resized once CMDEDIT is installed. *',EOL
;jmh 980506: removed the ending * from the configuration messages and
;	     capitalised all the first letters
;    980508: rearranged them in the order they're displayed
msg_omode		db '* Default overwrite mode enabled.',EOL
msg_imode		db '* Default insert mode enabled.',EOL
;jmh 010405: modified these messages
msg_dcursor_type	db '* Block insert cursor.',EOL
msg_scursor_type	db '* Block overwrite cursor.',EOL
;jmh 980506: added the enable slash
msg_enable_slash	db '* Backslash appending enabled.',EOL
msg_disable_slash	db '* Backslash appending disabled.',EOL
;jmh 010406: added following two
msg_enable_empty	db '* Empty match moves cursor to end.',EOL
msg_disable_empty	db '* Empty match leaves cursor at start.',EOL
msg_enable_autorecall	db '* Autorecall enabled.',EOL
msg_disable_autorecall	db '* Autorecall disabled.',EOL
msg_enable_macro	db '* Macro translation enabled.',EOL
msg_disable_macro	db '* Macro translation disabled.',EOL
;jmh 011218: added following two
msg_enable_appl_macro	db '* Application macro translation enabled.',EOL
msg_disable_appl_macro	db '* Application macro translation disabled.',EOL
;jmh 990526: added following two
msg_enable_lfn		db '* Long filename completion enabled.',EOL
msg_disable_lfn		db '* Long filename completion disabled.',EOL
msg_enable_bell 	db '* Error bell enabled.',EOL
msg_disable_bell	db '* Error bell disabled.',EOL
;jmh 980506: changed this to display the ignore character
msg_ignore_char 	db '* Ignore character is "'
ignore_char		db ?, '".',EOL
;jmh 980507: actually display the length (provided it's less than ten)
msg_length_changed	db '* Minimum history line length is '
msg_length_len		db ?, '.',EOL
;jmh added the following two msgs:
msg_cooked_mode 	db '* Cooked mode.',EOL
msg_raw_mode		db '* Raw mode.',EOL
msg_disable		db '* CMDEDIT disabled.',EOL
msg_enable		db '* CMDEDIT enabled.',EOL
;jmh 011221: added the following two:
msg_app_disable 	db '* CMDEDIT (application) disabled.',EOL
msg_app_enable		db '* CMDEDIT (application) enabled.',EOL
;jmh added the following msgs:
msg_file_read		db '* Configuration file read.',EOL
msg_history_buf 	db '- History$'
IFNDEF NODIRS
msg_dir_buf		db '- Directories$'
ENDIF
msg_symbol_buf		db '- Symbols$'
msg_macro_buf		db '- Macros$'
msg_app_buf		db NL,NL,'-@ Application',NL,EOL
tabs			db TAB,TAB		;Separate symbol from its name
sym_kluge		db 0			;Kluge to not output extra
;						 space after defs line
;Following added by jmh 010402
msg_size		db ' occupy $'
msg_size_hist		db ' occupies $'        ;added by jmh 011222
msg_bytes		db ' bytes.',EOL

mcb_name		db 'CmdEdit',0          ;added by jmh 980622


cmdedit_seg	dw	0	;Segment of the resident copy
cmdedit_res	db	0	;1 if CMDEDIT is resident (jmh 980508)
cmdedit_low	db	0	;-1 to not load high (jmh 011220)

;jmh 011221: the order of these values is important!!
histsize	dw	512, 64 ;Default size of history buffer (DOS/app)
macrosize	dw	512, 0	;Default size of macro buffer
symsize 	dw	512, 0	;Default size of symbol buffer
IFNDEF NODIRS
dirsize 	dw	128	;Default size of directory stack buffer
ENDIF

argflags	dw	0	;Used to remember what args have been seen.
				;dfa changed from db to dw to allow more args.
filearg 	equ	0001h
option_arg	equ	0002h	;If this is set, display all the stats (jmh)
uninstall_arg	equ	0004h
ignore_arg	equ	0008h
;added by dfa:
length_arg	equ	0010h
not_inst_arg	equ	0020h
no_check_arg	equ	0040h
;added by jmh:
keep_arg	equ	0080h
help_arg	equ	0100h
apparg		equ	0200h
quietarg	equ	0400h

bufflags	db	0	;Flags for buffer options (jmh 011222)
sizeflags	db	0	;Indicate which sizes to display
histdos 	equ	01h	;This order must follow the size order!!
histapp 	equ	02h
macrodos	equ	04h
macroapp	equ	08h
symdos		equ	10h
symapp		equ	20h
dirbuf		equ	40h
msgout		equ	80h	;Indicate a buffer/size has been displayed


DispStr macro string		;Alternative to @DispStr for the quiet option
IFNB <string>
	mov	dx,offset string
ENDIF
	call	display_str
	endm


argument_buffer db	LINEBUF_SIZE DUP (?)

;	This location is jumped to when the program is run from the DOS
;	command line. The program parses the input line, takes
;	appropriate actions, initializes buffers etc. and then TSR's
;	after taking over the interrupts. The memory taken up by the ISEG
;	segment will then be used for various data buffers since this
;	code is no longer needed.
install proc	near
	cld			;Entire program assumes direction flag will
;				 be cleared

	ASSUME CS:DGROUP,DS:DGROUP,ES:DGROUP,SS:DGROUP

; Make sure we are running DOS 5.x or higher
	call	near ptr get_dosversion
	cmp	dos_version_major,5		;DOS 5.x ?
	jae	@install_1
	mov	dx,offset inv_dosver
	jmp	abort_install			;not supported.

@install_1:
;Determine if CMDEDIT is already installed.
	call	near ptr locate_cmdedit
	mov	cmdedit_seg,ax			;Remember the resident segment
	jne	@install_5
	mov	cmdedit_res,1			;Indicate already resident

@install_5:
	mov	si,offset cmdlen
	lodsb			;SI == line in PSP, AL == line length
	mov	cl,al
	xor	ch,ch		;CX = line length
	xor	ax,ax		;Function = get argument count
	push	cx		;Save line length
	call 	near ptr getargs ;AX := # arguments in command line
	pop	cx		;Restore line length
	mov	bp,1		;Argument number
	mov	di,ax		; DI == number of arguments
	or	di,di			;added by jmh 980508
	jnz	@install_10		; if no options specified, set the
	or	argflags,option_arg	; option_arg to display the status
	jmp	short @install_21
@install_10:
	mov	bx,offset DGROUP:argument_buffer
	mov	dx,SIZE argument_buffer
	mov	ax,bp		;Argument number
	push	cx		;save line length
	call 	near ptr getargs ;Get argument into argument_buffer
;				  (no chance of buffer being too short)
	call 	near ptr process_args ;Process the argument
	pop	cx		;Restore line length
	inc	bp		;Any more arguments ?
	cmp	bp,di		;Any more arguments ?
	jbe	@install_10	;Yes, loop back to process remaining args


@install_20:
; OK now we have done all necessary parsing without errors. Now first check
; if the uninstall option was specified.
	test	argflags,uninstall_arg
	je	@install_21		;Nope

; We have to unload a previously installed copy of CMDEDIT. See if it is
; really there.
	cmp	cmdedit_res,0		;Are we the only copy of CMDEDIT ?
	je	@install_20_a		;There is no other copy
	jmp	uninstall

@install_20_a:
	mov	dx,offset msg_notdup	;Indicate no copy to uninstall
	jmp	abort_install		;Exit

@install_21:
; No uninstall option specified. Now check if help is required.
	test	argflags,help_arg
	je	@install_21_a0		;Nope
	mov	dx,offset help_msg
	jmp	abort_install

@install_21_a0:
; No uninstall or help option specified. Now check if this is the only copy of
; CMDEDIT.
	test	argflags,no_check_arg	;is checking disabled? (added by dfa)
	jnz	@install_21_a1
	cmp	cmdedit_res,0
	je	@install_21_a1		;We are the only copy. Proceed with
;					 installation
	jmp	change_installed_options ;Else go change installed options.

@install_21_a1: 			;added by dfa
	mov	cmdedit_seg,ds		;in case /n specified.
	test	argflags,not_inst_arg	;Do we want to install or not?
	jz	@install_21_a
	mov	dx,offset msg_notinst	;Indicate no copy installed to change.
	jmp	abort_install		;Exit

@install_no_mem:
	mov	dx,offset msg_nomem
	jmp	abort_install

@install_21_a:
; At this point all the arguments have been parsed. Now check
; that the sum total of the various buffer sizes does not exceed
; segment limits. Also check for mandatory minimum sizes.
; Finally, transfer control to the resident portion which will
; relocate buffers to overlay the installation code.
; jmh 011221: created check_size procedure.
;	      ORDER IS IMPORTANT.

	mov	ax,OFFSET DGROUP:install_begin
					;add starting address where buffers
					; will begin
	add	ax,STACK_SIZE
	jc	@install_no_mem
	mov	bx,offset histsize	;DOS history buffer
	mov	cx,HISTSIZE_MIN
	call	check_size
	call	check_size		;Application history buffer
	mov	cl,MACROSIZE_MIN	;CH remains zero
	call	check_size		;DOS macro buffer
	call	check_size		;Application macro buffer
	mov	cl,SYMSIZE_MIN
	call	check_size		;DOS symbol buffer
	call	check_size		;Application symbol buffer
IFNDEF NODIRS
	mov	cl,DIRSIZE_MIN
	call	check_size		;Shared directory buffer
ENDIF
;added by jmh
	cmp	ax,MAX_BUFFER_SIZE + offset DGROUP:install_begin
	jae	@install_no_mem

;jmh 011221: allocate high memory
	rol	cmdedit_low,1
	jc	@install_40
	xchg	dx,ax			;DX = last resident offset
	mov	ax,5802h		;Get current UMB state
	int	21h
	push	ax			; and save it
	mov	ax,5800h		;Get current allocation strategy
	int	21h
	push	ax			; and save it
	mov	bx,1			;Link in UMB
	mov	ax,5803h
	int	21h
	mov	bx,40h			;High memory only, first fit
	mov	ax,5801h
	int	21h
	add	dx,15 - 80h		;+15 for paragraph rounding, -80h for
IF i286 				; offset prompt
	shr	dx,4
ELSE
	mov	cl,4
	shr	dx,cl
ENDIF
	mov	bx,dx
	mov	ah,48h			;Allocate the memory
	int	21h
	sbb	cmdedit_low,0		;If not enough memory, it will become -1
	xchg	dx,ax			;Keep new segment in DX
	pop	bx			;Restore allocation strategy
	mov	ax,5801h
	int	21h
	pop	bx			;Restore UMB state
	mov	ax,5803h
	int	21h
	rol	cmdedit_low,1		;Was memory actually allocated?
	jc	@install_40		;Nope
	xchg	ax,dx
	dec	ax
	mov	es,ax			;The MCB of the newly allocated memory
	inc	ax
	mov	word ptr es:[1],ax	;Make the memory block own itself
	mov	si,offset mcb_name	;Change the MCB name
	mov	di,8
	movsw
	movsw
	movsw
	movsw
	push	ds
	pop	es
	sub	ax,8			;Modify the segment so we can use
	mov	cmdedit_seg,ax		; the same offsets

;Enough memory, so keep resident.
@install_40:
	DispStr copyrite
	jmp	near ptr init_over
install endp


;+
; FUNCTION : check_size
;
;	Ensure the buffer is big enough, and add it to the total.
;
; Parameters:
;	AX := current total
;	BX -> address of buffer size
;	CX := minimum value
;
; Returns:
;	size at BX updated to minimum value, if required.
;	AX := updated total
;	BX -> points to next address
;
; Registers destroyed:
;
; Notes:
;	Does not return if segment overflow occurs.
;-
check_size proc near
	cmp	[bx],cx
	jnb	@check_size_10
	mov	[bx],cx
@check_size_10:
	add	ax,[bx]
	jc	@check_size_20
	add	bx,2
	ret

@check_size_20:
	jmp	@install_no_mem
check_size endp



;+
; FUNCTION : process_args
;
;	Processes the argument pointed to by BX. AX contains length
;	of argument. If a previously seen argument is repeated, it is
;	ignored (ie. the old value is kept). Note that the argument is
;	assumed not to contain any delimiters (like space or tab).
;
;	Options start with either a '-' or '/'. Any associated value must
;	follow immediately after without any intervening space/tab.
;
;	   <no option>	Configuration file to be read.
;		/?	Display help screen			(added by jmh)
;		/a	Application options follow		(added by jmh)
;		/b	Disable backslash appending		(added by dfa)
;		/c	Reverse cursor type for insert/overtype (added by dfa)
;		/d	Directory stack size
;		/e	Empty search string moves cursor to end (added by jmh)
;		/f	Disable long filename completion	(added by jmh)
;		/g	Silent mode
;		/h	DOS history buffer size
;		/i	Insert mode default
;		/j	Don't load high                         (added by jmh)
;		/k	Keep buffers across DOS sessions	(added by jmh)
;		/l	Min. command length to store in buffer	(added by dfa)
;		/m	Macro buffer size
;		/n	Install even if already installed	(added by dfa)
;		/o	Don't install                           (added by dfa)
;		/p	Macro ignore character
;		/q	Quiet - no installed or status msgs	(added by jmh)
;		/r	Auto recall mode
;		/s	Symbol buffer size
;		/t	Disable macro and symbol translation	(added by dfa)
;		/u	Uninstall CMDEDIT
;		/w	Alternate warning message display	(added by dfa)
;		/x	Use raw read mode for keystrokes
;		/z	Disable CMDEDIT
;
;	Invalid options result in program abortion. If an option is not
;	followed by a value, the default value for the option is assumed.
;
; jmh 980505: allowed the valueless options to be joined together. ie. "-cig"
;	      is equivalent to "-c -i -g". The valued options can be the last
;	      of a joining, but not the first ("-cp;" good; "-p;c" bad).
;	      I also rewrote the way the options are parsed.
;
;	Note that this is NOT a general purpose getopt type routine.
;
; Parameters:
;	BX	= Pointer to argument
;	AX	= Number of characters in argument
;
; Returns:
;	Nothing.
;	Various globals may be set according to the specified argument.
;
; Register(s) BX (call to aton),CX  are destroyed.
;-
process_args proc near
	@save	si,di
	test	argflags,uninstall_arg or help_arg
	jne	@process_args_5	;If /U or /? specified, no other arg is allowed

	xchg	ax,cx			;CX = character count
	mov	si,bx			;SI = arg buffer address
	lodsb				;AL = first character
	cmp	al,'/'                  ;Should be either '/'
	je	@process_args_9
	cmp	al,'-'                  ; or '-'
	je	@process_args_9

;It's the configuration file
	test	argflags,filearg	;Already seen file argument ?
	je	@process_args_1 	;No, then update
	jmp	@process_args_50	;Yes, then don't update
@process_args_1:
	cmp	cx,80			;Max path length must be < 80
	jb	@process_args_2
	jmp	@read_cmdfile_92	;jmh 980514: file error
@process_args_2:
	or	argflags,filearg	;Remember we've seen this option
					;(jmh moved after length test)
	dec	si			;jmh 980508: the first character again
	mov	di,offset mfilename	;Temp storage for filename
	rep	movsb			;Copy file name
	mov	byte ptr [di],cl	;Terminate with a NULL char
	mov	mfile_handle,ax 	;Indicate that init file specified
	jmp	@process_args_50	;(cl == 0 from rep; ax != 0 from arg)

@process_args_5a:			;added by jmh 980508
	mov	dx,offset option_error2
	jmp	short @process_args_5b
@process_args_5:
; Error processing
	mov	option_let,al		;added by jmh 980506
	mov	dx,offset option_error
@process_args_5b:
	jmp	abort_install

@process_args_9:			;added by jmh
	sub	cx,2			;Delete switch char and option letter
	jb	@process_args_5a	;Error in option specification
					;  if num chars less than 2
	mov	ah,-1			;Use register instead of immediate
					; to set the flags
@process_args_10:			;Saw a valid switch char
	lodsb				;AL = option letter
	cmp	al,'?'                  ;Help option
	jne	@process_args_11	;No
	or	argflags,help_arg	;Indicate /? seen
	jmp	@process_args_50	;Return

@process_args_11:
	cmp	al,'+'
	jne	@process_args_11a
	cmp	cmdedit_res,1
	jne	@process_args_u 	;Not installed - leave at -1
	mov	ah,1			;Explicitly turn on option
	jmp	short @process_args_11b
@process_args_11a:
	cmp	al,'-'
	jne	@process_args_u
	mov	ah,0
	cmp	cmdedit_res,1
	jne	@process_args_u 	;Not installed - leave at 0
	mov	ah,2			;Explicitly turn off option
@process_args_11b:
	dec	cx
	js	@process_args_5 	;No following option letter
	lodsb

@process_args_u:
	or	al,32			;Lowercase (non alphabetic will be
					; ignored anyway)
	cmp	al,'u'			;Uninstall option ?
	jne	@process_args_a 	;No
	or	argflags,uninstall_arg	;Indicate /U seen
	jmp	@process_args_50	;Return

@process_args_a:
	cmp	al,'a'                  ;application options follow
	jne	@process_args_c
	or	argflags,apparg
	jmp	short @process_args_12_b

@process_args_c:
	cmp	al,'c'                  ;swap cursor types option ?
	jne	@process_args_i
	mov	cursor_type,ah		;Swap insert/overtype cursor mode
	jmp	short @process_args_12_a

@process_args_i:
	cmp	al,'i'                  ;insert mode option ?
	jne	@process_args_g
	mov	default_imode,ah	;Change default insert mode
	jmp	short @process_args_12_a

@process_args_g:
	cmp	al,'g'			;silent mode option ?
	jne	@process_args_r
	mov	silent,ah		;Silent mode
	jmp	short @process_args_12_a

@process_args_r:
	cmp	al,'r'			;auto-recall option ?
	jne	@process_args_t
	mov	auto_recall,ah		;Set auto-recall mode

@process_args_12_a:
	or	argflags,option_arg	;Indicate an option specified
@process_args_12_b:
	dec	cx			;Are there more options?
	jns	@process_args_10	;Yep, go process
	jmp	@process_args_50	;Nup, all done

@process_args_t:
	cmp	al,'t'                  ;disable macro and symbol translation
	jne	@process_args_w
	test	argflags,apparg
	jnz	@t_10
	mov	disable_macro,ah	;disable
	jmp	short @process_args_12_a
@t_10:
	not	ah
	mov	appl_macro,ah		;Allow macros in applications
	jmp	short @process_args_12_a

@process_args_w:			;added by dfa
	cmp	al,'w'			;alternate error message display
	jne	@process_args_z
	;mov	msg_flag,ah		;disable
	jmp	short @process_args_12_b ;don't display stats when resident

@process_args_z:
	cmp	al,'z'                  ;disable CMDEDIT
	jne	@process_args_o
	test	argflags,apparg
	jnz	@z_10
	mov	cmdedit_disable,ah
	jmp	short @process_args_12_a
@z_10:
	mov	appl_disable,ah
	jmp	short @process_args_12_a

@process_args_o:			;added by dfa
	cmp	al,'o'			;do not install
	jne	@process_args_n
	or	argflags,not_inst_arg
	jmp	short @process_args_12_a

@process_args_n:			;added by dfa
	cmp	al,'n'                  ;reinstall
	jne	@process_args_b
	or	argflags,no_check_arg
	jmp	short @process_args_12_a

@process_args_b:			;added by dfa
	cmp	al,'b'			;disable backslash appending
	jne	@process_args_x 	;(jmh 010405 - renamed from 'a')
	mov	no_append_slash,ah
	jmp	short @process_args_12_a

@process_args_x:			;added by wd
	cmp	al,'x'			;raw input mode
	jne	@process_args_f
	mov	getkey_funcnum,7	;use raw input for getkey call
	jmp	short @process_args_12_a

@process_args_f:			;added by jmh 990526
	cmp	al,'f'                  ;disable LFN completion
	jne	@process_args_k
	mov	disable_lfn,ah
	jmp	short @process_args_12_a

@process_args_k:			;added by jmh 010403
	cmp	al,'k'                  ;keep buffers across DOS sessions
	jne	@process_args_e
	or	argflags,keep_arg
	jmp	@process_args_12_a

@process_args_e:			;added by jmh 010406
	cmp	al,'e'                  ;move cursor to end when empty search
	jne	@process_args_q
	mov	empty_hist,ah
	jmp	@process_args_12_a

@process_args_q:			;added by jmh 010406
	cmp	al,'q'                  ;Quiet option
	jne	@process_args_j
	or	argflags,quietarg
	jmp	@process_args_12_b

@process_args_j:			;added by jmh 011220
	cmp	al,'j'                  ;Don't load high option
	jne	@process_args_p
	mov	cmdedit_low,ah
	jmp	@process_args_12_b

@process_args_p:
	cmp	al,'p'			;ignore character option
	jne	@process_args_20
	cmp	cx,1			;Should be only one more char
	je	@process_args_18_c
@process_args_5c:
	jmp	@process_args_5
@process_args_18_c:
	lodsb
	or	argflags,ignore_arg+option_arg	;Indicate that ignore char set
	mov	macro_ignore_char,al
	jmp	short @process_args_50	;Return

@process_args_20:
	push	ax			;Save the option letter
	push	cx			;Save the remaining chars (jmh 980513)
	xchg	ax,cx			;AX = num of char
	call	near ptr aton		;All other options are numeric values
;AX contains numeric value
	xchg	ax,cx			;CX = numeric value
	pop	dx			;Restore remaining chars
	pop	ax			;Restore option letter
	jc	@process_args_5c	;Error

@process_args_22:
;next 7 lines added by dfa (jmh reduced to 6)
	cmp	al,'l'			;Is it the minimum length option ?
	jne	@process_args_23
	jcxz	@process_args_5c	;Length has to be > 0 (jmh 980507)
	or	argflags,length_arg + option_arg
	mov	min_length,cx
	jmp	short @process_args_50

@process_args_23:
	cmp	al,'h'                  ;Is it the history size option ?
	jne	@process_args_25
	mov	al,histdos
	mov	bx,offset histsize
	jmp	short @process_args_30

@process_args_25:
	cmp	al,'m'			;Is it the macro buf size option ?
	jne	@process_args_26
	mov	al,macrodos
	mov	bx,offset macrosize
	jmp	short @process_args_30

@process_args_26:
	cmp	al,'s'                  ;Is it the symbol buf size option ?
	jne	@process_args_27
	mov	al,symdos
	mov	bx,offset symsize
	jmp	short @process_args_30

@process_args_27:
IFNDEF NODIRS
	cmp	al,'d'                  ;Is it the dir stack option ?
	je	@process_args_28	;Yes
ENDIF
	jmp	@process_args_5 	;Invalid arg
IFNDEF NODIRS
@process_args_28:
	mov	al,dirbuf
	mov	bx,offset dirsize
	jmp	short @process_args_30a
ENDIF

@process_args_30:
	test	argflags,apparg 	;Are we using buffer options?
	jz	@process_args_30a	;No
	shl	al,1			;Indicate the app version
	add	bx,2			; and point to the app size
@process_args_30a:
	cmp	byte ptr [si],'?'       ;Want size rather than contents?
	jne	@process_args_31
	inc	si			;Skip the question mark
	dec	dx
	or	sizeflags,al
	jmp	short @process_args_35
@process_args_31:
	or	bufflags,al		;Remember we've seen this option
@process_args_35:
	mov	[bx],cx
;jmh 980513: if the size is zero, assume another option.
	jcxz	@process_args_12_c

@process_args_50:
	@restore
	ret

@process_args_12_c:
	mov	cx,dx
	jmp	@process_args_12_b

process_args endp


;+
; FUNCTION : aton
;
;	Returns the positive integer value of an ASCII string. The value
;	must be between 0 and 65535. If length of string is 0, then a 0
;	value is returned with no error flags.
;
; Parameters:
;	SI	:= address of first character in string
;	AX	:= count of characters
;
; Returns:
;	AX = 16 bit result if no errors.
;	CF = 1 if error (magnitude too large, invalid character etc.)
;	     0 if no errors.
; Register(s) CX,DX  are destroyed.
;-
aton	proc	near
	@save	si,di
	xor	di,di		;Result
	xchg	ax,cx		;CX <- num of chars
	jcxz	@aton_99	;No chars, result = 0 (CF is also 0)
;jmh 980513: if the next character is (probably) a letter, assume it's one of
;	     a series and use 0 as the result. (Thus when installing "-hsm"
;	     will use zero-size buffers; when resident it will display them.)
	cmp	byte ptr [si],'?'
	jnc	@aton_99
	cmp	cx,6		;Shouldn't be more'n 5 chars
	cmc			;CF=1 if error
	jb	@aton_99	;Error exit
@aton_10:
	lodsb			;al = char
	sub	al,'0'		;Is ASCII value < '0'
	jb	@aton_99
	mov	dx,9		;Comparison with 9, NOT '9'
	cmp	dl,al
	jb	@aton_99	;Not valid char
	cbw			;ah = 0, since al < 128
	inc	dx		;DX = 10 (multiplier)
	xchg	ax,di		;ax = result so far
	mul	dx		;Multiply by 10
	jb	@aton_99	;Overflow
	add	ax,di		;Add new character
	jb	@aton_99	;Overflow
	xchg	ax,di		;Remember new result in di
	loop	@aton_10	;Go onto next char
@aton_99:
	xchg	ax,di
	@restore
	ret
aton	endp


;+
; FUNCTION : get_dosversion
;
;
; Stores DOS major and minor versions in dos_version_major and
; dos_version_minor.
;-
get_dosversion proc near
	mov	ah,30h
	int	21h
	mov	dos_version_major,al
	mov	dos_version_minor,ah
	ret
get_dosversion endp



;+
; FUNCTION : abort_install
;
;	Called to abort program before installation. Exits to DOS.
;
; Parameters:
;	DX	= Error message to display (jmh 980508)
;
; Returns:
;	Nothing.
;
; Register(s) destroyed:
;	N/A
;-
abort_install proc near
	push	dx			;Save the error message
	@DispStr copyrite		;added by jmh (moved here jmh 980508)
	pop	dx
abort_file label near			;File error already has copyright
	@DispStr			;Display the error
	@Exit	255			;-1 results in AX=4BFFh
abort_install endp



;+
; FUNCTION : locate_cmdedit
;
;	Use int 2fh service 4800h (DOSKey installation check) to determine if
;	CMDEDIT is installed.
;
; Parameters:
;	None.
;
; Returns:
;	AX - segment of CMDEDIT in memory.
;	ZF = 1 if already resident.
;
; Register(s) destroyed:
;	<TBA>
;
; jmh 980622: change the MCB name, just for the fun of it.
;-
locate_cmdedit proc near
	push	es

	mov	ax,cs
	dec	ax
	mov	es,ax			;ES -> Memory control block
	mov	si,offset mcb_name
	mov	di,8
rept 4
	movsw
endm

	mov	ax,4800h
	int	2fh
	cmp	ax,0aaceh		;CMDEDIT's signature
	je	@is_installed
	push	cs
	pop	es
@is_installed:
	mov	ax,es
	pop	es
	ret
locate_cmdedit endp


;+
; FUNCTION : uninstall
;
;	Called to uninstall a previously loaded copy of CMDEDIT.
;	The routine does not return. It exits CMDEDIT.
;
; Parameters:
;	None.
;
; Returns:
;	None.
;
; Register(s) destroyed:
;	<TBA>
;-
uninstall proc near
; Free up the memory occupied by the previous loaded copy of CMDEDIT.
; First give back the all interrupts that were taken over.

; Store the resident segment in DX for comparing.
	mov	dx,cmdedit_seg

; Make sure no one else has taken over the 1b break handler since we
; took it over.
	mov	ax,351bh		;Retrieve current 1b handler
	int	21h
	cmp	bx,offset our_break_handler ;Is the offset ours ?
	je	@uninstall_20		;Yes
@uninstall_10:
; Someone else's taken over the interrupt. Display message and exit.
	mov	dx,offset msg_unload
	jmp	abort_install
@uninstall_20:
	mov	ax,es			;Now compare segment
	cmp	ax,dx			;Is it what we expect?
	jne	@uninstall_10		;No, display error

; Test the int 2fh handler.
	mov	ax,352fh		;Retrieve current 2f handler
	int	21h
	cmp	bx,offset our_mpx_handler ;Is the offset ours ?
	jne	@uninstall_10		;No, error
	mov	ax,es			;Now compare segment
	cmp	ax,dx			;Is it what we expect?
	jne	@uninstall_10		;No, display error

; Now check the int 21h handler.
	mov	ax,3521h		;Retrieve current 21 handler
	int	21h
	cmp	bx,offset cmdedit_isr	;Is the offset ours ?
	jne	@uninstall_10		;No, error
	mov	ax,es			;Now compare segment
	cmp	ax,dx			;Is it what we expect?
	jne	@uninstall_10		;No, display error

; OK, now restore all interrupts
	lds	dx,DWORD PTR es:prev_isr1b    ;DS:DX->original 1b handler
	mov	ax,251bh		      ;Return int 1b
	int	21h

	lds	dx,DWORD PTR es:prev_isr2f    ;DS:DX->original 2f handler
	mov	ax,252fh		      ;Return int 2f
	int	21h

	lds	dx,DWORD PTR es:old_int21vec  ;DS:DX->original int 21h handler
	mov	ax,2521h		      ;Return int 21
	int	21h

	push	cs
	pop	ds			;Restore DS
;
; Finally, return the block of memory taken up by the previous CMDEDIT copy.
	mov	es,cmdedit_seg		;ES<-segment of loaded CMDEDIT

;jmh 011220: if we self-load high, the segment above is not correct - test for
;	     the MCB name before the prompt storage.
	mov	si,offset mcb_name
	mov	di,78h			;offset prompt - 8
	mov	cx,4
	repe	cmpsw
	jne	@uninstall_30		;Didn't match, assume /j was used
	mov	ax,es
	add	ax,8			;Restore proper location of segment
	mov	es,ax

@uninstall_30:
	mov	ah,49h			;Release block function, ES->segment
	int	21h			;Free the block
	jnc	@uninstall_90		;No errors
	mov	dx,offset msg_memerr	;Memory allocation error !
	jmp	abort_install

@uninstall_90:
	DispStr msg_uninstalled 	;added by jmh 980508
	@Exit	0			;Exit to DOS

uninstall endp


;+
; FUNCTION : change_installed_options
;
;	Called to change settings on a previously installed copy of
;	CMDEDIT and display the current settings.
;	The function exits to DOS.
;
;	This function allows the following options to be toggled or
;	changed:
;		/r - toggles autorecall mode
;		/i - toggles insert mode
;		/g - toggles silent mode
;		/p - sets a new ignore character
;following added by dfa:
;	/c - swaps cursor type used for insert & overtype modes.
;	/w - alternate error message display
;	/l - sets the minimum command line size stored in history buffer
;	/t - toggles macro and symbol translation off/on
;	/z - toggles CMDEDIT disable
;jmh added:
;	/x - toggle raw/cooked mode
;   <file> - read configuration file
;	/h - display history
;	/d - display directory stack
;	/s - display symbols
;	/m - display macros
;	/f - toggles long filename completion
;	/e - toggles moving cursor to end on empty searches
;
;jmh 980506: rewrote the method of toggling options.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
;
; Register(s) destroyed:
;	<TBA>
;-

change_installed_options proc near
	;@DispStr msg_dup		;removed by jmh 980508

; jmh 980519: If any of the buffer options were specified with a size, error.
; Can't do it in process_args because the /n option may have been specified;
; if it was done later here, some options may be processed, resulting in
; probably unwanted changes to the resident version.
; jmh 011222: rewrote as a loop.
	mov	al,histdos		;First flag value
	mov	bx,offset histsize	;First size address
IFNDEF NODIRS
	mov	cx,7			;Seven buffers
ELSE
	mov	cx,6			;Six buffers
ENDIF
@test_size:
	test	bufflags,al
	jz	@okay_size		;Buffer wasn't specified
	cmp	word ptr [bx],0
	je	@okay_size		;No size was given
	mov	dx,offset msg_resize
	jmp	abort_install
@okay_size:
	shl	al,1			;Next flag value
	add	bx,2			;Next size address
	loop	@test_size

@change_installed_options_1:
; Change the options specified.
	mov	es,cmdedit_seg		;ES->segment of previously loaded
;					 CMDEDIT

; alternative error message display (jmh 980514: moved to the start)
; jmh 011223: removed altogether.
	;mov	bx,offset msg_flag
	;mov	dx,offset msg_imode - 1 ;Not really necessary to confirm this
	;mov	cx,dx			; switch - just point to a dollar sign
	;call	@toggle_option

;jmh 980508: if no options have been specified, skip ahead
	test	argflags,option_arg
	jnz	@change_installed_options_10
	jmp	@change_installed_options_20

@change_installed_options_10:
	call	near ptr output_newline ;jmh changed from DispCh LF

; First toggle the insert mode option.
	mov	bx,offset default_imode ;Address of option to toggle
	mov	dx,offset msg_omode	;Message when 0
	mov	cx,offset msg_imode	;Message when non-zero
	call	@toggle_option

;Cursor type toggle (added by dfa)
	mov	bx,offset cursor_type
	mov	dx,offset msg_dcursor_type
	mov	cx,offset msg_scursor_type
	call	@toggle_option

;Backslash append toggle (added by dfa & wd)
	mov	bx,offset no_append_slash
	mov	dx,offset msg_enable_slash
	mov	cx,offset msg_disable_slash
	call	@toggle_option

;Empty toggle (added by jmh 010406)
	mov	bx,offset empty_hist
	mov	dx,offset msg_disable_empty
	mov	cx,offset msg_enable_empty
	call	@toggle_option

;Autorecall toggle
	mov	bx,offset auto_recall
	mov	dx,offset msg_disable_autorecall
	mov	cx,offset msg_enable_autorecall
	call	@toggle_option

; Macro translation toggle
	mov	bx,offset disable_macro
	mov	dx,offset msg_enable_macro
	mov	cx,offset msg_disable_macro
	call	@toggle_option

; Application macro translation toggle
	mov	bx,offset appl_macro
	not	byte ptr [bx]		;If specified, it will be 0; -1 required
	cmp	byte ptr [bx],0 	;If explicitly setting it, invert that
	jle	@toggle_app_1
	xor	byte ptr [bx],3 	;1 becomes 2, 2 becomes 1
@toggle_app_1:
	mov	dx,offset msg_enable_appl_macro
	mov	cx,offset msg_disable_appl_macro
	call	@toggle_option

; Long filename completion toggle
	mov	bx,offset disable_lfn
	mov	dx,offset msg_enable_lfn
	mov	cx,offset msg_disable_lfn
	call	@toggle_option

; Silent mode
	mov	bx,offset silent
	mov	dx,offset msg_enable_bell
	mov	cx,offset msg_disable_bell
	call	@toggle_option

; Change the ignore character if one was specified.
	test	argflags,ignore_arg
	jnz	@ignore_option_1		;Specified
	seges					;Keep the current character
; Change to the new macro ignore char
@ignore_option_1:
	mov	al,macro_ignore_char
	mov	es:macro_ignore_char,al
	mov	ignore_char,al			;added by jmh 980506
	DispStr msg_ignore_char

; Minimum line length option. jmh 980507 - always display.
	test	argflags,length_arg
	jnz	@length_option_1		;Specified
	seges					;Retain current value
@length_option_1:
	mov	ax,min_length
	mov	es:min_length,ax		;Change memory resident value
	add	al,'0'                          ;I'll assume length < 10
	mov	msg_length_len,al
	DispStr msg_length_changed

; Toggle raw/cooked mode added by jmh 980506
	mov	bx,offset getkey_funcnum
	mov	dx,offset msg_cooked_mode
	mov	cx,offset msg_raw_mode
	cmp	BYTE PTR [bx],8
	je	@mode_option_1			;no change
	mov	al,15				;15 - 8 = 7; 15 - 7 = 8
	sub	al,es:[bx]
	mov	es:[bx],al
@mode_option_1:
	cmp	BYTE PTR es:[bx],8		;set the flag for the display
	call	@disp_option

; Disable CMDEDIT toggle
	mov	bx,offset cmdedit_disable
	mov	dx,offset msg_enable
	mov	cx,offset msg_disable
	call	@toggle_option

; Disable CMDEDIT (app) toggle
	mov	bx,offset appl_disable
	mov	dx,offset msg_app_enable
	mov	cx,offset msg_app_disable
	call	@toggle_option

; Read in another configuration file
@change_installed_options_20:
	test	argflags,filearg
	je	@change_installed_options_30
	call	read_cmdfile
; Restore the resident copy's source value
	mov	es:source,offset DGROUP:get_kbd_line
	jnc	@readfile_option_1
	jmp	@change_installed_options_90
@readfile_option_1:
	test	argflags,option_arg
	jz	@change_installed_options_30
	DispStr msg_file_read

@change_installed_options_30:
	push	cs
	push	es
	pop	si			;SI -> resident segment
	pop	di			;DI -> executable segment

	mov	al,histdos		;Display the DOS history stack
	mov	bx,offset history
	mov	dx,offset msg_history_buf
	mov	bp,offset strstk_output
	call	display_stack

	mov	al,symdos		;Display the DOS symbol stack
	mov	bx,offset sym_stk
	mov	dx,offset msg_symbol_buf
	mov	bp,offset output_symbols
	call	display_stack

	mov	al,macrodos		;Display the DOS macro stack
	mov	bx,offset mac_stk
	mov	dx,offset msg_macro_buf
	mov	bp,offset output_macros
	call	display_stack

IFNDEF NODIRS
	mov	al,dirbuf		;Display the directory stack
	mov	bx,offset dir_stk
	mov	dx,offset msg_dir_buf
	mov	bp,offset strstk_output
	call	display_stack
ENDIF

	test	bufflags,histapp + macroapp + symapp
	jnz	@change_installed_options_40
	test	sizeflags,histapp + macroapp + symapp
	jz	@change_installed_options_90
@change_installed_options_40:
	mov	dx,offset msg_app_buf		;If no DOS buffers have been
	test	bufflags,msgout 		; displayed, skip the two blank
	jnz	@change_installed_options_41	; lines
	test	sizeflags,msgout
	jnz	@change_installed_options_41
	add	dx,4				;2 * (CR+LF)
@change_installed_options_41:
	@DispStr

	and	bufflags,not msgout
	and	sizeflags,not msgout

	mov	al,histapp		;Display the application history stack
	mov	bx,offset history + TYPE $string_stack
	mov	dx,offset msg_history_buf
	mov	bp,offset strstk_output
	call	display_stack

	mov	al,symapp		;Display the application symbol stack
	mov	bx,offset sym_stk + TYPE $string_stack
	mov	dx,offset msg_symbol_buf
	mov	bp,offset output_symbols
	call	display_stack

	mov	al,macroapp		;Display the application macro stack
	mov	bx,offset mac_stk + TYPE $string_stack
	mov	dx,offset msg_macro_buf
	mov	bp,offset output_macros
	call	display_stack

@change_installed_options_90:
; All done
	@Exit	0

; If the option was specified it will be 1, else 0. xor'ing something with 1
; inverts that something; xor'ing with 0 leaves it unchanged. So if we xor the
; memory resident option with the specified option, it will toggle it.
@toggle_option proc near
	mov	al,[bx] 		;Get the specified option
	cmp	al,0			;Are we explicitly setting it?
	jle	@toggle_option_0	;No
	sub	al,2
	mov	es:[bx],al
	jmp	short @disp_option
@toggle_option_0:
	xor	es:[bx],al		;Toggle memory resident option
@disp_option LABEL near
	jz	@toggle_option_1
	mov	dx,cx
@toggle_option_1:
	DispStr
	ret
@toggle_option endp

change_installed_options endp


;+
; FUNCTION : display_stack
;
;	Display the size and/or contents of a stack (jmh 011222).
;
; Parameters:
;	AL := option flag of stack
;	BX -> stack descriptor
;	DX -> name of stack
;	BP -> function to actually display the stack
;	SI -> resident segment
;	DI -> executable segment
;
; Returns:
;	Nothing.
;
; Registers destroyed:
;	AX,CX,DX
;
; Notes:
;	Only adding a blank line where I want one is terribly complicated.
;-
display_stack proc near
	test	sizeflags,al
	jnz	@display_stack_0
	test	bufflags,al
	jz	@display_stack_99	;Nothing to display
@display_stack_0:
	test	bufflags,msgout 	;Add a blank line after the previous
	jnz	@display_stack_1	; buffer's contents
	test	sizeflags,msgout	;Add a blank line before displaying
	jz	@display_stack_2	; this buffer's contents, if a size
	test	bufflags,al		; was previously displayed (ie. it's
	jz	@display_stack_2	; not the first buffer to display)
@display_stack_1:
	push	ax
	push	dx
	call	output_newline
	pop	dx
	pop	ax
@display_stack_2:
	and	bufflags,not msgout
	and	sizeflags,not msgout

	test	sizeflags,al
	jz	@display_stack_10
	@save	bx,dx,ax		;Display the size of the stack
	@DispStr
	mov	dx,offset msg_size_hist
	pop	ax
	push	ax
	cmp	al,histapp		;History requires "occupies", but
	jbe	@display_stack_5	; everything else wants "occupy"
	mov	dx,offset msg_size
@display_stack_5:
	@DispStr
	call	strstk_used
	call	display_num
	@DispStr msg_bytes
	@restore
	or	sizeflags,msgout
@display_stack_10:
	test	bufflags,al
	jz	@display_stack_99
	test	sizeflags,al		;If the size was displayed, don't
	jnz	@display_stack_20	; comment again
	@DispStr
	call	output_newline
@display_stack_20:
	mov	ds,si			;Point to the resident data
	call	bp			;Display the contents of the stack
	mov	ds,di			;Back to the executable data
	or	bufflags,msgout
@display_stack_99:
	ret
display_stack endp


;+
; FUNCTION : display_str
;
;	Display a string if the quiet flag is not set.
;
; Parameters:
;	DX =	Pointer to string to display
;-
display_str proc near
	test	argflags,quietarg
	jnz	@display_str_99
	@DispStr
@display_str_99:
	ret
display_str endp



;+ FUNCTION : getargs
;
;	getargs does one of two functions depending on the value in AX.
;	If AX = 0, returns count of arguments in the line,
;	else if AX = n, returns the nth argument.
;
;	The argument separators are tab and space. Note that a
;	carriage return (0Dh) terminates a line even if the byte
;	count indicates otherwise.  Arguments containing a SPACE or
;	TAB separator may be specified by enclosing them in a pair of
;	quotes ("). The quotes do NOT act as argument delimiters. For
;	example the following line
;		this"is a single "arg
;	contains exactly one argument. An unmatched quote causes the
;	remaining characters in the line to be treated as a single
;	argument. A quote character can be included as part of an
;	argument by preceding it with a ESCARG character. An ESCARG preceding
;	any other character does not have any special meaning.
;	  Note that all other characters including the NUL char (00h)
;	have no special significance.
;
; Parameters:
;	DS:SI points to the line
;	AX = argument number n
;	CX = Length of line
;	If parameter n != 0,
;	then BX = address of user buffer where the returned argument is to
;		be stored. This param need not be present if n is 0.
;	     DX = length of user buffer.
;
;
; Returns:
;	If parameter n was 0,
;		return argument count in AX (CF is undefined),
;	else
;		Store n'th argument in the buffer pointed to by BX and
;		return the number of chars in the argument in AX.
;		The returned argument has quotes and ESCARGes stripped
;		out where appropriate. If the buffer is too small, CF
;		is set to 1, else it is 0. In this case the user buffer
;		contents are undefined.
;	BX is explicitly unchanged.
;
; Registers CX,DX are destroyed.
;-
getargs	proc	near
ESCARG	EQU	PERCENT
	@save	si,di
userbuf_len EQU <temp1>
	mov	di,ax		;save argument number

	xor	ax,ax		;al will hold char, ah will hold state
	mov	userbuf_len,dx	;Save size of user buffer
	xor	dx,dx		;dx counts arguments
				;CX = line length
	or	cx,cx		;Check if CX is 0 (jump too far for jcxz)
	jne	@getargs_2
	jmp	@getargs_99	;0 length, jump around ajnup
@getargs_2:			;(jmh - what the hell is ajnup?)
;	At the start of this loop, the following hold :
;	(1) CX >= 1. CX holds count of remaining characters.
;	(2) ah holds the current "state" with the following encoding -
;		When Bit 1 is 0, bit 0=0 indicates we're outside an argument
;		and bit0=1 indicates we are inside an arg.
;		When Bit 1 is 1, we are inside a quoted argument. In this
;		case, bit 0 "remembers" the state we were in before the
;		quotes so that it can be restored upon reaching the closing
;		quotes.
;		Bit 2 remembers if prev char was a ESCARG (=1) or not (=0)
;		Bit 3 = 1 indicates this argument is to be copied into the
;			  user buffer

in_arg		equ	01h
in_quote 	equ	02h
saw_ESCARG	equ	04h
	lodsb				;Get next char
	cmp	al,CR			;If carriage-return
	je	@getargs_50		;  then terminate processing.
	call	near ptr isspace	;Check if space or tab
	jne	@getargs_10		;No, jump
;	Process separator
	and	ah,NOT saw_ESCARG	;Remember char is not a ESCARG
	test	ah,in_quote		;Are we inside quotes ?
	jnz	@getargs_49		;If so go onto next char
	and	ah,NOT in_arg		;else reset the inside arg flag
	jmp	short @getargs_49	;and go onto next char


@getargs_10:				;Not a separator
	test	ah,in_arg OR in_quote	;Were we inside an arg or quoted arg ?
	jnz	@getargs_11		;Yes, then skip the increment
					;else entering an arg, so
	inc	dx			;increment arg count
	or	di,di			;If function is return arg count
	je	@getargs_11		;then go on
	cmp	di,dx			;else check if this is the arg we want
	jne	@getargs_11		;Nope, keep on
					;Yep, this be the one
	mov	di,bx			;di = destination buf, ES assumed = DS
	xor	dx,dx			;Zero the character count
	jmp	short @getargs_80	;Go to the copy loop
@getargs_11:
	cmp	al,QUOTE		;Is this a quote ?
	jne	@getargs_15		;No, normal processing
	test	ah,saw_ESCARG		;Found quote, was prev char a ESCARG ?
	jnz	@getargs_15		;Yes, normal processing
	xor	ah,in_quote		;else toggle the quote flag
	jmp	short @getargs_49	;go onto next char
@getargs_15:				;Normal processing
	and	ah,NOT saw_ESCARG	;assume char is not a ESCARG
	cmp	al,ESCARG		;Is this a ESCARG ?
	jnz	@getargs_20		;No
	or	ah,saw_ESCARG		;Set ESCARG flag
@getargs_20:
	test	ah,in_quote		;Are we inside quotes ?
	jnz	@getargs_49		;If so go onto next char
	or	ah,in_arg		;else set the inside arg flag

@getargs_49:
	loop	@getargs_2		;Go onto next char if any

@getargs_50:				;Finished with the line
	or	di,di			;Were we supposed to return an argument?
	je	@getargs_99		;No, so go on
	xor	dx,dx			;Yes, but arg num was > number of args
					;so return a 0 count
	jmp	short @getargs_99	;Skip over copy arg section



;Copy argument loop begins.

@getargs_70:
	lodsb				;Get next char
	cmp	al,CR			;If carriage-return
	je	@getargs_99		;  then terminate processing.

	call	near ptr isspace	;Check if space or tab
	jne	@getargs_80		;No, jump

;	Process separator
	test	ah,in_quote		;Are we inside quotes ?
	jz	@getargs_99		;No, terminate processing
	jmp	short @getargs_85	;Treat like any other char

@getargs_80:				;Not a separator
	;At this point CX = num of bytes remaining in the line including the
	;one in AL.

	cmp	al,QUOTE		;Is this a quote ?
	jne	@getargs_85		;No, normal processing
	test	ah,saw_ESCARG		;Found quote, was prev char a ESCARG ?
	jnz	@getargs_84		;Yes, jump
	xor	ah,in_quote		;else toggle the quote flag
	jmp	short @getargs_89	;go onto next char

@getargs_84:			;Found a \" combination
	dec	di		;Previous \ shouldn't have been written
	dec	dx		;or counted.
				;fall thru for normal processing

@getargs_85:			;Normal processing
	sub	userbuf_len,1	;Decrement space remaining in buffer. Do
;				 NOT use DEC here since CF needs to be set
	jb	@getargs_100	;No more space, exit with CF set
	stosb			;Store the char
	inc	dx		;and incr count

	and	ah,NOT saw_ESCARG ;assume char is not a ESCARG
	cmp	al,ESCARG	;Is this a ESCARG ?
	jnz	@getargs_89	;No
	or	ah,saw_ESCARG	;Set ESCARG flag

@getargs_89:
	loop	@getargs_70	;Go onto next char if any

@getargs_99:
	xchg	ax,dx		;AX<-arg count or num chars in returned arg
	clc			;Clear CF for no error
@getargs_100:
	@restore
	ret
getargs	endp



; jmh 980507: From here till init_buffers is the display code, moved from
;	      the other source files.

;+
; FUNCTION: disp_until_sep
;
;	This function outputs the contents of the specified stack until the
;	separator is seen. Each element of the stack is followed by a
;	CR-LF. The current pointer is left pointing to the separator.
;
; Parameters:
;	BX	- pointer to stack descriptor
;
; jmh 980508: partial rewrite, used tabs between symbol and its name
;-
disp_until_sep proc near
	@save	si,di
	cmp	bx,offset DGROUP:sym_stk
	jb	@disp_until_sep_10
	mov	cs:sym_kluge,1			;We're doing a symbol

@disp_until_sep_10:
; Copy the string into the display buffer and display it.
;jmh 980508: use the line buffer since it's common in resident and exe.
	mov	ax,offset linebuf
	mov	cx,LINEBUF_SIZE
	call	near ptr strstk_copy
	xchg	cx,ax				;CX<-length of string
	push	bx				;Save BX
	push	cx				;Save CX
	mov	dx,offset linebuf		;DX->string, CX is count
	call	near ptr output_counted_string
	pop	cx
	pop	bx
	cmp	cs:sym_kluge,0
	je	@disp_until_sep_30
	cmp	cl,10				;If the name is 10 or greater
	jb	@disp_until_sep_15		; just use one space (980514)
	@DispCh SPACE
	jmp	short @disp_until_sep_25
@disp_until_sep_15:
	cmp	cl,2				;If the name is less than two
	mov	cl,1				; characters, use two tabs,
	ja	@disp_until_sep_20		; otherwise just one.
	inc	cx
@disp_until_sep_20:
	push	bx
	mov	dx,offset tabs
	push	ds				;The tabs string is in the code
	push	cs				; segment, NOT the resident.
	pop	ds
	call	near ptr output_counted_string	;If DispCh is used with redir-
	pop	ds				; ection, tabs are expanded to
	pop	bx				; eight spaces.
@disp_until_sep_25:
	mov	cs:sym_kluge,0
	jmp	short @disp_until_sep_32
@disp_until_sep_30:
	call	near ptr output_newline
@disp_until_sep_32:
	xor	cx,cx
	call	near ptr strstk_fwd_match
	call	near ptr check_separator	;Have we found the separator?
	jne	@disp_until_sep_10		;No, so loop back

@disp_until_sep_99:
	@restore
	ret
disp_until_sep endp



;+
; FUNCTION: output_symbols,output_macros
;
;	Outputs to standard output the current buffer contents of
;	the symbol/macro buffer.
;
; Parameters:
;	BX -> stack descriptor
;-
output_macsym proc near
output_symbols LABEL near
	mov	ax,offset DGROUP:defs
	jmp	short @output_macsym_5
output_macros LABEL near
	mov	ax,offset DGROUP:defm
@output_macsym_5:
	mov	temp2,0 			;Used to indicate first macro
	@save	si,di
	mov	di,ax				;DI->'defs' or 'defm'
	call	near ptr strstk_save_cur	;Save current pointer.
;						 Probably not necessary for
;						 symbols, but do anyway
	call	near ptr strstk_setbot		;Go to bottom of stack

@output_macsym_10:
; Start of loop. One entire macro or symbol has been processed so far.
; The current stack pointer is at the separator. First skip over the separator.
	xor	cx,cx
	call	near ptr strstk_fwd_match
; Try to move one more to make sure there is another string
	xor	cx,cx
	call	near ptr strstk_fwd_match
	jc	@output_macsym_99		;End of buffer
; OK move back to get the name of the symbol / macro.
	xor	cx,cx
	call	near ptr strstk_bck_match
; Separate each macro with a newline
	cmp	temp2,0
	je	@output_macsym_20
	call	near ptr output_newline
@output_macsym_20:
; Now output either 'defs' or 'defm'
	mov	dx,di
	inc	dx				;Point to string
	xor	cx,cx
	mov	cl,[di]				;Length of string
	push	bx				;Save BX
	call	near ptr output_counted_string	;Show command string
	@DispCh	' '				;followed by a space
	pop	bx
;	Now display all strings until a separator is seen
	call	near ptr disp_until_sep

; Now if we are showing a macro, output the 'endm'
	cmp	di,offset DGROUP:defm		;Macro ?
	jne	@output_macsym_70		;No, repeat loop
;	Output the 'endm'
	push	bx
	mov	bx,offset DGROUP:endm_cmd
	xor	cx,cx
	mov	cl,[bx]
	mov	dx,bx
	inc	dx
	call	near ptr output_counted_string	;AX,BX,CX,DX destroyed
	pop	bx
	call	near ptr output_newline
	mov	temp2,1

@output_macsym_70:
	jmp	short @output_macsym_10

@output_macsym_99:
	call	near ptr strstk_restore_cur	;Restore the current pointer
	@restore
	ret
output_macsym endp


;+
; FUNCTION : output_counted_string
;
; Parameters :
;	CX - Number of bytes to display
;	DX - address of string
; Registers destroyed:
;	AX,BX,CX,DX
output_counted_string proc near
	mov	ah,40h
	mov	bx,1				;stdout handle
	int	21h				;Params ax,bx,cx,dx
	ret
output_counted_string endp


;+
; FUNCTION : strstk_output by wd
;
;	Output the entire stack, one item per line.
;
; Parameters:
;	BX  := address of buffer descriptor
;
;Returns:
;	Nothing.
;
; jmh 980508: slightly modified due to relocation
; jmh 990523: display directories using long names.
;-
strstk_output proc near
	@save	si,di

	call	near ptr strstk_save_cur	;Save current pointer.
;						 Probably not necessary for
;						 symbols, but do anyway
	call	near ptr strstk_setbot		;Go to bottom of stack

@strstk_output_10:
; Copy the string into the display buffer and display it.
;jmh 980508: use the line buffer since it's common in resident and exe.
	mov	ax,offset linebuf
	mov	cx,LINEBUF_SIZE
	call	near ptr strstk_copy
	test	ax,ax
	jz	@strstk_output_20
	mov	cx,ax
IFNDEF NODIRS
	cmp	bx,offset DGROUP:dir_stk
	jne	@strstk_output_15
	mov	ax,7160h		;LFN canonicalize path
	mov	cx,8002h		;Use SUBST'ed drive, full long name
	mov	si,offset linebuf
	mov	di,si			;Overwrite short name with long
	int	21h
	call	near ptr strlen
@strstk_output_15:
ENDIF
	push	bx
	mov	dx,offset linebuf		;DX->string, CX is count
	call	near ptr output_counted_string
	call	near ptr output_newline
	pop	bx

@strstk_output_20:
	xor	cx,cx
	call	near ptr strstk_fwd_match
	jnc	@strstk_output_10

@strstk_output_99:
	call	near ptr strstk_restore_cur ;Restore the current pointer

	@restore
	ret
strstk_output endp


;+
; FUNCTION : display_num
;
;	Display AX as a decimal string.
;
; Parameters:
;	AX := number to display (unsigned)
;
; Returns:
;	Nothing (output number).
;-
display_num proc near
	mov	bx,10			;Divisor (base 10)
	xor	cx,cx			;Count of digits
@display_num_10:
	cwd				;Divide uses DX:AX, assume AX < 32768
	div	bx
	add	dl,'0'                  ;Remainder as a character
	push	dx			;Store it on the stack
	inc	cx
	or	ax,ax			;Nothing left?
	jnz	@display_num_10
@display_num_20:
	pop	dx			;Display most-significant first
	@DispCh
	loop	@display_num_20
	ret
display_num endp


; Let's assume the user will never allocate more than 4K for the buffers. This
; allows us to free quite a few bytes in memory, but at the expense of bloating
; the executable (but with cluster sizes as they are, it hardly matters).
	IF	($ - install_begin) LT MAX_BUFFER_SIZE
	db	MAX_BUFFER_SIZE - ($ - install_begin) dup (?)
	ENDIF


; The following functions are not resident, but are used by the resident code.
; They are placed here so they don't get overwritten by the buffers. jmh 980509

;+
; FUNCTION : init_buffers
;
;	Initialise all the buffers (jmh 011222).
;
; Parameters:
;	None.
;
; Returns:
;	SI -> end of resident memory
;
; Registers destroyed:
;	All.
;-
init_buffers proc near
	xor	cx,cx			;Indicate DOS mode
	call	set_type
	mov	si,offset DGROUP:install_begin
					;First location in installation code
	mov	bx,si			; is where the buffers start.
	mov	ax,histsize		;Size of DOS history buffer
	add	si,ax			;SI <- end of DOS buffer
	call	near ptr hist_init	;Initialize DOS history buffer
	mov	bx,si			;Repeat for the macros
	mov	ax,macrosize
	add	si,ax
	call	near ptr macro_init
	mov	bx,si			;The symbols
	mov	ax,symsize
	add	si,ax
	call	near ptr symbol_init

	mov	cl,1			;Indicate application mode
	call	near ptr set_type
	mov	ax,histsize+2		;Determine where the app. history goes
	rol	cmdedit_low,1		;Are we staying low?
	jnc	@init_over_4		;No
	cmp	ax,64
	jbe	@init_over_5		;Small enough to fit in the PSP
@init_over_4:
	mov	bx,si			;(Don't bother modifying instance data)
	add	si,ax
	jmp	short @init_over_6
@init_over_5:
	mov	bx,40h			;Application history buffer (jmh)
	mov	ax,bx			;Isn't that convenient
@init_over_6:
	call	near ptr hist_init
	mov	bx,si			;Repeat for the macros
	mov	ax,macrosize+2
	add	si,ax
	call	near ptr macro_init
	mov	bx,si			;The symbols
	mov	ax,symsize+2
	add	si,ax
	call	near ptr symbol_init

IFNDEF NODIRS
	mov	bx,si			;Finally the directory stack
	mov	ax,dirsize
	add	si,ax
	call	near ptr dirs_init
ENDIF
	ret
init_buffers endp


;+
; FUNCTION : hist_init
;
; Initializes the various data structures associated with the
; command history buffers. CALLER MUST ENSURE PASSED ADDRESSES ARE VALID
; AND BUFFER IS LARGE ENOUGH.
;
; Parameters:
;	AX 	- length of buffer in bytes
;	BX	- address of buffer
; 
; Returns:
;	Nothing.
;
; Registers destroyed:
;	BX,CX
;-
hist_init proc	near
	push	bx
	mov	bx,hist_ptr
	xchg	ax,cx			;CX = buffer size
	pop	ax			;AX = Buffer address
					;BX points to appropriate descriptor
	call	near ptr strstk_init	;Initialize buffer and descriptor
	xor	al,al			;Push a zero length string onto stack
	call	near ptr strstk_push	;Assumes no errors
	ret
hist_init	endp


;+
; FUNCTION : macro_init,symbol_init
;
; Initializes the various data structures associated with the
; macro /symbol buffer. CALLER MUST ENSURE PASSED ADDRESSES ARE VALID
; AND BUFFER IS LARGE ENOUGH.
;
; Parameters:
;	AX 	- length of buffer in bytes
;	BX	- address of buffer
;
; Returns:
;	Nothing.
; Registers destroyed :
;	BX
;-
macro_init proc	near
	push	bx
	mov	bx,mac_ptr		;bx := address of buffer descriptors
	jmp	short @macsym_init
symbol_init LABEL near
	push	bx
	mov	bx,sym_ptr		;bx := address of buffer descriptors
@macsym_init:
	xchg	ax,cx			;CX = buffer size
	pop	ax			;AX = Buffer address
					;BX points to appropriate descriptor
	call	near ptr strstk_init	;Initialize buffer and descriptor

;Store a separator into the macro buffer
	call	near ptr separate

	ret
macro_init	endp


;+
; FUNCTION : dirs_init
;
; Initializes the various data structures associated with the
; directory stack buffer. CALLER MUST ENSURE PASSED ADDRESSES ARE VALID
; AND BUFFER IS LARGE ENOUGH.
;
; Parameters:
;	AX 	- length of buffer in bytes
;	BX	- address of buffer
;
; Returns:
;	AX =	0 if no errors
;		-1 otherwise
; Registers destroyed : BX
;-
IFNDEF NODIRS
dirs_init proc	near
	push	bx
	mov	bx,offset DGROUP:dir_stk ;bx := address of buffer descriptors
	xchg	ax,cx			;CX = buffer size
	pop	ax			;AX = Buffer address
					;BX points to appropriate descriptor
	call	near ptr strstk_init	;Initialize buffer and descriptor
	ret
dirs_init	endp
ENDIF


;+
; FUNCTION : strstk_init
;
; see strstack.asm
;-
strstk_init proc near

	cmp	cx,2		;Need at least 2 bytes in buffer (sentinel)
	jb	@strstk_init_90	;
	cmp	cx,32767	; and LESS than 32767 (Need to represent
	jnc	@strstk_init_90 ; + and - differences between first location
				; and first location beyond buffer in 16 bits)
	mov	[bx].low_end,ax	;starting address
	add	cx,ax		;Location after last addressable byte of buffer
				;Various functions assume byte beyond last
				;buffer location is addressable without
				;segment wraparound.
	jc	@strstk_init_90	;Segment wraparound.
	dec	cx		;CX->Last byte in buffer
	mov	[bx].high_end,cx
	call	near ptr strstk_reset
	xor	ax,ax		;success
;	jmp	short @strstk_init_99
	ret
@strstk_init_90:		 ;Buffer too small or 
	mov	ax,-1		 ; overflow (add ax,cx instruction)
@strstk_init_99:
	ret

strstk_init endp


;+
; FUNCTION : strstk_used
;
;	Returns the used size of the stack.
;
; Parameters:
;	ES:BX := address of descriptor
;
; Returns:
;	AX = used bytes
;-
strstk_used proc near
	mov	ax,es:[bx].top
	sub	ax,es:[bx].low_end
	inc	ax
	ret
strstk_used endp


;+
; FUNCTION : install_vectors
;
;	Installs all our interrupt vectors.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
;
; Notes:
;	ES is saved before entering this routine (when the env. is freed)
;-
install_vectors proc near
;Install our Ctrl-Break handler.
	push	ds
	mov	ds,cmdedit_seg

	mov	ax,351bh
	int	21h			;Get current handler address
	mov	cs:prev_isr1b,bx	;Offset of previous handler
	mov	cs:prev_isr1b+2,es	;Segment of previous handler
	mov	dx,offset our_break_handler
	mov	ax,251bh
	int	21h
;
;Install our switcher multiplex handler.
	mov	ax,352fh
	int	21h
	mov	cs:prev_isr2f,bx
	mov	cs:prev_isr2f+2,es
	mov	dx,offset our_mpx_handler
	mov	ax,252fh
	int	21h

	mov	ax,3521h		;Get old interrupt vector
	int	21h
	mov	cs:old_int21vec,bx	;Remember offset
	mov	cs:old_int21vec+2,es	;Remember segment

	mov	dx,offset DGROUP:cmdedit_isr ;Our handler
					;DS = CS already
	mov	ax,2521h		;Set intr vector
	int	21h

	pop	ds
	ret
install_vectors endp


;+
; FUNCTION : init_sis
;
;	Initialises the switcher instance data.
;
; Parameters:
;	SI	= end of buffers (start of stack)
;	DX	= end of stack (length of resident code)
;
; Returns:
;	None.
;
; Register(s) destroyed:
;	AX,BX,DI
;-
init_sis proc near
	mov	ax,cmdedit_seg
	mov	sisInstData+2,ax
	mov	DataBlockPtr1+2,ax	;Copy all the segment addresses
	mov	DataBlockPtr2+2,ax
	mov	DataBlockPtr3+2,ax
	mov	DataBlockPtr4+2,ax
	test	argflags,keep_arg
	je	@init_over_20
	mov	byte ptr DataBlockPtr1,80h	;Skip application history
	sub	DataBlockSize1,40h		; and it's size
	xor	ax,ax
	mov	DataBlockPtr5,ax 	;Terminate list early
	mov	DataBlockSize5,ax
	mov	di,offset DataBlockPtr4
	mov	bx,si			;Start of the stack
	jmp	short @init_over_30
@init_over_20:
	mov	DataBlockPtr5+2,ax
IFNDEF NODIRS
	mov	DataBlockPtr6+2,ax
ENDIF
	mov	StackBlockPtr+2,ax
	mov	bx,offset DGROUP:install_begin
	mov	di,offset StackBlockPtr
@init_over_30:
	mov	[di],bx
	mov	ax,dx
	sub	ax,bx			;Length of all the stacks
	mov	[di+4],ax
	ret
init_sis endp



;+
; FUNCTION : read_cmdfile
;
;	Reads commands from a file. The filename is in the variable
;	mfilename.
;
;	jmh: As I added the history recording capability, I also changed the
;	     execute_defm function. So now if the macro table should fill, the
;	     history will contain the commands that didn't fit. This can be
;	     seen as a bug or a feature, depending on your point of view.
;
; Parameters:
;	None.
;
; Returns:
;	CF =	0 on success, any other value if failure
;
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
read_cmdfile proc near
	@save	si,di
	cmp	mfile_handle,0
	je	@read_cmdfile_100	;No file specified

	@OpenFil mfilename,0
	jc	@read_cmdfile_92	;CF=1 for errors
@read_cmdfile_30:
	mov	mfile_handle,ax 	;Save file handle

	push	es			;added by jmh - set DS to the RESIDENT
	pop	ds			; memory (can't do it earlier because
					; of the filename)
	mov	source,offset DGROUP:get_file_line
;					We want get_next_line to read
;					from the file.

	xor	cx,cx			;Set the DOS buffers
	call	set_type

@read_cmdfile_50:
	mov	dx,offset DGROUP:linebuf ;Destination for file line
	mov	ax,LINEBUF_SIZE
	call	near ptr freadline	;Get next line into buffer
					;AX contains line length
	jnc	@read_cmdfile_80	;no error or EOF
	or	ax,ax			;No more bytes ?
	jz	@read_cmdfile_99	;EOF is not error
	jmp	short @read_cmdfile_90	;Error, abort install
@read_cmdfile_80:
	mov	dx,offset DGROUP:linebuf
	add	dx,ax
	mov	lastchar,dx		;Update end of line
	call	near ptr blankline	;jmh 980505 - test blank lines first
	jnc	@read_cmdfile_50	;Ignore blank lines

	call	near ptr cmdedit_cmd	;Execute as a command
	jnc	@read_cmdfile_50

;	If not blank line and not CMDEDIT command, then assume a DOS command
;	and place it in the history (added by jmh 980506)
	call	remember
	jmp	short @read_cmdfile_50

@read_cmdfile_90:
; Come here for error
	push	cs			;Restore DS to the executable's memory
	pop	ds
	@ClosFil mfile_handle		;Close the file
@read_cmdfile_92:
	mov	dx,offset file_error
	jmp	abort_file		;Exit program

@read_cmdfile_99:
	push	cs			;Restore DS to the executable's memory
	pop	ds
	@ClosFil mfile_handle		;Close the file
	jc	@read_cmdfile_92	;Abort if error closing file
@read_cmdfile_100:
	@restore
	ret
read_cmdfile endp



;+
; FUNCTION : get_file_line
;
;	Called indirectly through the global variable 'source'.
;	Currently this routine exists only during installation and must
;	NOT be called once the program is a TSR. The next line from the
;	file is copied to linebuf. If there is no next line, the
;	installation is aborted.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
get_file_line proc near
	mov	dx,offset DGROUP:linebuf
	mov	ax,LINEBUF_SIZE
	call	near ptr freadline
	jnc	@get_file_line_99
;Error reading file or EOF
	push	cs			;jmh 980508: restore DS for the
	pop	ds			; correct message segment
	mov	dx,offset file_error
	jmp	abort_install
@get_file_line_99:
	mov	dx,offset DGROUP:linebuf
	add	dx,ax
	mov	lastchar,dx		;Update end of line
	ret
get_file_line	endp



;+
; FUNCTION : freadline
;
;	Reads a line at a time from the file whose handle is in
;	mfile_handle into the buffer pointed to by AX. If the buffer is
;	too small or if there are any errors, the routine returns with CF set.
;	If EOF, then AX = 0 and CF is set.
;
; Parameters:
;	DX	= address of buffer
; 	AX	= size of buffer
;
; Returns:
;	CF 	= 0 if no errors (and not EOF), else 1.
;	AX	= num chars in line if CF = 0.
;		  0 if EOF, ffff for other errors if CF = 1
;
; Register(s) destroyed:
;-
freadline proc	near
	@save	si,di
	mov	di,dx			;DI->buffer
	mov	bx,cs:mfile_handle
	mov	si,ax			;SI<-num bytes to read
	xchg	cx,ax			;CX<-num bytes to read
	mov	ah,3Fh			;File read function
	int	21h
	jc	@freadline_99		;Error!
	mov	cx,ax			;CX<-num bytes read
	jcxz	@freadline_99_a		;EOF (note AX is 0 indicating EOF)
	xchg	dx,ax			;DX<-num bytes read
	mov	bx,di			;BX->start of buffer
	mov	al,CR
	repne	scasb			;Hunt for CR
	je	@freadline_50		;Found
;	No CR found, this better be the last line in file.
	cmp	dx,si			;Were fewer bytes read than requested?
	cmc
	jc	@freadline_99		;Error
	push	dx			;Save length of line
	xor	ax,ax			;AX<-num extra bytes read
	jmp	short @freadline_60
@freadline_50:
	stc				;Assume line too long
	jcxz	@freadline_99		;error if match in last char
;					 (line too long)
	cmp	BYTE PTR [di],LF	;Next char must be linefeed
	stc				;Assume error
	jne	@freadline_99		;Indeed an error if not LF
	mov	ax,di
	sub	ax,bx			;AX<-num chars including CR
	sub	dx,ax
	xchg	ax,dx
	dec	dx			;DX<-num chars in line
	dec	ax			;AX<-num extra chars read
	push	dx			;Save num chars in line

@freadline_60:
; Top of stack contains num bytes in line.
; Now position file pointer to 'unread' characters.
; AX contains the num of extra characters read.
	neg	ax
	cwd
	mov	cx,ax
	xchg	cx,dx			;CX:DX<-num bytes to 'unread'
	mov	bx,cs:mfile_handle
	mov	ax,4201h		;Move file ptr relative
	int	21h			;Seek file relative
; CF set/reset by error status
	pop	ax			;AX<-num bytes in line
	clc				;No errors
	jmp	short @freadline_100
@freadline_99:
	mov	ax,0ffffh		;Non-EOF error
@freadline_99_a:
	stc				;Indicate error or EOF
@freadline_100:
	@restore
	ret
freadline endp



;+
; FUNCTION : blankline
;
;	Checks whether the line in linebuf is blank or not. Also treats it
;	as blank line if it begins with a `-'. jmh 980505 - test ^Z, too.
;	jmh 010405: If the comment is "-@", switch to application buffers.
;
; Parameters:
;	None.
;
; Returns:
;	CF	= 0 if blank line, else 1.
;
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
blankline proc	near
	@save	si
	call	near ptr get_line_len
	cmp	byte ptr [si],'-'		;Comment char ?
	jne	@blankline_5
	cmp	byte ptr [si+1],'@'             ;App. buffers selected ?
	jne	@blankline_10
	;mov	cx,1				;Set application buffers
	call	set_type			;(CX is !0 - line isn't blank)
@blankline_10:
	clc
	jmp	short @blankline_99
@blankline_5:
	cmp	byte ptr [si],CTL_Z		;EOF char ?
	je	@blankline_10
@blankline_20:
	call	near ptr skip_whitespace	;CF=1 if end of string
	cmc
@blankline_99:
	@restore
	ret
blankline endp


CSEG	ENDS

	END
