;
; CMDEDIT.ASM
; (c) 1989, 1990 PC Magazine and Ashok P. Nadkarni
; Addition of /c,l,w,n,o,t & z options by David Abbott (dfa), Nov-Dec, 1990.
; also /k option May 1991, /n July 1991 and /a June 1992.
; Additional commands and options authored by Wayne Davison (wd) have also
; been added to this version by dfa in May 1992.
; /y added by wd and dfa and /x by wd June 1992.
; Places in the code that have been changed or added by dfa have a suitable
; comment attached so a search for "dfa" will find them.  Likewise changes
; authored by wd are commented so that a search for "wd" will find them.
; (jmh - not all, see below)
;
; A note on EXTRA_HANDLER (defined in COMMON.INC) by wd:
; Support for a secondary readline entry point has been added by wd.  It is
; conditionally compiled and turned off in the code.  It is ONLY intended for
; experienced programmers who know what they are doing!
; The reason for this is to support programs (such as the epsilon editor)
; that take over the readline function themselves (epsilon allows a command
; shell in a window).  In such a senario command.com is modified to use the
; int 21 function 0xEA to read a command line.  cmdedit grabs this call, and
; calls the normal int 21 function 0x0A.  If it finds itself on the receiving
; end of the call, it returns back with a flag set so that the calling
; function will do the usual function 0x0A thing and return.  If someone else
; handles the input of the text line, it passes that line on to the
; alias/history/etc processing that occurs after the edited input.  The only
; problem with this code is that my modified command.com is sometimes rejected
; as invalid, which halts the system!  Otherwise it appears to work fine.
;
; A note on CMD_PROMPT (defined in COMMON.INC) by jmh:
; I've used ANSI (NANSI actually) to create a bright yellow on blue prompt (my
; first computer was an Amstrad CPC464). I recently thought, Why not have a
; multi-colour prompt? I also thought that a lower-case path might be nice and
; might as well use slashes while I was at it (UNIXify). And since the extra
; lines in macros bug me, what the heck, get rid of them, too. In order for
; this to work correctly ECHO should be ON (otherwise macros will end up on the
; wrong line) and the PROMPT variable should have no effect on the cursor
; (otherwise the colour prompt will not be displayed). Since PROMPT= results in
; a default $N$G (at least in DOS7) you need to fudge it: I do so by the
; b.yellow on blue ANSI sequence (ie. $E[1;33;44m) but I think an invalid
; dollar sequence will also result in no prompt (eg. $Z). Currently the prompt
; is a fixed $P$G, lower-cased drive and path (if not LFN), backslashes
; replaced with slashes and colours EQUated (also in COMMON.INC). If there's an
; interest I'll create some more options. (One other thing: since it creates
; its own prompt, there's no need for the dir command kludge; I have not yet
; removed this.)
;
; Conditionally compiled the dir commands by jmh, 9 March, 1997.
; Moved the config file code to install.asm, jmh, 13 March.
; Used DOSKey interface, keeping buffered input as application, jmh, 15 Dec.
; Application (buffered input) disables macros by default, jmh, 4 May, 1998.
; Interrupt vector moved to a function in install.asm, jmh, 5 May.
; Removed the display internal commands and rearranged the others, jmh, 7 May.
; Removed all commented code, jmh, 9 May.
; Added internal prompt, jmh, June/July.
; Disable CMDEDIT in DOS and expand braces last, jmh, 2 April, 2001.
; Keep buffers across DOS option added jmh, 3 April, 2001.
; Application history size option added jmh, 5 April, 2001.
; Shaved off a few bytes in create_disp_prompt, rewrote line_to_scr and
;  various other size optimizations, jmh, 9 November, 2001.
; Set application macro state independent of DOS, jmh, 18 December, 2001.
; Self-load high, jmh, 20 December, 2001.
; Removed /w alternate error message, jmh, 23 December, 2001.


; Main module for command line editor.

	INCLUDE common.inc
	INCLUDE general.inc
	INCLUDE ascii.inc
	INCLUDE buffers.inc
	INCLUDE dos.inc
	INCLUDE bios.inc

	PUBLIC	dos_version_major
	PUBLIC	dos_version_minor
	PUBLIC	source
	PUBLIC	env
	PUBLIC	macro_level
	PUBLIC	cmd_level
	PUBLIC	cur_macro
	PUBLIC	cur_macro_len
	PUBLIC	linebuf
IFNDEF NODIRS
	PUBLIC	abort_dir_op		;moved by jmh
	PUBLIC	user_command		;moved by jmh
ENDIF
	PUBLIC	linelimit
	PUBLIC	dot
	PUBLIC	lastchar
	PUBLIC	edit_mode
	PUBLIC	default_imode
	PUBLIC	caller_cursor
	PUBLIC	omode_cursor
	PUBLIC	mfilename
	PUBLIC	mfile_handle
	PUBLIC	macro_ignore_char
	;PUBLIC msg_flag		;removed jmh 011223
	PUBLIC	cmdlen
	PUBLIC	silent
	PUBLIC	endm_cmd
	PUBLIC	defs
	PUBLIC	defm
	PUBLIC	tsr_install_end
	PUBLIC	source
	PUBLIC	line_too_long		;created by jmh 990520
	PUBLIC	abort_processing
	PUBLIC	disp_line
	PUBLIC	set_disp_marks
	PUBLIC	get_line_len		;created by jmh 980517
	PUBLIC	get_len 		;ditto
	PUBLIC	insert_at_dot
	PUBLIC	insert_chars
	PUBLIC	remove_chars
	PUBLIC	erase_to_dot
	PUBLIC	init_over
	PUBLIC	line_to_scr
	PUBLIC	get_next_line
	PUBLIC	reset_line
	PUBLIC	in_appl
	PUBLIC	our_break_handler
	PUBLIC	our_mpx_handler
	PUBLIC	prev_isr1b
	PUBLIC	prev_isr2f
	PUBLIC	old_int21vec
	PUBLIC	cmdedit_isr
	PUBLIC	locate_dosenv
	PUBLIC	cmdedit
	PUBLIC	set_type		;added by jmh 011221
;dfa added following:
	PUBLIC	cursor_type
	PUBLIC	disable_macro
	PUBLIC	appl_macro		;added by jmh 011218
	PUBLIC	min_length
	PUBLIC	cmdedit_disable
	PUBLIC	appl_disable		;added by jmh 011221

	PUBLIC	screen_width		;added by jmh 980512
	PUBLIC	initial_curpos		;added by wd (was row jmh 980630)
	PUBLIC	get_curpos		;wd made PUBLIC
	PUBLIC	disp_prompt		;moved from below by wd

	PUBLIC	cmdedit_cmd		;made public by jmh
	PUBLIC	init_screen
	PUBLIC	new_sp

	PUBLIC	temp1			;these two added by jmh 980512
	PUBLIC	temp2

	PUBLIC	delim_list		;these by jmh 980512
	PUBLIC	pend_list
	PUBLIC	invalid_list
	PUBLIC	delim_len
	PUBLIC	pend_len
	PUBLIC	invalid_len

	IFE	TSR
	PUBLIC	debug_loop
	PUBLIC	freadline
	PUBLIC	get_file_line
	PUBLIC	prompt
	ENDIF

	PUBLIC	StartupInfo		;the following publics added by jmh
	PUBLIC	sisInstData
	PUBLIC	DataBlockPtr1
	PUBLIC	DataBlockSize1
	PUBLIC	DataBlockPtr2
	PUBLIC	DataBlockSize2
	PUBLIC	DataBlockPtr3
	PUBLIC	DataBlockSize3
	PUBLIC	DataBlockPtr4
	PUBLIC	DataBlockSize4
	PUBLIC	DataBlockPtr5
	PUBLIC	DataBlockSize5
IFNDEF NODIRS
	PUBLIC	DataBlockPtr6
	PUBLIC	DataBlockSize6
ENDIF
	PUBLIC	StackBlockPtr
	PUBLIC	StackBlockSize
	PUBLIC	CmdEdit_instance_offset
	PUBLIC	CmdEdit_instance_size

DGROUP	GROUP	CSEG

