; EDIT.ASM
; (c) 1989, 1990 Ashok P. Nadkarni
;
; This module contains the line editing functions of CMDEDIT.
; Macro disable toggle added by dfa,  December 1990.
; Ability to switch ESC to filename completion added by dfa, May 1991.
; Twiddle key and filename display added by wd.
; Disable backslash appending to directory name added by dfa,  June 1992.
; Tab completion cycling added by wd and dfa,  June 1992.
; jmh, March, 1997:
;	Invalid filename characters added to file completion
;	Multiple commands on the one line
;	FEXEC and FIGNORE added to file completion
;	Changed the key mapping
; jmh, 15 December, 1997: tested for Win95 close command.
; jmh,	5 May, 1998:	  made TAB cycle by default.
; jmh, 13 May, 1999:	  added long filename (LFN) completion.
; jmh, 26 May, 1999:	  added option to disable LFN completion, using
;			   ^\ as the default.
; jmh, 2 April, 2001:	  don't add closing quote for completed directories.
; jmh, 6 April, 2001:	  modified order of expansion, removed recursion.
; jmh, 9 November, 2001:  fixed a slight bug with filename completion (dotstar);
;			  renamed "full word" to "string".
; jmh, 18 December, 2001: set application macro state independent of DOS.
; jmh, 21 March, 2003:	  fixed quote bug with empty filename completion string.

	PUBLIC	get_kbd_line
	PUBLIC	auto_recall
	PUBLIC	expand_fnkey
	PUBLIC	no_append_slash		;Added by dfa & wd
	PUBLIC	getkey_funcnum		;Added by wd
	PUBLIC	disable_lfn		;Added by jmh 990526
	PUBLIC	empty_hist		;Added by jmh 010406

	PUBLIC	Edit_instance_offset	;Added by jmh
	PUBLIC	Edit_instance_size

	INCLUDE common.inc
	INCLUDE buffers.inc		;Added by wd
	INCLUDE ascii.inc
	INCLUDE bios.inc
	INCLUDE dos.inc
	INCLUDE general.inc


CSEG	SEGMENT	BYTE PUBLIC 'CODE'
CSEG	ENDS

DGROUP	GROUP	CSEG

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

CSEG	SEGMENT	BYTE PUBLIC 'CODE'

	EXTRN	linebuf:BYTE
	EXTRN	lastchar:WORD
	EXTRN	caller_cursor:WORD
	EXTRN	dot:WORD
	EXTRN	edit_mode:BYTE
	EXTRN	omode_cursor:WORD
	EXTRN	disable_macro:BYTE	;added by dfa
	EXTRN	in_appl:BYTE		;added by jmh 011218
	EXTRN	appl_macro:BYTE 	;added by jmh 011218
	EXTRN	initial_curpos:WORD	;added by wd (was row jmh 980630)
	EXTRN	sym_stk:WORD
	EXTRN	linelimit:WORD		;changed from LINEBUF_END jmh 980512
	EXTRN	temp1:BYTE		;added by jmh 980512
	EXTRN	screen_width:BYTE	;ditto
	EXTRN	invalid_list:BYTE	;ditto
	EXTRN	invalid_len:ABS 	;ditto
	EXTRN	get_dosenv:PROC 	;added by jmh
	EXTRN	match_ext:PROC		;ditto 980513
	EXTRN	macro_level:BYTE	;added by jmh 980519
	EXTRN	cur_macro:BYTE

	EXTRN	disp_line:PROC
	;EXTRN	hist_back:PROC
	;EXTRN	hist_fwd:PROC
	EXTRN	hist_bck_match:PROC
	EXTRN	hist_fwd_match:PROC
	EXTRN	remember:PROC
	EXTRN	execute_auto_recall:PROC
	EXTRN	insert_at_dot:PROC
	EXTRN	erase_to_dot:PROC
	EXTRN	isalphnum:PROC
	EXTRN	set_disp_marks:PROC
	EXTRN	bell:PROC
	EXTRN	line_to_scr:PROC
	EXTRN	isspace:PROC
	EXTRN	xlate_lower:PROC
	EXTRN	associate:PROC		;added by jmh 980513
	EXTRN	expand_braces:PROC	;added by jmh 980430
	EXTRN	expand_var:PROC
	EXTRN	expand_macro:PROC	;added by jmh 980518
	EXTRN	expand_symbol:PROC	;ditto
	EXTRN	ismacsym:PROC
	EXTRN	get_symbol:PROC
	EXTRN	tolower:PROC		;added by wd
	EXTRN	disp_prompt:PROC	;added by wd
	EXTRN	get_curpos:PROC 	;added by wd
	EXTRN	output_newline:PROC	;added by wd
	EXTRN	strlen:PROC		;added by jmh 980623
	EXTRN	strsilen:PROC		;added by jmh 990517
	EXTRN	get_line_len:PROC	;added by jmh 981106
	EXTRN	stre_cmp:PROC		;added by jmh 990515
	EXTRN	remove_chars:PROC	;added by jmh 990517
	EXTRN	insert_chars:PROC	;added by jmh 990517
	

Edit_instance_offset = $

; These 7 lines added by wd
; jmh 990513: made the DTA permanent.
ffblk		LABEL	byte
ffblk_rsvd	db	21 dup (?)
ffblk_attr	db	?
ffblk_time	dw	?
ffblk_date	dw	?
ffblk_size	dd	?
ffblk_name	db	13 dup (?)
		db	0		;Extra for quote

ffhandle	dw	0		;Handle for LFN searching

CompleteFlags	label	word
CompleteFlag	db	0		;bit 1 == 1 if cmd is either complete
					;bit 0 == 1 if last cmd was ''
CycleFlag	db	0		;bit 2 == 1 if cmd is cycle_complete
					;bit 1 == 1 if starting cycle
					;bit 0 == 1 if continuing cycle
ExtFlags	db	0		;bit 0 == 1 if want only exes/dirs (jmh)
					;bit 1 == 1 to ignore extensions
ExtList 	dd	?		;Pointer to the list of extensions
fnameptr	dw	0		;fname location in linebuf	
pathptr 	dw	0		;Pathname location in linebuf
bnameptr	dw	0		;fname location in pattern
bnamelen	dw	0		;Length of above, plus one
dotstar 	dw	0		;Position in pattern to add ".*"
QuoteFlag	db	0		;bit 0 == 1 if fname starts with a quote
					;bit 6 == 1 if a quote was added

auto_recall	db	0		;By default no auto recall
auto_recall_on	db	?		;1 if auto-recall in effect,
;					 else 0
continue_recall db	0		;auto-recall state variable
backslash_char	db	'\'             ;added by dfa & wd
no_append_slash db	0		;added by jmh 990515
disable_lfn	db	0		;added by jmh 990526
empty_hist	db	0		;added by jmh 010406
empty_flag	db	0		;same as CompleteFlag

Edit_instance_size = $ - offset Edit_instance_offset

