;    File              : $HISTORY.ASM$
;
;    Description       :
;
;    Original Author   : DIGITAL RESEARCH
;
;    Last Edited By    : $CALDERA$
;
;-----------------------------------------------------------------------;
;    Copyright Work of Caldera, Inc. All Rights Reserved.
;      
;    THIS WORK IS A COPYRIGHT WORK AND CONTAINS CONFIDENTIAL,
;    PROPRIETARY AND TRADE SECRET INFORMATION OF CALDERA, INC.
;    ACCESS TO THIS WORK IS RESTRICTED TO (I) CALDERA, INC. EMPLOYEES
;    WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF
;    THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN CALDERA, INC. WHO
;    HAVE ACCEPTED THE CALDERA OPENDOS SOURCE LICENSE OR OTHER CALDERA LICENSE
;    AGREEMENTS. EXCEPT UNDER THE EXPRESS TERMS OF THE CALDERA LICENSE
;    AGREEMENT NO PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED,
;    COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED,
;    CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST,
;    TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF
;    CALDERA, INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT
;    AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND
;    CIVIL LIABILITY.
;-----------------------------------------------------------------------;
;
;    *** Current Edit History ***
;    *** End of Current Edit History ***
;
;    $Log$
;    ENDLOG
;
; 	DOSPLUS Command Line Editor Routines
;

group PCMCODE PCM_HISTORY
group PCMDATA PCMODE_DATA FDOS_DSEG GLOBAL_DATA

ASSUME DS:PCMDATA

	[list -]
	%include "pcmode.mac"
	%include "msdos.mac"
	%include "char.mac"
	%include "cmdline.mac"
	[list +]


section PCM_HISTORY public align=2 class=CODE

	public	init_history, save_history
	public	del_history_buffers, del_cur_history_buffer
	public	prev_cmd, next_cmd, match_cmd, search_cmd
	public	prev_word, next_word, match_word
	public	goto_eol
	public	del_bol
	public	deln_word

	extrn	next_char:near
	extrn	save_line:near
	extrn	space_out:near
	extrn	bs_out:near
	extrn	goto_bol:near
	extrn	del_eol:near
	extrn	del_line:near
	extrn	char_info:near
	extrn	put_string:near
	extrn	prev_w20:near
	extrn	deln_w10:near

init_history:
;------------
; On Entry:
;	SS:BP -> history structure
; On Exit:
;	SS:SP filled in with appropriate history buffer
;	Preserve DX
;
	push	ds
	push	es
	push	ss
	pop	es
	lea	di,[RL_HIST_SEG]		; point at history buffer variables
	mov	si,offset histbuf1	; assume root buffer
	test	word [RL_FLAGS],RLF_INROOT	; root task after all?
	 jnz	init_history10		; if not we need to switch
	mov	si,offset histbuf2	;  application buffer
init_history10:
	movsw				; copy the seg
	lodsw				; get overall length
	sub	ax,2*2		; last two words contain pointers
	stosw				; save the working length
	xchg	ax,si			; SI -> saved buffer positions
	mov	ds,[RL_HIST_SEG]		;  in the buffer segment
	movsw				; update save and
	movsw				;  recall positions
	pop	es
	pop	ds
	ret


save_history:
;------------
; We are exiting from READLINE - if line has been modified save the
; current line and update our pointers.
;
	test	dx,dx			; skip update of sav/recall pointers
	 jz	save_history30		;  if empty line
	call	save_current		; save away current command line
	mov	di,[RL_HIST_SAVE]		; start with the old one
	call	find_current_hist	; wrap it if we need to
	call	find_next_null		; find end of "new" command
	inc	di			; onto next character
	cmp	di,[RL_HIST_SIZE]		; do we need to wrap ?
	 jb	save_history10
	xor	di,di			; wrap the line
save_history10:
	mov	[RL_HIST_SAVE],di		; update "save" pointer
	test	word [RL_FLAGS],RLF_DIRTY	; if a line has been changed we'd
	 jz	save_history30		;  better update "recall" pointer
	mov	[RL_HIST_RECL],di		;  too
save_history30:
	push	ds
	push	es
	push 	ss
	pop 	ds
	lea	si,[RL_HIST_SAVE]		; point at history buffer variables
	mov	es,[RL_HIST_SEG]
	mov	di,[RL_HIST_SIZE]
	movsw				; save the "save" pointer
	movsw				;  and the "recall" one
	pop	es
	pop	ds
	ret

save_current_if_dirty:
;---------------------
	test	word [RL_FLAGS],RLF_DIRTY	; if data is dirty (ie. user modified)
	 jz	save_current20		;  then save it
