		TITLE	KEYBOARD BUFFER
		PAGE 60, 132

;**********************************************************************
;* Keyboard Buffer and Editor	VERSION 2.0
;*
;*	  This program provides two independent functions:
;*	   1) it extends the DOS_keyboard_buffer to 128 characters and
;*	   2) it provides command-line storage and editing facilities
;*
;*	  Source: self contained 8088/8086 Macro Assembler Language
;*	  Script: MASM KBEDIT;
;*			  LINK KBEDIT;
;*			  EXE2BIN KBEDIT.EXE KBEDIT.SYS
;*
;*	  Usage:  install as a device driver by including a statement
;*			  in the  CONFIG.SYS file of the form:
;*
;*				DEVICE=KBEDIT.SYS [B:nnn] [C:nnn] [K:nnn] [/A] [/N]
;*
;*			  B:nnn	  is buffer size for stored strings in bytes
;*					   (default = 512)
;*			  C:nnn	  is maximum number of strings in the string buffer
;*					   (default = 32)
;*			  K:nnn	  is keyboard buffer size in characters
;*					   (default = 128)
;*
;**********************************************************************

JMPS			MACRO	LABEL
				JMP		SHORT	LABEL
				ENDM

CLR				MACRO	REG
				SUB		REG, REG
				ENDM

GET_CHAR		MACRO

		;----- return keyboard input in AL through DOS-call
		;

				MOV		AH, 8	;; function number
				INT		21H
				ENDM


		; -----DOS-keyboard pointers
		;

DOS_DATA_AREA			SEGMENT AT 40H

						ORG		17H
DOS_KB_FLAG				DB		?
						ORG		1AH
KB_HEAD					DW		?
KB_TAIL					DW		?
						ORG		71H
DOS_BREAK_FLAG			DW		?
						ORG		80H
DOS_BUFFER_START		DW		?
DOS_BUFFER_END			DW		?

DOS_DATA_AREA			ENDS


		;----- interrupt_vector_adresses_equates
		;

KB_INTERRUPT			EQU		4*9H
KB_IO_ROUTINE			EQU		4*16H
DOS_FUNCTION_INT		EQU		4*21H

		;----- equates
		;

  NULL			EQU		00H
  CR			EQU		0DH
  LF			EQU		0AH
  BACKSPACE		EQU		08H
  ESCAPE		EQU		1BH
  CTRL_U		EQU 	15H
  CTRL_W		EQU 	17H
  EOF			EQU		1AH
  ACK			EQU		06H
  TAB			EQU		09H

  OFF			EQU		0
  ON			EQU		NOT OFF
  NOT_INST		EQU		0AAH			;


		;-----
		; the following keys are identified by extended ascii-codes
		; the second byte is given in these equates
		;

  INSERT		EQU		052H
  DELETE		EQU		053H
  CURSOR_LEFT	EQU		04BH
  CURSOR_RIGHT	EQU		04DH
  PREV_LINE		EQU		048H	; = cursor up
  NEXT_LINE		EQU		050H	; = cursor down
  CURSOR_BEGIN	EQU		047H	; = home key
  CURSOR_END	EQU		04FH	; = end key
  F1			EQU		03BH
  F2			EQU		03CH
  F3			EQU		03DH
  F4			EQU		03EH
  F5			EQU		03FH
  F6			EQU		040H
  F7			EQU		041H
  CTRL_LEFT		EQU		073H
  CTRL_RIGHT	EQU		074H
  CTRL_HOME		EQU		077H
  CTRL_END		EQU		075H
  CTRL_PGUP		EQU		084H
  CTRL_PGDN		EQU		076H
  SHIFT_F5		EQU		058H
  SHIFT_F6		EQU		059H
  ALT_1			EQU		078H
  ALT_2			EQU		079H
  ALT_3			EQU		07AH
  ALT_4			EQU		07BH
  ALT_5			EQU		07CH
  ALT_6			EQU		07DH
  ALT_7			EQU		07EH
  ALT_8			EQU		07FH
  ALT_9			EQU		080H
  ALT_0			EQU		081H




CODE			SEGMENT PUBLIC
				ASSUME	CS:CODE

START_CODE		=		$

	  HEADER	DD		-1				; device_driver obligatory
										; first four bytes
	  ATTRIBUTE DW		8000H			; attribute byte:

				DW		D_STRATEGY
				DW		D_INTERRUPT

	 DRVNAME	DB		'KBEDT	 '

		;----- installation messages
		;

 MSG_VERSION	DB		'Keyboard Buffer and Command Line Editor,'
				DB		'	Version 2.0', CR, LF, LF, NULL
				DB		' author: P. Kranenburg'
				DB		' University of Leiden, The Netherlands'


		;----- keyboard related messages
		;

 MSG_KB_1		DB		'  Keyboard buffer size			: ', NULL
 MSG_KB_2		DB		' characters', CR, LF, NULL

 MSG_KB_NO_1	DB		'  KeyBoard Buffer size ', NULL
 MSG_KB_NO_2	DB		' is too small: not installed', CR, LF, NULL
 MSG_KB_IN		DB		'  KeyBoard Buffer Installed'
				DB		CR, LF, NULL
 MSG_KB_UN		DB		'  KeyBoard Buffer Uninstalled'
				DB		CR, LF, NULL
 MSG_KB_PR		DB		'  Keyboard Buffer already installed'
				DB		CR, LF, NULL
 MSG_KB_NOT_PR	DB		'  Keyboard Buffer was not active'
				DB		CR, LF, NULL


		;----- editor related messages
		;

 MSG_EDT_1		DB		'  Command line buffer size		: ', NULL
 MSG_EDT_2		DB		' bytes', CR, LF
				DB		'  Number of commands in buffer : ', NULL
 MSG_EDT_3		DB		' ', CR, LF, LF, NULL

 MSG_EDT_IN		DB		'  Command Line Editor Installed'
				DB		CR, LF, NULL

 MSG_EDT_UN		DB		'  Command Line Editor Uninstalled'
				DB		CR, LF, NULL

 MSG_EDT_PR		DB		'  Command Line Editor already installed'
				DB		CR, LF, NULL

 MSG_EDT_NOT_PR DB		'  Command Line Editor was not active'
				DB		CR, LF, NULL


		;----- pointers to request_header
		;

REQ_HEADER_OFS	DW		?
REQ_HEADER_SEG	DW		?


		;----- control data sent to output-routine of driver
		;

EXPECTSECOND	DB		OFF		; first/second byte indicator
FIRSTPART		DB		LOW ON	; first byte of incoming control codes
KB_INST			DB		OFF		; keyboard buffer presence flag
EDT_INST		DB		NOT_INST; keyboard editor presence flag
BIOS_TTY_SW		DB		LOW ON	; true on driver initialization
TEN				DW		10		; operand for MULtiply operation


		;----- local stack for processing buffered input function call
		;

LOCAL_STACK		DW		256 DUP(?)


		;----- containers for user stack-segment and -pointer
		;

CALLERS_STACK_SEGMENT	DW		?
CALLERS_STACK_POINTER	DW		?

RET_FROM_STACK_ROUTINES DW		?		; temp var for SETSTACK and RESETSTACK


		;----- routines to switch stacks
		;

SETSTACK		PROC	NEAR

				POP		RET_FROM_STACK_ROUTINES	   ; save return address
				MOV		CALLERS_STACK_SEGMENT, SS  ; now save user stack
				MOV		CALLERS_STACK_POINTER, SP  ;
				PUSH	CS
				POP		SS						   ; install new stack
				MOV		SP, OFFSET LOCAL_STACK + SIZE LOCAL_STACK
				JMP		RET_FROM_STACK_ROUTINES	   ; return
SETSTACK		ENDP

RESETSTACK		PROC	NEAR

				POP		RET_FROM_STACK_ROUTINES
				PUSH	CALLERS_STACK_SEGMENT
				POP		SS						   ; restore stack registers
				MOV		SP, CALLERS_STACK_POINTER  ;
				JMP		RET_FROM_STACK_ROUTINES
RESETSTACK		ENDP



		;----- enlarged keyboard buffer
		;

KBBUFFER_SIZE	DW		256
KBBUFFER		DW		?


		;----- containers for old interrupt_vector_adresses
		;

OLD_KEYB_IO				DD		?
OLD_KEYB_INT			DD		?
OLD_FUNCTION_INT		DD		?
OLD_DOS_BUFFER_START	DW		?
OLD_DOS_BUFFER_END		DW		?




;#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
;#
;# *** Description of data-structure used in Editor-routines ***
;#
;#	Quantities										   Assignments
;#
;#
;#	  - actice/inactive switch						- - -	 EDT_INST
;#
;#	 Buffer data:
;#
;#	  - current character pointer in buffer			- - -	 register DI
;#	  - current number of characters in buffer		- - -	 register BL
;#	  - current number of characters up to but		- - -	 register BH
;#		not including the current cursor
;#	  - maximum number of characters in buffer		- - -	 register DL
;#		exclusive final Carriage Return
;#	  - insert/overwrite mode						- - -	 INSERT_TOGGLE
;#
;#	 Display data:
;#
;#	  - screen coordinates of start displayed string- - -	 STR_START_POS
;#	  - screen coordinates of end displayed string	- - -	 STR_END_POS
;#	  - current video page							- - -	 VIDEO_PAGE
;#	  - current cursor position on the screen		- - -	 CURSOR_POS
;#	  - cursor shape								- - -	 CURSOR_TYPE
;#	  - current screen width						- - -	 MAX_COL
;#	  - number of character rows					- - -	 MAX_ROW (fixed:25)
;#
;#	  - full/partial editing capability				- - -	 FULL_EDIT
;#	  - defer-mode when redirected input/output		- - -	 DEFER_DSPL
;#	  - cursor positioning through ANSI-escape codes- - -	 ANSI
;#
;#
;#
;#	Lay-out of the buffer passed through DS:DX
;#
;#
;#	 +----------+----------+-------+--/ /--+---------+-------+--/ /--+--------+
;#	 | BufLen	| #chars   | D(1)  |  ...  |D(#chars)| 'CR'	 |	...	 |	N+3	  |
;#	 |	  = N	|  in buf  |	   |	   |		 |		 |		 |		  |
;#	 +----------+----------+-------+--/ /--+---------+-------+--/ /--+--------+
;#
;#	at offset 0: length of buffer (exclusive first two bytes and final CR)
;#			  1: length of actual number of characters in buffer
;#								  (exclusive final CR)
;#			  2 .. [byte(1)]+1 : data
;#			  [byte(2)]+2 : 'Carriage Return'
;#
;#
;# The buffer can contain a string on input as well. This is checked for at
;# entrance ( a carriage return must be present at the byte
;#			  with index: [byte(2)]+2 )
;# If present the input string is inserted in the string buffer to make it
;# available for editing.
;#
;#
;#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#



INSERT_TOGGLE			DB		OFF

DEFER_DSPL				DB		OFF
FULL_EDIT				DB		OFF
CURSOR_TYPE				DW		?				; cursor-status on entry
CURSOR_MASK				EQU		00011111B		; mask for set cursor
INSERT_SHIFT			DB		4				; determines cursor-shape
												; change in INSERT-mode
CURSOR_POS				LABEL	WORD
CURSOR_COL				DB		?
CURSOR_ROW				DB		?
STR_START_POS			LABEL	WORD
STR_START_COL			DB		?
STR_START_ROW			DB		?
STR_END_POS				LABEL	WORD
STR_END_COL				DB		?
STR_END_ROW				DB		?

MAX_ROW					EQU		25
MAX_COL					DB		80		; assumed default for display-width
VIDEO_PAGE				DB		0