fnkey_tab	db	'S','F',0	;Used to look for function key
;					 definitions. 
fnkey_exec_char db	'@'		;If the last character a fn key
;					 definition is this, the key is
;					 executed immediately
getkey_funcnum	db	8		;what function to use for getkey

exevar		db	5,0,'FEXEC'     ;File completion extension lists
exelist 	db	'exe.com.bat',0
ignvar		db	7,0,'FIGNORE'
ignlist 	db	'exe.com.obj.o.bak',0

keymap	STRUC
key	dw	?
func	db	?
keymap	ENDS

; Must be IDENTICAL to the string in CMDCFG.C including the
; terminating '\0' byte. This must be followed immediately by ctrl_key_table.
; jmh 990520: moved ^Backspace to the ctrl_key_table, testing it separately.
;	      This was done so characters could be stored directly, without
;	      searching the function keys.
cmdedit_keymap_id db	'CMDEDIT key map',0

ctrl_key_table:
	db	 0		;^@			ignore
	db	 9		;^A			bol
	db	 3		;^B			char_left
	db	28		;^C			cooked_char
	db	18		;^D			del_right
	db	10		;^E			eol
	db	 4		;^F			char_right
	db	25		;^G			abort_and_store
	db	17		;^H (Backspace) 	del_left
	db	15		;^I (Tab)		cycle_complete
	db	31		;^J (^Enter)		vars
	db	22		;^K			del_eol
	db	19		;^L			del_prev_word
	db	32		;^M (Enter)		done_editing
	db	12		;^N			next_line
	db	23		;^O			del_eol_exec
	db	11		;^P			prev_line
	db	 1		;^Q			quote
	db	13		;^R			search_back
	db	26		;^S			com_sep
	db	27		;^T			twiddle
	db	11		;^U			prev_line
	db	14		;^V			search_forw
	db	20		;^W			del_next_word
	db	21		;^X			del_bol
	db	29		;^Y			autorecall
	db	 2		;^Z			default_action
	db	24		;^[ (Esc)		erase_line
	db	35		;^\			lfn
	db	26		;^]			com_sep
	db	33		;^^			done_wipeout
	db	30		;^_			macro
	db	19		;^Backspace		del_prev_word

cmd_key_table:
	keymap	<271, 16>	;SHIFTTAB - added by jmh
	keymap	<327,  9>	;HOME
	keymap	<328, 11>	;UP
	keymap	<331,  3>	;LEFT
	keymap	<333,  4>	;RIGHT
	keymap	<335, 10>	;END
	keymap	<336, 12>	;DOWN
	keymap	<338, 34>	;INS
	keymap	<339, 18>	;KDEL
	keymap	<371,  5>	;CTLLEFT
	keymap	<372,  6>	;CTLRIGHT
	keymap	<373, 22>	;CTLEND
	keymap	<375, 21>	;CTLHOME
	keymap	<411,  7>	;ALTLEFT  - added by jmh
	keymap	<413,  8>	;ALTRIGHT - ditto

;number of command keys
NUM_CMD_KEYS	EQU	($ - cmd_key_table) / TYPE keymap


;The list of available functions, with default key assignments and func. number
cmd_key_funcs:
	dw	@ignore 		;^@		 0
	dw	@quote			;^Q		 1
	dw	@default_action 	;^Z		 2
	dw	char_left		;^B, Left	 3
	dw	char_right		;^F, Right	 4
	dw	word_left		;^Left		 5
	dw	word_right		;^Right 	 6
	dw	string_left		;@Left		 7	(@ = Alt)
	dw	string_right		;@Right 	 8
	dw	go_bol			;^A, Home	 9
	dw	go_eol			;^E, End	10
	dw	@prev_line		;^P, ^U, Up	11
	dw	@next_line		;^N, Down	12
	dw	@search_back		;^R		13
	dw	@search_forw		;^V		14
	dw	@cycle_complete 	;^I/Tab 	15
	dw	@complete		;#Tab		16	(# = Shift)
	dw	@del_left		;^H/Backspace	17
	dw	@del_right		;^D, KDel	18
	dw	@del_prev_word		;^L, ^Backspace 19
	dw	@del_next_word		;^W		20
	dw	@del_bol		;^X, ^Home	21
	dw	@del_eol		;^K, ^End	22
	dw	@del_eol_exec		;^O		23
	dw	@erase_line		;^[/Esc 	24
	dw	@abort_and_store	;^G		25
	dw	@com_sep		;^S, ^] 	26
	dw	@twiddle		;^T		27
	dw	@cooked_char		;^C		28
	dw	@autorecall		;^Y		29
	dw	@macro			;^_		30
	dw	@vars			;^J/^Enter	31
	dw	@done_editing		;^M/Enter	32
	dw	@done_wipeout		;^^		33
	dw	@toggle_insert		;Insert 	34
	dw	@lfn			;^\		35


;+
; FUNCTION : get_kbd_line
;
;	Gets a line from the user and stores it in linebuf. The name is
;	a misnomer because the line is from standard input, not necessarily
;	from the keyboard.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
; Register(s) destroyed:
;
; jmh 990520: call the functions, instead of jumping.
; jmh 990521: allow @del_left, @erase_line, @abort_and_store and @autorecall
;	       to continue recall.
;-

get_kbd_line	proc	near
	@save	si,di
@get_kbd_line_8:			;added by jmh 990521
	mov	al,auto_recall
	mov	auto_recall_on,al	;Indicate if auto-recall is enabled
@get_kbd_line_9:			;added by jmh 990521
	dec	continue_recall 	;Init auto-recall memory

; Main editing loop
@get_kbd_line_10:
	mov	al,0
        xchg    al,continue_recall      ;Reset the flag. It will be set
;                                        for the default actions keys.
	and	auto_recall_on,al	;Indicate if we should keep
;					 auto-recalling
	call	near ptr disp_line	;Show current line
	shr	CompleteFlag,1		;shift the completion flag (jmh)
	shr	CycleFlag,1		;shift the cycle_complete flag (wd)
	shr	empty_flag,1		;shift the empty flag (jmh)
	mov	ah,getkey_funcnum	;might be a raw keystroke (wd)
	call	near ptr getkey 	;get next key from the user
	jc	@get_kbd_line_15	;function key
	cmp	al,127			;Control backspace ?
	jne	@get_kbd_line_14
	mov	bx,offset DGROUP:ctrl_key_table+32
	jmp	short @get_kbd_line_40
@get_kbd_line_14:
	cmp	al,32			;Control character ?
	jae	@default_action 	;No, then store key
	mov	bx,offset DGROUP:ctrl_key_table ;jmh 980516: modified to test
	add	bx,ax				; for default action
	jmp	short @get_kbd_line_40
@get_kbd_line_15:			;Check if function key
	cmp	al,59			;>= F1 ?
	jb	@get_kbd_line_20	;No, then check next table
	cmp	al,68			;<= F10
	ja	@get_kbd_line_20	;Not fn key
	jmp	@fn_key 		;Handle the function key