CSEG	SEGMENT	BYTE PUBLIC 'CODE'
	EXTRN	install:PROC
	EXTRN	cmdedit_seg:WORD
	EXTRN	cmdedit_low:BYTE
	EXTRN	init_buffers:PROC	;added by jmh 011222
	EXTRN	hist_top:PROC
	EXTRN	read_cmdfile:PROC
	EXTRN	install_vectors:PROC	;added by jmh
	EXTRN	init_sis:PROC		;added by jmh 010405

	EXTRN	multi_cmd:PROC
	EXTRN	get_multi_line:PROC	;added by jmh 980510
	EXTRN	associate:PROC		;added by jmh 980513
	EXTRN	expand_braces:PROC
	EXTRN	expand_macro:PROC
	EXTRN	expand_symbol:PROC
	EXTRN	get_macro_line:PROC
	EXTRN	skip_whitespace:PROC
	EXTRN	skip_nonwhite:PROC
	EXTRN	stre_cmp:PROC
	EXTRN	get_kbd_line:PROC
	EXTRN	file_error:BYTE
	EXTRN	abort_install:PROC
	EXTRN	expand_var:PROC
	EXTRN	execute_defs:PROC
	EXTRN	execute_defm:PROC
	EXTRN	execute_dels:PROC
	EXTRN	execute_delm:PROC
	EXTRN	execute_rsthist:PROC
	EXTRN	execute_rstmac:PROC
	EXTRN	execute_rstsym:PROC
IFNDEF NODIRS
	EXTRN	execute_chd:PROC
	EXTRN	execute_pushd:PROC
	EXTRN	execute_popd:PROC
	EXTRN	execute_backdir:PROC
	EXTRN	execute_rstdir:PROC
	EXTRN	abort_install:PROC
ENDIF
	EXTRN	output_newline:PROC
	EXTRN	dosify_line:PROC

IFDEF CMD_PROMPT			;this section added by jmh 980623
IFNDEF NODIRS
	EXTRN	save_drv_path:PROC
ENDIF
	EXTRN	strlen:PROC
	EXTRN	xlate_lower:PROC
ENDIF
	EXTRN	history:WORD			;all these added by jmh 011221
	EXTRN	mac_stk:WORD
	EXTRN	sym_stk:WORD
	EXTRN	hist_ptr:WORD
	EXTRN	mac_ptr:WORD
	EXTRN	sym_ptr:WORD

	EXTRN	Edit_instance_offset:ABS	;All these added by jmh
	EXTRN	Edit_instance_size:ABS
	EXTRN	History_instance_offset:ABS
	EXTRN	History_instance_size:ABS
	EXTRN	Macro_instance_offset:ABS
	EXTRN	Macro_instance_size:ABS
IFNDEF NODIRS
	EXTRN	Dirs_instance_offset:ABS
	EXTRN	Dirs_instance_size:ABS
ENDIF

; Define important fields in the PSP.
	ASSUME	CS:DGROUP
	ORG	2Ch
env	dw	?	;Segment of environment block

	ORG	40h	;Use 64 bytes in PSP for application history (jmh)
apphist db	40h dup (?)

PROMPT_BUF_SIZE EQU 79	;jmh 980710: originally 80
prompt	LABEL	BYTE	;buffer used for prompt after TSRing
cmdlen	DB	?	;Offset 80h in the PSP contains length of command
;			 line when program is invoked

	ORG	80h+PROMPT_BUF_SIZE+1	;jmh 980710: add one for NUL terminator
cur_macro LABEL	BYTE	;Start of area used after TSRing to
;			 store the current macro expansion.


	ASSUME	CS:DGROUP,DS:DGROUP,ES:DGROUP,SS:DGROUP
	ORG	100h
entry:	jmp	install

; The following data & code are LOST after TSRing since the space is
; reused for other purposes.
mfilename	db	CR,VERSION_STR,CR,LF,CTL_Z ;jmh 010403
;assume above is less than 80 bytes (jmh 980514: increased filename to 80)
		db 80 - ($ - mfilename) DUP (0) ;Storage for ASCIIZ filename
mfile_handle	dw	0		;Handle for open file
;					 Also indicate if command line
;					 specified an init file (jmh)

init_over proc	near
; The command parameters have been parsed. Now get ready to terminate.
	call	init_buffers		; SI->end of buffer area
; Read in the command file
	call	near ptr read_cmdfile
; Initialize var source to get the next line from the keyboard.
	mov	source,offset DGROUP:get_kbd_line

; All data structures initialized. Now setup stack pointer, release
; unneeded memory back to DOS, set up interrupt handler and TSR.
	push	es			;save ES
	mov	es,env			;Don't need environment block
	ASSUME	ES:NOTHING
	mov	ah,49h
	int	21h			;Release the block
	IF	TSR
	call	install_vectors 	;added by jmh
	ENDIF
	pop	es			;Restore ES
	ASSUME	ES:DGROUP

	lea	dx,STACK_SIZE+15[si]	;Calculate end of TSR portion
;					 DX<-num bytes to keep resident
	and	dl,0f0h			;    rounded to para
; 	Note DX->BEYOND last byte of program
	mov	new_sp,dx		;Remember it

IF 0
	mov	di,si			;Fill the stack to see
	mov	cx,STACK_SIZE / 2	;how much is being used.
	mov	ax,0addeh
	rep	stosw
ENDIF

IFNDEF NODIRS
	mov	abort_dir_op,offset abort_processing
ENDIF

	IFE	TSR
; Don't actually TSR
debug_loop:
@init_over_10:
	call	near ptr output_newline
	lea	dx,dummy_prompt
	@DispStr dx
	mov	dx,offset DGROUP:debug_buf 	;Offset
	mov	debug_buf,DEBUG_BUFSIZE-2
	mov	ah,0Ah			;Function code
	pushf				;Simulate interrupt
	push	cs			;Simulate interrupt
	call	near ptr cmdedit_isr	;Simulate interrupt
	jmp	short @init_over_10	;Keep looping
debug_buf	db	256 DUP (?)
DEBUG_BUFSIZE	equ $-debug_buf
dummy_prompt	db	"dummy>",DOLLAR
	ENDIF

;Set up the switcher instance data. Added by jmh.
;Moved to install.asm by jmh 010405.
	call	init_sis

;jmh 011220: if loading high, relocate and exit
	rol	cmdedit_low,1
	jc	@init_over_99
	mov	es,cmdedit_seg
	mov	si,offset prompt
	mov	di,si
	sub	dx,si
	mov	cx,dx
	rep	movsb
	@Exit	0

@init_over_99:
	int	27h			;TSR
init_over endp



; Extend the resident part of the installation code to form a buffer to
; hold the prompt and one to hold the current macro line arguments.
;  - 128 bytes from PSP + initial portion of CSEG.
tsr_install_end LABEL BYTE
	IF	($-entry) LT (2+PROMPT_BUF_SIZE+LINEBUF_SIZE - 128)
	DB	(2+PROMPT_BUF_SIZE+LINEBUF_SIZE - 128 - ($-entry)) DUP (?)
	ENDIF

; Major and minor DOS versions.
dos_version_major	db	?
dos_version_minor	db	?

IFNDEF NODIRS
abort_dir_op	dw	offset abort_install	;location to jump on bad dir cmd
ENDIF
abort_entry_stack dw ?			;Storage for stack state to be
;					 restored when processing is aborted
abort_msg_hd	db '*** CMDEDIT : '     ;Header for abort message
ABORT_HDR_LEN   equ $-abort_msg_hd
abort_msg_tl	db ' Macro aborted!'    ;Tail for abort message (macro)
abort_msg_tl1	db ' ***'               ;Tail for abort message (no macro)
ABORT_TAIL_LEN  equ $-abort_msg_tl
ABORT_TAIL_LEN1 equ $-abort_msg_tl1

; The following are error messages displayed by routine abort_processing.
; ALL MESSAGES MUST BE SHORT ENOUGH TO FIT INTO LINEBUF TOGETHER WITH
; abort_msg_hd and abort_msg_tl. The order of messages must be same as
; the order of Error code definitions in file common.inc
; jmh 011223: store length as a byte, as part of the message (not in the table).
abort_msg_table LABEL	BYTE
line_trunc_msg	 db	saw_sig_msg-$-1,      'Line too long.'
saw_sig_msg	 db	nested_macro_msg-$-1, 'Command aborted by user.'
nested_macro_msg db	nested_delm_msg-$-1,  'Nested macro definition.'
nested_delm_msg  db	ctrl_brk_msg-$-1,     'DELM used inside macro.'
ctrl_brk_msg	 db	ctrl_brk_end-$-1,     'Control-Break.'
ctrl_brk_end	 LABEL	BYTE
IFNDEF NODIRS
dirstk_empty_msg db	dirstk_msg-$-1,       'Directory stack empty.'
dirstk_msg	 db	abort_msg_end-$-1,    'Invalid dir or stack full.'
ENDIF
abort_msg_end	LABEL	BYTE