ANSI					DB		OFF
ANSI_GET_CURSOR			DB		ESCAPE, '[6n', NULL


	 DEVICE_PROLOGUE	PROC	NEAR

		;****************************************************************
		;*	Find out about devices attached to standard input and ouput by
		;*	issuing a IOCTL (044H) function call.
		;*
		;*	If either STDIN or STDOUT is attached to a file then writing
		;*	characters to STDOUT is deferred until editing is complete.
		;*
		;*	If STDOUT is NOT the Console Output then direct cursor
		;*	manipulation is disabled, i.e. when a multiple line string,
		;*	the cursor position on the screen does not reflect the current
		;*	position in the string.
		;*
		;*

	STDIN		EQU		0
	STDOUT		EQU		1
	ISDEV		EQU		0080H
	ISCOT		EQU		0002H

				PUSH	DX
				PUSH	BX

		;----- initialize variables
		;
				MOV		DEFER_DSPL, ON
				MOV		FULL_EDIT, OFF;
				MOV		STR_START_POS, 0
				MOV		STR_END_POS, 0
				MOV		CURSOR_POS, 0

		;----- check standard input
		;

				MOV		BX, STDIN		; BX is handle
				MOV		AX, 4400H		; AL = 0: get device information
				INT		21H				; status returned in DX
				TEST	DX, ISDEV
				JZ		DP_LX			; if bit off then file
										;			 else device

		;----- check standard output
		;

				MOV		BX, STDOUT		; BX is handle
				MOV		AX, 4400H		; AL = 0: get device information
				INT		21H				; status returned in DX
				TEST	DX, ISDEV
				JZ		DP_LX			; bit not set: file
				MOV		DEFER_DSPL, OFF ;
				TEST	DX, ISCOT
				JZ		DP_LX

				CMP		ANSI, ON		; ANSI or BIOS cursor handling ?
				JE		DP_L1

		;----- get video state
		;

				MOV		AH, 0FH
				INT		10H
				MOV		MAX_COL, AH
				MOV		VIDEO_PAGE, BH

		;----- get cursor position and attributes
		;

				MOV		AH, 03			; BH still contains video page number
				INT		10H				; get current cursor
				MOV		CURSOR_TYPE, CX ; save cursor data
				JMPS	DP_L2

		 DP_L1:
				LEA		SI, ANSI_GET_CURSOR		; get cursor position through
				CALL	PRINT					;  ANSI-sequence <esc>[6n

				CALL	ANSI_REPLY				; get ANSI-reply

		 DP_L2:
				MOV		CURSOR_POS, DX			; initialize cursor variables
				MOV		STR_START_POS, DX		;
				MOV		STR_END_POS, DX			;
				MOV		FULL_EDIT, ON			;

		 DP_LX:
				POP		BX
				POP		DX
				RET
	 DEVICE_PROLOGUE	ENDP


	 DEVICE_EPILOGUE	PROC	NEAR

		;-----------------------------------------------------
		; in: SI = pointer to start of string
		; If DEFER_DSPL is ON then now is the time to display
		; the completed current string.
		; A final carriage return is sent to standard output
		; in all cases.
		;

				MOV		INSERT_TOGGLE, OFF
				CALL	SET_CURSOR_TYPE

				CMP		DEFER_DSPL, ON
				MOV		DEFER_DSPL, OFF ; tell DISPLAY_COMPLEX to no longer
										;  postpone display
				JNE		DE_L2

		;-----	must send string to STDOUT now

				CLR		CH
				MOV		CL, [SI-1]		; load length of string
				JCXZ	DE_L2

				CLD
		DE_L1:
				LODSB
				CALL	DISPLAY_COMPLEX
				LOOP	DE_L1

		DE_L2:
				MOV		AL, CR
				CALL	DISPLAY_CHAR
				RET
	 DEVICE_EPILOGUE	ENDP


	 SET_CURSOR_POS		PROC	NEAR

		;-----------------------------------------------
		; If FULL_EDIT is ON then the cursor position
		; on the screen is updated according to the value
		; in CURSOR_POS.
		; If ANSI is ON then cursor-positioning is done
		; through ANSI-escape-sequences, else through
		; BIOS video-routines.
		;
				PUSH	DX
				PUSH	BX
				PUSH	AX
				CMP		FULL_EDIT, ON
				JNE		SCP_LX
				MOV		DX, CURSOR_POS

				CMP		ANSI, ON
				JE		SCP_L1

		;----- cursor positioning through BIOS
		;	   function no. 2, DX = cursor-pos
		;
				MOV		BH, VIDEO_PAGE
				MOV		AH, 2
				INT		10H
				JMPS	SCP_LX

		SCP_L1:

		;----- cursor positioning through ANSI
		;	   output ANSI-sequence <esc>[<col>;<row>f
		;	   Note: ANSI row- and column-numbers are
		;	   in the range: 1 .. MaxRow/MaxCol
		;
				MOV		AL, ESCAPE
				CALL	WRITE_CHAR		; <esc>
				MOV		AL, '['
				CALL	WRITE_CHAR		; [
				MOV		AL, DH			; print row number
				INC		AL
				CLR		AH				;
				CALL	PRINT_NUM		;
				MOV		AL, ';'			; ;
				CALL	WRITE_CHAR
				MOV		AL, DL			; print column number
				INC		AL
				CLR		AH				;
				CALL	PRINT_NUM		;
				MOV		AL, 'f'			; f
				CALL	WRITE_CHAR
		SCP_LX:
				POP		AX
				POP		BX
				POP		DX
				RET
	 SET_CURSOR_POS		ENDP


	 SET_CURSOR_TYPE	PROC	NEAR

		;----------------------------------------------------------------
		; Set cursor shape to value in variable CURSOR_TYPE.
		; This can only be done through BIOS-VIDEO call so
		; full edit mode must be ON and ANSI mode OFF
		;

				CMP		FULL_EDIT, ON
				JNE		SCT_L2
				CMP		ANSI, OFF		; no cursor type change
				JNE		SCT_L2			; if ANSI on
				PUSH	CX
				PUSH	AX
				MOV		CX, CURSOR_TYPE			; set proper cursor type
				CMP		INSERT_TOGGLE, ON
				JNE		SCT_L1					; if insert-state
				SUB		CH, INSERT_SHIFT		; make larger cursor
		SCT_L1:
				AND		CH, CURSOR_MASK
				MOV		AH, 01
				INT		10H
				POP		AX
				POP		CX
		SCT_L2:
				RET
	 SET_CURSOR_TYPE	ENDP

		ANSI_REPLY		PROC	NEAR

		;-------------------------------
		; get reply from ANSI-driver
		; must be of the form <esc>[#;#R<cr>
		; out: DX=#;#
		;
				PUSH	CX
				PUSH	BX

				GET_CHAR				;
				CMP		AL, ESCAPE		; <esc> ?
				JNE		AR_LX
				GET_CHAR
				CMP		AL, '['			; [
				JNE		AR_LX

				MOV		BL, ';'			; get first # (row)
				CLR		CX
		AR_L1:							; CX = intermediate number
				GET_CHAR
				CMP		AL, BL			;
				JE		AR_L3			;
				CMP		AL, '9'			; read number and convert
				JA		AR_LX			;
				SUB		AL, '0'			;
				JB		AR_LX			;
				XCHG	CX, AX
				PUSH	DX
				MUL		TEN
				POP		DX
				JNC		AR_L2			; test for overflow
				OR		AX, TEN			; do something to reset Z-flag
				JMPS	AR_LX
		AR_L2:
				CLR		CH				; add new digit
				ADD		AX, CX
				XCHG	CX, AX
				JMP		AR_L1

		AR_L3:
				DEC		CL				; 1 .. Max --> 0 .. Max-1
				CMP		BL, 'R'			; read second # (column)
				JE		AR_L4
				MOV		DH, CL
				MOV		BL, 'R'
				CLR		CX
				JMP		AR_L1

		AR_L4:
				MOV		DL, CL

		AR_LX:
				GET_CHAR				; wait for Carriage Return
				CMP		AL, CR			;
				JNE		AR_LX			;

				POP		BX
				POP		CX
				RET
		ANSI_REPLY		ENDP


SECOND_CODE_TABLE		LABEL	BYTE

		;----- table of extended ascii-codes
		;

						DB		INSERT
						DB		DELETE
						DB		CURSOR_LEFT
						DB		CURSOR_RIGHT
						DB		PREV_LINE
						DB		NEXT_LINE
						DB		CURSOR_BEGIN
						DB		CURSOR_END
						DB		F1
						DB		F2
						DB		F3
						DB		F4
						DB		F5
						DB		F6
						DB		F7
						DB		CTRL_LEFT
						DB		CTRL_RIGHT
						DB		CTRL_HOME
						DB		CTRL_END
						DB		CTRL_PGUP
						DB		CTRL_PGDN
						DB		SHIFT_F5
						DB		SHIFT_F6
						DB		ALT_1
						DB		ALT_2
						DB		ALT_3
						DB		ALT_4
						DB		ALT_5
						DB		ALT_6
						DB		ALT_7
						DB		ALT_8
						DB		ALT_9
						DB		ALT_0
						DB		00				; end of table

CODE_TABLE_LENGTH		=	  $ - OFFSET SECOND_CODE_TABLE

		;----- ECJT = Extended Code Jump Table
		;

		ECJT	LABEL	WORD
				DW		MAIN_LOOP_RETURN
				DW		ALT_DIGIT		; get old string
				DW		ALT_DIGIT
				DW		ALT_DIGIT
				DW		ALT_DIGIT
				DW		ALT_DIGIT
				DW		ALT_DIGIT
				DW		ALT_DIGIT
				DW		ALT_DIGIT
				DW		ALT_DIGIT
				DW		ALT_DIGIT

				DW		SHIFT_F6_R		; cursor shape control
				DW		SHIFT_F5_R		; for INSERT-mode

				DW		CTRL_PGDN_R		; cursor-bottom up
				DW		CTRL_PGUP_R		; cursor-top up
				DW		CTRL_END_R		; cursor-bottom down
				DW		CTRL_HOME_R		; cursor-top down
				DW		CTRL_RIGHT_R	; cursor up
				DW		CTRL_LEFT_R		; cursor down

				DW		F7_R			; zero-byte in string
				DW		F6_R			; control-Z in string
				DW		F5_R			; add string to buffer
				DW		F4_R			; erase from begin to cursor
				DW		F3_R			; erase from cursor to end
				DW		F2_R			; word right
				DW		F1_R			; word left

				DW		CURSOR_END_R
				DW		CURSOR_BEGIN_R
				DW		NEXT_LINE_R
				DW		PREV_LINE_R
				DW		CURSOR_RIGHT_R
				DW		CURSOR_LEFT_R
				DW		DELETE_R
				DW		INSERT_R

				PAGE

;#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-
;#
;#	*** Data structure to maintain a buffer of previous strings ***
;#
;#
;#													   ___/------ STR_BUF_START
;#	 C_TBL_START ------\ _________					  |_x_|
;#						|_________|					  |_x_|
;#						|_________|					  |_x_|
;#						|_________|					  |_x_|
;#	  C_TBL_HEAD ------>|_________|-------\			  |_x_|
;#						|_________|----\   \		  |_x_|
;#						|_________|		\	\-------> |_x_|
;#						|_________|		 \			  |_y |
;#						|_________|		  \			  |_y |
;#	  C_TBL_TAIL ------>|_________|-- \	   \		  |_y_|
;#						|_________|	   \	\		  |_y_|
;#						|_________|		\	 \		  |_y |
;#	C_TBL_END ---------/				 \	  \-----> |_y_|
;#										  \			  ~	  ~
;#						C-table			   \		  ~_ _~
;#											\		  |_z_|
;#											 \		  |_z_|
;#											  \		  |_z_|
;#											   \----> |_z_|
;#													  |___|
;#													  |___|
;#													  |___|
;#														   \------- STR_BUF_END
;#
;#												  string-buffer
;#
;#
;#	The entries in C_TABLE point to the last character of a string
;#	in STR_BUF.
;#
;#	C_TBL_HEAD points to the entry in C-table which identifies the oldest
;#	string in the string-buffer.
;#
;#	C_TBL_TAIL points to the entry in C-table which points to the last
;#	byte of free space in the string-buffer.
;#
;#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-


	STR_BUF_SIZE		DW		512				; default string-buffer
												; size is 512
	STR_BUF_START		DW		?
	STR_BUF_END			DW		?

		;----- pointers to mark begin and end of data in the string buffer
		;

	STR_BUF_HEAD		DW		?
	STR_BUF_TAIL		DW		?

		;----- table of pointers to strings in string buffer
		;
		;	   pointers in string pointer table to locate
		;	   the string currently being processed
		;
		;	   C_TBL_HEAD identifies the oldest string in STR_BUF
		;	   C_TBL_TAIL points to the last free byte in STR_BUF, i.e. the byte
		;	   before the begin of the string pointed to by C_TBL_HEAD
		;


C_TBL_SIZE		  DW	  60					; default 30 commands

	C_TBL_START	  DW	  ?
	C_TBL_END	  DW	  ?

	C_TBL_HEAD	  DW	  ?
	C_TBL_TAIL	  DW	  ?

	CURRENT		  DW	  ?						; identifies the string current-
												; ly being edited

		;************************************************************
		; Next are a couple of routines to handle pointer-
		; updating for the cyclic buffers defined above.
		; Pointers are passed in the BX register
		; BX is advanced to the next or previous table-item.
		;************************************************************

  SCROLLUP_TBL	PROC	NEAR
				CMP		BX, C_TBL_START ; at start of table?
				JNE		SUT_L1			; no: then go update pointer
				MOV		BX, C_TBL_END	; yes: scroll to end of
										;	   table first
	  SUT_L1:
				DEC		BX				;
				DEC		BX				; pointer to previous entry
				RET
  SCROLLUP_TBL	ENDP


  SCROLLDO_TBL	PROC	NEAR
				INC		BX				; pointer to next entry
				INC		BX				;
				CMP		BX, C_TBL_END	; past end?
				JNE		SDT_L1			; no: all is well
				MOV		BX, C_TBL_START ; yes: back to start of table

	  SDT_L1:
				RET
  SCROLLDO_TBL	ENDP



		BEEP	PROC	NEAR

	  ;----------------------------------------------
	  ; Buffer full/error beep
	  ;

				PUSH	CX
				PUSH	AX

				MOV		AL, 10010110B	; select Timer 2, low order Count
				OUT		43H, AL
				CLR		AL				; set low order Count
				JMP		$+2				; allow for AT pecularities
				OUT		42H, AL			;  (slow 8053-chip)

				MOV		AL, 10100110B	; select Timer 2, high order Count
				OUT		43H, AL
				MOV		AL, 3			; pitch
				JMP		$+2
				OUT		42H, AL			; set high order Count

				IN		AL, 61H
				MOV		AH, AL			; save value
				OR		AL, 03H			; turn speaker on
				JMP		$+2
				OUT		61H, AL

				MOV		CX, 20000		; beep length

		BRRR:
				LOOP	BRRR

				MOV		AL, AH			; restore old value
				OUT		61H, AL

				POP		AX
				POP		CX
				RET
		BEEP	ENDP

  WRITE_CHAR	PROC	NEAR

		;------------------------------------------
		; Display character in AL via OS call 2
		; or through BIOS TTY routine 0EH
		;  (necessary at initialization)
		;

				PUSH	AX
				CMP		BIOS_TTY_SW, ON
				JNE		W_CH_L1
				MOV		AH, 0EH			; TTY-command
				INT		10H				; video-BIOS-routine
				JMPS	W_CH_LX

		W_CH_L1:
				PUSH	DX
				MOV		DL, AL			; deliver in DL
				MOV		AH, 2			; function number
				INT		21H
				POP		DX

		W_CH_LX:
				POP		AX
				RET
  WRITE_CHAR	ENDP

	 PRINT		PROC	NEAR

		;-------------------------------------------------------------
		;  This routine displays a string a string of characters on
		;  the crt-screen.
		;  The string is assumed to start at address DS:SI
		;  and must end with a null byte.
		;

				CLD

	  PRINT_L1:
				LODS	BYTE PTR CS:[SI]
				OR		AL, AL			; load bytes until
				JZ		PRINT_L2		; a zero is read
				CALL	WRITE_CHAR
				JMP		PRINT_L1

	  PRINT_L2:
				RET
	 PRINT		ENDP

	 PRINT_NUM	PROC	NEAR

		;---------------------------------------
		; Convert to ascii and display number
		; in: AX: unsigned integer
		;
		;
				PUSH	DX
				PUSH	CX
				PUSH	BX
				MOV		BX, 10			; BX = divisor
				CLR		CX				; CX = digit count

		PR_N_L1:
				CLR		DX				; DX:AX = dividend
				DIV		BX
				PUSH	DX				; remainder on stack
				INC		CX				; keep count
				OR		AX, AX			; quotient = 0 ?
				JNE		PR_N_L1			; if not, compute next digit

		PR_N_L2:
				POP		AX				; get digits from stack
				ADD		AL, '0'			; convert to ascii
				CALL	WRITE_CHAR		; and display
				LOOP	PR_N_L2			;

				POP		BX
				POP		CX
				POP		DX
				RET
	 PRINT_NUM	ENDP

				PAGE

DISPLAY_CHAR	PROC	NEAR

		;---------------------------------------------------------------
		;  Display character in AL.
		;  Update cursor data.
		;

				CALL	WRITE_CHAR

				CMP		AL, CR
				JNE		DCH_L1

		;----- carriage return
		;
				MOV		CURSOR_COL, 0
				RET

		DCH_L1:
				CMP		AL, LF
				JNE		DCH_L3

		;----- line feed
		;
				CMP		CURSOR_ROW, MAX_ROW - 1
				JE		DCH_L2
				INC		CURSOR_ROW

		DCH_L2:
				RET

		DCH_L3:
				CMP		AL, BACKSPACE
				JNE		DCH_L5

		;----- backspace
		;
				DEC		CURSOR_COL				; cursor one left
				JNS		DCH_L4					; if < 0
				MOV		AL, MAX_COL				; wrap back to previous row
				ADD		CURSOR_COL, AL			;
				DEC		CURSOR_ROW
				CALL	SET_CURSOR_POS			; must set cursor explicitly

		DCH_L4:									; for wrap back
				RET

		DCH_L5:

		;----- other characters
		;

				MOV		AX, STR_END_POS			; AX := difference between
				SUB		AX, CURSOR_POS			;		STR_END and CURRENT
				PUSH	AX
				INC		CURSOR_COL
				MOV		AL, MAX_COL				; increment column count
				CMP		CURSOR_COL, AL			; if at end move to next line
				JB		DCH_L6					;
				MOV		CURSOR_COL, 0			;
				CMP		CURSOR_ROW, MAX_ROW - 1 ; on last row ?
				JE		DCH_L6					; yes, don't increment ROW
				INC		CURSOR_ROW				; because of scrolling

		DCH_L6:
				POP		AX
				OR		AX, AX					; update STR_END_POS
				JNZ		DCH_L7					; if necessary
				MOV		AX, CURSOR_POS
				MOV		STR_END_POS, AX

		DCH_L7:
				RET

DISPLAY_CHAR	ENDP


DISPLAY_COMPLEX PROC	NEAR

		;**************************************************************
		;* This procedure takes an ascii-code looks for control-code
		;* and displays it on the screen in the proper format
		;* in: AL : ascii-code
		;*
		;* Irregular service for BS and TAB
		;*
		;* If DEFER_DSPL is ON then display of the string being edited
		;* is deferred until editing is complete.
		;***************************************************************

				CMP		DEFER_DSPL, ON
				JE		DC_X
				CMP		AL, 20H			; is it a control character ?
				JNB		DC_NO_CTRL		;

				CMP		AL, BACKSPACE	; control characters,
				JE		DC_NO_CTRL		; handle BS and TAB different
				CMP		AL, TAB			;
				JNE		DC_L2			;

		;----- display TAB character, ie. display spaces until
		;	   current column number = 0 (MOD 8)
		;

				PUSH	CX
				CLR		CH
				MOV		CL, CURSOR_COL	;
				OR		CL, 0F8H		;  CL := 8 - (CURSOR_COL MOD 8)
				NEG		CL				;

	  DC_L1:
				MOV		AL, ' '
				CALL	DISPLAY_CHAR
				LOOP	DC_L1
				POP		CX
				JMPS	DC_X

		;----- display control character other then TAB or BS
		;	   first display a caret, then the corresponding capital
		;	   ie. ascii 1 displays as: '^A'
		;

	  DC_L2:
				PUSH	AX
				MOV		AL, '^'			; display ascii-control-character
				CALL	DISPLAY_CHAR
				POP		AX
				OR		AL, 40H			; make it readable

  DC_NO_CTRL:
				CALL	DISPLAY_CHAR

	   DC_X:
				RET
DISPLAY_COMPLEX ENDP



DISPLAY_TAIL	PROC	NEAR

		;*************************************************
		;* this routine displays the substring starting
		;* at the character pointed to by DI, to the end
		;* of the buffer
		;* in: CX: number of blanks to display after the
		;* string to blank out any remnant characters
		;*************************************************

				PUSH	CURSOR_POS		; save current screen position
				PUSH	CX
				MOV		CL, BL			; get length of substring
				SUB		CL, BH			; in CX
				CLR		CH
				JCXZ	DT_L2			; display specified blanks if
										; the substring is empty
				MOV		SI, DI

	   DT_L1:
				LODSB					; get character to display
				CALL	DISPLAY_COMPLEX
				LOOP	DT_L1

	   DT_L2:
				POP		CX				; recover number of additional blanks
				JCXZ	DT_L4

	   DT_L3:
				MOV		AL, ' '			;
				CALL	DISPLAY_COMPLEX ;
				LOOP	DT_L3

	   DT_L4:
				POP		CX				; recover screen position
				MOV		AL, CH			; AL := (OLD)STR_END_ROW
				SUB		CX, CURSOR_POS	;
				NEG		CX				; !! CX := CURSOR_POS - (OLD)STR_END_POS
				SUB		AL, CURSOR_ROW
				NEG		AL				; AL := CURSOR_ROW - (OLD)STR_END_ROW
				MOV		AH, MAX_COL		; sofar we pretended each row is 256
				NEG		AH				; characters long, so now we must
				MUL		AH				; compensate for this by subtracting
				SUB		CX, AX			; (256 - characters/column)*ROW_diff.
										;
				JCXZ	DT_L6

	   DT_L5:
				MOV		AL, BACKSPACE	; use as backspace count
				CALL	DISPLAY_COMPLEX ; move cursor back
				LOOP	DT_L5

	   DT_L6:
				RET
DISPLAY_TAIL	ENDP


MOVE_TAIL_LEFT	PROC	NEAR

		;***************************************************
		;* routine to move the substring starting right of
		;* the current cursor position, pointed to by DI,
		;* one position to the left, overwriting the current
		;* cursor position
		;* none of the pointers are updated
		;***************************************************

				PUSH	CX
				PUSH	DI
				CLR		CH				;
				MOV		CL, BL			; get length of substring
				SUB		CL, BH			; in CX
				DEC		CX
				JNA		MTL_LX			; test for non-empty substring

				MOV		SI, DI			;
				INC		SI				; SI at first item to move
				CLD						; count forward
				REP MOVSB				;

	   MTL_LX:
				POP		DI
				POP		CX
				RET
MOVE_TAIL_LEFT	ENDP


MOVE_TAIL_RIGHT PROC	NEAR

		;***************************************************
		;* routine to move the substring starting at
		;* the current cursor position, pointed to by DI,
		;* one position to the right.
		;* none of the pointers are updated
		;***************************************************

				PUSH	CX
				PUSH	DI
				CLR		CH				;
				MOV		CL, BL			; length of substring in CX
				SUB		CL, BH			;
				JCXZ	MTR_LX			; no business for empty string

				ADD		DI, CX			; DI one past end string
				MOV		SI, DI
				DEC		SI				; SI at end string
				STD						; count backward
				REP MOVSB				;

	   MTR_LX:
				POP		DI
				POP		CX
				RET
MOVE_TAIL_RIGHT ENDP


BACK_TAB		PROC	NEAR

		;************************************************************
		;* Compute number of backspaces needed to remove a TAB
		;* at the current cursor position.
		;* It is assumed that string pointers are consistent
		;* i.e. DI points to position of TAB to be removed
		;*	and BL, BH give length of whole string and prefix resp.
		;* required number = 8 - (current screen colunm MOD 8)
		;*
		;* out: CL: equivalent number of spaces for TAB in question
		;************************************************************

				PUSH	DI		; save current string index
				PUSH	DX		; save string length
				DEC		DI		; position on previous character
				CLR		CH
				MOV		CL, BH	; CX := prefix length
				MOV		DH, 07H
				MOV		AL, 20H
				JCXZ	BT_3
				STD				; scan downwards through prefix

		BT_1:
				SCASB
				JBE		BT_2	; control character ?
				CMP		BYTE PTR ES:[DI+1], TAB	 ;
				JE		BT_4	; if tab then exit loop (we certainly are
								;  at screen position which equals 0 (MOD 8) )
				DEC		DH

		BT_2:
				LOOP	BT_1
										; here: CL = length to penultimate tab
										;			 or zero (if no tab)
		BT_3:							;  and: DH = - (no of carets)
				SUB		DH, STR_START_COL ; DH := - carets - STR_START_COL

		BT_4:
				SUB		DH, BH			; DH := - carets {- STR_START_COL}
										;		- total
				ADD		CL, DH			; CL := - carets {- STR_START_COL}
										;		- (length since penultimate tab)
				AND		CL, 07H			; CL := 8 - ((-CL) MOD 8)
				INC		CL				;
				CLD				; reset direction flag
				POP		DX		; restore string length
				POP		DI		; and string index
				RET
BACK_TAB		ENDP

				PAGE

;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
;
;		K E Y B O A R D	  E D I T O R	 ( ENTRY POINT )
;
;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*


FUNCTION_INT_ENTRY		PROC

		;****************************************************************
		;* the address of this routine is placed in the interrupt vector
		;* to intercept interrupt 21H. Function call 0AH is handled here,
		;* others are passed on to DOS
		;*
		;****************************************************************

				CMP		EDT_INST, OFF
				JZ		FI_NOT_ACTIVE
				CMP		AH, 0AH
				JZ		FI_ENTRY

FI_NOT_ACTIVE:

		;----- go on to DOS-routines
		;

				JMP		CS: OLD_FUNCTION_INT

	FI_ENTRY:
				STI
				PUSH	ES				;
				PUSH	DS				;
				PUSH	BP				;
				PUSH	DI				;
				PUSH	SI				; save registers
				PUSH	DX				;
				PUSH	CX				;
				PUSH	BX				;
				PUSH	AX
				CALL	SETSTACK		; use own stack

				CALL	DEVICE_PROLOGUE

				MOV		AX, DS			; make ES point to buffer-
				MOV		ES, AX			; segment also
				MOV		SI, DX
				CLD
				LODSW					; get length of the passed buffer
				PUSH	SI				; save address of start of actual string
				OR		AL, AL			; if length = zero return
				JZ		FI_EXIT

				MOV		DL, AL			; DL will contain the string's
				DEC		DL				; maximum length, without the
										; final Carriage Return
				MOV		CL, AH
				CLR		CH
				MOV		BX, CX
				JCXZ	FI_RESTART
				CMP		BYTE PTR DS:[SI+BX], CR	 ; is input-string
												 ; terminated properly ?
				JNE		FI_RESTART				 ; no, ignore
				CALL	STR_BUF_INSERT

  FI_RESTART:
				MOV		DH, STR_START_COL		; initialize column counter
				MOV		CURSOR_COL, DH
				CALL	SET_CURSOR_POS

				MOV		INSERT_TOGGLE, OFF		; overwrite mode
				CALL	SET_CURSOR_TYPE

				CLR		BL				; BL will contain the current number
										; of characters in the string
				CLR		BH				; BH will contain the number of
										; characters up to but not including
										; the cursor position
				POP		DI				; DI is the current position in the
										; buffer
				PUSH	DI				; address back on stack
				GET_CHAR				; get first character
				CMP		AL, LF			; if first character is a
				JE		MAIN_LOOP		; line-feed then throw it away
				JMP		MAIN_ENTRY		; enter main loop at main
										; entrance-point

	FI_EXIT:
				POP		SI
				CALL	DEVICE_EPILOGUE

				CALL	RESETSTACK		; restore user stack

				POP		AX				;
				POP		BX				;
				POP		CX				;
				POP		DX				;
				POP		SI				; restore registers
				POP		DI				;
				POP		BP				;
				POP		DS				;
				POP		ES				;
				IRET

FUNCTION_INT_ENTRY		ENDP


MAIN_LOOP		PROC	NEAR

				GET_CHAR

MAIN_ENTRY:
				CMP		AL, ACK
				JE		MAIN_LOOP				; no ACK's

				LEA		BP, MAIN_LOOP			; set up return address on
				PUSH	BP						; the stack for all routines
												; wishing to return to MAIN_LOOP
				CMP		AL, LF
				JE		LF_RN					;
				CMP		AL, BACKSPACE			; find out whether one
				JE		BACKSPACE_RN			; of these special
;				CMP		AL, ESCAPE				; characters was read
;				JE		ESCAPE_RN				;
				CMP  	AL, CTRL_U
				JE		ESCAPE_RN
				CMP		AL, CR					; search for editing
				JE		CR_R					; keys
				CMP 	AL, CTRL_W
				JE		CW_RN
				CMP		AL, 0					;
				JE		EXTENDED_ASCIIN			;


		;----- a character must be inserted in the current string
		;

		M_L1:
				CMP		INSERT_TOGGLE, ON		; insert- or
				JNE		OVERWRITE				; overwrite-mode?

				CMP		BL, DL					; is buffer full ?
				JB		M_L2					; no: put this character
				CALL	BEEP					; in the buffer
				RET								; else: ring the bell

		M_L2:
				CALL	MOVE_TAIL_RIGHT			; make room for new character
				CLD								;
				STOSB							; insert the new character
				INC		BL						; update length
				INC		BH						; and cursor position
				CLR		CX

		;---- display updated string
		;	  also postlude for OVERWRITE routine
		;

		M_L3:
				CALL	DISPLAY_COMPLEX			; display the new character
				CALL	DISPLAY_TAIL			; and those to the right of it
		M_L4:
				RET


	OVERWRITE:
				CMP		BH, BL					; if cursor position
				JB		M_L6					; at end, then check for
				CMP		BL, DL					; full buffer
				JB		M_L5					;
				CALL	BEEP					;
				RET								;

		M_L5:									;
				INC		BL
		M_L6:									; if a character is added at
				INC		BH						; the end, update total
												; length also
				CLR		CX
				CMP		BH, BL					; at end of string ?
				JE		M_L8					; yes, just add it

		;----- must overwrite some character
		;	   get character to overwrite in AH
		;	   character to overwrite with still in AL

				MOV		AH, ES:[DI]

				CMP		AH, TAB					; is it a TAB ?
				JNE		M_L7					; go if not

		;----- get rid of TAB
		;

				PUSH	AX
				DEC		BH						; BACKTAB expects string
				CALL	BACK_TAB				; pointers set up for tab prefix
				INC		BH						; restore prefix length
				DEC		CX
				POP		AX
				JMPS	M_L8

		M_L7:
				CMP		AH, 20H					; if control character
				JAE		M_L8					; then check character to write
				INC		CX
				CMP		AL, TAB					; tab can also show as one space
				JE		M_L8
				CMP		AL, 20H
				JNB		M_L8					; if not cntr-char
				DEC		CX						; display extra space

		;----- character to buffer
		;

		M_L8:
				CLD
				STOSB
				JMP		M_L3					; go update screen



		;----- boost some conditional jumps
		;

LF_RN:			JMP		LF_R
BACKSPACE_RN:	JMP		BACKSPACE_R
ESCAPE_RN:		JMP		ESCAPE_R
CW_RN:			JMP		CTRL_W_R
EXTENDED_ASCIIN:JMP		EXTENDED_ASCII


CR_R:

		;----------------------------
		; add a Carriage Return to the end of the string, set string-length byte
		; insert in STR_BUF, and go to exit
		; ! the carriage return character is sent to STDOUT in DEVICE_EPILOGUE
		;

				CLR		CH				;
				MOV		CL, BL			; set DI to end of string
				SUB		CL, BH			;
				ADD		DI, CX			;
				STOSB					; and put carriage return there

				PUSH	STR_END_POS
				POP		CURSOR_POS
				CALL	SET_CURSOR_POS

				POP		BP				; remove return address from stack
				POP		SI				; recover first-byte address
				PUSH	SI
				LEA		BP, FI_EXIT
				PUSH	BP

				MOV		[SI-1], BL		; store count

				CLR		CH
				MOV		CL, BL			; length in CX


STR_BUF_INSERT:

	  ;-----------------------------------------------------
	  ; On entry CX contains the length of the string
	  ;		 and SI points to the string's first byte.
	  ; note: this code is called as a subroutine at entry
	  ;		   and by F5_R
	  ; When freeing space to insert a new string, as many of
	  ; the oldest strings are removed as is necessary to make
	  ; room for the new one. Because maximum string length is
	  ; is 255 and minimum length of strings in the buffer is 1
	  ; it follows that no more than 255 should ever be removed.
	  ; Therefore, a counter is maintained during the the loop
	  ; which removes old strings from the buffer to prevent an
	  ; accidentally corrupted data-structure from causing an
	  ; endless loop here.
	  ;

				JCXZ	SBI_L17			; if string-length = 0 don't insert

	  ;----- load register DI with address of last byte before
	  ;		 begin of free space in STR_BUF
	  ;

				MOV		BX, C_TBL_TAIL	;
				MOV		CURRENT, BX		; must update CURRENT
				CMP		BX, C_TBL_HEAD
				JE		SBI_L1
				CALL	SCROLLUP_TBL

	  SBI_L1:
				MOV		DI, CS:[BX]
				JE		SBI_L18			; if table empty, we should not enter
										; compare routine
										; ! proc SCROLLUP does not set Z-flag

	  ;----- determine whether string is different from previous
	  ;

				PUSH	DI
				MOV		AX, DI
				CMP		BX, C_TBL_HEAD	; wrap pointers if appropriate
				JNE		SBI_L11			;
				MOV		BX, C_TBL_TAIL	;
				JMPS	SBI_L12

	  SBI_L11:
				CALL	SCROLLUP_TBL

	  SBI_L12:
				MOV		DI, CS:[BX]
				SUB		AX, DI			; compute length of previous string
				JAE		SBI_L13			;
				ADD		AX, STR_BUF_SIZE; string can wrap in STR_BUF

	  SBI_L13:
				CMP		AX, CX			; compare lengths
				JNE		SBI_L16			; go if not equal

				PUSH	ES				; save registers altered
				PUSH	SI				; when comparing strings
				PUSH	CX				;

				MOV		AX, CS			; set up addressing
				MOV		ES, AX			;
				CLD						;
				INC		DI				;

	  SBI_L14:
				CMP		DI, STR_BUF_END ; compare string in
				JNE		SBI_L15			; STR_BUF with input string
				MOV		DI, STR_BUF_START ;
										;
	  SBI_L15:							;
				CMPSB					;
				LOOPZ	SBI_L14			;

				POP		CX				; restore registers
				POP		SI				;
				POP		ES				;

	  SBI_L16:
				POP		DI
				JNE		SBI_L18

	  SBI_L17:
				RET						; return if strings proved equal

	  SBI_L18:

	  ;----- next get an entry in C_TBL for the new string
	  ;

				MOV		BX, C_TBL_TAIL	; C_TBL_TAIL is moved to make room,
				MOV		AX, CS:[BX]		; save its contents first, ...
				PUSH	DI				;
				ADD		DI, CX			; calculate where new string
				CMP		DI, STR_BUF_END ; will end in STR_BUF,
				JB		SBI_L2			;
				SUB		DI, STR_BUF_SIZE

	   SBI_L2:
				MOV		CS:[BX], DI		; ... then fill it with new length
				POP		DI

				CLR		DH				; use as retry count for loop
										; starting at label SBI_L3

				CALL	SCROLLDO_TBL	;
				MOV		C_TBL_TAIL, BX	; new tail
				MOV		CURRENT, BX		; and new current also
				CMP		BX, C_TBL_HEAD	; collision with head ?
				JNE		SBI_L3			; no, go and restore contents

				PUSH	BX				; yes, remove oldest string
				CALL	SCROLLDO_TBL	; space is automatically deallocated
				MOV		C_TBL_HEAD, BX	; by moving the pointers
				POP		BX				; BX back at tail

				MOV		AX, CS:[BX]		; new value for end of free space

	   SBI_L3:
				MOV		CS:[BX], AX		; set new C_TBL_TAIL to current
										; end of free space

	  ;----- determine if there is enough free space to hold the new
	  ;		 string and keep removing old entries until there is
	  ;

				SUB		AX, DI			; AX contains current end of free space
				JA		SBI_L4			; AX=DI means STR_BUF is empty (free-
										; pointer = last-string-pointer)
				ADD		AX, STR_BUF_SIZE; undo wrap-around effect

	   SBI_L4:
				CMP		AX, CX			; is there enough ?
				JA		SBI_L5			; yes, go and do the transfer
										; note: always keep one byte free
				DEC		DH				; how many times did we try ?
				JZ		SBI_ERR			; if zero too many: something is wrong
										; exit with beep, also RESET is called
				PUSH	BX				; no, remove another entry from TABLE
				MOV		BX, C_TBL_HEAD
				MOV		AX, CS:[BX]		; get address of last byte occupied
				CALL	SCROLLDO_TBL	; by this entry's string
				MOV		C_TBL_HEAD, BX	; new head of the table
				POP		BX				;

				JMP		SBI_L3			; try again

	  ;----- now we are ready for the transfer
	  ;

	   SBI_L5:
				MOV		AX, CS			;
				MOV		ES, AX			; set up target pointers
				INC		DI				;
				CLD						; count in right direction

	   SBI_L6:
				CMP		DI, STR_BUF_END ;
				JNE		SBI_L7			; wrap-around if neccesary
				MOV		DI, STR_BUF_START

	   SBI_L7:
				MOVSB					; store bytes of string
				LOOP	SBI_L6			; until done (CX=0)

				MOV		AX, DS			;
				MOV		ES, AX			; restore ES

	   SBI_LX:
				RET

	   SBI_ERR:
				CALL	RESET_EDT
				CALL	BEEP
				RET

LF_R:
				CALL	CURSOR_END_R
				MOV		AL, LF
				CALL	DISPLAY_CHAR
				MOV		AL, CR
				CALL	DISPLAY_CHAR
				RET


BACKSPACE_R:
				OR		BH, BH			; is cursor at begin of string ?
				JZ		BS_L1			; yes, then nothing to do
				CALL	CURSOR_LEFT_R	; otherwise, delete the character
				CALL	DELETE_R		; left of the current cursor-position
		BS_L1:	RET



;---------------------------------
; abandon current string and start editing a new one
ESCAPE_R:		CALL	CURSOR_BEGIN_R
	  			CALL	F4_R
				CALL	CURSOR_END_R
				ADD		SP, 2			; discard return address on stack
				JMP		FI_RESTART		; redo from scratch

; Delete previous whitespace, or alphanumerics
CTRL_W_R:		CALL	BACKSPACE_R		; delete a character in any case
				OR		BH, BH			; at beginning of string?
				JZ		CW_RET			; yes, done
				CMP 	BYTE PTR [DI-1],'	' ; tab or...
				JZ		CTRL_W_BL
				CMP 	BYTE PTR [DI-1],' ' ; blank or...
				JZ		CTRL_W_BL
CTRL_W_LOOP:	CMP		BYTE PTR [DI-1],'0'	; numeric
				JB		CW_RET
				CMP		BYTE PTR [DI-1],'9' ; ...
				JBE		CTRL_W_NB
				CMP		BYTE PTR [DI-1],'A' ; or alpha, continue deletion
				JB		CW_RET
				CMP		BYTE PTR [DI-1],'Z'
				JBE		CTRL_W_NB
				CMP		BYTE PTR [DI-1],'a'
				JB		CW_RET
				CMP		BYTE PTR [DI-1],'z'
				JBE		CTRL_W_NB
CW_RET:			RET

CTRL_W_BL:		CALL	BACKSPACE_R		; Flush previous character
				OR		BH,BH			; at beginning?
				JZ		CW_RET			; Yes...
				CMP 	BYTE PTR [DI-1],'	' ; tab or...
				JZ		CTRL_W_BL
				CMP 	BYTE PTR [DI-1],' ' ; blank or...
				JZ		CTRL_W_BL
				JMP		CW_RET

CTRL_W_NB:		CALL	BACKSPACE_R		; delete a character in any case
				OR		BH, BH			; at beginning of string?
				JZ		CW_RET			; yes, done
				JMP		CTRL_W_LOOP

EXTENDED_ASCII:
				GET_CHAR
				PUSH	ES				; the second code is in AL now
				PUSH	DI
				MOV		DI, CS
				MOV		ES, DI
				LEA		DI, SECOND_CODE_TABLE
				MOV		CX, CODE_TABLE_LENGTH
				CLD
				CLI						; we need CX after scan
				REPNE SCASB				; search for extended code in table
				STI
				SHL		CX, 1			; CX is now offset in jump-table
				MOV		BP, CX
				POP		DI
				POP		ES
				JMP		[BP+ECJT]

INSERT_R:

		;----- flip the insert-toggle
		;

				NOT		INSERT_TOGGLE

				CALL	SET_CURSOR_TYPE
				RET


DELETE_R:

		;----- delete the character under the cursor
		;

				CMP		BL, BH			; if cursor at end
				JE		DR_L2			; then nothing to delete

				MOV		AH, ES:[DI]		; remember victim for display update
				CALL	MOVE_TAIL_LEFT	; remove from buffer
				DEC		BL				; update total length

		;----- next update display
		;

				MOV		CX, 1			; at least one character to erase

				CMP		AH, 20H			; control character ?
				JAE		DR_L1			;

				INC		CX				; yes, erase the caret too
				CMP		AH, TAB			; beware of the TAB
				JNE		DR_L1			; if TAB then some calculation required
										; to determine shift of display
				CALL	BACK_TAB

		DR_L1:
				CALL	DISPLAY_TAIL

		DR_L2:
				RET

CURSOR_LEFT_R:

		;----- move current cursor position one to the left
		;

				OR		BH, BH
				JZ		CL_ATBEGIN		; cursor at begin of string
				DEC		DI				; update pointers
				DEC		BH				;

		;----- update display
		;

				MOV		CX, 1			; set up for one backspace

				MOV		AH, [DI]		; get character under cursor
				CMP		AH, 20H			; if control-character then
				JAE		CL_BS			; two backspaces are required

				INC		CL				; if ctrl-char then two backspaces
				CMP		AH, TAB			; exception: tab-character
				JNE		CL_BS			;

				CALL	BACK_TAB		; compute number of backspaces
										; needed for this tab (returned in CL;)

		CL_BS:
				MOV		AL, BACKSPACE
				CALL	DISPLAY_COMPLEX
				LOOP	CL_BS
				RET

   CL_ATBEGIN:
				CALL	BEEP
				RET


CURSOR_RIGHT_R:

		;----- move current cursor position one to the right
		;

				CMP		BL, BH
				JE		CR_AT_END		; cursor at end of string
				MOV		AL, [DI]		; get character under cursor
				INC		DI				; update pointers
				INC		BH				;

		;----- update display
		;

				CMP		AL, 20H
				JB		CRR_L2

		;----- non-control ascii character, so
		;	   cursor must move right one position
		;	   see if this can be done by cursor control
		;	   if not then redisplay character
		;

				CMP		FULL_EDIT, ON
				JNE		CRR_L2

				MOV		AX, CURSOR_POS
				INC		AL
				CMP		AL, MAX_COL
				JB		CRR_L1
				CLR		AL
				CMP		AH, MAX_ROW - 1
				JE		CRR_L1
				INC		AH

		CRR_L1:
				MOV		CURSOR_POS, AX
				CALL	SET_CURSOR_POS
				RET

		CRR_L2:

		;----- move cursor right by redisplaying the character
		;	   which is under cursor now
		;

				CALL	DISPLAY_COMPLEX
				RET

	CR_AT_END:
				CALL	BEEP
				RET

PREV_LINE_R:

		;------------------------------
		; empty the current buffer and get the one less
		; recent string from the string buffer

				MOV		BP, SP			; need access to buffer address
										; which is saved on the stack
				PUSH	STR_END_POS		; save current end of string on screen
				CALL	CURSOR_BEGIN_R

		;----- next find a new string to be edited
		;

				MOV		AX, C_TBL_HEAD	; take a look in the
				CMP		AX, C_TBL_TAIL	; string pointer table
				JE		PNL_EMPTY		; no string there
										; proceed with empty string
				MOV		BX, CURRENT		;
				CMP		BX, AX			; determine previous entry
				JNE		PL_L1			; in TABLE,
				MOV		BX, C_TBL_TAIL	;
		PL_L1:
				CALL	SCROLLUP_TBL	;
				MOV		CURRENT, BX		; and make it the current one
				MOV		CX, CS:[BX]		;
				CMP		BX, AX			; determine length of this
				JNE		PL_L2			; candidate	 by scrolling up
				MOV		BX, C_TBL_TAIL	; another entry ...
				JMPS	PL_L3			;
		PL_L2:
				CALL	SCROLLUP_TBL	;
		PL_L3:							;
				MOV		SI, CS:[BX]		; ... and subtracting the

	   PNL_L1:
				SUB		CX, SI			; addresses of the strings'
				JA		PNL_L2			; last bytes.
				ADD		CX, STR_BUF_SIZE; we have wrapped around in STR_BUF
	   PNL_L2:
				JCXZ	PNL_EMPTY
				MOV		BL, CL			; CL now contains the candidate's
				MOV		BH, CL			; length.
				CMP		DL, CL			; initialize string-pointers
				JNB		PNL_L3

				CALL	BEEP			; if too long ring bell twice (as long)
				CALL	BEEP

	PNL_EMPTY:
				ADD		SP, 4			; discard return address and
										; EOS which are on the stack
				JMP		FI_RESTART


		;----- the new string can be transferred now
		;

	   PNL_L3:
				MOV		DI, [BP+2]		; get address of target buffer

				INC		SI				; set source pointer
	   PNL_L4:
				CMP		SI, STR_BUF_END ; wrap-around in STR_BUF
				JNE		PNL_L5			; if nessecary
				MOV		SI, STR_BUF_START
	   PNL_L5:
				LODS	BYTE PTR CS:[SI]; do the transfer
				STOSB					;
				CALL	DISPLAY_COMPLEX ; display this byte
				LOOP	PNL_L4			;

		;-----the pointers BH, BL, and DI are properly set up now
		;	  the current cursor-position is at the end of the new string
		;

		;----- compute difference in string length
		;

				POP		CX				; recover saved STR_END_POS
										;  of previous string
				MOV		AL, CH			; AL := (OLD)STR_END_ROW
				SUB		CX, CURSOR_POS	; !! CX := CURSOR_POS - (OLD)STR_END_POS
				JBE		PNL_L8			; new > old ?
				SUB		AL, CURSOR_ROW	; AL := CURSOR_ROW - (OLD)STR_END_ROW
				MOV		AH, MAX_COL
				NEG		AH
				MUL		AH				; multiply by (256 - characters/column)
				SUB		CX, AX			; compensate for subtraction !!
										; CX := (oldROW - newROW)*MAX_COL +
										;		(oldCOL - newCOL)
				PUSH	CX

		PNL_L6:
				MOV		AL, ' '			; blank out remnant characters
				CALL	DISPLAY_COMPLEX
				LOOP	PNL_L6
				POP		CX

		PNL_L7:
				MOV		AL, BACKSPACE	; set cursor back
				CALL	DISPLAY_COMPLEX
				LOOP	PNL_L7

		PNL_L8:

				PUSH	CURSOR_POS
				POP		STR_END_POS
				RET						; top of stack = address MAINLOOP




NEXT_LINE_R:

		;---------------------------
		; empty the current buffer and get the one more
		; recent string from the string buffer

				MOV		BP, SP			; need access to buffer address
										; which is saved on the stack
				PUSH	STR_END_POS		; save current end of string on screen
				CALL	CURSOR_BEGIN_R

		;----- find next string
		;

				MOV		AX, C_TBL_TAIL
				CMP		AX, C_TBL_HEAD
				JE		PNL_EMPTY		; no string found
				MOV		BX, CURRENT
				MOV		SI, CS:[BX]		; get current string's end
				CMP		BX, AX			; current=tail ?
				JE		NL_L2			; yes, wraparound
				CALL	SCROLLDO_TBL	; else, scroll down
				CMP		BX, AX			; at tail now ?
				JNE		NL_L3			; no, go and update CURRENT
				MOV		SI, CS:[BX]		; else, new old string's end

		NL_L2:
				MOV		BX, C_TBL_HEAD	; wraparound

		NL_L3:
				MOV		CURRENT, BX		; new current string
				MOV		CX, CS:[BX]		; new string's end
				JMP		PNL_L1			; next check the length


CURSOR_BEGIN_R:

		;----- moves the cursor to the start of the buffer
		;

				OR		BH, BH			; is cursor at start ?
				JZ		CBR_DONE		; if so then return
				CALL	CURSOR_LEFT_R	; move one to the left
				JMP		CURSOR_BEGIN_R	; and again until at start
   CBR_DONE:
				RET

CURSOR_END_R:

		;----- moves the cursor to the end of the buffer
		;

				CMP		BL, BH			; is cursor at end ?
				JE		CER_DONE		; yes: return
				CALL	CURSOR_RIGHT_R	; move one towards the end
				JMP		CURSOR_END_R	; until done
   CER_DONE:
				RET

F1_R:

		;----- move cursor one word left
		;

				CALL	CURSOR_LEFT_R			;
				OR		BH, BH					; move one character left
				JZ		F1_L2					; until a non-blank is
				CMP		BYTE PTR [DI-1], ' '	; reached
				JE		F1_R					;

		F1_L1:
				CALL	CURSOR_LEFT_R			; now move left until
				OR		BH, BH					; the next blank
				JZ		F1_L2					;
				CMP		BYTE PTR [DI-1], ' '	;
				JNE		F1_L1

		F1_L2:									;
				RET


F2_R:

		;----- move cursor one word right
		;

				CALL	CURSOR_RIGHT_R			;
				CMP		BL, BH					; move one character right
				JE		F2_L2					; until a blank is reached
				CMP		BYTE PTR [DI], ' '		;
				JNE		F2_R

		F2_L1:
				CALL	CURSOR_RIGHT_R
				OR		BH, BH					; move on until the
				JZ		F2_L2					; next non-blank
				CMP		BYTE PTR [DI], ' '		;
				JE		F2_L1					;

		F2_L2:									;
				RET


F3_R:

		;----- erase from begin of the string to the current cursor position
		;

				CLR		CH
				MOV		CL, BH
				JCXZ	F3_L2			; CX=0: we are at start of string

		F3_L1:
				PUSH	CX				; save number of characters
				CALL	BACKSPACE_R		; we are going to remove
				POP		CX				; recover number of characters
				LOOP	F3_L1			;

		F3_L2:
				RET


F4_R:

		;----- erase from the current cursor position to the end
		;

				CLR		CH
				MOV		CL, BL
				SUB		CL, BH
				JZ		F4_L2
				CALL	CURSOR_END_R

		F4_L1:
				PUSH	CX
				CALL	BACKSPACE_R
				POP		CX
				LOOP	F4_L1

		F4_L2:
				RET


F5_R:

		;----- insert current string in STR_BUF and start editing a fresh one
		;

				MOV		BP, SP			; need access to buffer address
										; which is saved on the stack
				CALL	CURSOR_END_R
				MOV		AL, '@'			; show what is going to happen
				CALL	DISPLAY_CHAR	;
				MOV		AL, CR
				CALL	DISPLAY_CHAR
				MOV		AL, LF
				CALL	DISPLAY_CHAR

				MOV		SI, [BP+2]		; get source pointer from stack
				CLR		CH
				MOV		CL, BL
				CALL	STR_BUF_INSERT

				ADD		SP, 2			; discard return address on stack
				JMP		FI_RESTART


F6_R:

		;----- End Of File character on function-key 6
		;

				MOV		AL, EOF
				JMP		M_L1			; go insert this character


F7_R:

		;----- Nul on function-key 7
		;

				CLR		AL
				JMP		M_L1			; go insert this character


CTRL_LEFT_R:

		;----- move cursor downwards
		;

				MOV		AX, CURSOR_TYPE
				CMP		AL, CURSOR_MASK
				JE		CTRL_LR_X
				INC		AL
				INC		AH
				MOV		CURSOR_TYPE, AX
				CALL	SET_CURSOR_TYPE
	CTRL_LR_X:
				RET

CTRL_RIGHT_R:

		;----- move cursor upwards
		;

				MOV		AX, CURSOR_TYPE
				OR		AH, AH
				JZ		CTRL_RR_X
				DEC		AL
				DEC		AH
				MOV		CURSOR_TYPE, AX
				CALL	SET_CURSOR_TYPE
	CTRL_RR_X:
				RET

CTRL_HOME_R:

		;----- move cursor-top downwards
		;

				MOV		AX, CURSOR_TYPE
				CMP		AH, CURSOR_MASK
				JE		CTRL_HR_X
				INC		AH
				MOV		CURSOR_TYPE, AX
				CALL	SET_CURSOR_TYPE
	CTRL_HR_X:
				RET

CTRL_END_R:

		;----- move cursor-bottom downwards
		;

				MOV		AX, CURSOR_TYPE
				CMP		AL, CURSOR_MASK
				JE		CTRL_ER_X
				INC		AL
				MOV		CURSOR_TYPE, AX
				CALL	SET_CURSOR_TYPE
	 CTRL_ER_X:
				RET

CTRL_PGUP_R:

		;----- move cursor-top upwards
		;

				MOV		AX, CURSOR_TYPE
				OR		AH, AH
				JZ		CTRL_PU_X
				DEC		AH
				MOV		CURSOR_TYPE, AX
				CALL	SET_CURSOR_TYPE
	 CTRL_PU_X:
				RET

CTRL_PGDN_R:

		;----- move cursor-bottom upwards
		;

				MOV		AX, CURSOR_TYPE
				OR		AL, AL
				JZ		CTRL_PD_X
				DEC		AL
				MOV		CURSOR_TYPE, AX
				CALL	SET_CURSOR_TYPE
	 CTRL_PD_X:
				RET

SHIFT_F5_R:

		;----- decrement change in INSERT cursor-shape
		;

				CMP		INSERT_SHIFT, 0
				JE		SHIFT_F5_X
				DEC		INSERT_SHIFT
				CALL	SET_CURSOR_TYPE
	 SHIFT_F5_X:
				RET

SHIFT_F6_R:

		;----- increment change in INSERT cursor-shape
		;

				CMP		INSERT_SHIFT, CURSOR_MASK
				JE		SHIFT_F6_X
				INC		INSERT_SHIFT
				CALL	SET_CURSOR_TYPE
	 SHIFT_F6_X:
				RET


ALT_DIGIT:

		;----- scroll up through previous strings a number of
		;	   times given by the digit key which was pressed
		;	   along with the ALT-key.
		;

				SUB		AL, ALT_1		; make use of contiguous range of codes
										; generated by the ALT-digit keys
										; AL now in range 0 .. 9
				CLR		CH
				MOV		CL, AL
				JCXZ	DA_LX			; so ALT_1 same as arrow-up

				PUSH	BX
				MOV		BX, CURRENT		; get CURRENT

		DA_L1:
				CMP		BX, C_TBL_HEAD	; at oldest string in table ?
				JNE		DA_L2			; if so, stop, no wrap around
				CALL	SCROLLDO_TBL	; and undo last scrollup
				JMPS	DA_L3

		DA_L2:
				CALL	SCROLLUP_TBL	; else one entry up
				LOOP	DA_L1			; repeat CX times

		DA_L3:
				MOV		CURRENT, BX		; update CURRENT
				POP		BX

		DA_LX:
				JMP		PREV_LINE_R		;



MAIN_LOOP_RETURN:

		;----- no match in extended ascii found, return to main loop
		;

				RET



MAIN_LOOP		ENDP


REQ_HDR			STRUC

	LENGTH		DB		?
	UNIT		DB		?
	COMMAND		DB		?
	STATUS		DW		?
	RESERV		DB		8 DUP(?)

REQ_HDR			ENDS

INIT_HDR		STRUC

				DB		SIZE REQ_HDR DUP(?)
	NO_UNITS	DB		?
	END_ADR		DD		?
	PARMS		DD		?

INIT_HDR		ENDS

IO_HDR			STRUC

				DB		SIZE REQ_HDR DUP(?)
	MEDIA		DB		?
	BUFFER_ADR	DD		?
	COUNT		DW		?

IO_HDR			ENDS

;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
;
;		D E V I C E	  S T R A T E G Y	 ( ENTRY POINT )
;
;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

D_STRATEGY		PROC	FAR

		;----- save request header address
		;

				MOV		REQ_HEADER_OFS, BX
				MOV		REQ_HEADER_SEG, ES
				RET

D_STRATEGY		ENDP


D_F_TABLE		LABEL	WORD			; this table contains routine addresses
										; for the device-driver functions

				DW		D_INIT			;  0: driver initialization
				DW		D_NOP			;  1: media check
				DW		D_NOP			;  2: build BPB
				DW		D_IOCTL_IN		;  3: IO-control input
				DW		D_INPUT			;  4: input
				DW		D_NON_DSTR_IN	;  5: non-destructive input
				DW		D_INPUT_STATUS	;  6: input status
				DW		D_INPUT_FLUSH	;  7: input flush
				DW		D_OUTPUT		;  8: output
				DW		D_OUTPUT_VER	;  9: output with verify
				DW		D_OUTPUT_STATUS ; 10: output status
				DW		D_OUTPUT_FLUSH	; 11: output flush
				DW		D_IOCTL_OUT		; 12: IO-control output
				DW		D_OPEN			; 13: device open
				DW		D_CLOSE			; 14: device close
				DW		D_NOP			; 15: removable media

D_F_TABLE_LEN	=		$ - D_F_TABLE

;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
;
;		D E V I C E	  I N T E R R U P T	   ( ENTRY POINT )
;
;-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

D_INTERRUPT		PROC	FAR								;
				STI
				PUSH	ES								;
				PUSH	DS								;
				PUSH	BP								; save
				PUSH	DI								; registers
				PUSH	SI								;
				PUSH	DX								;
				PUSH	CX								;
				PUSH	BX								;
				PUSH	AX

				CALL	SETSTACK						; to internal stack
				MOV		BIOS_TTY_SW, ON
				MOV		ES, REQ_HEADER_SEG				; get request-
				MOV		BX, REQ_HEADER_OFS				; header block
				MOV		AL, ES: [BX].COMMAND			; get opcode
				CLR		AH
				SHL		AX, 1
				CMP		AX, D_F_TABLE_LEN
				JAE		D_ERROR
				MOV		SI, AX
				JMP		D_F_TABLE[SI]					; do request

		;----- it is assumed that all routines preserve
		;	   the ES:BX pointer to the Request Header
		;

	  D_ERROR:
				OR		ES: [BX].STATUS, 8003H			; invalid code

	  D_EXIT:
				OR		ES: [BX].STATUS, 0100H			; status_ok
				MOV		BIOS_TTY_SW, OFF
				CALL	RESETSTACK						; back to
														; callers stack
				POP		AX
				POP		BX
				POP		CX								;
				POP		DX								;
				POP		SI								; restore
				POP		DI								; registers
				POP		BP								;
				POP		DS								;
				POP		ES								;
				RET


D_INTERRUPT		ENDP


	D_NOP			 :
	D_IOCTL_IN		 :
	D_INPUT			 :
	D_NON_DSTR_IN	 :
	D_INPUT_STATUS	 :
	D_INPUT_FLUSH	 :
	D_OUTPUT_STATUS	 :
	D_OUTPUT_FLUSH	 :
	D_IOCTL_OUT		 :
	D_OPEN			 :
	D_CLOSE			 :
						JMP		D_ERROR


INSTALL_BUF		PROC	NEAR

		;***************************************************************
		;* Redirect the DOS-keyboard-buffer pointers to enlarged buffer
		;* Copy old buffer contents to the new buffer
		;***************************************************************

				PUSH	DS
				CMP		KB_INST, ON		;
				JNE		IB_L1			; buffer is already here
				LEA		SI, MSG_KB_PR	; already present message
				JMP		IB_LX
	   IB_L1:
				CMP		KBBUFFER_SIZE, 32		; worth the trouble ?
				JA		IB_L2					;

				LEA		SI, MSG_KB_NO_1			; message: no succes
				CALL	PRINT
				MOV		AX, KBBUFFER_SIZE
				SHR		AX, 1
				CALL	PRINT_NUM
				LEA		SI, MSG_KB_NO_2
				JMP		IB_LX

	   IB_L2:
				ASSUME	DS: DOS_DATA_AREA		;
				MOV		AX, DOS_DATA_AREA		; data segment on
				MOV		DS, AX					; DOS-OS Data
				CLR		DI
				MOV		BX, KBBUFFER
				CLI
				MOV		SI, KB_HEAD				; move any data already
	   IB_L3:
				CMP		SI, KB_TAIL				; in the DOS-buffer to
				JE		IB_L4					; new buffer
				CMP		DI, 32					; can not copy indefinitely
				JE		IB_L4					; because new buffer overlays
												; initialization code
				MOV		AX, [SI]				;
				MOV		CS: [BX+DI], AX			; transfer item
				INC		DI						; keep character count
				INC		DI						;
				INC		SI						;
				INC		SI						;
				CMP		SI, DOS_BUFFER_END		; wraparound if
				JNE		IB_L3					; neccesary
				MOV		SI, DOS_BUFFER_START	;
				JMP		IB_L3					;
	   IB_L4:
				MOV		AX, DOS_BUFFER_START
				MOV		OLD_DOS_BUFFER_START, AX
				MOV		AX, DOS_BUFFER_END
				MOV		OLD_DOS_BUFFER_END, AX
				MOV		AX, CS					;
				SUB		AX, DOS_DATA_AREA		; make AX point to offset
				MOV		CL, 4					; KBBUFFER relative to
				SHL		AX, CL					; segment DOS_DATA_AREA
				ADD		AX, KBBUFFER			;
				MOV		DOS_BUFFER_START, AX	;
				MOV		KB_HEAD, AX				; change pointers to
				MOV		KB_TAIL, AX				; our own buffer
				ADD		KB_TAIL, DI				;
				ADD		AX, KBBUFFER_SIZE		; add number of characters
				MOV		DOS_BUFFER_END, AX		; taken over from old buffer
				STI
				ASSUME	DS: NOTHING

				MOV		KB_INST, ON		; presence-flag on
				LEA		SI, MSG_KB_IN	; message: succes
				CALL	PRINT
				LEA		SI, MSG_KB_1	; give size of buffer
				CALL	PRINT
				MOV		AX, KBBUFFER_SIZE
				SHR		AX, 1
				CALL	PRINT_NUM
				LEA		SI, MSG_KB_2

		 IB_LX:
				CALL	PRINT			;
				POP		DS
				RET
INSTALL_BUF		ENDP



UNINSTALL_BUF	PROC	NEAR

		;************************************************
		;* Restore original keyboard buffer pointers
		;*************************************************

				PUSH	DS

				CMP		KB_INST, OFF
				JE		UB_L1

				ASSUME	DS: DOS_DATA_AREA		;
				MOV		AX, DOS_DATA_AREA		; data segment on
				MOV		DS, AX					; DOS-OS Data
				CLI
				MOV		AX, OLD_DOS_BUFFER_START
				MOV		DOS_BUFFER_START, AX	; restore old
				MOV		KB_HEAD, AX				; buffer addresses
				MOV		KB_TAIL, AX				; note:
				MOV		AX, OLD_DOS_BUFFER_END	;	buffer is flushed
				MOV		DOS_BUFFER_END, AX
				STI
				ASSUME	DS: NOTHING

				MOV		KB_INST, OFF
				LEA		SI, MSG_KB_UN	; buffer-gone message
				JMPS	UB_LX

		 UB_L1:
				LEA		SI, MSG_KB_NOT_PR		; error message

		 UB_LX:
				CALL	PRINT			; display message

				POP		DS
				RET
UNINSTALL_BUF	ENDP


INSTALL_ED		PROC	NEAR

		;**********************************************************
		;* Redirect interrupt 21H function 0AH to enable enhanced
		;* editing and buffering
		;**********************************************************

				PUSH	ES
				CMP		EDT_INST, ON	;
				JE		IE_L2			; editor already installed

				CMP		EDT_INST, NOT_INST		; test for first turn on
				MOV		EDT_INST, ON			; presence-flag on
				JNE		IE_L1					; install INT21H interrupt
												; address, if not already done
				CLR		AX						; segment 0 for interrupt-
				MOV		ES, AX					; address manipulation
				LES		BX, ES: [DOS_FUNCTION_INT]
												; save old address
				MOV		WORD PTR OLD_FUNCTION_INT, BX
				MOV		WORD PTR OLD_FUNCTION_INT[2], ES
				MOV		ES, AX
												; replace with new address
				CLI
				MOV		ES: [DOS_FUNCTION_INT], OFFSET FUNCTION_INT_ENTRY
				MOV		ES: [DOS_FUNCTION_INT + 2], CS
				STI

		IE_L1:
				LEA		SI, MSG_EDT_IN	; reinitialize message
				CALL	PRINT
				LEA		SI, MSG_EDT_1	; editor specification
				CALL	PRINT
				MOV		AX, STR_BUF_SIZE
				CALL	PRINT_NUM
				LEA		SI, MSG_EDT_2
				CALL	PRINT
				MOV		AX, C_TBL_SIZE
				SHR		AX, 1			; size in bytes --> no-of-entries
				DEC		AX				; table-size = entries - 1
				CALL	PRINT_NUM
				LEA		SI, MSG_EDT_3
				JMPS	IE_LX

		IE_L2:
				LEA		SI, MSG_EDT_PR	; already present message

		IE_LX:
				CALL	PRINT			;

				POP		ES
				RET
INSTALL_ED		ENDP

UNINSTALL_ED	PROC	NEAR

		;*****************************************************
		;* restore INT 21H interrupt address
		;* if not possible, zero flag is OFF at exit
		;*****************************************************

				PUSH	ES

				CMP		EDT_INST, ON
				JNE		UE_L2
				MOV		EDT_INST, OFF

				CLR		AX
				MOV		ES, AX

		;----- check whether our address still in vector
		;

				CMP		ES: [DOS_FUNCTION_INT], OFFSET FUNCTION_INT_ENTRY
				JNE		UE_L1
				MOV		AX, CS
				CMP		ES: [DOS_FUNCTION_INT + 2], AX
				JNE		UE_L1

		;----- OK, undo revectoring
		;

				MOV		AX, WORD PTR OLD_FUNCTION_INT
				MOV		ES: [DOS_FUNCTION_INT], AX
				MOV		AX, WORD PTR OLD_FUNCTION_INT[2]
				MOV		ES: [DOS_FUNCTION_INT + 2], AX

				MOV		EDT_INST, NOT_INST

		UE_L1:
				LEA		SI, MSG_EDT_UN	; editor-gone message
				JMPS	UE_LX
		UE_L2:
				LEA		SI, MSG_EDT_NOT_PR
		UE_LX:
				CALL	PRINT			; editor-already-gone message

				POP		ES
				RET
UNINSTALL_ED	ENDP

	RESET_EDT	PROC	NEAR
				MOV		AX, C_TBL_START
				MOV		C_TBL_HEAD, AX
				MOV		C_TBL_TAIL, AX
				MOV		CURRENT, AX
				RET
	RESET_EDT	ENDP

INSTALL_ANSI	PROC	NEAR
				MOV		ANSI, ON
				MOV		MAX_COL, 80
				RET
INSTALL_ANSI	ENDP

UNINSTALL_ANSI	PROC	NEAR
				MOV		ANSI, OFF
				RET
UNINSTALL_ANSI	ENDP

RECALL			PROC	NEAR

		;----- display all strings in STR_BUF
		;

				CLR		DX						; initialize
				MOV		DI, C_TBL_SIZE			; DX = sequence number
				SHR		DI, 1					; DI = maximum number of strings
				DEC		DI						;
				MOV		BX, C_TBL_TAIL			; BX = pointer into C_TBL entry
				MOV		SI, CS:[BX]				; of string currently displayed
				MOV		BX, C_TBL_HEAD			;

	 REC_L1:
				CMP		BX, C_TBL_TAIL			;
				JE		REC_LX					; all strings done, normal exit

				MOV		AL, CR
				CALL	WRITE_CHAR
				MOV		AL, LF
				CALL	WRITE_CHAR

				INC		DX
				CMP		DX, DI			; savety check, prevent endless looping
				JA		RESET_EDT		; this jump should not occur, error exit

				MOV		AX, DX
				CALL	PRINT_NUM

				MOV		AL, '.'
				CALL	WRITE_CHAR
				MOV		AL, ' '
				CALL	WRITE_CHAR

				MOV		CX, CS:[BX]				; last character of string
				SUB		CX, SI					; compute length
				JAE		REC_L2
				ADD		CX, STR_BUF_SIZE

	 REC_L2:
				INC		SI						; next character
				CMP		SI, STR_BUF_END
				JNE		REC_L3					; wrap around if necessary
				MOV		SI, STR_BUF_START

	 REC_L3:
				MOV		AL, CS:[SI]
				CALL	DISPLAY_COMPLEX
				LOOP	REC_L2

				CALL	SCROLLDO_TBL
				JMP		REC_L1

	 REC_LX:
				RET
RECALL			ENDP



D_OUTPUT:
D_OUTPUT_VER:

		;************************************************************
		;* Output routine.
		;* Characters output to this device are interpreted as
		;* control codes to switch the editor and buffer on and off.
		;* Such control codes consist always of two characters:
		;*		 "IK" = Install Keyboard Buffer.
		;*		 "IE" = Install Keyboard Editor.
		;*		 "UK" = Uninstall Keyboard Buffer.
		;*		 "UE" = Uninstall Keyboard Editor.
		;*		 "RE" = Reset Editor
		;*		 "IA" = ANSI mode On
		;*		 "UA" = ANSI mode Off
		;*
		;* On entry:  ES:BX contains long pointer to Request Header
		;************************************************************

				PUSH	ES
				PUSH	BX
				MOV		CX, ES: [BX].COUNT		; number of characters in CX
				OR		CX, CX			;
				JZ		O_LX			; check for a mistake
				LES		BX, ES: [BX].BUFFER_ADR ; load address of buffer
				MOV		AL, ES: [BX]	; get first character from buffer
				CMP		EXPECTSECOND, ON; second part expected?
				JE		O_L1			; yes: treat second part
				MOV		FIRSTPART, AL	; no: save this first part
				MOV		EXPECTSECOND, ON; now we do expect the second part
				CMP		CX, 1			; was there more then one?
				JBE		O_LX			; no: wait for next arrival
				MOV		AL, ES: [BX+1]	; yes: get second character

		;----- at this point there is a complete command
		;	   and we are ready for interpreting
		;

		 O_L1:
				MOV		EXPECTSECOND, OFF
				MOV		AH, FIRSTPART	; AX now contains the
										; two letter code
				AND		AX, 0DFDFH		; capitalize
				CMP		AX, 'IK'		;
				JNE		O_L2
				CALL	INSTALL_BUF		; code = 'IK'
				JMPS	O_LX
		O_L2:
				CMP		AX, 'IE'		;
				JNE		O_L3
				CALL	INSTALL_ED		; code = 'IE'
				JMPS	O_LX
		O_L3:
				CMP		AX, 'UK'		;
				JNE		O_L4
				CALL	UNINSTALL_BUF	; code = 'UK'
				JMPS	O_LX
		O_L4:
				CMP		AX, 'UE'		;
				JNE		O_L6
				CALL	UNINSTALL_ED	; code = 'UE'
				JMPS	O_LX
		O_L6:
				CMP		AX, 'IA'		;
				JNE		O_L7
				CALL	INSTALL_ANSI	; code = 'IA'
				JMPS	O_LX
		O_L7:
				CMP		AX, 'UA'		;
				JNE		O_L8
				CALL	UNINSTALL_ANSI	; code = 'UA'
				JMPS	O_LX
		O_L8:
				CMP		AX, 'RC'		;
				JNE		O_L9
				CALL	RECALL			; code = 'UA'
				JMPS	O_LX
		O_L9:
				CMP		AX, 'RE'
				JNE		O_LX
				CALL	RESET_EDT		; code = 'RE'

	  ;----- otherwise invalid combination
	  ;

		O_LX:
				POP		BX
				POP		ES
				JMP		D_EXIT			; to exit protocol

	  ;----- round origin to paragraph boundary
	  ;

				ORG		($ - START_CODE + 15) AND 0FFF0H

END_CODE		=		$


   ACTIVATE		DB		OFF				; activate immediate switch
   P_CHAR_COUNT DW		?				; character count on parameter line

 MSG_INVA_SW_1	DB		' Invalid switch at position		  : ', NULL
 MSG_INVA_SW_2	DB		' in Configuration file'
				DB		CR, LF, LF, NULL

 MSG_INVA_CHR_1 DB		' Invalid character at position		  : ', NULL
 MSG_INVA_CHR_2 DB		' in Configuration file'
				DB		CR, LF, LF, NULL

 MSG_ADR_OVL	DB		' Buffer sizes too large, reduce parameter values'
				DB		' in Configuration file'
				DB		CR, LF
				DB		' Default values substituted'
				DB		CR, LF, LF, NULL

 MSG_OVL_1		DB		' Parameter value overflow at position: ', NULL
 MSG_OVL_2		DB		' in Configuration file'
				DB		CR, LF, NULL

 MSG_ADJUST_1	DB		'  Specified size for keyboard buffer too large,'
				DB		'  size adjusted to : ', NULL
 MSG_ADJUST_2	DB		' bytes', CR, LF, NULL


 GET_PARMS		PROC	NEAR

;*************************************************************
;  This procedure scans the string addressed by DS:SI for
;  the presence of parameters to the driver.
;
;  Parameters must have the following format:
;
;	 DEVICE=\path\drv_name K:nnn B:nnn C:nnn [/A] [/N]
;
;	where  nnn	is a decimal number.
;
;	K:nnn  nnn is keyboard buffer size
;	B:nnn  nnn is buffer size for command strings
;	C:nnn  nnn is maximum number of commands that can be stored
;	/A	   if present: activation on driver installation
;	/N	   if present: cursor control through ANSI-codes
;
;
;  The code should implement the following:
;
;	 ----------------------
;
;	 C: CHAR;
;	 Separators = [NULL, ' ', ',', '=', ';', '+', TAB];
;
;	 ON EndOfLine: EXIT;
;	 REPEAT GetChar(C) UNTIL C IN Separators;
;	 REPEAT
;	   WHILE (C IN Separators) DO GetChar(C);
;	   CASE C OF
;		  'K': OUT1 := GetNum;
;		  'B': OUT2 := GetNum;
;		  'C': OUT3 := GetNum;
;		  '/': [GetChar(C); IF C='A' THEN ACTIVATE := ON
;							ELSE IF C='N' THEN ANSI := ON
;			   ];
;
;	 UNTIL EndOfLine;
;
;	 GetNum:
;	   GetNum := 0;
;	   IF C = ':' THEN GetChar(C);
;	   WHILE (C IN ['0'..'9']) DO [
;		 GetNum := GetNum*10 + (Ord(C) - Ord('0'));
;		 GetChar(C)
;	   ];
;
;	  ---------------------
;
;

				LDS		SI, ES: [BX].PARMS
				MOV		P_CHAR_COUNT, 7			; assume 'DEVICE=' occupies
												; first 7 positions in CONFIG
		GP_L1:
				CALL	GET_P_CHAR
				JZ		GP_LX

				CALL	IN_SEPARATORS
				JNZ		GP_L1

	  GP_NEXT:
				CALL	GET_P_CHAR
				JZ		GP_LX

				CALL	IN_SEPARATORS
				JZ		GP_NEXT

				CMP		AL, 'K'
				JNE		GP_LB

		;----- collect keyboard buffer size
		;

				CALL	GET_NUM
				OR		CX, CX			; check result
				JZ		GP_LK1			; zero ?
				JS		GP_LO			; > 32K ?
				SHL		CX, 1			; each keystroke fills two bytes
				MOV		KBBUFFER_SIZE, CX		; in buffer
		GP_LK1:
				JMP		GP_NEXT

		GP_LB:
				CMP		AL, 'B'
				JNE		GP_LC

		;----- collect string buffer size
		;

				CALL	GET_NUM
				OR		CX, CX			; check result
				JZ		GP_LB1
				MOV		STR_BUF_SIZE, CX
		GP_LB1:
				JMP		GP_NEXT

		GP_LC:
				CMP		AL, 'C'
				JNE		GP_LS

		;----- collect maximum number of commands
		;

				CALL	GET_NUM
				OR		CX, CX			; check result
				JZ		GP_LC1
				INC		CX				; table-size = no-of-entries + 1
				JS		GP_LO			; > 32K ?
				SHL		CX, 1			; CX = no-of-entries, convert to bytes
				MOV		C_TBL_SIZE, CX
		GP_LC1:
				JMP		GP_NEXT

		GP_LX:
				RET

		GP_LS:
				CMP		AL, '/'
				JNE		GP_L6

		;----- collect switches
		;

				CALL	GET_P_CHAR
				JZ		GP_LX
				CMP		AL, 'A'
				JNE		GP_LS1
				MOV		ACTIVATE, ON
				JMP		GP_NEXT
		GP_LS1:
				CMP		AL, 'N'
				JNE		GP_LS2
				MOV		ANSI, ON
				JMP		GP_NEXT

		GP_LS2:

		;----- invalid switch
		;

				PUSH	SI
				LEA		SI, MSG_INVA_SW_1
				CALL	PRINT
				MOV		AX, P_CHAR_COUNT
				CALL	PRINT_NUM
				LEA		SI, MSG_INVA_SW_2
				CALL	PRINT
				POP		SI
				JMP		GP_L1

		GP_LO:

		;----- overflow
		;

				PUSH	SI
				LEA		SI, MSG_OVL_1
				CALL	PRINT
				MOV		AX, P_CHAR_COUNT
				DEC		AX
				CALL	PRINT_NUM
				LEA		SI, MSG_OVL_2
				CALL	PRINT
				POP		SI
				JMP		GP_NEXT

		GP_L6:

		;----- invalid character on line
		;

				PUSH	SI
				LEA		SI, MSG_INVA_CHR_1
				CALL	PRINT
				MOV		AX, P_CHAR_COUNT
				CALL	PRINT_NUM
				LEA		SI, MSG_INVA_CHR_2
				CALL	PRINT
				POP		SI
				JMP		GP_L1					; go on after next separator


IN_SEPARATORS	PROC	NEAR

		;----- determine if character is in the separator set
		;	   in: AL: character
		;	   return: zero flag set if Al in separators
		;	   !! CR and LF are also return zero
		;
				CMP		AL, NULL
				JE		IS_LX
				CMP		AL, ' '
				JE		IS_LX
				CMP		AL, ','
				JE		IS_LX
				CMP		AL, '='
				JE		IS_LX
				CMP		AL, '+'
				JE		IS_LX
				CMP		AL, ';'
				JE		IS_LX
				CMP		AL, TAB
				JE		IS_LX
				CMP		AL, CR
				JE		IS_LX
				CMP		AL, LF
		IS_LX:
				RET
IN_SEPARATORS	ENDP

  GET_P_CHAR	PROC	NEAR

		;----- return next character from parameter-string in AL
		;	   if EndOfLine reached then zero-flag is set
		;
				CMP		AL, CR
				JE		GPC_LX
				LODSB
				INC		P_CHAR_COUNT
				CMP		AL, CR
				JE		GPC_LX
				CMP		AL, LF
		GPC_LX:
				RET
  GET_P_CHAR	ENDP

  PUT_P_CHAR	PROC	NEAR

		;----- reverse last action of GET_P_CHAR
		;	   AL is pushed back onto the "input-stream"
		;
				DEC		SI
				DEC		P_CHAR_COUNT
				RET
  PUT_P_CHAR	ENDP

  GET_NUM		PROC	NEAR

		;----- read a decimal number from parameter string
		;	   out: CX contains the number
		;	   this routine reads only digits, when an other
		;	   character is read it is pushed back through
		;	   PUT_P_CHAR and the routine exits
		;

				CLR		CX
				CALL	GET_P_CHAR
				JZ		GN_LX
				CMP		AL, ':'
				JNE		GN_L2
		GN_L1:
				CALL	GET_P_CHAR
				JZ		GN_LX
		GN_L2:
				CMP		AL, '9'			; test for digit
				JA		GN_LX
				CMP		AL, '0'
				JB		GN_LX
				SUB		AL, '0'
				XCHG	CX, AX			; multiplication operand in AX
				MUL		TEN				; x 10
				XCHG	CX, AX
				JNC		GN_L3			; test for overflow
				CLR		CX				; if so, return zero
				JMPS	GN_LX1
		GN_L3:
				CLR		AH				;
				ADD		CX, AX			; add next digit
				JMP		GN_L1

		GN_LX:
				CALL	PUT_P_CHAR
		GN_LX1:
				RET
  GET_NUM		ENDP

 GET_PARMS		ENDP


 D_INIT			PROC	NEAR

		;************************************************************
		;* Driver initialization routine.
		;*
		;* !! version 2.0 only installs driver at boot-time
		;* !! NO immediate activation of buffers and editor
		;************************************************************

				PUSH	ES
				PUSH	BX
				MOV		BIOS_TTY_SW, ON

				MOV		AX, CS
				MOV		DS, AX					; tell about the instal-
				LEA		SI, MSG_VERSION			; lation of this driver
				CALL	PRINT					;

				CALL	GET_PARMS

		;----- check the parameters
		;

				MOV		CL, 4
				MOV		AX, KBBUFFER_SIZE		; compute end of KBBUFFER
				ADD		AX, 0FH					;
				SHR		AX, CL					; paragraph format
				ADD		AX, (END_CODE - START_CODE) SHR 4
				MOV		CX, CS					; add segment paragraph number
				ADD		AX, CX					;
				SUB		AX, 1000H				; must not exceed first 64K
				JBE		D_IN_L2
				MOV		CL, 4					;
				SHL		AX, CL					; in bytes again
				SUB		KBBUFFER_SIZE, AX		; adjust KBBUFFER_SIZE
				JNC		D_IN_L1					; no negative values
				MOV		KBBUFFER_SIZE, 0

		D_IN_L1:
				LEA		SI, MSG_ADJUST_1		;
				CALL	PRINT					; display message
				MOV		AX, KBBUFFER_SIZE		; and new size of KBBUFFER
				CALL	PRINT_NUM				;
				LEA		SI, MSG_ADJUST_2		;
				CALL	PRINT

		D_IN_L2:
				LEA		AX, END_CODE
				MOV		KBBUFFER, AX			; set start address of KB-buffer

				ADD		AX, KBBUFFER_SIZE
				MOV		STR_BUF_START, AX		; initialize command string
				MOV		STR_BUF_HEAD, AX		; buffer data-structure
				MOV		STR_BUF_TAIL, AX		;
				ADD		AX, STR_BUF_SIZE		;
				JC		D_IN_ERR				; test for overflow

				MOV		STR_BUF_END, AX			;

				MOV		SI, AX					; initialize first entry
				MOV		CS:[SI], AX				; in C_TBL with address of
				DEC		WORD PTR CS:[SI]		; last byte in STR_BUF

				MOV		C_TBL_START, AX			; start address of C_TBL
				MOV		C_TBL_HEAD, AX			;
				MOV		C_TBL_TAIL, AX			;
				MOV		CURRENT, AX
				ADD		AX, C_TBL_SIZE			; now AX = end of resident code
				JC		D_IN_ERR				; test for overflow
				MOV		C_TBL_END, AX			; end of C_TBL

		;----- fill in in Request Header
		;

				MOV		WORD PTR ES: [BX].END_ADR, AX
				MOV		WORD PTR ES: [BX].END_ADR+2, CS

				CMP		ACTIVATE, ON			; /A switch present ?
				JNE		D_IN_LX
				CALL	INSTALL_BUF				; if so, install now
				CALL	INSTALL_ED				;

		D_IN_LX:
				MOV		BIOS_TTY_SW, OFF		; display through DOS
				POP		BX
				POP		ES
				JMP		D_EXIT					; initialization done:
												;  to exit protocol
	 D_IN_ERR:
				LEA		SI, MSG_ADR_OVL			; address-overflow message
				CALL	PRINT					;
				MOV		C_TBL_SIZE, 32			; set default values
				MOV		STR_BUF_SIZE, 512		; these values should not
				MOV		ACTIVATE, OFF			; cause trouble
				JMP		D_IN_L2					; try again

 D_INIT			ENDP

	CODE		ENDS
				END