@get_kbd_line_20:
	cmp	al,84			;>= Shift-F1 ?
	jb	@get_kbd_line_25	;No, then check next table
	cmp	al,93			;<= Shift-F10
	ja	@get_kbd_line_25	;
	jmp	@sfn_key		;Handle the shifted function key
@get_kbd_line_25:
	mov	cx,NUM_CMD_KEYS		;size and...
	mov	bx,offset DGROUP:cmd_key_table+2  ;beginning of jump table
@get_kbd_line_30:			;Loop begin
	cmp	ax,[bx-2]		;Is this the key ?
	je	@get_kbd_line_40	;Hurrah, found
	inc	bx			;point to next key
	inc	bx
	inc	bx
	loop	@get_kbd_line_30	;If more keys, repeat
	jmp	short @default_action	;key not in table
@get_kbd_line_40:
	cmp	byte ptr [bx],2 	;jmh 980516: Default action ?
	je	@default_action 	;Yep.
	mov	al,[bx] 		;Get the function assigned to the key
	cbw
	add	ax,ax
	xchg	di,ax
	call	word ptr cmd_key_funcs[di]
@get_kbd_line_10_a:
	jmp	short @get_kbd_line_10

@cooked_char:				;added by wd
	mov	ah,8			;get a cooked keystroke (wd)
	jmp	short @get_kbd_key

@quote:					;Insert next char
;					 without interpreting it
	mov	ah,7			;get a raw keystroke (wd)
@get_kbd_key:
	call	near ptr getkey
@default_action_a:
	pop	dx			;Remove return address

@default_action:			;Insert the character (in AX)
	call	near ptr store_char
	jc	@get_kbd_line_10	;No room for char, keep looping
	rol	auto_recall_on,1	;Are we auto-recalling ?
	jnc	@get_kbd_line_10_a	;No
	call	near ptr execute_auto_recall
	jc	@get_kbd_line_10_a	;If carry set, no matching
;					 string in history buffer
	jmp	@get_kbd_line_9 	;Else indicate auto-recall is
;					 still to go on


@prev_line:				;Get previous history line
	mov	ax,offset DGROUP:hist_bck_match
@history_line:
	mov	dot,offset DGROUP:linebuf	;added by jmh 010406
	call	ax
	mov	ax,lastchar
	mov	dot,ax			;Leave cursor at end of line
@history_search:
	jnc	@history_line_done	;Line found
	call	near ptr strstk_settop	;added by wd
	call	near ptr bell		;No line
@history_line_done:
	ret

@next_line:				;Get next history line
	mov	ax,offset DGROUP:hist_fwd_match
	jmp	short @history_line

@search_back:				;Search back thru history buffer
	mov	ax,offset DGROUP:hist_bck_match
@search:
	rol	empty_hist,1
	jnc	@search_1
	or	empty_flag,2
	jpe	@history_line		;Continue as a history command
@search_10:
	call	get_line_len
	jcxz	@history_line
	shr	empty_flag,1		;Turn off the empty flag
@search_1:
	;mov	di,dot			;Save current dot (removed jmh 010402)
	call	ax
	;mov	dot,di			;Restore it
	jmp	short @history_search

@search_forw:				;Search forward thru history buffer
	mov	ax,offset DGROUP:hist_fwd_match
	jmp	short @search

@cycle_complete:
	or	CycleFlag,4
@complete:				;Try to find a matching filename
	or	CompleteFlag,2
	call	near ptr match_file
	rol	auto_recall_on,1	;Autorecall ongoing?
	jc	@del_eol		;Yes, then delete to end of line
	ret

@del_left:				;Delete char before cursor
	mov	ax,offset dgroup:char_left
	dec	continue_recall
	jmp	short @delete

@del_right:				;Delete char at cursor
	mov	ax,offset dgroup:char_right
	jmp	short @delete

@del_prev_word:				;Delete upto word beginning
	mov	ax,offset dgroup:word_left
	jmp	short @delete

@del_next_word:				;Delete to start of next word
	mov	ax,offset dgroup:word_right
	jmp	short @delete

@del_bol:				;Delete to beginning of line
	mov	ax,offset dgroup:go_bol
	jmp	short @delete



@abort_and_store:			;Erases current line but remembers
;					 it in the history buffer
	call	near ptr remember
;	Fall through to erase line
@erase_line:
	call	near ptr go_bol 	;Move dot to beginning of line
	dec	continue_recall
;	fall thru to delete to end of line

@del_eol:				;Delete to end of line
	mov	ax,offset dgroup:go_eol

@delete:				;General purpose delete
	mov	si,dot			;Remember the dot
	call	ax			;Move dot to new position
	xchg	si,ax
	jmp	near ptr erase_to_dot	;Delete characters between ax and dot
;	ret

@toggle_insert:
	not	edit_mode		;Toggle edit mode
	IF	TOGGLE_CURSOR
	mov	al,edit_mode
	cbw
	add	ax,ax			;Word offset
	xchg	bx,ax
	@SetCurSz omode_cursor[bx]	;cx<-cursor shape
	ENDIF
	ret

@com_sep:				;command separator, added by jmh
	mov	ax,CTL_S
	jmp	@default_action_a

@twiddle:				;added by wd, rewritten by jmh 981106
	call	get_line_len		;SI->start of line, CX = line length
	cmp	cl,2			;Need at least two characters
	jb	@ignore

	mov	ax,dot
	xchg	si,ax			;SI->current pos., AX->start of line
	cmp	si,ax			;If at the beginning of the line
	je	@twiddle_20		; go ahead and swap
	cmp	si,lastchar		;If beyond the end of line,
	jne	@twiddle_10		; need to decrease twice, otherwise
	dec	si			; just once (want to swap this char.
@twiddle_10:				; with the next).
	dec	si
@twiddle_20:
IF i286
	rol	word ptr [si],8 	;Do the swap by rotating bits
ELSE
	mov	cl,8
	rol	word ptr [si],cl
ENDIF
	mov	dx,si			;Indicate the display has changed
	lea	ax,[si+2]
	jmp	near ptr set_disp_marks
;	ret

@autorecall:
	not	auto_recall		;Toggle auto recall mode
	pop	dx			;Ignore return address
	jmp	@get_kbd_line_8

@macro: 				;added by dfa
	not	disable_macro		;Toggle macro,symbol expansion disable
	rol	in_appl,1		;jmh 011218: update application macro
	jnc	@macro_10
	not	appl_macro
@macro_10:
	ret

@lfn:
	not	disable_lfn		;Toggle LFN completion
	ret


@vars:
; Expand the variables on the line.
; jmh 010406: modified order and removed recursion.
; jmh 010705: restored order.
	call	near ptr expand_braces	;added by jmh 980430
	call	near ptr expand_var
	call	near ptr associate	;added by jmh 980513
	call	near ptr expand_macro	;added by jmh 980518
	shr	macro_level,1		;jmh 980519: ignore rest of macro
	call	near ptr expand_symbol	;added by jmh 980518

@ignore:
	ret				;Ignore the character