; The following table holds pointers to each entry in the message table above.
abort_msg_ptrs	LABEL	WORD
		dw	line_trunc_msg
		dw	saw_sig_msg
		dw	nested_macro_msg
		dw	nested_delm_msg
		dw	ctrl_brk_msg
IFNDEF NODIRS
		dw	dirstk_empty_msg
		dw	dirstk_msg
ENDIF

;+----------------------------------------------------------+
;| CMDEDIT commands. All commands preceded by a length byte.|
;| For each command that is added, make sure you update the |
;| table cmd_func_table below.				    |
;+----------------------------------------------------------+
cmd_table	LABEL	BYTE
defs		db	4,'defs'	;Define a single line macro
defm		db	4,'defm'        ;Start multiline macro definition
dels		db	4,'dels'	;Delete a symbol
delm		db	4,'delm'	;Delete a macro
rsthist		db	7,'rsthist'	;Reset history stack
rstmac		db	6,'rstmac'	;Reset macro buffer
rstsym		db	6,'rstsym'	;Reset symbol buffer
IFNDEF NODIRS
chd		db	3,'chd'         ;Change disk and directory
pushd		db	5,'pushd'	;Push on directory stack
popd		db	4,'popd'	;Pop from directory stack
backdir 	db	4,'back'	;Change to previous directory
rstdir		db	6,'rstdir'	;Reset directory stack
ENDIF
cmd_table_end	db	0		;Terminate with a 0
MAX_CMD_LEN	equ	7		;Length of longest command
; Note endm is not a command except during a macro definition.
endm_cmd	db	4,'endm'	;End multiline macro definition


;+--------------------------------------------------------------+
;| CMDEDIT command functions. Must be in same order as commands.|
;+--------------------------------------------------------------+
cmd_func_table	label WORD
		dw	execute_defs
		dw	execute_defm
		dw	execute_dels
		dw	execute_delm
		dw	execute_rsthist
		dw	execute_rstmac
		dw	execute_rstsym
IFNDEF NODIRS
		dw	execute_chd
		dw	execute_pushd
		dw	execute_popd
		dw	execute_backdir
		dw	execute_rstdir
ENDIF