save_current:
;------------
; Copy current buffer contents to next free location in history buffer
;
	push	si
	push	di
	push	dx
	cmp	dx,[RL_HIST_SIZE]		; is history buffer big enough ?
	 jae	save_current10		;  no, skip saving this line
	call	find_free_hist		; Find the next bit of space
	pop 	cx
	push 	cx			; CX = chars to save
	 jcxz	save_current10		; none, forget about it
	push	ds
	push	es
	lds	si,[RL_BUFPTR]
	mov	es,[RL_HIST_SEG]
	rep	movsb			; and save the data.  
	xor	ax,ax			; Null terminate it.
	stosb
	pop	es
	pop	ds
	push	di
	call	find_next_null		; Find the end of the entry we just
	mov	cx,di			;  overwrote - if any remains zap it
	pop	di
	cld
	xor	ax,ax			; Null terminate this entry
	sub	cx,di			; Figure out number of zeros to write
	 jbe	save_current10
	push	es
	mov	es,[RL_HIST_SEG]
	rep	stosb			; and pad to the next entry	
	pop	es
save_current10:
	pop	dx
	pop	di
	pop	si
save_current20:
	ret


;
; Here is the code to deal with history buffers
;
; match_cmd	Does a string search based on what the user has typed so far
; search_cmd	Matches string, but doesn't change match state
; prev_cmd	Retrieves the previous command from the history buffer
; next_cmd 	     "     "  next       "      "     "    "      "
;
match_cmd:
	mov	al,[@hist_flg]		; is matching enabled ?
	and	ax,RLF_MATCH
	 jz	search_cmd		; no, always match
	xor	[RL_FLAGS],ax		; toggle match bit
	test	[RL_FLAGS],ax		; turning matching off ?
	 jz	match30			;  then just exit
search_cmd:
	mov	[RL_SAVPOS],si		; any chars to save ?
	test	si,si
	 jz	prev_cmd		; blank line - just get previous
	call	save_current_if_dirty	; save current line if it needs saving
	push	si			; save current offset in line
	push	di
	push	dx			; Save index to end of current line
	mov	dx,si			; discard rest of line if no match
	mov	di,[RL_HIST_RECL]		; Point to the current entry
match_loop:
	call	find_prev_hist		; DI-> previous cmd in buffer
	push	di			; save this command position
	mov	di,[RL_HIST_RECL]		; get starting position
	call	find_current_hist	;  handle any wrapping
	pop	si			; recover previous command
	cmp	di,si			; have we been all round ?
	 je	match_error		; YES - We've looped so no match found
	push	ds
	push	si			; save this entry
	mov	ds,[RL_HIST_SEG]
	mov	di,[RL_BUFOFF]		; ES:DI -> match string
	mov	cx,[RL_SAVPOS]		; try to match CX characters
	repe	cmpsb
	pop	di			; recover this entry
	pop	ds
	 jne	match_loop		; try again if we didn't match
	call	copy_from_hist		; if it did match copy it
match_error:
	pop	cx			; Get end of displayed line
	pop	di			; user's buffer
	pop	si
	push	si			; save current position in lines
	call	space_out		; Rub the displayed line out
	call	bs_out			; And backspace to it's start
	call	goto_eol		; display the line
	call	goto_bol		; Move cursor to begining
	pop	cx			; CX = end of matched portion
	mov	[RL_SAVPOS],cx		; CX = Current position in command
	 jcxz	match20
match10:				; Move cursor forward to current
	push	cx			;  position
	call	next_char		; next_char destroys our loop count
	pop	cx			;  so keep it
	loop	match10
match20:
	mov	cx,[RL_SAVMAX]		; we can't copy any more
	mov	[RL_SAVPOS],cx
match30:
	ret


prev_cmd:
; Get the previous command from the buffer
	test	word [RL_FLAGS],RLF_RECALLED	; is this the 1st recall ?
	 jnz	prev_cmd10
	push	cx
	push	di
	mov	di,[RL_HIST_SAVE]		; get existing "save" ptr
	call	find_current_hist	; wrap it if we need to
	mov	[RL_HIST_RECL],di		;  and update "recall" pointer
	pop	di
	pop	cx
prev_cmd10:
	call	save_current_if_dirty	; save current line if it needs saving
	call	del_line		;  then delete it
	push	si
	push	di
	mov	di,[RL_HIST_RECL]		; point to the current entry
	call	find_prev_hist		; DI -> previous entry
	jmp	found_cmd		; now go and copy it to the user buffer

next_cmd:
; Get the next command from the buffer
	call	save_current_if_dirty	; save current line if it needs saving
	call	del_line		;  then delete it
	push	si
	push	di
	mov	di,[RL_HIST_RECL]		; point to the current entry
	call	find_next_hist		; DI -> next entry
