PUBLIC	.SCREEN
PUBLIC  .InCh, .OutCh, .OutStr, .Piss, .?Col, CRUBOUT
PUBLIC  .Error0, .Error1, Sound
PUBLIC  $History, $KBufPtr, $KBufStart, $KBufEnd, .Makespace, MainScreenDraw
PUBLIC	MainCommand

EXTRN	FSCREEN:NEAR, FPARSE:NEAR, FMODELINE:NEAR
EXTRN	CCENTER:NEAR, .MSG:NEAR
EXTRN	ESCAPE_CHARACTER:NEAR, EXTENDED_CHARACTER:NEAR
EXTRN	CTRL_X_SEQUENCE:NEAR, CTRL_CHARACTER:NEAR, FVINIT:NEAR
EXTRN	CDELK:NEAR, $REPEAT:WORD, .CLRMsg:Near, $NoUpdate:Word
EXTRN	CBACK:NEAR, WindowStart:Word, WindowEnd:Word, GetColumn:Near
EXTRN	LastLineUpdate:NEAR, LASTLASTLine:WORD, .InvMsg:Near
EXTRN	SwitchMenu:Near

EXTRN	MACPtr:Word, MacCtr:Byte, MacBack:Byte, MacArg:Word
EXTRN	MacroDefinition:Byte, MacroInvocation:Byte, Store:Byte

EXTRN	LastLabel:Near

EXTRN	$StackInit:WORD, Alloc:NEAR

;---------------------------------------------------------------------------
	;	MAX
	;
	;	This is the main routine calling all other routines. Some
	;	remarks at the end of this file

PROGRAM SEGMENT Para	PUBLIC  'Code'
	Org 100h

;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
%Out	Definitions

	INCLUDE		FDEF.DEF

if	mono
	noflicker = false
endif

	Autotype	equ	70	; 70 characters maximum can be typed
					; before they are all actually
					; inserted in buffer ( and not only
					; quickly to screen )

	MS_FN	MACRO	Fun
		mov	AH, Fun
		int	21h
	ENDM

	Prt	MACRO	text
		push	DX
		push	AX
		push	DS
		mov	AX, CS
		mov	DS, AX
		mov	DX, Offset text
		MS_FN	9
		pop	DS
		pop	AX
		pop	DX
	ENDM


	case	MACRO	Index, Table	;; does a near jump via an Indextable
		push	BX		;; to an 8bit Register
		push	AX
		mov	BL, Index
		sub	BH, BH
		add	BX, BX
		call	CS:[BX]. Table
		pop	AX
		pop	BX
	ENDM







;***************************************************************************
Main	PROC	Far
	ASSUME  CS:PROGRAM, DS:PROGRAM, ES:PROGRAM, SS:PROGRAM

	;**************
	jmp	MAX
	;**************

;***************************************************************************
; Data Section

Welcome	db	'		MAX   [Version 1.01], '
	db	CR, LF
	db	'		(C) 1984, Ivo Welch. All Rights Reserved.'
	db	CR, LF
	db	CR, LF, LF, LF
	db  'This software is protected by U.S. Copyright Law (Title 17 U.S. code).'
	db	CR, LF
	db  'Unauthorized reproduction and/or sales may result in imprisonment of up'
	db	CR, LF
	db  'to one year and fines of up to Dollar 10,000 (17 USC 506).'
	db	CR, LF, LF
	db  'Copyright Infringers may be subject to civil liability.'
	db	CR, LF, LF, '$'

	Copyright	db	'                                                                 [MAX, Copyright (C) Ivo Welch, 1984]'

BNoFit		db	' INSUFFICIENT MEMORY [NO BUFFER SPACE]$'
Helpcode	db	'00'

MEMsg		db	'*** Macro Invocation inside Definition ***'
MEMsg2		db	'*** Macro Overflow ***'
MacCancel	db	'*** User Abort ***'