@sfn_key:
; A shifted function key has been struck.
	sub	ax,256+84-'1'           ;added by jmh 980511
	mov	dx,3
	jmp	short @fn_key_1
@fn_key:
; A function key has been struck.
	sub	ax,256+59-'1'           ;F1 = '1' ... F9 = '9', F10 = ':'
	mov	dx,2
@fn_key_1:
	call	near ptr expand_fnkey
	push	ax			;Push a dummy value for return address
	jc	@fn_key_20		;Error or no expansion
	or	dx,dx			;Line to be executed
;					 immediately ?
	je	short @done_editing_2	;Yes, all done (this needs the dummy)
@fn_key_20:
	pop	ax
	jmp	@get_kbd_line_10		;else keep editing

@del_eol_exec:				;Deletes characters from the dot
;					 to end of the line and then
;					 executes the line
	call	@del_eol
	jmp	short @done_editing


@done_wipeout:
; The line is executed. However it is not stored in the history buffer and
; is also removed from the screen (this is a little klugy).
; jmh 990524: used @erase_line.
	mov	ax,lastchar
	push	ax
	call	@erase_line
	call	disp_line
	pop	ax
	mov	lastchar,ax			;Restore line length
	inc	continue_recall 		;Need to reset after erase_line
	jmp	short @get_kbd_line_90

@done_editing:
	call	near ptr remember	;Store in history buffer
;					 Picks up line from global linebuf
@done_editing_2:
	call	near ptr disp_line	;Necessary for @del_eol_exec,
;					 might as well do for others
	mov	ax,lastchar
	mov	dot,ax			;Set dot to end of line
	call	near ptr line_to_scr	;Set cursor beyond last character

@get_kbd_line_90:
	pop	ax			;Retrieve return address
	@DispCh CR			;Do a carriage return because
;					 some applications like EDLIN
;					 only do a LF to go to next line
;	@DispCh LF			;Go onto next line
;	all done
@get_kbd_line_99:
	@restore
	ret
get_kbd_line endp



;+
; FUNCTION : getkey
;
;	Read a keystroke, interpreting function keys into a 16-bit value.
;
; Parameters:
;	AH	= 7 for a raw keystroke, 8 for a cooked keystroke
;
; Returns:
;	AX	= 16-bit key value
;	CF	= 1 if value is actually 16 bits (AH != 0)	(added by)
;		= 0 if value is 8 bits (AH == 0)		(jmh 990520)
;
; Register(s) destroyed:
;	DL
;-
; Requires AH be set to either 7 (raw) or 8 (cooked)
;
getkey proc near
	mov	dl,ah
	int	21h

	push	dx		;jmh 971215 - Test if Win95 has closed the DOS
	push	ax		; box. If so, fudge pressing enter to allow it
	mov	ax,168fh	; to be closed.
	mov	dh,1		;Win95 - Query Close function
	int	2fh
	or	ax,ax
	pop	ax
	pop	dx
	jnz	@not_closed
	mov	al,13		;I'll assume no-one has re-mapped ^M
@not_closed:
	or	al,al		;jmh 980511: reorganized this section
	je	@114		;not '\0'
@113:
	xor	ah,ah		;Clears carry
	ret
@114:
	mov	ah,0Bh
	int	21h		;check if key available
	or	al,al
	jz	@113		;no character available
	mov	ah,dl
	int	21h
	mov	ah,1
	stc			;Indicate function key
	ret
getkey	endp



;+
; FUNCTION : expand_fnkey
;
;	Inserts the expansion corresponding to a symbol key into the
;	linebuffer. If the buffer is too small, only as many characters as
;	possible are inserted and the function returns an error. The
;	function also returns an error if the symbol is not defined. The
;	function also updates the displayed line. The function also checks
;	if the line is to be executed immediately or not, based on the last
;	character of the fn key expansion.
;
; Parameters:
;	AX	= function key character, between '1' and ':' (sic).
;	DX	= length of string (2 for function, 3 for shifted).
;
; Returns:
;	CF	= 0 if no error, else 1
;		  If CF is 1, then AX is 0 if symbol not found or non-zero
;		  if symbol found but no room in line.
;	AX	= 1 if symbol was present    (jmh 980511: currently unused)
;		  0 if symbol was not found.
;	DX	= 0 if the line is to be executed immediately
;		  non-0 otherwise
;		  DX is only valid if CF=0 and AX=1
; Register(s) destroyed:
; 	<TBA>
;-
expand_fnkey proc near
	@link	LINEBUF_SIZE
exp_buf	equ <byte ptr [bp-LINEBUF_SIZE]>

	mov	si,offset DGROUP:fnkey_tab+3	;SI->end of 'SFn'
	mov	di,si
	dec	di				;DI->the digit
	sub	si,dx				;SI->the symbol
@expand_fnkey_15:
	cmp	al,':'                          ;F10 must appear as F0
	jne	@expand_fnkey_20
	sub	al,10
@expand_fnkey_20:
	mov	[di],al 			;Store in 'SFn' string
; OK now try and expand the symbol.
;						 SI->symbol
	mov	ax,LINEBUF_SIZE
	xchg	ax,dx				;AX<-length of synbol
;						 DX<-length of expansion buffer
	lea	di,exp_buf
	call	near ptr get_symbol		;Params SI,AX,DI,DX
	jc	@expand_fnkey_99		;No symbol (buffer too
;						 small case not possible).
;						 AX will be 0 for this case.
; OK now we have the symbol expansion, so try to insert it into the linebuffer.
;	AX contains length of expansion
	mov	si,di				;SI->expansion
	add	di,ax
	dec	di				;DI->last char of expansion
	xor	dx,dx
	mov	dl,byte ptr [di]		
	sub	dl,fnkey_exec_char		;Is this line to be
;						 immediately executed
	jne	@expand_fnkey_80		;No
	dec	ax				;The last char of expansion
;						 is a special char. Do not
;						 include it in the insert
;						 string 
@expand_fnkey_80:
	push	dx
	call	near ptr insert_at_dot		;Params SI,AX
;	mov	ax,1				;AX<-exit code (; by jmh 980511)
	pop	dx				;DX is 0 if line is to be
;						 executed immediately
@expand_fnkey_99:
	@unlink
	ret

expand_fnkey endp


;+
; FUNCTION : store_char
;
;	Stores	the character in AX into the line buffer if max line
;	length will not be exceeded. Characters may be inserted or
;	overwrite chars in the buffer depending on the edit mode and whether
;	auto-recall is on.
;
; Parameters:
;	AX	= character
;
; Returns:
;	CF	= 0 if no error, else 1
; Register(s) destroyed:
; 	<TBA>
;-
store_char proc near
	@save	si
	or	ah,ah			;char >= 256
	jnz	@store_char_90		;Ignore if so
	cmp	edit_mode,ah		;-1 if insert mode
	je	@store_char_20		;Jump if overtype mode
	cmp	auto_recall_on,ah	;Is auto-recall on ?
	jne	@store_char_20		;Yes, behave as if in overtype mode
	mov	si,offset dgroup:temp1	;temporary storage
	mov	[si],al			;Store char
	mov	al,1			;Length of string
	call	near ptr insert_at_dot	;Insert the character
	jnc	@store_char_99		;No problemo
	jmp	short @store_char_90	;No room in buffer