;	jmp	found_cmd		; now go and copy it to the user buffer

found_cmd:
	call	copy_from_hist		; Copy from history to user buffer
	pop	di
	pop	si
;	jmp	goto_eol		; Display new line.

goto_eol:				; Move the cursor to the end of the
	mov 	cx,dx
	sub 	cx,si			; displayed line
	 jcxz	goto_e10		; Already at the EOL
	add	si,[RL_BUFOFF]		; Get the Offset in the buffer
	call	put_string		; Output the sub_string
	add	di,cx			; Update the local variables
	mov	si,dx			; and return
goto_e10:
	ret

; DI-> Current entry in history buffer
;
copy_from_hist:
	or	word [RL_FLAGS],RLF_RECALLED	; remember we have recalled something
	and	word [RL_FLAGS],~ RLF_DIRTY	; this entry is already in buffers
	mov	[RL_HIST_RECL],di		; update pointer for next time
	call	find_next_null		; how big is this entry ?
	mov	cx,di
	mov	si,[RL_HIST_RECL]
	sub	cx,si			; (CX-SI)=No of bytes to copy
	cmp	cx,[RL_MAXLEN]		; is the line bigger than our buffer ?
	 jb	copy_from_hist1		; if so we want to clip it
	mov	cx,[RL_MAXLEN]		; to the maximum possible value
copy_from_hist1:
	mov	dx,cx
	push	ds
	mov	ds,[RL_HIST_SEG]
	mov	di,[RL_BUFOFF]
	rep	movsb			; copy to line buffer
	pop	ds
	call	save_line		; save the line
	mov	cx,[RL_SAVMAX]		; we can't copy any more
	mov	[RL_SAVPOS],cx
	ret

; Some primitives for history buffer handling:
;
; find_free_hist	Find the next free entry in the history buffer
; find_next_null	Finds the end of the current entry
; find_prev_hist	Finds the start of the previous entry
; find_next_hist	Finds the start of the next entry
; find_current_hist	Finds the start of current entry

;
; Make DI-> next entry of sufficient length for the current cmd buffer
; If we can't fit at the end zero the remainder of the buffer then wrap
; back to the start.
;

find_free_hist:
	mov	di,[RL_HIST_SAVE]		; Point at current last entry
	push	di			; Will DX bytes fit?
	add	di,dx
	cmp	di,[RL_HIST_SIZE]		; Did we run out of buffer?
	pop	di
	 jb	find_free_hist20
	xor	ax,ax			; doesn't fit, so zero to end of buffer
find_free_hist10:
	push	es
	mov	es,[RL_HIST_SEG]
	stosb				; keep on zeroing
	pop	es
	cmp	di,[RL_HIST_SIZE]
	 jb	find_free_hist10
	xor	di,di			; wrap back to start of history buffer
find_free_hist20:
	ret

; Entry DI-> entry in the history buffer
; Make DI-> next NULL in the history buffer ( end of current entry )
;
find_next_null:
	mov	cx,[RL_HIST_SIZE]
	sub	cx,di			; Calc no of bytes left
	xor	ax,ax			; Look for next terminator
	push	es
	mov	es,[RL_HIST_SEG]
	repnz	scasb	
	dec	di			; point at the NUL
	pop	es
	ret

; Entry DI-> entry in the history buffer
; Make DI-> previous entry in the history buffer
; Preserve CX
find_prev_hist:
	call	find_current_hist	; handle any wrapping
	mov	ax,di			; AX = entry we want previous for
find_prev_hist10:
	push	ax			; save current offset
	push	di			; save initial offset
	xchg	ax,di			; find next from here until
	call	find_next_hist		;  we get back where we started
	xchg	ax,di			; AX = current offset
	pop	di			; recover initial value
	pop	bx			; recover previous offset
	cmp	ax,di			; have we wrapped yet ?
	 je	find_prev_hist20	; yes, BX = previous entry
	 ja	find_prev_hist10	; we are above target so continue
	cmp	bx,di			; we are below target - if previous
	 jae	find_prev_hist10	;  was above target we are OK
	cmp	ax,bx			; if new below previous then we
	 jnb	find_prev_hist10	;  have ERROR wrap, so stop
find_prev_hist20:
	mov	di,bx			; DI -> Previous entry
	ret

find_next_hist:	
	call	find_current_hist	; handle any wrapping
	call	find_next_null		; point to end of current entry
;	jmp	find_current_hist	; handle any wrapping

find_current_hist:
; This routine is complicated by the need to handle switchers, where
;  our buffers and pointers may get out of step
	push	es
	xor	ax,ax
	mov	es,[RL_HIST_SEG]		; now work backwards to start of line