$History	db	11 DUP (7)
$KBufptr	dw	Offset $KBufStart
$KBufStart	db	70 DUP ( '$')
$KBufEnd	db	'()'

Commandtable	dw	Offset Character
		dw	Offset Ctrl_Character
		dw	Offset Ctrl_X_Sequence
		dw	Offset Escape_Character
		dw	Offset Extended_Character



;***************************************************************************

MAX:					; initialize the SCREEN
	mov	CX, 0			; clear the screen via BIOS
	mov	dx, 184fh
	mov	bh, 7
	mov	AX, 600h
	int 10h

	mov	AH, 1			; set the cursor to block via BIOS
	mov	CH, 0
	mov	CL, 0Fh
	int	10h

	mov	AH, 2			; Cursor home
	sub	DX, DX
	mov	bh, 0
	int	10h

	mov	CX, Offset LastLabel+110h	; and so with this



	Prt	Welcome			; say Hi via DOS

	cld
	call	Alloc			; allocate buffer space
	call	FVINIT			; visit default files
					; afterwards, ES=DS=CS:$Buffer1

	mov	AH, 1			; set the cursor to block
	mov	CH, 0
	mov	CL, 0Fh
	int	10h

	mov	SI, Offset CopyRight	; display a second copyright msg
	mov	CX, 101
	call	.Msg

	mov	AX, 7*256+0		; and initialize the menu at the
	nop				; last line
	call	Switchmenu

MainScreenDraw:
		call	.Screen		; display the first screen

MainWait:
	call	FModeline		; always update modelines in spare t
	call	FParse			; chack for newly typed character
	or	AH, AH
	jz	MainWait		; none, so reupdate

MainCommand:
	dec	AH			; determine CTL-, ESC-, ESC-X or
	case	AH, CommandTable	; other such character, and call

	cmp	CS:$Repeat, 0		; and do it until the arg is 0
	jz	MainScreenDraw
	dec	CS:$Repeat
	jz	MainScreenDraw

	mov	AX, Word Ptr CS:$Repeat+2	; but reload the command
	mov	DL, Byte Ptr CS:$Repeat+4	; that was pressed
	jmp	MainCommand

Main	ENDP



;***************************************************************************
	; Subroutines

	; Character [(AL=Character) -> (*)]

Character	PROC	Near
	mov	DS:BCB.Dirty, TRUE	; buffer is modified

	cmp	AL, 127			; first check whether this is delete
	je	CRubout			; which is more like a control char.

	cmp	CS:$Repeat, 0		; if there is an argument to a char,
	jnz	CharacterArg		; call	Autowrap to put up n chars.


Charac1:call	.OutCH			; wait for more quick inserts
	mov	CX, AutoType

Charac2:call	FParse			; wait for input
	or	AH, AH
	jnz	FastTyped		; loop until you get a char or
	loop	Charac2			; until it is unlikely that he types
					; another one quickly

	call	.Piss			; finally output it
	call	.?Col			; and update the current column
	ret

Fasttyped:
	cmp	AH, 1			; if the character is not a normal
	jne	CharacterExit		; character, or if it is DEL, then
	cmp	AL, 127			; clean out the fastype buffer and
	je	CharacterExit		; execute the proper function
	 jmp	Character		; actually autowrap

CharacterExit:
	push	AX
	push	DX
	call	.Piss
	pop	DX
	pop	AX
	pop	BP		; do not return normal, since we already got
	pop	BX		; a real command, and do not simply piss and
	pop	BX		; return to main loop
	jmp	MainCommand

CharacterArg:
	mov	CX, CS:$Repeat		; make space for all to be inserted
	call	.MakeSpace

	cld				; and store a string into memory
	mov	DI, DS:BCB.FCursor
	mov	CX, CS:$Repeat
	mov	AL, Byte Ptr CS:$Repeat+4
	push	DS
	pop	ES
	rep	stosb
	mov	DS:BCB.FCursor, DI	; and readjust for the new cursor
	mov	CS:$Repeat, 0
	ret