;jmh 980512: list of characters for various functions.
;Invalid filename characters. '@' is not actually invalid, but since it's mostly
;used for response lists, I'll include it.
invalid_list	db	'"[]=@'
pend_list	db	',;+'                   ;Brace expansion delimiters.
;DOS delimiters. command.com also includes ',;=' as delimiters, but using them
;prevents their use as symbol/macro commands. (Speaking of which, DON'T use the
;delimiters for that, otherwise it'll crash.)
delim_list	db	SPACE,TAB,'<|>/'
delim_len	=	$ - offset delim_list
pend_len	=	$-1 - offset pend_list		;These two don't
invalid_len	=	$-1 - offset invalid_list	;include /
;
;Switcher global data structures
;
StartupInfo     =       $
 sisVersion     dw      3                       ;Switcher structure ID
 sisNextDev	dw	0,0			;Ptr to prev startup structure
 sisVirtDevFile dd      0                       ;Ptr to name of opt dev drvr
 sisReferenceData dd    0                       ;Data for Win dev drivr
 sisInstData	dw	DataBlockPtr1,0 	;Ptr to instance mem list

DataBlockPtr1	dw	40h,0			;app. hist., prompt, cur_macro
DataBlockSize1	dw	40h+PROMPT_BUF_SIZE+2+LINEBUF_SIZE
DataBlockPtr2	dw	CmdEdit_instance_offset,0
DataBlockSize2	dw	CmdEdit_instance_size
DataBlockPtr3	dw	Edit_instance_offset,0
DataBlockSize3	dw	Edit_instance_size
DataBlockPtr4	dw	History_instance_offset,0
DataBlockSize4	dw	History_instance_size
DataBlockPtr5	dw	Macro_instance_offset,0
DataBlockSize5	dw	Macro_instance_size
IFNDEF NODIRS
DataBlockPtr6	dw	Dirs_instance_offset,0
DataBlockSize6	dw	Dirs_instance_size
ENDIF
StackBlockPtr	dw	0,0
StackBlockSize	dw	0
	        dd      0                       ;Ptr to next block = 0 to
		dw	0			;  terminate list

CmdEdit_instance_offset = $


;+-------------------------+
;| CMDEDIT state variables |
;+-------------------------+
; The variables source and macro_level together indicate the source of
; the next line. If macro_level is non-zero, the next line is obtained
; from an ongoing macro expansion. If macro_level is 0, then the
; variable source contains the address of the function to call to
; return the next line. This will be either get_kbd_line, get_int21_line (wd),
; or get_file_line.
macro_level	db	0		;byte by jmh
cmd_level	dw	0		;added by jmh - multiple commands
source		dw	?		;filled in during initialization


IF EXTRA_HANDLER
linebuf_kludge	db	?		;added by wd
ENDIF
linebuf_prefix	db	0		;Fill byte/Sentinel before linebuf.
;					 Used in code to allow uniform
;					 checking of first linebuf character.
linebuf		db	LINEBUF_SIZE DUP (?)	;Temporary line buffer.
linebuf_suffix	db	?			;Need a byte at end of
;						 linebuf in various places
macro_ignore_char db	';'             ;Character used to prevent macro,
;					 symbol and brace expansion.
disable_macro	db  0	;added by dfa. -1 to disable macro & symbol t'lation
cmdedit_disable db  0	;added by dfa. -1 to disable CMDEDIT
appl_disable	db  0	;added by jmh. -1 to disable CMDEDIT (appl)
min_length	dw  1	;added by dfa. Minimum line length to store.
;msg_flag	db  0	;added by dfa. If -1, changes error message strings
lastchar	dw	?		;Points beyond last char in the line
cur_macro_len	dw	?		;Length of data in cur_macro
dot		dw	?		;Current position in line
disp_begin	dw	?		;disp_begin and disp_end are
disp_end	dw	?		; markers into the line buffer
;					  that are used to keep track
;					  of the range that has been
;					  changed. This is used to
;					  selectively update the display.
edit_mode	db	?		;-1 if insert mode, else 0
default_imode	db	0		;By default overtype mode
cursor_type	db	0	;Added by dfa.	Default 0 indicates unchanged
;				 for overtype mode, changed for insert mode.
;				 Set to 1 to reverse behaviour.
linelimit	dw	?		;Upper limit for linebuf based
;					 on user's buffer length
in_appl 	db	0		;0 if dos, -1 if application
appl_macro	db	-1		;jmh 011218: macros allowed in app?
old_dis_macro	db	0		;Added by jmh. Remember the disable_-
;					 macro flag in application.
IFNDEF NODIRS
user_command	db	0		;This is set to 1 by certain
;					 CMDEDIT commands to return a
;					 string to the caller.
;					 (Basically put in as a kludge
;					 to get the prompt right after
;					 a pushd/popd/chd)
ENDIF
;+------------+
;| Video data |
;+------------+
video_data	label	word		;added by jmh 980516
IFDEF CMD_PROMPT
screen_attr	db	CMD_COL 	;Display attribute added by jmh 980516
ELSE
screen_attr	db	7
ENDIF
video_page	db	?		;Current video page
screen_width	db	?		;width of screen

initial_curpos	label	word		;Initial cursor position
initial_curcol	db	?		;initial cursor column
initial_currow	db	?		;initial cursor row

IF TOGGLE_CURSOR			;added by jmh 980509
;Next two words must be contiguous
imode_cursor	dw	?		;Cursor for insert mode
omode_cursor	dw	?		;Cursor for overtype mode
caller_cursor	dw	?		;Cursor shape of caller
ENDIF

silent		db	0		;non-0 if bell should not be rung

;+-------------------------------------------------------------------------+
;|Storage areas for various registers when called through INT 21 interface.|
;+-------------------------------------------------------------------------+

ssreg	dw	?
spreg	dw	?

; check_break is set to 1 on entry to cmdedit, and restored to 0 on exit. If
; 1 on entry, then calling program must have been aborted with a break or
; critical error. The CMDEDIT Ctrl-Break ISR increments this flag every
; time it is called. If it is > 1, inside CMDEDIT, it indicates that a
; ctrl-break was entered. This allows runaway macros and symbols to be
; aborted.
check_break	dw	0
trap_break	db	0		;If 1, does not allow original
;					 Ctrl-Break handler to see the
;					 Ctrl_break

; Create a couple of temporary storage variables. (It takes at least 5 bytes
; to create a stack frame, which is wasteful if you only want 2 or 4 bytes.)
temp1		dw	?
temp2		dw	?

CmdEdit_instance_size = $ - offset CmdEdit_instance_offset

IF  EXTRA_HANDLER		;these 3 lines added by wd
ahreg	db	0Ah
ENDIF

old_int21h LABEL DWORD		;Storage for previous int 21h vector
old_int21vec	DW	2 DUP (?)

new_sp		dw	?	;Store our stack ptr (bottom of stack).
				;This is first para BEYOND cmdedit's memory.

prev_isr1b	dd	?	;Previous control break handler
prev_isr2f	dd	?	;Previous multiplex


;+
; FUNCTION : our_mpx_handler
;
;	Handle the switcher instance data and DOSKey interface.
;-
our_mpx_handler proc far
	ASSUME	CS:DGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING
	cmp	ah,48h			;DOSKey
	je	@doskey
	cmp	ax,1605h		;See if Windows launch
	je	@init_instance
	cmp	ax,4b05h		;See if switcher get instance
	je	@init_instance		;  data.
@old_int_2f:
	jmp	CS:prev_isr2f

@doskey:
	cmp	al,10h			;DOSKey get line
	jne	@doskey_installed
	rol	cmdedit_disable,1	;Is cmdedit disabled? (jmh 010402)
	jc	@old_int_2f		;Yes, ignore it.
	mov	in_appl,0		;Indicate it's DOS
	call	cmdedit_start
	xor	ax,ax
	iret

@doskey_installed:
	or	al,al			;DOSKey installation check
	jne	@old_int_2f
	push	cs
	pop	es
	mov	ax,0aaceh		;CmdEdit's signature
	iret

@init_instance:
	pushf
	call	cs:prev_isr2f		;Call old int
	mov	cs:sisNextDev,bx
	mov	cs:sisNextDev+2,es
	push	cs			;ES:BX point to switcher struc
	pop	es
	mov	bx,offset cs:StartupInfo
	iret

our_mpx_handler endp



;+
; FUNCTION : cmdedit_isr
;
;	This is our replacement for the DOS INT 21h handler.
;
; Parameters:
;	AH = function code
;
; Register(s) destroyed:
;-
cmdedit_isr proc far
	ASSUME	CS:DGROUP,DS:NOTHING,ES:NOTHING,SS:NOTHING
	pushf				;Save flags
	cmp	ah,0Ah			;Is it the buffered input function ?
	IF  EXTRA_HANDLER		;This bit added by wd,
	je	@cmdedit_isr_4		;slightly modified by dfa.
	cmp	ah,0EAh			;Is this the kludged input function?
	ENDIF
	jne	@cmdedit_isr_5		;No. Added by dfa
@cmdedit_isr_4:
	rol	appl_disable,1		;Is cmdedit disabled? ;added by dfa
	jnc	@cmdedit_isr_10 	;If not go on carry out our duty
@cmdedit_isr_5:
	popf				;restore flags
	jmp	cs:old_int21h		;and execute the original ISR
@cmdedit_isr_10:
	IF  EXTRA_HANDLER		;added by wd
	xchg	cs:ahreg,ah		;Save call type
	test	ah,ah			;and check if this is a check
	js	@cmdedit_isr_20 	;If so, just return with ahreg changed
	ENDIF
	push	ax
	mov	in_appl,-1		;This section modified by jmh 011218.
	mov	al,appl_macro		;Do macro/symbol/brace expansion
	xchg	al,disable_macro	;independently of the command line.
	mov	old_dis_macro,al
	pop	ax
	call	cmdedit_start
	push	ax
	mov	al,old_dis_macro
	mov	disable_macro,al
	pop	ax
	IF  EXTRA_HANDLER   ;added by wd
	mov	cs:ahreg,0Ah
	ENDIF

@cmdedit_isr_20:
	popf
	iret
cmdedit_isr	endp



;+
; FUNCTION : cmdedit_start
;
;	Routine called by the interrupts to setup the cmdedit function proper.
;	Created by jmh due to using int2f as well.
;-
cmdedit_start proc near
					;Save registers
	mov	cs:ssreg,ss		;Stack segment
	mov	cs:spreg,sp		; and pointer
	cli				;Wanna change stack
	push	cs
	pop	ss
	mov	sp,cs:new_sp		;Bottom of stack
	ASSUME	SS:DGROUP
	sti				;OK to interrupt now
	@saveall
	xchg	bx,dx
	mov	al,byte ptr ds:[bx]	;Length of caller buffer
	xchg	dx,bx
	xor	ah,ah			;AX<-length of caller's buffer
	push	ds			;Save user segment
	mov	cx,cs
	mov	ds,cx			;Init DS, ES to point to DGROUP
	mov	es,cx
	ASSUME	DS:DGROUP,ES:DGROUP
	add	ax,offset dgroup:linebuf ;AX->last allowable linebuf
;					  location + 1
	dec	ax			;Need room for CR at end of line
	mov	linelimit,ax		;Store it
	pop	ax			;AX <- User's buffer segment
					;DX already contains offset of
					; user buffer
	call	near ptr cmdedit	;Main routine
	@restoreall
	cli
	mov	ss,cs:ssreg
	mov	sp,cs:spreg
	sti
	ret
cmdedit_start endp



;+
; FUNCTION : cmdedit
;
;	Main routine called by the INT 21h/2fh ISR to get next line.
;	General Algorithm:
;	(1) Get the next line from the keyboard/macro expansion/file.
;	(2) Check for line begins with a macro. If so, expand it and
;		repeat step (2). Else go onto step (3).
;	(3) Check if the line is an internal CMDEDIT command. If so, execute
;		it and return to step (1).
;	(4) Copy line to caller's buffer and return.
;
; Parameters:
;	AX	= segment of user's buffer
;	DX	= offset of user's buffer
;
; Returns:
;	The next input line is copied into the user's buffer.
; Register(s) destroyed:
;	All except segment registers.
;-
cmdedit	proc	near
	push	es			;Save ES
	push	ax			;Caller's buffer segment
	push	dx			;Caller's buffer offset
	mov	cx,1
	mov	trap_break,cl		;Trap Ctrl-Break handler
	xchg	cx,CS:check_break	;Check if last call did not
;					 exit normally. Also set flag
;					 for this call.
	jcxz	@cmdedit_0		;Last exit was OK
	shr	macro_level,1		;No it was not, so reset input
	mov	source,offset DGROUP:get_kbd_line
@cmdedit_0:
	IF  EXTRA_HANDLER		;added by wd
	cmp	ahreg,0Ah		;Is this a normal readline?
	je	@cmdedit_00		;If not, use int21 for normal readline
	mov	source,offset DGROUP:get_int21_line
@cmdedit_00:
	ENDIF
	call	near ptr init_screen	;Get screen/cursor data

	mov	cl,in_appl
	mov	ch,0
	call	near ptr set_type	;Set the descriptors (DOS/appl)

; cmdedit_abort_entry is the entry point when command proessing is
; aborted for any reason. It is jumped to from abort_processing
	mov	abort_entry_stack,sp	;Remember stack state
cmdedit_abort_entry	LABEL PROC
@cmdedit_3:
	call	near ptr reset_line	;Reset cursor, line etc.
	call	near ptr get_next_line	;Get the next line from appropriate
;					 source (stored in linebuf)
	rol	disable_macro,1 	;added by jmh 980513
	jc	@cmdedit_10
	call	multi_cmd		;added by jmh

@cmdedit_10:
	cmp	check_break,2		;Check for any control breaks
	jb	@cmdedit_11a		;No ctrl-breaks
	mov	check_break,1
	mov	al,E_CTRL_BREAK		;Message number
	jmp	abort_processing

@cmdedit_11a:  ;added by dfa
;If macros disabled, do not do a macro or symbol expansion, or even check
;for ignore character.
;jmh 011222: skip internal commands and variable expansion.
	rol	disable_macro,1
	jc	@cmdedit_30a

;If the first character is an ignore character, do not do a macro or symbol
;expansion.
	call	get_line_len
	jcxz	@cmdedit_15		;Empty line, keep going since it
;					 can still be a macro or symbol
	mov	al,[si]			;AL<-first char of line
	cmp	al,macro_ignore_char
;	lahf				;jmh 980510: save the result
	je	@cmdedit_13
	cmp	al,'@'			;should we convert / to \ and - to / ?
	jne	@cmdedit_15		;No
	call	near ptr dosify_line	;dosify-it
; First is an ignore character so move up all characters and return
@cmdedit_13:
	mov	di,si			;DI->start of line
	inc	si			;SI->first char to copy
	dec	cx			;1 less character
	dec	lastchar
;	Assume ES==DS, direction flag clear
	rep	movsb			;Move the bytes
;	sahf				;ZF = ignore; NZ = dosify
;	jz	@cmdedit_30		;Also skip internal commands (jmh)
;	jmp	short @cmdedit_25	;jmh 980510: Should dosify do expansion?
	jmp	short @cmdedit_30
;jmh 980511: Dosify line currently skips the internal commands (since they
;	     don't need to be dosified). It also skips the expansion functions
;	     (which is what 2e6 did). If you'd like the internal commands to
;	     work, uncomment the lahf, sahf and two jumps and comment the third
;	     jump. If you want expansion, uncomment the lahf, sahf and first
;	     jump, and comment the third jump.

@cmdedit_15:
	call	near ptr expand_braces	;Do any brace expansion (mvd jmh 010405)
					;(moved back jmh 010705)
	call	near ptr associate	;added by jmh 980517
	jnc	@cmdedit_10		;added by jmh 990526
	call	near ptr expand_symbol	;Check if symbol and expand
	jnc	@cmdedit_10		;If expanded, recurse
	call	near ptr expand_macro	;Check if line is a macro
;					 and expand if possible.
	jnc	@cmdedit_10		;If expanded, do recursively.
;					 (note that currently recursion
;					 will take place only on the
;					 last line of a macro definition)

@cmdedit_25:
	call	near ptr cmdedit_cmd	;Check if CMDEDIT command
	jnc	@cmdedit_3		;Internal command, loop back

@cmdedit_30:
; Expand variables if any.
	call	near ptr replace_vars
@cmdedit_30a:				;added by jmh
; Check if line too long for user buffer.
	mov	ax,lastchar		;AX->last character in buffer
	cmp	ax,linelimit
	ja	@cmdedit_80		;We're not OK
	sub	ax,offset DGROUP:linebuf ;AX<-length of line
; OK now we have a line to give to the caller. Copy it into caller's
; buffer and return.
	pop	di			;Caller's buffer offset
	pop	es			;Caller's buffer segment
	inc	di			;ES:DI->second byte of user buffer
	stosb				;Store line length
	mov	si,offset DGROUP:linebuf ;SI->Source string
	xchg	cx,ax			;CX<-length of string
	rep	movsb			;Copy bytes
	mov	al,CR
	stosb				;Store terminating carriage-return
; Set cursor shape to caller's shape
	call	near ptr restore_cursor ;Restore user's cursor shape
	xor	ax,ax			;jmh 980508 - ax/al instead of 0
	mov	check_break,ax		;Reset flag
	mov	trap_break,al		; Ctrl-Break handler
	pop	es			;Restore ES
	ret

@cmdedit_80:
	jmp	near ptr line_too_long
cmdedit endp



;+
; FUNCTION : set_type
;
;	Point to the DOS or application descriptors for the current caller.
;
; Parameters:
;	CX =	0 if DOS, anything else for application
;
; Returns:
;	Nothing.
;
; Registers destroyed:
;	AX,BX,DX
;-
set_type proc near
	mov	bx,offset DGROUP:history ;address of first descriptor
	mov	ax,offset DGROUP:mac_stk
	mov	dx,offset DGROUP:sym_stk
	jcxz	@set_type_1		 ;if DOS, bx OK
	add	bx,TYPE $string_stack	 ;else point to application descriptor
	add	ax,TYPE $string_stack
	add	dx,TYPE $string_stack
@set_type_1:
	mov	hist_ptr,bx
	mov	mac_ptr,ax
	mov	sym_ptr,dx
	ret
set_type endp



;+
; FUNCTION : get_next_line
;
;	Gets the next line from the appropriate source and stores it in
;	the line buffer. THe source of the line may be either a macro
;	expansion or a file or the keyboard.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing
; Register(s) destroyed:
;-
get_next_line proc near
	mov	lastchar,offset DGROUP:linebuf
					;Empty line (in case not
;					 already done)
	call	near ptr get_macro_line	;Get next line in expansion
	jnc	@get_next_line_99	;Jump if there is a next line
	call	near ptr get_multi_line ;Get next command on the line
	jnc	@get_next_line_99	;No other command, so
					;get line from keyboard/file
@get_next_line_10:
	jmp	[source]		;get_{kbd,int21,file}_line

@get_next_line_99:
;jmh 011218: output a CR for programs that expect it (such as the DEBUG
;	     clone by Paul Vojta).
	@DispCh CR
	ret
get_next_line endp


	IF  EXTRA_HANDLER
;+
; FUNCTION : get_int21_line by wd
;
;	Call readline through the int21 handler.  If we found ourselves,
;	the entry code changed ahreg to 0Ah and promptly returned, so use
;	this as a flag to switch to get_kbd_line if found.  Otherwise,
;	someone else handled the command input, so grab their line and
;	run with it.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing
; Register(s) destroyed:
;-
get_int21_line proc near
	mov	dx,offset DGROUP:linebuf_kludge
	mov	ax,linelimit
	sub	ax,dx
	dec	ax
	mov	bx,dx
	mov	[bx],al
	mov	ah,0Ah
	int	21h
	cmp	ahreg,0Ah
	jne	get_int21_line_10
	jmp	get_kbd_line
get_int21_line_10:
	xor	ax,ax
	xchg	linebuf_prefix,al
	add	ax,offset DGROUP:linebuf
	mov	lastchar,ax
	ret
get_int21_line endp
	ENDIF



;+
; FUNCTION : replace_vars
;
;	Replaces all the variables in the current line with their
;	expansions. If the line is too long, aborts with a truncation
;	error.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
replace_vars proc near
	call	near ptr expand_var		;CF set if error. AX
;						 contains error code
	jnc	@replace_vars_99
	jmp	near ptr abort_processing	;Abort processing

@replace_vars_99:
	ret
replace_vars endp



;+
; FUNCTION : get_curpos
;
;	Returns the current cursor position.
;
; Parameters:
;	Global	video_page indicates the page.
;
; Returns:
;	DX	= Current cursor position.
;	CX	= Current cursor scan lines.
; Register(s) destroyed: AX,BX
;-
get_curpos proc	near
	@GetCur	video_page
	ret
get_curpos	endp



;+
; FUNCTION : set_disp_marks
;
;	Sets the marks disp_begin and disp_end to indicate the start
;	and end positions in the line that have been changed. The
;	routine is passed two parameters which indicate
;	the potentially new values for disp_begin and disp_end
;	respectively. However the global disp_begin is changed only if
;	the new value is less than the current value. Similarly
;	disp_end is changed only if the new value is greater than the
;	current value.
;
; Parameters:
;	AX	= potential disp_end
;	DX	= potential disp_begin
;
; Returns:
;	Nothing.
;	May set globals disp_begin and disp_end.
;
; Register(s) destroyed: None.
;-
set_disp_marks	proc near
	cmp	ax,disp_end		;New value greater ?
	jb	@set_disp_marks_10	;No
	mov	disp_end,ax		;New disp_end
@set_disp_marks_10:
	cmp	dx,disp_begin		;New value smaller
	jnb	@set_disp_marks_20	;No
	mov	disp_begin,dx		;New disp_begin
@set_disp_marks_20:
	ret
set_disp_marks	endp



;+
; FUNCTION : disp_line
;
;	Displays the current contents of the line buffer. Since the
;	entire line is not redisplayed everytime, all procedures that
;	change the contents of the line buffer have to follow certain
;	rules in order to make sure the display correctly shows the
;	line. The variable disp_begin must be set to the earliest
;	position in the line that has been changed and disp_end to beyond
;	last position in the line that has been changed.
;
; jmh 980518: DOS goes directly to screen; application goes to stdout.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing
;
; Register(s) destroyed:
;-
disp_line proc near
	@save	si,di
	mov	ax,disp_begin		;Lower limit of changed chars
	mov	si,ax
	mov	cx,disp_end		;CX->byte after last char that
;					 has changed
	sub	cx,si			;CX<-num chars to be output
	jcxz	@disp_line_90		;Nothing to be updated
	call	near ptr line_to_scr	;Move cursor to corresponding
;					 position on the screen.
					;DX<-current cursor position
;	OK, now we are ready to begin updating the screen.
	inc	cx			;Counter the initial loop
	jmp	short @disp_line_25
@disp_line_10:				;Loop to output chars
	push	cx			;Save count
	lodsb				;AL<-next char
	rol	in_appl,1
	jnc	@disp_line_15
	push	dx			;Keep the cursor position
	@DispCh al
	pop	dx
	jmp	short @disp_line_16
@disp_line_15:
	mov	ah,09h			;jmh 980516: write directly to screen
	mov	bx,video_data		;BIOS display character with attribute
	mov	cx,1			;One's enough
	int	10h
@disp_line_16:
	inc	dx			;Next column
	cmp	dl,screen_width 	;End of the screen ?
	jb	@disp_line_18		;No
	push	dx			;Save the row
	rol	in_appl,1		;If it's an application, the
	jc	@disp_line_17		; line has already wrapped.
	call	near ptr output_newline ;Next line
@disp_line_17:
	call	near ptr get_curpos
	pop	ax			;AH->old row, DH->new row
	cmp	dh,ah			;If we're on the same row
	jne	@disp_line_20		; the screen has scrolled and
	dec	initial_currow		; the prompt has moved up.
@disp_line_18:
	@SetCurPos			;Set the new column
@disp_line_20:
	pop	cx
@disp_line_25:
	cmp	si,lastchar		;Beyond last char?
	loopne	@disp_line_10		;Keep looping until count exhausted or 
;					 beyond last char
;	Now all changed positions have been displayed. If CX is not 0, 
;	then the remaining char positions have to be 
;	replaced with blanks. Note that since we are now overwriting  
;	previously displayed positions, no need to check for line 
;	wraparound or scroll. 

	jcxz	@disp_line_90		;No more chars

	mov	dl,' '                  ;Overwrite with blanks
	mov	ah,2			;DOS service display character
@disp_line_30:
	int	21h			;jmh 980509: replaced @DispCh
	loop	@disp_line_30

@disp_line_90:
	mov	ax,dot
	mov	disp_begin,ax		;Initialize for next call
	mov	disp_end,ax
	@restore
;	call	near ptr line_to_scr	;Set cursor at dot
	;jmh 011223: fall through
;	ret
disp_line endp



;+
; FUNCTION : line_to_scr
;
;	Places the cursor at the screen position corresponding to a
;	specific position in the line buffer. The entire line buffer
;	upto that position must have been displayed before.
;
; Parameters:
;	AX	= Pointer into the line buffer
;
; Returns:
;	DX	= cursor position
;
; Register(s) destroyed: AX, BX
;-
line_to_scr proc near
	sub	ax,offset DGROUP:linebuf ;AX<-num chars
	add	al,initial_curcol	;Compensate for initial position.
	adc	ah,0			;AX is now the 'virtual column'
	div	screen_width
	add	al,initial_currow	;Increment the row
	xchg	ah,al
	xchg	dx,ax			;DX<-screen position
	@SetCurPos video_page		;Set the cursor position
	ret
line_to_scr endp



;+
; FUNCTION : get_line_len and get_len
;
;	Point SI to the beginning of the line buffer (get_line_len) and set
;	CX to the length of the line.
;
; Parameters:
;	None.
;
; Returns:
;	SI -> linebuf
;	CX := length of line
;
; Registers destroyed:
;	None.
;-
get_line_len proc near
	mov	si,offset DGROUP:linebuf ;SI->line buffer
get_len label near
	mov	cx,lastchar		;End of line
	sub	cx,si			;CX<-length of line
	ret
get_line_len endp



;+
; FUNCTION : insert_chars
;
;	Inserts a string of chars at the specified position in the
;	linebuffer. If the length would exceed the size of the line buffer,
;	chars are only stored until the buffer is full and the carry flag is
;	set. Dot is updated appropriately.
;
; Parameters :
;
;	SI	- ptr to source string
;	DI	- ptr to insert position. This must lie in the line buffer.
;	AX	- length of source string
;
; Returns:
;	CF	= 1 if could not be fitted into linebuf
;		  0 otherwise
;
; Registers destroyed:
;	AX,CX,DX
insert_chars proc near
	@save	si,di
	mov	dx,di			;Save insert position in DX
	mov	di,lastchar		;First empty position
	mov	cx,linelimit
	sub	cx,di			;Subtract current last position
;					 CX<-max chars that will fit
	cmp	cx,ax			;Will all chars fit ?
	jb	@insert_chars_5 	;Not all chars will fit
	xchg	ax,cx			;All chars will fit
@insert_chars_5:
;	CX is number of chars to insert
	pushf				;Remember CF
;	Make place for the characters to be inserted by moving current
;	characters up by CX.
	mov	ax,di
	sub	ax,dx			;AX<-num chars to move
	push	si			;Remember source address
	mov	si,di			;SI->first char to be moved
	add	di,cx			;DI -> new value of lastchar
	mov	lastchar,di		;Store it
	xchg	ax,cx			;AX<-num chars to insert
;					 CX<-num chars to move
	std				;Direction is downward
	cmpsb				;Decrement SI,DI
	rep	movsb			;Make place
	cld
	pop	si			;Restore string source
; Before inserting the chars, update the dot if it is affected.
	cmp	dot,dx			;Is the dot at or after the insert
;					 position ?
	jb	@insert_chars_50	;No, jump
	add	dot,ax			;Else update the dot
@insert_chars_50:
	mov	di,dx			;DI->insert position
	xchg	cx,ax			;CX<-num chars to insert
	rep	movsb			;Copy string into linebuffer
	mov	ax,lastchar
	call	near ptr set_disp_marks	;AX,DX are parameters
	popf				;Restore CF

	@restore
	ret
insert_chars endp



;+
; FUNCTION : insert_at_dot
;
;	Inserts a string of characters into the line buffer in the
;	position pointed to by dot. If the length specified in global
;	caller_buflen will be exceeded,chars are only stored until the
;	buffer is full and CF is set.
;
; Parameters:
;	SI	= ptr to source string
;	AL	= length of string
;
; Returns:
;	CF	= 1 if could not be fitted into linebuf
;		  0 otherwise
; Register(s) destroyed:
;	<TBA>
;-
insert_at_dot proc near
	@save	si,di
	xor	ah,ah			;AX<-length of source string
	mov	di,dot			;DI-> insert position
	call	near ptr insert_chars	;Params SI,DI,AX, returns status in CF
	@restore
	ret
insert_at_dot endp



;+
; FUNCTION : remove_chars
;
;	Removes a string of chars at the specified position in the
;	linebuffer. The display markers and the lastchar global are updated
;	accordingly. 
;
; Parameters :
;
;	SI	- ptr to position in linebuf from which to delete
;	AX	- number of chars to delete.
;
; Returns:
;	Nothing.
;
; Registers destroyed:
;	AX,CX,DX
remove_chars proc near
	@save	si,di
	xchg	di,ax			;Save delete count

; First update the display markers
	mov	ax,lastchar
	mov	dx,si
	call	near ptr set_disp_marks	;AX,DX params
	sub	ax,si			;Num chars after delete position
	cmp	ax,di			;More than the specified number ?
	jb	@remove_chars_10	;No, so just delete that many bytes
	xchg	ax,di
@remove_chars_10:
; AX is number of bytes to delete. See if the dot needs to be updated.
	mov	di,si			;DI->delete position
	add	si,ax			;SI->first char after delete string
	cmp	di,dot
	jnb	@remove_chars_40	;dot before delete pos, so
;					 unaffected
	cmp	si,dot			;Is dot beyond delete range
	jb	@remove_chars_30	;Yes
; dot is in delete region. Update it to point to first delete position
	mov	dot,di
	jmp	short @remove_chars_40
@remove_chars_30:
; dot is beyond delete position. So subtract delete bytes from it.
	sub	dot,ax

@remove_chars_40:
; Now that the screen markers and dot have been updated, get down to the
; real business at hand. SI points to first char after delete string, DI is
; the delete position. AX is number of bytes to be deleted.
	call	near ptr get_len	;CX<-num bytes to move
	sub	lastchar,ax		;Update lastchar
	rep	movsb			;Move 'em
; All done

	@restore
	ret
remove_chars endp



;+
; FUNCTION : erase_to_dot
;
;	Deletes all characters from the line buffer between the
;	positions AX and dot. (Either AX or dot may specify the
;	beginning of range to be deleted). The markers disp_begin and
;	disp_end are set to reflect the changed positions in the line.
;	Global lastchar is also updated.
; Parameters:
;	AX	= One endpoint of the range to be deleted.
;	Global	dot is the other.
; Returns:
;	Nothing.
; Register(s) destroyed:
;-
erase_to_dot proc near
	@save	si
	mov	si,dot
	cmp	ax,si			;Make sure AX is after dot
	jnb	@erase_to_dot_10	;Yes it is
	xchg	si,ax			;Else exchange
@erase_to_dot_10:			;AX is low end, SI high end
	sub	ax,si			;AX is num bytes to delete
	call	near ptr remove_chars	;Delete AX chars starting at [SI]
	@restore
	ret
erase_to_dot endp



;+
; FUNCTION : cmdedit_cmd
;
;	Checks if the line buffer contains a CMDEDIT command and if so
;	executes it.
;
; Parameters:
;	None.
;
; Returns:
;	CF	= 0 if the line was a command
;		  1 otherwise.
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
cmdedit_cmd proc near
	@save	si,di
	call	get_line_len			;SI->linebuf
						;CX<-num chars in linebuf
	call	near ptr skip_whitespace	;SI->first non-whitespace char
;						 CX<-num remaining chars
	jcxz	@cmdedit_cmd_98			;No command on line
	mov	di,si				;DI->first char of word
; Skip first word (name of this command)
	call	near ptr skip_nonwhite		;SI->first whitespace after
;							 command name
;						 CX<-num remaining chars
	xchg	ax,si
	sub	ax,di				;AX<-num chars in word
	cmp	al,MAX_CMD_LEN			;Word too long to be command?
	ja	@cmdedit_cmd_98			;Yes, exit
; Now search thru the command table to see if the first word in the line is a
; CMDEDIT command. Currently, DI->start of word, AX = num chars in word
; jmh 990520: AH = CH = 0, since line length can never be above 255.
	mov	si,offset dgroup:cmd_table	;SI->Start of commands (name)
	mov	bx,offset dgroup:cmd_func_table ;BX->Start of commands (func)

@cmdedit_cmd_10:
	mov	cl,[si]				;CX<-Length of command
	jcxz	@cmdedit_cmd_98			;End of table, exit
	inc	si				;SI->command
	cmp	cx,ax				;Lengths match
	jne	@cmdedit_cmd_30 		;No, go try next command
	xchg	dx,ax				;DX<-num chars in word
	call	near ptr stre_cmp		;Compare strings
	xchg	ax,dx				;AX<-num chars in word
	je	@cmdedit_cmd_50 		;Command matched

	mov	cx,ax				;CX->length of command (jmh)
@cmdedit_cmd_30:
	add	si,cx				;SI->length of next command
	inc	bx				;Increment the command pointer
	inc	bx
	jmp	short @cmdedit_cmd_10		;Try next command

@cmdedit_cmd_50:				;Found command
	mov	si,di				;SI->first char of command
	add	si,ax				;SI->first char after command
	call	get_len 			;CX<-num chars after command
	call	[bx]				;Execute it
;						 Params:
;						 SI->first char after command
;						 CX = remaining length of line
;						 (after command)
	IF  EXTRA_HANDLER   ;added by wd
	cmp	source,offset DGROUP:get_int21_line
	jne	@cmdedit_cmd_55
	mov	source,offset DGROUP:get_kbd_line
	IFNDEF NODIRS
	mov	user_command,1
	ENDIF
@cmdedit_cmd_55:
	ENDIF
	cmp	source,offset DGROUP:get_kbd_line
	jne	@cmdedit_cmd_60
	call	near ptr output_newline ;this and next 2 lines added by wd
IFNDEF NODIRS
	cmp	user_command,1	  	;If line's being returned to dos
	je	@cmdedit_cmd_60 	;skip the prompt
ENDIF
IFE  EXTRA_HANDLER			;added by jmh, because of NODIRS
	call	near ptr disp_prompt		;Display user prompt
ENDIF
@cmdedit_cmd_60:
IFNDEF NODIRS
	shr	user_command,1		; klugery here for PUSHD/POPD/CHD
;					 to intentionally return a
;					 blank line to DOS in order to
;					 get prompt right.
ELSE
	clc
ENDIF
	jmp	short @cmdedit_cmd_99
@cmdedit_cmd_98:				;No command found
	stc					;CF = 1
@cmdedit_cmd_99:
	@restore
	ret
cmdedit_cmd endp



;+
; FUNCTION : abort_processing
;
;	Called by various routines in case of any errors that require
;	aborting of any ongoing processing. An error message is
;	displayed and CMDEDIT state is reset to accept input from the
;	keyboard. The routine adjusts the stack pointer to a previously
;	stored state. Execution then continues at an `abort entry'
;	point. The routine does NOT return to the caller.
;
; Parameters:
;	AL	= Error message number.
;
; Returns:
;	Does NOT return to caller.
;
; Register(s) destroyed:
;	Potentially all but irrelevant since routine does not return to
;	caller.
;
; jmh 990520: use of AL instead of AX for parameter;
;	      added line_too_long entry point.
;-
line_too_long label near
	mov	al,E_TRUNCATE

abort_processing proc near
	mov	sp,abort_entry_stack		;jmh 990523: moved from end
	mov	source,offset DGROUP:get_kbd_line ;Set input to keyboard

; Display a message
	mov	di,offset DGROUP:linebuf
	mov	dot,di				;dot MUST be at
;						 beginning of line
;						 since this position is
;						 stored in the main routine
;jmh 011223: removed the alternate warning display - it was broken and I had
;	     no complaints... I'll rewrite it, anyway, if it's still wanted.
	mov	si,offset DGROUP:abort_msg_hd
	mov	cx,ABORT_HDR_LEN
	rep	movsb				;Copy message header

	cbw					;Zero AH (assuming there's no
						; more than 127 errors :) )
	shl	ax,1				;AX is now index into msg table
	xchg	ax,bx
	mov	si,abort_msg_ptrs[bx]		;SI->message
	mov	cl,[si] 			;CX<-length of message
	inc	si				;(CH is zero from REP)
	rep	movsb				;Copy msg into linebuf

	shr	macro_level,1			;Test and reset macro level
	mov	si,offset DGROUP:abort_msg_tl1	;Assume not executing macro
	mov	cl,ABORT_TAIL_LEN1
	jnc	@abort_10
	mov	si,offset DGROUP:abort_msg_tl
	mov	cl,ABORT_TAIL_LEN
@abort_10:
	rep	movsb				;Copy tail of message

	xchg	ax,di				;Set display marks
	mov	lastchar,ax
	mov	dx,offset DGROUP:linebuf
	call	near ptr set_disp_marks
	call	disp_line			;Display message
	call	near ptr output_newline
	call	near ptr disp_prompt
	jmp	near ptr cmdedit_abort_entry

abort_processing endp



;+
; FUNCTION : restore_cursor
;
;	Restores the cursor to the user's shape.
;
; Parameters:
;	Global caller_cursor contains original shape
;
; Returns:
;	
; Register(s) destroyed:
;	None.
;-
restore_cursor	proc near
	IF	TOGGLE_CURSOR
	@save	ax,cx
	@SetCurSz caller_cursor
	@restore
	ENDIF
	ret
restore_cursor	endp



;+
; FUNCTION : reset_line
;
;	Called to init various things like cursor shape, history buffer
;	character positions etc.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
;
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
reset_line proc near
	call	near ptr hist_top	;Reset history stack ptr to top
	call	near ptr restore_cursor	;Reset cursor shape
	mov	ax,offset dgroup:linebuf
	mov	lastchar,ax		;End of line
	mov	dot,ax			;current pos in line
	mov	disp_begin,ax		;first pos that changed
	mov	disp_end,ax		;last pos that changed

; Initialize the cursor shapes for insert and overstrike mode
; dfa added cursor_type toggle for swapping insert/overtype shapes.
; modified (and replaced ELSE) by jmh 980509.
	IF	TOGGLE_CURSOR
	mov	ax,caller_cursor	;Caller's cursor shape
	mov	bx,offset omode_cursor	;Start with overtype cursor
	mov	ah,al
	sub	ah,3			;Half-block cursor
	mov	  [bx],ax		;Overtype mode cursor
	mov	-2[bx],ax		;Insert mode cursor
	add	ah,2			;Underscore cursor
	rol	cursor_type,1		;Swapping cursors?
	jnc	dfa1			;No, so set overtype cursor
	dec	bx			;Set insert cursor
	dec	bx
dfa1:
	mov	[bx],ax 		;Set the underscore cursor

; Init overstrike/insert mode
	mov	al,default_imode	;Default edit mode
;					 (insert/overstrike) 
	mov	edit_mode,al		;Init insert/overtype mode

; Init cursor shape
	cbw
	add	ax,ax
	xchg	bx,ax
	@SetCurSz omode_cursor[bx]

	ELSE

	mov	al,default_imode
	mov	edit_mode,al

	ENDIF

	call	near ptr get_curpos
	mov	initial_curpos,dx	;Initial cursor position

	ret
reset_line endp



;+
; FUNCTION : init_screen
;
;	Inits various screen parameters. Reads the current prompt from
;	the screen and store in the prompt buffer. prompt buffer is
;	assumed to be at most the width of the screen.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
init_screen proc near
	@save	si,di
	@GetMode			;Get the video mode
	mov	video_page,bh		;Store page
	mov	screen_width,ah		; and width of display

	call	near ptr get_curpos	;Get the cursor shape and position
	mov	caller_cursor,cx	;Caller's cursor shape

IFDEF CMD_PROMPT
	mov	al,in_appl		;If we're in an application
	or	al,dl			; or not at screen edge
	jnz	@init_screen_1		; read the prompt
	mov	al,macro_level		;Macro executing?
	or	al,byte ptr cmd_level	;Multi-command?
	jnz	@init_screen_0		;Yes
	call	near ptr create_disp_prompt ;No, create and display the prompt
	jmp	short @init_screen_99
@init_screen_0:
	sub	dh,2			;Move up two positions to counter the
	;jnb	@init_screen_1		; two command.com newlines, but don't
	;xor	dh,dh			; go offscreen (added jmh 010427)
	adc	dh,0			;Row never seems to be 0 (jmh 010429)
	;simply fall through
	;to save a few bytes
@init_screen_1:
ENDIF

	mov	di,offset DGROUP:prompt
	mov	cx,PROMPT_BUF_SIZE	;CX<-size of prompt buffer
;					 (assumed not 0)
	mov	si,dx			;DX<-cursor pos
	xor	dl,dl			;DX<-position at start of row

@init_screen_5:
; BH holds video page, DX is cursor position, SI is ending cursor
; position, CX is remaining space in prompt buffer
	@SetCurPos			;Set cursor position
	cmp	dx,si			;Reached original position ?
	je	@init_screen_10		;Yes, all done
	@GetChAtr			;Get char at cursor
	stosb				;Store in prompt buffer
IFNDEF CMD_PROMPT
	mov	screen_attr,ah		;jmh 980516: store attribute
ENDIF
	inc	dx			;Increment cursor position
	loop	@init_screen_5		;loop unless prompt buffer full
	mov	dx,si			;Use original cursor position (jmh)
@init_screen_10:
	mov	byte ptr [di],ch	;jmh 980704: terminate the prompt
					; (assume prompt buffer < 256 and the
					;  prompt doesn't contain a NUL)
@init_screen_99:
	mov	initial_curpos,dx	;Initial cursor position (moved by jmh)
	@restore
	ret
init_screen endp



;+
; FUNCTION : disp_prompt
;
;	Called to display the user's prompt. The prompt is taken from
;	the buffer 'prompt'.
;
; Parameters:
;	None.
;
; Returns:
;	Nothing.
;
; Register(s) destroyed:
;	AX,BX,CX,DX
;-
disp_prompt	proc near
	call	near ptr output_newline
IFDEF CMD_PROMPT
	rol	in_appl,1
	jnc	@disp_prompt_1
ENDIF
	;jmh 980704: use a zero terminator instead of length
	mov	bx,offset DGROUP:prompt ;jmh 011106: use BX instead of SI
	mov	ah,2			;DOS service display character in DL
	jmp	short @disp_prompt_0a
@disp_prompt_0:
	int	21h
	inc	bx
@disp_prompt_0a:
	mov	dl,[bx]
	test	dl,dl
	jnz	@disp_prompt_0
	ret

IFDEF CMD_PROMPT
create_disp_prompt label near		;Returns DX current cursor position
@disp_prompt_1:
	@link	260
	push	bp			;BIOS print string uses BP

	lea	di,[bp-260]
IFDEF NODIRS
	push	di
	@GetDrv 			;AL<-drive number
	add	al,'A'                  ;AL<-drive letter
	stosb				;Store current drive
	mov	ax,':' or ('\' shl 8)
	stosw
	@GetDir di			;Store current path
	pop	di
ELSE
	mov	ax,di
	call	near ptr save_drv_path
ENDIF
	mov	ax,7160h		;LFN canonicalize path
	mov	cx,8002h		;Use SUBST'ed drive, full long name
	mov	si,di			;Overwrite short name with long
	stc				;For compatibility with pre-DOS7
	int	21h
	jnc	@disp_prompt_10
	call	near ptr strlen 	;CX<-length of full path
	call	xlate_lower		;Convert the short name to lower-case
@disp_prompt_10:
	or	byte ptr [si],20h	;Always use lower-case drive letter
	mov	bp,si			;BP->drive
	call	near ptr get_curpos	;DX->current position, BH->video_page
	mov	cx,2			;Drive letter and colon
	mov	bl,DRIVE_COL
	call	@disp_string
	add	si,3			;SI->path
	mov	bl,DIR_COL		;Root directory is done in
	cmp	byte ptr [si],0 	; the directory, not slash, colour
	je	@root
@disp_prompt_20:
	mov	bl,SLASH_COL
@root:	mov	byte ptr [bp],'/'       ;Overwrite the previous string
	call	@disp_string_1

	mov	bp,si			;BP->current subdirectory
	sub	cx,cx			;Length of current subdirectory
	mov	bl,DIR_COL
@dp_10:
	lodsb
	test	al,al
	jz	@dp_gt
	inc	cx
	cmp	al,'\'
	jne	@dp_10
	dec	cx			;Don't include the backslash
	call	@disp_string
	jmp	@disp_prompt_20

@dp_gt:
	call	@disp_string		;Final subdirectory
	mov	byte ptr [bp],'>'
	mov	bl,GT_COL
	call	@disp_string_1

	pop	bp
	@unlink
	ret

@disp_string_1 label near
	mov	cx,1
@disp_string proc near
	mov	ax,1301h		;BIOS print string, update cursor,
					; attribute in BL
	int	10h			;Display the string
	jmp	near ptr get_curpos	;Update DX with new cursor position
;	ret				;commented jmh 990517
@disp_string endp
ENDIF ;CMD_PROMPT

disp_prompt	endp



;+
; Function : locate_dosenv
;
;	Locates the segment in which the current environment is located.
;	This environment is the 'current' environment which may not
;	necessarily be the root environment.
;
; Parameters:
;	None.
;
; Returns:
;	AX	- segment of environment
;
; Register(s) destroyed:
;	AX
;
; jmh 980504: removed version testing, since it now requires 5+.
;	   9: used DS instead of ES; don't do any searching;
;	      incorporated getpsp function.
;-
locate_dosenv proc near
	@save	bx,ds
	mov	ah,62h
	int	21h				;BX->PSP segment
	mov	ds,bx				;DS->segment of psp
	mov	ax,ds:[2ch]			;Offset 2c is env address
	@restore
	ret
locate_dosenv endp



;+
; FUNCTION : our_break_handler
;
;	This takes over the Ctrl-Break interrupt and sets a flag when
;	Ctrl-Break is hit. It then jumps to the original Ctrl-Break handler.
;
; Parameters:
;	
;
; Returns:
;	
; Register(s) destroyed:
;-
our_break_handler proc near
	inc	CS:check_break
	cmp	CS:trap_break,1
	jne	@our_break_handler_10
	iret
@our_break_handler_10:
	jmp	CS:prev_isr1b
our_break_handler endp

CSEG	ENDS

	END	entry