@store_char_20:
	mov	si,dot			;Current position in line
	cmp	si,linelimit		;At line end?
	jae	@store_char_90
;	Enough room for a char
	mov	[si],al			;Store the char
	mov	dx,si			;DX->changed character
	lea	ax,[si+1]		;AX->char after change
	mov	dot,ax			;Store it as new dot position
	cmp	si,lastchar		;Was it end of line ?
	jb	@store_char_30
	mov	lastchar,ax		;Store new end of line
@store_char_30:
	call	near ptr set_disp_marks	;AX,DX parameters
	clc				;Indicate no error
	jmp	short @store_char_99
@store_char_90:
	call	near ptr bell
	stc				;Indicate error
@store_char_99:
	@restore
	ret
store_char endp


;+
; FUNCTION : word_left, string_left
;
;	Move one word or string left. A word is delimited by non-alpha-
;	numerics; a string is delimited by space/tab. (String by jmh.)
;
; Parameters:
;	Globals linebuf and dot.
;
; Returns:
;	Nothing.
;
; Register(s) destroyed:
;
; jmh 980512: removed the return code, tidied code accordingly. Comments are
;	      for the word_left case; for string_left it skips over spaces,
;	      then everything but spaces.
;-
word_left proc near
	mov	dx,offset DGROUP:isalphnum
	jmp	short @word_left_10

string_left LABEL near
	mov	dx,offset DGROUP:isspace

@word_left_10:				;Loop to backup over non-alphanumeric
	call	near ptr char_left	;Move one char left
	jc	@word_left_99		;Already at beginning of line
	call	dx			;AL alphanumeric ?
	je	@word_left_10		;No, loop back
@word_left_15:				;Now backup to beginning of word
;	At this point, dot is always at an alphabetic char or beginning
;	of the line
	call	near ptr char_left
	jc	@word_left_99		;Were already at beginning of line
	call	dx			;Alphanumeric?
	jne	@word_left_15		;Yes, loop back
					;Found non-alphanumeric char
	call	near ptr char_right	;move over one
@word_left_99:
	ret
word_left endp



;+
; FUNCTION : word_right, string_right
;
;	Move one word or string right. A word is delimited by non-alpha-
;	numerics; a string is delimited by space/tab. (String by jmh.)
;
; Parameters:
;	Globals linebuf and dot.
;
; Returns:
;	Nothing.
;
; Register(s) destroyed:
;
; jmh 980512: Comments are for the word_right case; for string_right it
;	      skips over non-spaces, then spaces.
;-
word_right proc near
	mov	dx,offset DGROUP:isalphnum
	jmp	short @word_right_10

string_right LABEL near
	mov	dx,offset DGROUP:isspace

@word_right_10:				;Loop fwd over alphanumerics
	call	near ptr char_right	;One char right
					;AL<-char at dot
	jc	@word_right_99		;Already at end of line
	call	dx			;Is AL alphanumeric ?
	jne	@word_right_10		;Yes, loop back
@word_right_15:				;Now move to beginning of word
	call	near ptr char_right
	jc	@word_right_99		;Already at end of line
	call	dx			;Alphanumeric ?
	je	@word_right_15		;Not alphanumeric char, loop back
@word_right_99:
	ret
word_right endp



;+
; FUNCTION : char_left
;
;	Moves the 'dot' one character to the left unless
;	already at the beginning of the line.
;
; Parameters:
;	Global	dot is current position in the line.
;
; Returns:
;	AL	= char at new dot position.
;	CF	= 1 if OLD dot was at beginning of line
;		  0 otherwise.
; Register(s) destroyed:
;-
char_left proc	near
	@save	si
	mov	ax,dot			;Current position in line
	cmp	ax,offset dgroup:linebuf
	jne	@char_left_5
	stc				;Dot already at beginning of line
	jmp	short @char_left_99
@char_left_5:
	dec	ax			;Move dot left
	mov	dot,ax
@char_left_99:
	xchg	ax,si
	lodsb				;AL<-char at dot
	@restore
	ret
char_left endp


;+
; FUNCTION : char_right
;
;	Moves the 'dot' one character to the right unless
;	already at the end of the line.
;
; Parameters:
;	Global	dot is current position in the line.
;
; Returns:
;	AL	= char at new dot position. Undefined if new dot is at
;		  the end of the line.
;	CF	= 1 if OLD dot was already at end of the line.
;		  0 otherwise.
; Register(s) destroyed:
;-
char_right proc	near
	@save	si
	mov	ax,dot			;Current position in line
	mov	si,lastchar		;SI->Beyond last char
	dec	si			;SI->last char in line
	cmp	si,ax			;Dot at end ?
	jc	@char_right_99		;Already at line end
	inc	ax			;Move dot right, CF not affected
	mov	dot,ax
	xchg	ax,si
	lodsb				;AL<-char at dot, (undefined if
;					 dot is now at end of line).
;	CF	already clear
@char_right_99:
	@restore
	ret
char_right endp



;+
; FUNCTION : go_bol
;
;	Sets dot to point to the beginning of the line
;
; Register(s) destroyed: None.
;-
go_bol	proc	near
	mov	dot,offset dgroup:linebuf
	ret
go_bol	endp



;+
; FUNCTION : go_eol
;
;	Sets dot to point to the end of the line
;
; Register(s) destroyed: AX.
;-
go_eol	proc	near
	mov	ax,lastchar
	mov	dot,ax
	ret
go_eol	endp



;+
; FUNCTION : match_file
;
;	match_file tries to expand the filename to the left of the
;	cursor. If there are several matching files, the longest common
;	prefix is placed on the line.
;	Added by wd:	If no characters can be placed on the line,
;			list the possible matches.  Alternately, add
;			one match at a time to the line.
;
;	jmh 980512:	maximum path changed to 80 (66 characters for the drive,
;			colon and path, one character for the "trailing" back-
;			slash, 12 characters for the filename, dot and exten-
;			sion, and one more for the null). However, the biggest
;			path I have is 60 characters (including null).
;
;	jmh 990513:	lots of changes to handle LFN completion.
;			(jmh 990518: quite extensive changes)
;	jmh 990523:	use the cur_macro buffer to store the pattern, since
;			macros and completion are mutually exclusive. (In the
;			rare circumstance that all 255 characters are entered,
;			adding the "*.*\0" would only overwrite the signature.)
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
; Register(s) destroyed:
;-
match_file proc	near
	@link	2+2+2+2+2+2+318