CHaracter	ENDP

;***************************************************************************
	; %OUT CRubout [(BCB.FCursor) -> (Buffer)]
	; calls::	.Piss, CDELK, REPEATS
	; rubs out one character

CRubout PROC	NEAR

	mov	DS:BCB.Dirty, TRUE	; buffer is modified

	call	.Piss			; get rid of old stuff

	mov	SI, DS:BCB.FCursor
	cmp	SI, 100h
	je	CRuboutErr
	dec	SI
	mov	CX, 1
	cmp	Byte Ptr DS:[SI], LF
	jne	CRubout1
	dec	SI
	inc	CX
CRubout1:
	mov	DS:BCB.FCursor, SI
	call	CDELK
	cmp	CS:$Repeat, 0
	jne	CRuboutAgain
	ret
CRuboutAgain:
	dec	CS:$Repeat
	jmp	CRubout

CRuboutErrT	db	'*** File Start ***'
CRuboutErr:
	mov	SI, Offset CRuboutErrT
	mov	CX, 18
	jmp	.Error0
CRubout ENDP

;---------------------------------------------------------------------------
	; This routine calls the routine to update the screen, which
	; is called after each function is executed. It updates the
	; screen, checks whether the cursor is still inside the boundary
	; (if not, it reupdates the screen after centering around the cursor),
	; and withdraws.
	; For other PCs, this should probably not be changed, since everything
	; is in terms of symbolics screenstart and screenend etc.

	; .Screen [(BCB.ScrStart,BCB.Fcursor, BCB.ScrEnd) ->
	; %OUT	  (BCB.ScrStart,BCB.Fcursor, BCB.ScrEnd)]
	; calls FScreen, CCenter

.Screen PROC	Near

	mov	DX, CS:WindowEnd	; FScreen always requires start and
	mov	DI, CS:WindowStart	; end address of screen region, and
	mov	SI, DS:BCB.ScrStart	; the SI start of the buffer which
	call	FScreen			; shall be the first character in
					; this window

	mov	AX, DS:BCB.FCursor
	cmp	AX, DS:BCB.ScrStart	; if the cursor is above the scrstart
	jb	.Screen1
	cmp	AX, DS:BCB.ScrEnd	; or below ( which we only know after
	jae	.Screen1		; FSCREEN returns us the last char
					; that fit on screen ), reupdate
	ret

.Screen1:
	call	CCenter			; center anew

	mov	DX, CS:WindowEnd	; and reupdate screen
	mov	DI, CS:WindowStart
	mov	SI, DS:BCB.ScrStart
	call	FScreen
	ret

.Screen ENDP

;---------------------------------------------------------------------------
	; .?Col returns the column of the cursor position
	; %OUT .?Col [(BCB.FCursor) -> (BCB.FCurCol)]

.?Col  PROC	Near

	push	ES
	mov	BX, DS:BCB.FCursor
	call	GetColumn
	mov	DS:BCB.FCurCol, CX
	pop	ES
	ret

.?Col	ENDP

; <------------------------ .InCH ------------------------>

.InCH	PROC	Near
	cmp	CS:MacroInvocation, TRUE
	je	MacroChar
	call	GetChar
	jnz	.Inch0
	ret

.InCh0:	pushf
	cmp	CS:Macrodefinition, TRUE
	je	MacroDef
	popf
	ret

;...........................................................................
; we do not wait for keyboard input, but just play back our macro recording
MacroChar:
	cmp	CS:Macctr, 0			; exit on 0 characters left
	je	EndMacInv
	cmp	CS:Macrodefinition, TRUE	; error if we nest macros
	je	MacError
	mov	SI, CS:MacPtr			; take a character from the
	mov	AL, CS:[SI]			; buffer.
	mov	DL, AL				; for historic reasons
	inc	SI
	mov	CS:Macptr, SI
	dec	CS:MacCtr			; and remember the taking
	ret					; return a character