find_current_hist10:
	cmp	di,1			; are we at the start of the buffer
	 jb	find_current_hist30	;  then don't wrap
	std
	scasb				; is previous char a NUL ?
	cld				;  (it should be!)
	 jne	find_current_hist10
	inc	di			; ES:DI -> 1st char of line
find_current_hist20:
	mov	cx,[RL_HIST_SIZE]
	sub	cx,di			; CX= Remaining no: of bytes in buffer
	 jbe	find_current_hist30
	repe	scasb			; skip over zero's
	 jne	find_current_hist40
find_current_hist30:
	mov	di,1			; wrap to start of buffer
find_current_hist40:
	dec	di
	pop	es
	ret

del_history_buffers:
;-------------------
; Delete contents of both history buffers
;
	mov	ax,[histbuf1]		; Segment of 1st history buffer
	mov	cx,[histsiz1]		; End of 1st history buffer
	call	zap_buffer
	mov	ax,[histbuf2]		; Segment of 2nd history buffer
	mov	cx,[histsiz2]		; End of 2nd history buffer
	jmp	zap_buffer
	
del_cur_history_buffer:
;----------------------
; Delete contents of the current history buffer
;
	mov	ax,[RL_HIST_SEG]		; zero the current buffer
	mov	cx,[RL_HIST_SIZE]
zap_buffer:
	push	es			; zero fill CX bytes at AX:0
	push	di
	mov	es,ax
	xor	ax,ax
	mov	[RL_HIST_RECL],ax
	mov	[RL_HIST_SAVE],ax
	xor	di,di
	rep	stosb			; zero fill the buffer
	pop	di
	pop	es
	call	del_line		; then delete current line
	jmp	save_line


next_word:
	mov	cx,dx			; Calculate the number of bytes
	sub	cx,si			; left to scan
	jcxz	next_w10		; Skip if at the EOL
	push	si			; Save the current Index
	mov	si,di			; Scan from the current location
	call	match_word		; Get the next word boundary
	mov	cx,si
	sub	cx,di			; Calculate the string length
	mov	si,di			; to be displayed from the current
	call	put_string		; location and output the data
	pop	si
	add	si,cx			; Update the Index and Offset pointers
	add	di,cx
next_w10:
	ret

;
;	MATCH_WORD scans the buffer at ES:SI for word boundaries
;	and returns to the calling routine whenever it detects such
;	a boundary.
;
;	On Entry:	ES:SI	Buffer Address
;			CX	Maximum No of bytes to Scan
;
;	On Exit:	ES:SI	Next Word Boundary
;
match_word:
	call	char_info
	test	ah,CHAR_ALPHAN | CHAR_KANJI
	loopnz	match_word		; scan the rest of the current word
	 jcxz	match_w30		; end of word or kanji ?

	push	dx
match_w10:
	mov	dx,si
	call	char_info
	test	ah,CHAR_ALPHAN | CHAR_KANJI
	loopz	match_w10
	 jz	match_w20
	sub	si,dx			; Correct the count in CX
	add	cx,si			; and return the location of the
	mov	si,dx			; word boundary

match_w20:
	pop	dx
match_w30:
	ret

prev_word:
	mov 	cx,si
	 jcxz 	match_w30		; Initialize the count
	push 	dx
	push 	si
	push 	di
	mov	si,[RL_BUFOFF]		; Scan from the begining of the buffer
	mov	dx,si			; keeping the last match in DX
prev_w10:
	call	match_word		; Find the next word boundary
	jcxz	prev_w15		; Stop when we get to the current offset
	mov	dx,si			; Save current location
	jmp	prev_w10		; and repeat
prev_w15:
	jmp	prev_w20

del_bol:				; Delete to the begining of the line
	or 	si,si
	 jz 	del_bol10		; Ignore if at the begining
	or 	dx,dx
	 jz 	del_bol10		; Or the line is empty
	push	di			; Save the current index
	call	goto_bol		; Move to the start of the line
	pop	cx			; Restore the current offset
	jmp	deln_w10		; and jump to common code
del_bol10:
	ret

deln_word:
	mov	cx,dx			; Calculate the number of bytes
	sub	cx,si			; left to scan
	jcxz	del_bol10		; Skip if at the EOL
	push	si			; Save the current Index
	mov	si,di			; Scan from the current location
	call	match_word		; Get the next word boundary
	mov	cx,si
	pop	si
	jmp	deln_w10

PCM_HISTORY	ends

section PCMODE_DATA public align=2 class=DATA

	extrn	@hist_flg:byte		; To select between histbufs 1 or 2

PCMODE_DATA	ends

section GLOBAL_DATA public align=2 class=DATA

	extrn	histbuf1:word, histsiz1:word, histbuf2:word, histsiz2:word

GLOBAL_DATA	ends

	end