oldDTAseg 	equ	2
oldDTAoff 	equ	oldDTAseg+2
breakstate	equ	oldDTAoff+1	;Remembers break state
directory	equ	breakstate+1	;File or directory?
fname		equ	directory+2	;Pointer into appropriate ff structure
maxlen		equ	fname+2 	;Length of the longest name
lffblk		equ	maxlen+318	;Long filename find block
pathbuf 	equ	cur_macro	;Storage for entire filename with path

lffblk_attr	equ	lffblk		;Attr byte within LFN ffblk
lffblk_lname	equ	lffblk-2Ch	;long filename within LFN ffblk
lffblk_sname	equ	lffblk-130h	;short filename within LFN ffblk

	mov	ax,3300h		;Get current break state
	int	21h			;DL<-current break state
	mov	[bp-breakstate],dl	;Remember it
	xor	dl,dl			;Disable break check during
	inc	ax			; disk I/O
	int	21h

	push	es
	@GetDTA				;Get and save old DTA
	mov	[bp-oldDTAseg],es
	mov	[bp-oldDTAoff],bx
	pop	es
	mov	dx,offset ffblk
	@SetDTA 			;Set DTA to our DTA

	mov	bx,offset @match_file_prefix
	cmp	CompleteFlag,2
	je	@match_file_10
@match_file_which:
	mov	bx,offset @match_file_cycle
	test	CycleFlag,4
	jnz	@match_file_10
	mov	bx,offset @match_file_list
@match_file_10:
	call	bx

	push	ds
	lds	dx,[bp-oldDTAoff]
	@SetDTA 			;Restore DTA
	pop	ds
	mov	dl,[bp-breakstate]	;Restore previous break checking
	mov	ax,3301h
	int	21h
	@unlink
	ret
match_file endp


;+
; FUNCTION: match_file_prefix
;
;	Determine the filename pattern from the line buffer and place it in
;	the path buffer. If the pattern contains a wildcard (* or ?), start
;	the cycle or list immediately. Otherwise place the common prefix of
;	all matching files into the line buffer. If only one name matched,
;	reset the completion.
;
; Parameters:
;
; Returns:
;-
@match_file_prefix proc near
	mov	ExtFlags,1		;Assume executables wanted (jmh)
	mov	dx,offset dgroup:linebuf
	mov	bx,dot

; See if the filename is quoted. Does no testing for quoted quotes, so may
; fail in rare circumstances. Also assumes the quote begins the name, not a
; portion of the name.
	mov	di,dx			;DI -> beginning of line
	mov	cx,bx
	sub	cx,di			;CX = number of chars to current pos.
	mov	ax,QUOTE or 0ff00h
@match_file_q10:
	neg	ah			;1 = not in quote, -1 = in quote
	mov	si,di			;SI -> character after quote
	repne	scasb			;No need to jcxz since neg sets NZ
	je	@match_file_q10
@match_file_q20:
	dec	si			;SI -> quote
	shr	ah,1
	mov	QuoteFlag,ah
	jnz	@match_file_prefix_20

; Search backwards from the current position for an invalid filename character.
	mov	si,bx			;SI -> current position
@match_file_prefix_10:
	dec	si
	mov	al,[si]			;AL<-next char
	call	@match_file_invalid	;Changed from isspace by jmh
	jne	@match_file_prefix_10	;Not beginning of filename
	inc	si
@match_file_prefix_20:
	cmp	si,dx
	je	@match_file_prefix_30	;Beginning of line and filename
	inc	ExtFlags		;Ignore files
@match_file_prefix_30:
	mov	pathptr,si		;Save start of path name in linebuf
	call	@match_file_parse
	pushf				;Save wildcard flag
	call	@match_file_first
	jnc	@match_file_prefix_50	;No error
	and	ExtFlags,NOT 2		;Try again with ignore off
	jnz	@match_file_prefix_40	; but only if it wasn't exe
	call	@match_file_first
	jnc	@match_file_prefix_50
@match_file_prefix_40:
	popf
	shr	CompleteFlag,1		;Ensure cycle/list doesn't occur
	jmp	near ptr bell		;No files found
;	ret

@match_file_wild:
	or	CycleFlag,2		;Start cycling immediately
	pop	bx			;Remove the return address
	jmp	@match_file_which

@match_file_prefix_50: 			;At least one file found
	popf
	jnz	@match_file_wild	;No prefixes for wildcards

	push	[bp-directory]		;Remember the first file's attribute
	mov	di,offset pathbuf 	;Copy the first filename into the
	push	di			; path buffer for comparison
	rep	movsb

;	Now hunt for more files. For each file found, keep the longest 
;	prefix in common so far.
@match_file_prefix_60:
	push	cx
	call	@match_file_next	;Get the next file name
	pop	dx
	jc	@match_file_prefix_70	;No more files

	pop	di			;DI->start of file name
	push	di
	mov	bx,cx
	call	near ptr stre_cmp	;Compare strings
	inc	cx
	sub	bx,cx			;BX = number of common characters
	mov	[di+bx],bh		;Terminating null (BX always < 256)
	jmp	short @match_file_prefix_60  ;Try for more files

@match_file_prefix_70:
	pop	si
	pop	[bp-directory]
	call	near ptr strsilen
	or	dx,dx			;DX == 0 if only one file matched
	stc
	jnz	@match_file_prefix_80
	xor	ax,ax			;(Clears carry)
	mov	CompleteFlags,ax	;Reset the completion
@match_file_prefix_80:
	call	@match_file_display
	jmp	@match_file_parse	;Update pathbuf
;	ret
@match_file_prefix endp


;+
; FUNCTION: match_file_cycle
;
;	Cycle through all possible matches (not alphabetically, unfortunately).
;	Assumes pathbuf is filled with the filename pattern and there is at
;	least one match.
;
; Parameters:
;
; Returns:
;-
@match_file_cycle proc near
	cmp	CycleFlag,7
	je	@match_file_cycle_next
	call	@match_file_first
@match_file_cycle_10:
	dec	cx			;Don't want the NUL
	jmp	@match_file_display
;	ret

@match_file_cycle_next:
	call	@match_file_next
	jnc	@match_file_cycle_10
	call	near ptr bell		;Gone through them all
	jmp	@match_file_cycle_reset
;	ret
@match_file_cycle endp


;+
; FUNCTION: match_file_list
;
;	List all files matching the pattern in pathbuf (across the screen,
;	unsorted). Directories are given a suffix according to the last
;	directory separator character seen (ie. / or \). If this function is
;	used immediately after a cycle, reset the cycle and list what is being
;	cycled. Assumes there is at least one matching file.
;
; Parameters:
;
; Returns:
;-
@match_file_list proc near
	cmp	CycleFlag,0
	je	@match_file_list0
	call	@match_file_cycle_reset
	call	disp_line
@match_file_list0:
	call	near ptr output_newline
	mov	al,ExtFlags		;Remember the ignore flag (jmh)
	push	ax
	and	ExtFlags,NOT 2		; and turn it off for the list

;jmh 990514: find the longest name to determine column size.
	mov	word ptr [bp-maxlen],0
	call	@match_file_first