EndMacInv:
	call	GetChar				; first see whether there is
	jz	EMI2				; a ^G
	cmp	AL, 7
	jne	EMI2
		mov	SI, Offset MacCancel	; yes, so quit
		mov	CX, 18
		jmp	.Error0
EMI2:	cmp	CS:MacArg, 0			; now check whether we have
	je	EndMI				; to do the macro repeatedly
	dec	CS:MacArg			; yes, so remember we did
	mov	AL, CS:MacBack			; it once, and redo it
	mov	CS:MacCtr, AL
	mov	CS:MACptr, Offset Store
	mov	CS:MacroInvocation, TRUE
	jmp	MacroChar

EndMI:	mov	CS:MacroInvocation, FALSE	; reset the invocation
	cmp	CS:MacroInvocation, FALSE	; this will set the zero flag
	ret

MacError:
	mov	SI, Offset MEMsg
	mov	CX, 41
	jmp	.Error0
;...........................................................................
; this routine records everything that we are doing

MacroDef:
	mov	DI, CS:MacPtr
	mov	CS:[DI], AL
	inc	DI
	mov	CS:MACptr, DI
	inc	CS:MACctr
	inc	CS:MACBack
	cmp	CS:MacCtr, MacMaximum
	jae	MErr
	popf				; restore "something there"
	ret
MErr:	mov	SI, Offset MEMsg2
	mov	CX, 22
	jmp	.Error0



;...........................................................................
Getchar	PROC	NEAR
	mov	DL, 0FFh
	MS_FN	6
	jnz	.InHistory
	ret
.InHistory:
	pushf
	push	BX
	push	AX
	mov	BX, Offset $History

	mov	AX, CS:[BX+8]
	mov	CS:[BX+9], AX
	mov	AX, CS:[BX+6]
	mov	CS:[BX+7], AX
	mov	AX, CS:[BX+4]
	mov	CS:[BX+5], AX
	mov	AX, CS:[BX+2]
	mov	CS:[BX+3], AX
	mov	AX, CS:[BX]
	mov	CS:[BX+1], AX
	pop	AX
	mov	CS:[BX], AL
	pop	BX
	popf
	ret

GetChar	ENDP

.InCH	ENDP
;...........................................................................

;---------------------------------------------------------------------------
	; This routine outputs a character quickly to the screen. However, if
	; there are already too many quickly typed characters overflowing the
	; quickbuffer, it calls the routine that inserts all the quickly typed
	; characters into the real buffer.

.OutCH  PROC	Near

	push	BX
	mov	BX, CS:$KBufPtr			; always note the new char
	mov	CS:[BX], AL			; at the end of all typed
	inc	BX				; but not inserted characters
	mov	CS:$KBufPtr, BX

	cmp	CS:$KBufPtr, Offset $KBufEnd	; are there too many temporary
	jb	.OutCh0				; characters ? No
	pop	BX				; Yes, so insert all temporary
	jmp	.Piss				; typed characters into the
						; real buffer


;...........................................................................
	; SCREEN:
	;	Here are some routines that put a character at the
	;	location where the screencursor is.
	;
	; OK, we now have a recently typed character. This character sits
	; in the temporary "quick" or "fasttyped" buffer (KBUF), and DL.
	; There is not enough time to move around the possible 64K
	; that can be in the user's file to insert this one character,
	; so we just leave it in the buffer for a while, and put it to
	; the screen ( which is lots faster ). When the user stops
	; typing quickly, i.e. when we have time, .Piss will then take the
	; temporary character and insert it really into the edited file.

	; So, lets see how we put it to the screen quickly:

.OutCh0:mov	AX, Screensegment		; DL holds the character
	mov	ES, AX

	mov	BX, DS:BCB.ScrCursor		; ScrCursor holds the cursor
						; position on the screen

	mov	AH, nocolor			; The new quick char should be
	mov	AL, DL				; colorless



	; To make it look nicer, we try to guess where the actual line
	; ends, i.e. how it would look like if we had really inserted it.

.OutCh1:xchg	AX, ES:[BX]			; move all chars up to CR left
	add	BX, 2				; to make space. This makes it
	cmp	BX, 160*Textlastline-2		; look as if we really inser-
	ja	.OutCh2				; ted it 99% of the time

	cmp	AL, CR				; if we have a CR, we know
	je	.OutCh2				; the line is probably over
	cmp	AL, 1Ah				; analogous
	je	.OutCh2
	cmp	AL, LF
	je	.OutCH2
	cmp	AL, 7
	je	.OutCh2

	cmp	AX, lowintensity*256+'<'	; stop the game at the phy-
	je	.OutCH2				; sical lineend

	cmp	AX, lowintensity*256+' '
	je	.OutCH2

	cmp	AL, 9				; TABs are a problem. It looks
	je	.OutCh2				; best  to just stop insert

	cmp	AX, lowintensity*256+'!'	; This is a nice special case.
	jne	.OutCh1				; it looks good to leave the

if noflicker
		push	dx
		push	ax
		mov	dx, 3dah
	Wait:	in	al, dx
		test	al, 8
		jz	Wait
		pop	ax
		pop	dx
endif
	xchg	ES:[BX-2], AX			; '!' wraparound where it is
	jmp	.OutCh1				; and exchange for the follow
						; ing character

.OUTCH2:
	add	DS:BCB.ScrCursor, 2		; note that the cursor must
	mov	AH, 3				; be advanced now for another
	mov	BH, 0				; quick insert if necessary
	int	10h
	add	DL, 1
	mov	AH, 2
	int	10h

	pop	BX
	ret

.OutCH  ENDP