@match_file_list_1:
	cmp	cx,[bp-maxlen]
	jbe	@match_file_list_2
	mov	[bp-maxlen],cx
@match_file_list_2:
	call	@match_file_next
	jnc	@match_file_list_1

	call	@match_file_first

;jmh 980512: display as many files on a line as will fit
;jmh 990515: Assumes filename is 254 characters at most.
	xor	bx,bx			;BL counts columns
	mov	al,screen_width
	sub	al,[bp-maxlen]
	jbe	@match_file_list_10	;A name is longer than the screen
	mov	bh,al			;BH determines if the next file fits

@match_file_list_10:
	mov	ah,2			;DOS display character (@DispCh)
	mov	cx,[bp-maxlen]
	inc	cx			;Couple of spaces between columns
	inc	cx
@match_file_list_20:
	lodsb
	test	al,al
	jz	@match_file_list_30
	mov	dl,al
	int	21h
	inc	bx
	loop	@match_file_list_20

@match_file_list_30:
	mov	dl,SPACE
	test	byte ptr [bp-directory],16	;Subdirectory?
	je	@match_file_list_40		;No
	mov	dl,backslash_char		;If subdir, add a backslash
@match_file_list_40:
	int	21h
	inc	bx

	mov	dl,SPACE
	mov	dh,screen_width
@match_file_list_50:
	cmp	bl,dh
	jae	@match_file_list_53		;Name was longer than screen
	int	21h
	inc	bx
	loop	@match_file_list_50

	cmp	bl,bh				;Would any more files fit?
	jbe	@match_file_list_55		;Yep
@match_file_list_53:
	cmp	bl,dh
	je	@match_file_list_54		;Already wrapped
	call	near ptr output_newline
@match_file_list_54:
	xor	bl,bl				;Start over again
@match_file_list_55:
	push	bx
	call	@match_file_next
	pop	bx
	jnc	@match_file_list_10
	pop	ax				;Get the ignore flag back (jmh)
	mov	ExtFlags,al
	or	bl,bl
	jz	@match_file_list_60
	call	near ptr output_newline
@match_file_list_60:
IFDEF CMD_PROMPT
	pop	ax				;Return address
	add	sp,318+2+2			;Reclaim stack space for
	push	ax				; the prompt.
	call	near ptr disp_prompt
	pop	ax
	sub	sp,318+2+2
	push	ax
ELSE
	call	near ptr disp_prompt
ENDIF
	call	near ptr get_curpos		;DX<-current cursor position
	mov	initial_curpos,dx
	mov	ax,lastchar
	mov	dx,offset DGROUP:linebuf
	call	near ptr set_disp_marks
	jmp	disp_line			;Redisplay line
;	ret
@match_file_list endp


@match_file_cycle_reset proc near
; Reset the cycle search string.
	mov	CycleFlag,4
	mov	si,bnameptr
	mov	cx,bnamelen
	dec	cx
	stc
;	jmp	@match_file_display
;	ret
@match_file_cycle_reset endp


;+
; FUNCTION: match_file_display
;
;	Remove the old filename and display the new name. If the filename
;	needs to be quoted, and no quote was entered, add an opening quote.
;	If the filename does not need to be quoted, and an opening quote was
;	added, remove it.
;	If a suffix is required, add a space for files and the current
;	directory separator character for directories (provided backslash
;	appending is enabled). If the name needs to be quoted, also add the
;	closing quote, but not for directories.
;
; Parameters:
;	SI -> new name
;	CX := length
;	NC to add suffix, CF to display as is
;
; Returns:
;-
@match_file_display proc near
	push	cx			;Preserve unquoted, unsuffixed length
	jc	@match_file_display_5

; Add a space or backslash (if wanted) to the filename.
	mov	bx,cx			;BX = current length

	;jmh 010402: no closing quote for subdirectories.
	test	byte ptr [bp-directory],16 ;Subdirectory?
	jz	@match_file_suffix_5	;No, test for quote
	cmp	no_append_slash,bh	;Length will be less than 256
	jne	@match_file_display_5
	mov	dl,backslash_char
	jmp	short @match_file_suffix_20

@match_file_suffix_5:
	mov	dl,SPACE		;Assume space is appended
	test	QuoteFlag,1		;Opening quote entered?
	jnz	@match_file_suffix_10	;Yes, add a closing quote
	call	@match_file_quote	;Closing quote required?
	jne	@match_file_suffix_20	;No, don't add one

@match_file_suffix_10:
	inc	cx			;Plus one for the quote
	mov	dx,QUOTE or (SPACE shl 8)

@match_file_suffix_20:
	mov	[si+bx],dx
	inc	cx			;Plus one for space/backslash

@match_file_display_5:
	push	cx
	mov	ax,fnameptr		;AX->start of chars to be deleted
	call	near ptr erase_to_dot	;Delete chars between dot and start
	pop	ax
	call	near ptr insert_at_dot	;SI->source, AX == length
	pop	cx			;Retrieve unmodified length

	mov	al,QuoteFlag		;Do I need to add or remove an
	test	al,1			; opening quote?
	jnz	@match_file_display_99	;Nope
	push	ax
	call	@match_file_quote
	pop	ax
	lahf
	and	ah,64			;Just keep ZF
	xor	al,ah			;ZF && qf ==> do nothing
	jz	@match_file_display_99	;ZF && nq ==> add quote, set qf
	mov	QuoteFlag,ah		;NZ && qf ==> remove quote, clear qf
	or	ah,ah			;NZ && nq ==> do nothing
	mov	si,pathptr
	mov	ax,1
	jnz	@match_file_display_10
	call	near ptr remove_chars
	dec	fnameptr
;	jmp	short @match_file_display_99
	ret

@match_file_display_10:
	mov	di,offset temp1
	xchg	di,si
	mov	byte ptr [si],QUOTE
	call	insert_chars
	inc	fnameptr

@match_file_display_99:
	ret
@match_file_display endp


@match_file_parse proc near
;   Copy filename into ASCIIZ buffer.
;   Return NZ if filename contains metacharacters (* and ?).
	mov	di,offset pathbuf
	mov	si,pathptr
	mov	cx,dot
	sub	cx,si
	jcxz	@match_file_p1
	cmp	byte ptr [si],QUOTE
	jne	@match_file_p1
	inc	si
	dec	cx
@match_file_p1:
	inc	cx			;Prime for loop
	jmp	short @match_file_p30
@match_file_p0:
	lodsb				;AL<-next char
	stosb				;Store it
	cmp	al,'.'
	jne	@match_file_p10
	mov	dh,1			;Set flag to remember file type '.'
;	jmp	short @match_file_p40	;Check out next character
@match_file_p10:
	cmp	al,'*'
	je	@match_file_p15
	cmp	al,'?'
	jne	@match_file_p16
@match_file_p15:
	inc	dx			;Set flag to remember wildcard
;	jmp	short @match_file_p40
@match_file_p16:
	cmp	al,':'			;Disk?
	je	@match_file_p30
	cmp	al,'\'			;Directory separator?
	je	@match_file_p20
	cmp	al,'/'			;Same thing
	jne	@match_file_p40