;---------------------------------------------------------------------------
	; Piss is responsible for inserting all quickly tyoed characters
	; that are held in the KBUF into the file ( which is the "real
	; insert" ), and to finally reupdate the screen. After this
	; routine is called, we are sure we have one big, correct, nice
	; buffer holding all characters. "The hectic is over."
	;
	; .Piss [ (CS:$KBufptr=Typebufferend, *[CS:$KBufStart]=Typebuffer)
	; %OUT	-> (CS:$KBufptr, *[CS:$KBufStart], Buffer) ]
	; calls::	 .outstr, .screen

.Piss	PROC	Near

	cmp	CS:$KBufptr, Offset $KBufStart	; output characters only if
	je	.PissNo				; there are any temp ones

	push	ES
	push	SI
	push	CX
	mov	AX, CS
	mov	ES, AX
	mov	SI, Offset $KBufStart
	mov	CX, CS:$KBufPtr
	sub	CX, Offset $KBufStart

	call	.OutStr			; output all characters

	pop	CX
	pop	SI
	pop	ES

	mov	CS:$KBufPtr, Offset $KBufStart ; clear the temporary Kbuffer

	call	.Screen			; and update it to a correct screen
	ret

.PissNo:ret			; there is nothing in $KBuf

.Piss	ENDP

;---------------------------------------------------------------------------
	; This routine transfers bytes from ES:DI into a normal DS buffer
	; that usually points to a buffer the user is editing.
	; %OUT .OutStr [ (*ES:SI=InString, CX=Length) -> (Buffer) ]

.OutSTR PROC	Near

	push	ES
	call	.MakeSpace
	pop	AX

	cld				; part 2 -> transfer the instring
					; into the real buffer
	push	DS
	push	DS
	pop	ES

	mov	DI, DS:BCB.FCursor
	mov	DS, AX
	rep	movsb			; actually insert
	pop	DS

	mov	DS:BCB.FCursor, DI	; and readjust for the new cursor
	ret
;. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

OFBUFMSG	db	'*** NO ROOM IN BUFFER LEFT ***'
OFBUF:  sub	DS:BCB.FEnd, CX
	pop	CX
	pop	SI
	pop	ES
	cld
	mov	SI, Offset OFBUFMSG
	mov	CX, 30
	jmp	.Error0
.OutSTR  ENDP

;---------------------------------------------------------------------------
	; This routine makes space in the DS buffer
	; %OUT	MakeSpace ( CX=#bytes )

.MakeSpace	PROC	Near
	mov	DS:BCB.Dirty, TRUE

	std				; make space in DS Buffer
	push	ES
	push	SI
	push	CX
	mov	AX, DS
	mov	ES, AX

	mov	SI, DS:BCB.FEnd		; check whether we would overflow
	add	DS:BCB.FEnd, CX		; the 64KB RAM Buffer
	jc	OFBUF
	cmp	DS:BCB.FEnd, 0FFD0h
	jae	OFBUF
	mov	DI, SI
	add	DI, CX

	mov	CX, SI			; set up word (instead of Byte) move
	sub	CX, DS:BCB.FCursor
	inc	CX
	mov	AX, CX
	test	AX, 1			; isolate one bit
	  jz	.MakeSpace0
	  inc	SI
	  inc	DI
	  inc	CX
.MakeSpace0:
	clc
	shr	CX, 1			; CX:=CX/2+1
	inc	CX
	rep	movsw			; actually insert the space now

	pop	CX
	pop	SI
	pop	ES
	ret

.MakeSpace	ENDP




;---------------------------------------------------------------------------
	; Damn: User Error. Clear out any macro commands in progress,
	; reset the stack pointer, and print the passed error message

.Error0 PROC	Near

	mov	SP, CS:$StackInit
	mov	CS:$Repeat, 0
	mov	CS:MacArg, 0
	mov	CS:Macrodefinition, FALSE
	mov	CS:Macroinvocation, FALSE
	call	.InvMsg
	call	Sound
	jmp	MainScreendraw

.Error0 ENDP

;---------------------------------------------------------------------------
	; this is a non-fatal error ( hardly called )
	; %Out .Error1 [(*SI=Msg, CX=Length) -> (*CS:$Repeat)]

.Error1 PROC	Near

	call	.Msg
	call	Sound
	ret

.Error1  ENDP

;---------------------------------------------------------------------------
	; I hate the dumb long beep of the PC !!! /ivo

timer	EQU	40h		; Use to issue short beep.
port_b	equ	61h
Sound	PROC	NEAR
	mov al,10110110B	; Gen a short beep (long one losses data.)
	out timer+3,al		; Code snarfed from Technical Reference.
	mov ax,533H
	out timer+2,al
	mov al,ah
	out timer+2,al
	in al,port_b
	mov ah,al
	or al,03
	out port_b,al
	mov cx, 32000
snd0:	loop snd0
	mov al,ah
	out port_b,al
	ret
Sound	ENDP

;---------------------------------------------------------------------------
Comment $

Some remarks:

MAX always updates the screen after a function is executed. The screen
update function's speed is crucial, and presently apparently excellent.

There is one exception, the case when a user types a character. Since a
user can type faster than the 8088 can insert in its buffer ( which  can
be a file of up to 64K ), something must be done to let the user think the
computer has already digested his last character. I do it as follows: I
store the character in KBuf, and perform a "fake" insert on the screen,
i.e. I insert it on the screen, guessing where the user's line ends from
screen information only. This is very fast, and makes the user thinks the
insert was performed fine. As soon as he stops typing, or when the buffer
that holds these quickly faked characters overflows, I call .Piss which
will insert the quickly typed characters into the buffer, and everything
is fine. It works great !!

So, SCREEN updates are only in the FSCREEN routine, which updates the
entire screen, and the quick insert, which is somewhere up in this module.
There are also some further screen commands in the following areas, which
have to be modified for the color screen:

-	the Modeline
-	the Message Area
-	the Lastline
-	the Buffer/Window commands which draw a separating line
	between windows on the screen

I have tried to mark this with the keyword SCREEN, so fgrep and you will
get the locations to change.
$


IF2
	%OUT [Pass 1 Completed]
ENDIF

	Program ENDS
End Main