@match_file_p20:
	mov	backslash_char,al	;remember the type of slash (/ or \)
@match_file_p30:
	mov	fnameptr,si		;Update start of filename in linebuf
	mov	bnameptr,di		;Update start of filename in pathbuf
	mov	bnamelen,cx		;Update length of filename in pathbuf
	xor	dx,dx			;Forget flags

@match_file_p40:
	loop	@match_file_p0

	mov	dotstar,cx		;Assume no ".*" needed
	or	dl,dl			;Already seen wildcard?
	jnz	@match_file_p99		;Yes, just add terminator
	shr	dh,1			;Saw a file type ? (Sets ZF)
	jc	@match_file_p50 	;Yes
	mov	dotstar,di		;Remember position for ".*"
@match_file_p50:
	mov	cl,'*'                  ;Attach '*' and terminator
@match_file_p99:
	mov	[di],cx
	ret
@match_file_parse endp


@match_file_invalid proc near
; Test if the character in AL is an invalid filename character.
; Return: ZF = 1 if AL is an invalid filename character
;	       0 otherwise
	test	al,11100000b		;Control character?
	jz	@match_file_invalid_10	;Yep
	@save	di,cx
	mov	cx,invalid_len
	mov	di,offset invalid_list
	repne	scasb
	@restore
@match_file_invalid_10:
	ret
@match_file_invalid endp


@match_file_quote proc near
; Determine if a filename needs to be quoted.
; SI -> name, CX = length
; Returns ZF if it should be quoted
	@save	si,cx
	jcxz	@match_file_quote_20
@match_file_quote_10:
	lodsb
	call	@match_file_invalid
	loopne	@match_file_quote_10
@match_file_quote_15:
	@restore
	ret
@match_file_quote_20:
	inc	cx			;Ensure NZ
	jmp	short @match_file_quote_15
@match_file_quote endp


;+
; FUNCTION: match_file_{first,next}
;
;	Get the first and subsequent files for filename completion. For
;	match_file_first, pathbuf should be filled with the filename pattern.
;
;	ExtFlags indicates if extensions should be tested:
;	  0 - match all files
;	  1 - the extension must be in FEXEC or an association (if enabled)
;	  2 - the extension must not be in FIGNORE
;	ExtFlags should not change between calls.
;
;	The two directory shortcuts ("." and "..") are ignored.
;
; Parameters:
;
; Returns:
;	CF = 1 for no (more) files
;	CF = 0 for a file
;	  SI -> filename
;	  CX =	length+1
;	  directory = attribute
;-
@match_file_first proc near
	;Find the appropriate list of extensions
	lea	si,exevar		;Assume executables
	test	ExtFlags,1		;Do we want executables only?
	jnz	@match_file_e10 	;Yes
	test	ExtFlags,2		;Ignoring extensions
	jz	@match_file_e12 	;No, go start searching
	lea	si,ignvar
@match_file_e10:
	lodsw				;Length of env. variable
	xor	di,di			;Get the pointer to its expansion
	push	ax
	call	get_dosenv
	pop	ax			;Don't need its length
	jnc	@match_file_e11 	;Found it
	add	si,ax			;Point to the default list
	mov	di,si
@match_file_e11:
	mov	word ptr ExtList,di
	mov	word ptr ExtList+2,es
	push	ds
	pop	es

@match_file_e12:
	@LFNFindClose ffhandle		;Close the previous LFN search

	mov	dx,offset pathbuf	;DX->ASCII name
	mov	cx,16			;include directories
	xor	si,si			;don't care about LFN time format
	rol	disable_lfn,1		;Are we doing LFN search?
	jc	@match_file_e14 	;Nope
	lea	di,[bp-lffblk]
	@LFNGetFirst
	jc	@match_file_e13
	mov	ffhandle,ax
@match_file_lfn1:
	mov	cl,[bp-lffblk_attr]
	lea	di,[bp-lffblk_lname]
	jmp	short @match_file_e20

@match_file_e13:
	cmp	ax,7100h
	jne	@match_file_error
@match_file_e14:
	mov	ffhandle,si		;Indicate no LFN (SI is still zero)
	or	si,dotstar		;Do I need to add ".*"?
	jz	@match_file_e14a	;No
	mov	word ptr [si+1],'.' or ('*' shl 8)
	mov	[si+3],ch
@match_file_e14a:
	@GetFirst
	jmp	short @match_file_e16


@match_file_next LABEL near
	mov	bx,ffhandle
	or	bx,bx
	je	@match_file_e15
	lea	di,[bp-lffblk]
	xor	si,si
	@LFNGetNext
	jnc	@match_file_lfn1
@match_file_error:
	stc
	ret

@match_file_e15:
	@GetNext
@match_file_e16:
	jc	@match_file_error
	mov	cl,ffblk_attr
	mov	di,offset ffblk_name

@match_file_e20:
	mov	ax,'.'                  ;Test for "." and ".."
	cmp	[di],ax
	je	@match_file_next
	cmp	[di+1],ax		;Only ".." can end in a dot, so no
	je	@match_file_next	; need to test first character

	mov	[bp-directory],cl
	mov	[bp-fname],di

	test	cl,16			;Subdirectory?
	jnz	@match_file_e98 	;Yes, go exit
	cmp	ExtFlags,ah		;Testing extensions? (AH = 0)
	je	@match_file_e98 	;No, go exit

	call	near ptr strlen
	dec	di
	mov	si,di
	mov	al,'.'
	std
	repne	scasb
	cld
	jne	@match_file_e30 	;CX is zero, so SI is irrelevant
	mov	cx,di
	inc	cx
	inc	cx
	sub	si,cx
	xchg	si,cx			;SI -> extension, CX = length
@match_file_e30:
	push	es
	les	di,ExtList
	call	match_ext
	pop	es
	mov	al,ExtFlags
	jne	@match_file_e40 	;Didn't find the extension
	test	al,1			;If matching executables then
	jnz	@match_file_e98 	; we can exit
@match_file_next1:
	jmp	@match_file_next	; otherwise get the next file

@match_file_e40:
	test	al,2			;If it didn't match the ignore list,
	jnz	@match_file_e98 	; it's okay.
	cmp	disable_macro,ch	;It didn't match the executable list so
	jne	@match_file_next1	; try the associations list, if it's
	call	strstk_ext_find 	; enabled. (CH is zero from ext. len.)
	jc	@match_file_next1	;Still no good.

@match_file_e98:
	mov	si,[bp-fname]
	call	near ptr strsilen
	xor	ax,ax
	cmp	ffhandle,ax
	je	@match_file_lower
	cmp	[bp-lffblk_sname],al	;If there's no long name, the short
					; name doesn't exist.
	jnz	@match_file_e98a
@match_file_lower:
	call	xlate_lower
@match_file_e98a:
	inc	cx
	clc
	ret
@match_file_first endp


CSEG	ENDS

	END
