implementation module EdSupport;

/*	Auxilary function for 'EdTextWindow' */

import StdClass, StdChar, StdBool, StdInt, StdString,StdArray, StdTuple, StdMisc;
import deltaEventIO, deltaWindow, deltaDialog;

import EdProgramState, EdPath, EdLists, EdText, EdTextFind;

import UtilDiagnostics;

RuleDoesn`tMatch functionName defaultValue
	=	Unexpected ("EdSupport: " +++ functionName +++ " does not match\n") defaultValue;

//
//	Calculate cursorpos and charnr in a line nearest to 1st arg.
//

CalcCharNumber :: !Int !Int !TLine !Font -> (!Int,!Int);
CalcCharNumber mx tabw line font
	=  CalcCharNr 0 LinesLeft mx tabw line font;

CalcCharNr	:: !Int !Int !Int !Int !TLine Font -> (!Int,!Int);
CalcCharNr charnr cx mx tabw Nil font
	=  (cx,charnr);
CalcCharNr charnr cx mx tabw (str:!Nil) font
	=  CharNrInString 0 charnr cx mx (dec (size str)) str font;
CalcCharNr charnr cx mx tabw (str:!rest) font
	| toofar && itsatab
		= if (newcx-mx < mx-cx)
			(newcx,inc charnr)
			(cx,charnr);
	| toofar
		= CharNrInString 0 charnr cx mx length str font;
	| itsatab
		= CalcCharNr (inc charnr) newcx mx tabw rest font;
		= CalcCharNr (charnr + length) newcx mx tabw rest font;
	where {
		toofar			= newcx > mx;
		length			= size str;
		(newcx,itsatab)	= SkipNextString str cx tabw font;
	};

SkipNextString	:: !String !Int !Int !Font -> (!Int,!Bool);
SkipNextString str cx tabw font
	| str == TabStr	= (tabw *  inc (cx / tabw) , True);
					= (cx + strw, False);
	where {
		strw= FontStringWidth str font;
	};

CharNrInString	:: !Int !Int !Int !Int !Int !String !Font -> (!Int,!Int);
CharNrInString id charnr cx mx len str font
	| id >= len
		=  (cx,charnr);
	| newcx > mx
		= if (newcx-mx < mx-cx)
			(newcx,inc charnr)
			(cx,charnr);
		= CharNrInString (inc id) (inc charnr) newcx mx len str font;
	where {
		newcx= cx + charw;
		charw= FontCharWidth (str.[id]) font;
	};

//
//	CalcSelection takes care that the begin of the Selection is always to
//	the upper-left of the end of the Selection

CalcSelection :: !Int !Int !Int !Int !Int !Int !Int !Int -> Selection;
CalcSelection lnr cnr cx cy linenr charnr sx sy
	| linenr > lnr || (linenr == lnr && charnr > cnr)
		= {	tsel	= {	l1	= lnr,
						c1	= cnr,
						l2	= linenr,
						c2	= charnr},
			psel	= {	bx	= cx,
						by	= cy,
						ex	= sx,
						ey	= sy }};
		= {	tsel	= {	l1	= linenr,
						c1	= charnr,
						l2	= lnr,
						c2	= cnr},
			psel	= {	bx	= sx,
						by	= sy,
						ex	= cx,
						ey	= cy} };
						
//
// Returns before and cursorpos of first non-space, non-tab char
//

IndentCurrentLine :: !Int !Int !Bool !TLine -> (!Int, !Int, !TLine);
IndentCurrentLine spcw tabw False	line = (LinesLeft,0,Nil);
IndentCurrentLine spcw tabw True	line = SearchNonOutline 0 0 spcw tabw line Nil;

SearchNonOutline :: !Int !Int !Int !Int !TLine !TLine -> (!Int, !Int, !TLine);
SearchNonOutline cx cnr spcw tabw Nil before =  (cx,cnr,before);
SearchNonOutline cx cnr spcw tabw (TabStr:!rest) before
	=  SearchNonOutline cx` (inc cnr) spcw tabw rest (TabStr:!before);
	where {
	cx`= tabw *  inc (cx / tabw) ;
	};
SearchNonOutline cx cnr spcw tabw (str:!rest) before
	|  str.[0]  <> ' '	= (cx,cnr,before);
						= StringNonOutline 1 cnr cx spcw tabw (size str) str rest before;

StringNonOutline :: !Int !Int !Int !Int !Int !Int !String !TLine !TLine -> (!Int, !Int, !TLine);
StringNonOutline id cnr cx spcw tabw len str rest before
	| id >= len
		= SearchNonOutline cx (cnr + id) spcw tabw rest (str:!before);
	|  str.[id]  <> ' '
		= (cx + spcw,cnr + id, (str % (0, dec id) :! before));
		=  StringNonOutline (inc id) cnr (cx + spcw) spcw tabw len str rest before;
		
//
// Inserts text into one line of the text window
//

InsertOneLine :: !EditWindow !PartTSel !String !WindowUpdate -> (!EditWindow, !WindowUpdate);
InsertOneLine	wd=:{wstate,wtext=wtext`=:{text,nrlines,curline}}
				tsel=:{l1,c1} repl update
	= (wd`, update`);
	where {
	update`				= {WindowUpdate | update & added={tsel & l2=l1, c2=c1 + size repl}};
	wd`					= {wd &	wstate={wstate & saved=False},
								wtext={wtext` & text=text`,curline=cline`}};
	cline`				= PasteInCurLine repl c1 cline2;
	(_,text`,cline1)	= EW_ResetCurLine in_cline text curline;
	cline2 | in_cline	= cline1;
						= {CurLine | changed=True, before=Nil, after=line, lnr=l1, cnr=0};
	line				= Text_GetLine l1 text`;
	in_cline			= curline.CurLine.lnr == l1;
	};
	
PasteInCurLine :: !String !Int !CurLine -> CurLine;
PasteInCurLine paste c1 cline=:{CurLine | before, after, cnr}
	= {CurLine | cline & changed=True,before=bef`,after=aft,cnr=cnr`};
	where {
	(bef,aft)			= bef_aft;
	bef_aft | c1 <> cnr	= Line_SplitLine c1 (Line_GlueLine before after);
						= (before, after);
	bef`				= AddStringBefore paste bef;
	cnr`				= c1 + size paste;
	};

//
// Inserts zero or two or more lines into the text window
//
	
InsertLines ::	!EditWindow !PartTSel !Int !String !Clipboard !WindowUpdate
				-> (!EditWindow, !WindowUpdate);
InsertLines wd tsel cliplen last Nil update
	= (wd, update);
InsertLines	wd=:{wstate,wtext=wtext`=:{text,nrlines,curline}}
			tsel=:{l1,c1} cliplen last clip update
	= (wd`, update`);
	where {
	update`			= {WindowUpdate | update & added={tsel & l2=ln, c2=cn} }; 
	wd`				= {wd &	wstate={wstate & saved=False},
							wtext={wtext` & text=text`,nrlines=nrlines`,curline=cline`}};
	nrlines`		= nrlines + pnrlines;
	text`			= Text_PasteClipboard clip l1 c1 text1;
	(_,text1,_)		= EW_ResetCurLine False text curline;
	cline`			= {CurLine | changed=False,before=bef,after=aft,lnr=ln,cnr=cn};
	(bef,aft)		= Line_SplitLine cn (Text_GetLine ln text`);
	cn				= size last;
	ln				= l1 + pnrlines;
	pnrlines		= dec cliplen;
	};
	
//
// Removes text from one line of the text window
//

RemoveOneLine :: !EditWindow !PartTSel -> (!EditWindow, !WindowUpdate);
RemoveOneLine	wd=:{wstate,wtext=wtext`=:{text,nrlines,cursorpos,curline,selection}}
				tsel=:{l1,c1,l2,c2}
	| is_empty	= (wd, EmptyWindowUpdate);
				= (wd`,update`);
	where {
	update`				= { EmptyWindowUpdate &	
								added		= {l1=l1,c1=c1,l2=l1,c2=c1},
								nrremoved	= 1, 
								removed		= clip };
	wd`					= {wd &	wstate={wstate & saved=False},
								wtext={wtext` & curline=cline`,text=text`}};
	cline`				= {CurLine | changed=True, before=bef`, after=aft`, lnr=l1, cnr=c1};
	(clip,bef`,aft`)	= CutFromCurLine tsel cline1;
	cline1 | in_cline	= curline;
						= {CurLine | changed=True, before=Nil, after=line, lnr=l1, cnr=0};
	(_,text`,_)			= EW_ResetCurLine in_cline text curline;
	line				= Text_GetLine l1 text`;
	in_cline			= curline.CurLine.lnr == l1;
	is_empty			= c1 == c2 /* && l1 == l2 */ ;
	};
	
//
// Removes two or more lines from the text window
//
	
RemoveLines :: !EditWindow !PartTSel -> (!EditWindow, !WindowUpdate);
RemoveLines	wd=:{wstate,wtext=wtext`=:{text,nrlines,curline}}
			tsel=:{l1,c1,l2,c2}
	= (wd`,update);
	where {
	update			= { EmptyWindowUpdate &
							added		= { l1=l1, c1=c1, l2=l1, c2=c1 },
							nrremoved	= inc nrremoved,
							removed		= clip };
	wd`				= {wd &	wstate={wstate & saved=False},
							wtext={wtext` & text=text`,curline=cline`,nrlines=nrlines`}};
	nrlines`		= nrlines - nrremoved;
	(clip,text`)	= Text_CutSelection tsel text1;
	(_,text1,_)		= EW_ResetCurLine False text curline;
	cline`			= {CurLine | changed=False, before=bef, after=aft, lnr=l1, cnr=c1};
	nrremoved		= l2 - l1;
	(bef,aft)		= Line_SplitLine c1 (Text_GetLine l1 text`);
	};
	
//
// Determines new cursor position/ selection (optionally) of the text window
//
	
SetCursorPos_and_Selection ::	!EditWindow !Bool !Int !Int !Int !Int !WindowUpdate
								-> (!EditWindow, !WindowUpdate);
SetCursorPos_and_Selection
			wd=:{	wtext=wtext`=:{text,nrlines,curline,cursorpos,selection},
					wformat={winfont,metrics,tabw} }
			select l1 c1 l2 c2 wu
	= (wd`, wu`);
	where {
	wu`					= { wu &	oldpos		= cursorpos,
									oldsel		= selection,
									curpos		= curpos`,
									selection	= selection` };
	wd`					= {wd & wtext={wtext` & cursorpos=curpos`,selection=selection`}};
	curpos`				= {vis=False,x=cursorx,y=cursory,u_d=cursorx};
	selection` | select	= {tsel=tsel,psel=psel};
						= EmptySelection;
	psel				= CalcPixelSelection tsel tabw.ptabw metrics.height curline text winfont.font;
	cursorx				= CalcCursorX LinesLeft tabw.ptabw (BeforeToLine curline.CurLine.before) winfont.font;
	cursory				= PictureTop + curline.CurLine.lnr * metrics.height;
	tsel				= {l1=l1,c1=c1,l2=l2,c2=c2};
	};

/* Aux functions: the move functions. */

//
//	GoLeftInText: Moves the cursor to the left:
//		mod	= Comd: go to the start of the current line
//		mod	= Optn: go left one word
//		mod	= None: go left one character
//	If del is True the text passed by the cursor is deleted.
//
	
GoLeftInText :: !Bool !KeyMode !CurLine !EditWindow -> (!EditWindow, !String, !Int);
GoLeftInText	del mod cline=:{CurLine | changed,before,after,lnr,cnr}
				front=:{wtext=wtext`=:{curline,cursorpos,text,nrlines},wformat={winfont,metrics,tabw},wstate}
	= case before of
		{	Nil | mod == Comd
				->	(front, "", 0 );
				
			Nil	| not_first // beginning of line, goto end of prev. line
				->	( update, NewlStr, 0 );
					where {
					update | del= {front & wtext={wtext` & nrlines=nrlines`,text=text`,curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & text=text1,curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | changed=del,before=bef,after=aft,lnr=linenr,cnr=charnr};
					curpos`		= {vis=False,x=cx`,y=cy`,u_d=cx`};
					text`		= Text_RemoveLine lnr text1;
					nrlines`	= dec nrlines; 
					cx`			= CalcCursorX LinesLeft tabw.ptabw line winfont.font;
					cy`			= oldpos.CursorPos.y - metrics.height;
					bef			= LineToBefore line Nil;
					aft | del	= after;
								= NewlStr:!Nil;
					line		= Text_GetLine linenr text1;
					linenr		= dec lnr;
					charnr		= Line_NrChars line;
					(_,text1,_)	= EW_ResetCurLine del text curline;	};
			
			str:!rest | mod == Optn // word left
				->	( update, word, width );
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=bef,after=aft,cnr=charnr};
					curpos`		= {oldpos & vis=False,x=cx`,u_d=cx`};
					cx`			= CalcCursorX LinesLeft tabw.ptabw (Reverse bef) winfont.font;
					width		= cx - cx`;
					(word,bef)	= RemoveWordBefore before;
					aft | del	= after;
								= AddWordAfter word after;
					charnr		= cnr -  size word; };

			str:!rest | mod == Comd // line left
				->	( update, befstr, width ); // line left
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=Nil,after=aft,cnr=0};
					curpos`		= {oldpos & vis=False,x=cx`,u_d=cx`};			
					cx`			= LinesLeft;
					width		= cx - cx`;
					aft | del	= after;
								= Line_GlueLine before after;
					befstr		= Line_LineToString (Reverse before); };
			
			TabStr:!rest // (tab) char left
				->	( update, TabStr, width );
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=rest,after=aft,cnr=dec cnr};
					curpos`		= {oldpos & x=cx`,u_d=cx`};
					cx`			= CalcCursorX LinesLeft tabw.ptabw (Reverse rest) winfont.font;
					width		= cx - cx`;
					aft | del	= after;
								= TabStr:!after; };
			
			str:!rest // char left
				->	( update, toString char, width ); // char left
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=bef,after=aft,cnr=dec cnr};
					curpos`		= {oldpos & x=cx`,u_d=cx`};
					cx`			= oldpos.CursorPos.x - width;
					(char,bef)	= RemoveCharBefore before;
					aft | del	= after;
								= AddCharAfter char after;
					width		= FontCharWidth char winfont.font; };
					
			_ // already in top left corner, skip
				-> (front, "", 0 );
		};
	where {
	not_first	= lnr <> 0;
	changed`	= changed || del;
	cx			= oldpos.CursorPos.x;
	oldpos		= {cursorpos & vis = False};
	};

//
//	GoRightInText: Moves the cursor to the right:
//		mod	= Comd: go to end of the current line
//		mod	= Optn: go right one word
//		mod	= None: go right one character
//	If del is True the text passed by the cursor is deleted.
//
	
GoRightInText :: !Bool !KeyMode !CurLine !EditWindow -> (!EditWindow, !String, !Int);
GoRightInText	del mod cline=:{CurLine | changed, before,after,lnr,cnr}
				front=:{wtext=wtext`=:{curline,cursorpos,text,nrlines},wformat={winfont,metrics,tabw},wstate}
	= case after of
		{	NewlStr:!rest | mod == Comd
				->	( front, "", 0 );
		
			NewlStr:!rest | not_last // end of line, goto beginning of next line
				->	(	update, NewlStr, 0 );
					where {
					update | del= {front & wtext={wtext` & nrlines=nrlines`,text=text`,curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & text=text1,curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | changed=del,before=bef,after=aft,lnr=lnr`,cnr=cnr`};
					curpos`| del= oldpos;
								= {oldpos & x=cx`,y=cy`,u_d=cx`};
					nrlines`	= dec nrlines;
					text`		= Text_RemoveLine linenr text1;
					lnr` | del	= lnr;
								= linenr;
					cnr` | del	= cnr;
								= 0;
					cx`			= LinesLeft;
					cy`			= oldpos.CursorPos.y + metrics.height;
					linenr		= inc lnr;
					bef	| del	= LineToBefore after before;
								= Nil;
					aft			= Text_GetLine linenr text1;
					(_,text1,_)	= EW_ResetCurLine del text curline; };
					
			NewlStr:!rest // already at bottom right corner, skip
				-> ( front, "", 0);
			
			str:!rest | mod == Optn // word right
				->	( update, word, width );
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=bef,after=aft,cnr=cnr`};
					curpos`
						| del	= oldpos;
								= {oldpos & x=cx`,u_d=cx`};
					cnr` | del	= cnr;
								= cnr +  size word;
					cx`			= CalcCursorX LinesLeft tabw.ptabw (Reverse before`) winfont.font;
					width		= cx` - cx;
					(word,aft)	= RemoveWordAfter after;
					bef | del	= before;
								= before`;
					before`		= AddWordBefore word before; };
					
			str:!rest | mod == Comd // line right
				->	( update, aftstr, width ); // line left
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=bef,after=NewlStr:!Nil,cnr=cnr`};
					curpos`
						| del	= oldpos;
								= {oldpos & x=cx`,u_d=cx`};
					cnr` | del	= cnr;
								= Line_NrChars bef;
					cx`			= CalcCursorX LinesLeft tabw.ptabw (Reverse before`) winfont.font;
					width		= cx` - cx;
					aftstr		= Line_LineToString after;
					bef	| del	= before;
								= before`;
					before`		= Line_GlueAfter before after; };
			
			TabStr:!rest // (tab) char right
				->	( update, TabStr, width );
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=bef,after=rest,cnr=cnr`};
					curpos`
						| del	= oldpos;
								= {oldpos & x=cx`, u_d = cx`};
					cnr` | del	= cnr;
								= inc cnr;
					cx`			= tabw.ptabw * inc (oldpos.CursorPos.x / tabw.ptabw);
					width		= cx` - cx;
					bef | del	= before;
								= TabStr:!before; };
			
			str:!rest // char right
				->	( update, toString char, width ); // char left
					where {
					update | del= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`},wstate={wstate & saved=False}};
								= {front & wtext={wtext` & curline=cline`,cursorpos=curpos`}};
					cline`		= {CurLine | curline & changed=changed`,before=bef,after=aft,cnr=cnr`};
					curpos`
						| del	= oldpos;
								= {oldpos & x=cx`,u_d=cx`};
					cnr` | del	= cnr;
								= inc cnr;
					cx`			= cx + width;
					(char,aft)	= RemoveCharAfter after;
					bef	| del	= before
								= AddCharBefore char before;
					width		= FontCharWidth char winfont.font; };
					
			_ // already at bottom right corner, skip
				->	( front, "", 0 );
		};
	where {
	not_last	= lnr <> dec nrlines;
	changed`	= changed || del;
	cx			= oldpos.CursorPos.x;
	oldpos		= {cursorpos & vis=False};
	};
	
//
//	GoUpInText: Moves the cursor up:
//		mod	= Comd: go to the top of the file
//		mod	= Optn: go to the top of the window
//		mod	= None: go up one line
//
	
GoUpInText :: !PictureDomain !KeyMode !Int !Int !EditWindow -> EditWindow;
GoUpInText	((left,top),(right,bot)) mod_new lnr cnr
			front=:{wtext=wtext`=:{curline,cursorpos,text,nrlines},wformat={winfont,metrics,tabw}}
	= case mod_new of
		{	Optn // scroll window up, if cursor not in top left corner
				->	update;
					where {
					update			= {front & wtext={wtext` & text=text`,curline=cline`,cursorpos=curpos`}};
					cline`			= {CurLine | changed=False,before=Nil,after=aft,lnr=linenr,cnr=0};
					curpos`			= {vis=False,x=cursorx,y=cursory,u_d=cursorx};
					aft				= Text_GetLine linenr text`;
					linenr			= Between 0 (dec nrlines) ((newtop - PictureTop) / metrics.height);
					newtop	| not_at_topleft
									= top;
									= top - (bot - top);
					cursorx			= LinesLeft;
					cursory			= PictureTop + metrics.height * linenr;
					not_at_topleft	= top == PictureTop || cnr <> 0 || lnr <> ((top - PictureTop) / metrics.height); };
			
			Comd // scroll to begin of text
				->	update;
					where {
					update			= {front & wtext={wtext` & text=text`,curline=cline`,cursorpos=curpos`}};
					cline`			= {CurLine | changed=False,before=Nil,after=aft,lnr=0,cnr=0};
					curpos`			= ZeroCursor;
					aft				= Text_GetLine 0 text`; };
					
			_  // scroll line up
				->	update;
					where {
					update			= {front & wtext={wtext` & text=text`,curline=cline`,cursorpos=curpos`}};
					cline`			= {CurLine | changed=False,before=bef,after=aft,lnr=linenr,cnr=charnr};
					curpos`			= {CursorPos | oldpos & x=cursorx,y=cursory};
					(cursorx,charnr)= CalcCharNumber oldpos.u_d tabw.ptabw line winfont.font;
					cursory			= PictureTop + metrics.height * linenr;
					(bef,aft)		= Line_SplitLine charnr line;
					linenr | not_first
									= dec lnr;
									= lnr;
					line			= Text_GetLine linenr text`; };
		};
	where {
	not_first		= lnr <> 0;
	oldpos			= {cursorpos & vis = False};
	(_,text`,_)		= EW_ResetCurLine False text curline;
	};
	
//
//	GoDownInText: Moves the cursor down:
//		mod	= Comd: go to the bottom of the file
//		mod	= Optn: go to the bottom of the window
//		mod	= None: go down one line
//
	
GoDownInText :: !PictureDomain !KeyMode !Int !Int !EditWindow -> EditWindow;
GoDownInText ((left,top),(right,bot)) mod_new lnr cnr
			front=:{wtext=wtext`=:{curline,cursorpos,text,nrlines},wformat={winfont,metrics,tabw}}
	= case mod_new of
		{	Optn | not_last // scroll window down, if cursor not in bottom left corner
				->	update;
					where {
					update			= {front & wtext={wtext` & text=text`,curline=cline`,cursorpos=curpos`}};
					cline`			= {CurLine | changed=False,before=Nil,after=aft,lnr=linenr,cnr=0};
					curpos`			= {vis=False,x=cursorx,y=cursory,u_d=cursorx};
					aft				= Text_GetLine linenr text`;
					linenr			= Between 0 (dec nrlines) (dec ((newbot-PictureTop) / metrics.height));
					newbot	| not_at_botright
									= bot;
									= bot - metrics.height + (bot - top);
					cursorx			= LinesLeft;
					cursory			= PictureTop + metrics.height * linenr;
					not_at_botright	= cnr <> 0 || lnr <> dec ((bot - PictureTop) / metrics.height); };
			
			Comd // scroll to end of text
				->	update;
					where {
					update			= {front & wtext={wtext` & text=text`,curline=cline`,cursorpos=curpos`}};
					cline`			= {CurLine | changed=False,before=bef,after=aft,lnr=linenr,cnr=charnr};
					curpos`			= {CursorPos | oldpos & x=cursorx,y=cursory};
					(cursorx,charnr)= CalcCharNumber 65535 tabw.ptabw line winfont.font;
					cursory			= PictureTop + metrics.height * linenr;
					(bef,aft)		= Line_SplitLine charnr line;
					linenr 			= lastlinenr;
					line			= Text_GetLine linenr text`; };

			_ // scroll line down
				->	update;
					where {
					update			= {front & wtext={wtext` & text=text`,curline=cline`,cursorpos=curpos`}};
					cline`			= {CurLine | changed=False,before=bef,after=aft,lnr=linenr,cnr=charnr};
					curpos`			= {CursorPos | oldpos & x=cursorx,y=cursory};
					(cursorx,charnr)= CalcCharNumber pixelOffset tabw.ptabw line winfont.font;
					cursory			= PictureTop + metrics.height * linenr;
					(bef,aft)		= Line_SplitLine charnr line;
					linenr | not_last
									= inc lnr;
									= lnr;
// RWS ...
					pixelOffset
						| not_last
							= oldpos.u_d 
						| otherwise
							= 65535;
// ... RWS
					line			= Text_GetLine linenr text`; };
		};
	where {
	not_last		= lnr <> lastlinenr;
	lastlinenr		= dec nrlines;
	oldpos			= {cursorpos & vis = False};
	(_,text`,_)		= EW_ResetCurLine False text curline;
	};

//	
// Determines the length and last element of a string list.
//

Length_and_Last	:: !(List String) !Int -> (!Int,!String);
Length_and_Last Nil		n = (n, "");
Length_and_Last (f:!Nil)	n = (inc n,f);
Length_and_Last (f:!r)	n = Length_and_Last r (inc n);

/* Misc. */

/*	Calculate the x-pos of the cursor, given an x-offset and a part before the cursor >> */

CalcCursorX	:: !Int !Int !TLine !Font -> Int;
CalcCursorX cx tabw Nil font =  cx;
CalcCursorX cx tabw (str:!Nil) font
	| str == TabStr				= tabw *  inc (cx / tabw) ;
	| str.[last]  <> NewlChar	=  cx +  FontStringWidth str font ;
	| last == 0					=  cx;
								=  cx +  FontStringWidth (str % (0, dec last)) font ;
	where {
	last= dec (size str);
	};
CalcCursorX cx tabw (str:!rest) font
	| str == TabStr				= CalcCursorX (tabw *  inc (cx / tabw) ) tabw rest font;
								= CalcCursorX (cx + strw) tabw rest font;
	where {
	strw= FontStringWidth str font;
	};

/*	Calculate the window-coordinates of a text-selection. */

CalcPixelSelection :: !PartTSel !Int !Int !CurLine !Text !Font -> PartPSel;
CalcPixelSelection {l1,c1,l2,c2} tabw hght cline=:{CurLine | before, after, lnr} text font
	= {	by	= PictureTop +  l1 * hght,
		ey	= PictureTop +  l2 * hght,
		bx	= CalcPixelX LinesLeft c1 tabw line1 font,
		ex	= CalcPixelX LinesLeft c2 tabw line2 font };
	where {
	line1 | l1 == lnr	= line;
						= Text_GetLine l1 text;
	line2 | l2 == lnr	= line;
						= Text_GetLine l2 text;
	line				= Line_GlueLine before after;
	};
	
CalcPixelX :: !Int !Int !Int !TLine !Font -> Int;
CalcPixelX cx 0 tabw line font =  cx;
CalcPixelX cx cn tabw (str:!rest) font
	| str == TabStr	= CalcPixelX (tabw *  inc (cx / tabw) ) (dec cn) tabw rest font;
	| cn >= len		= CalcPixelX (cx + strw) (cn - len) tabw rest font;
					= CalcPixelXInString 0 cn cx str font;
	where {
	strw	= FontStringWidth str font;
	len 	= size str;
	};
CalcPixelX cx _ _ _ _
	=	RuleDoesn`tMatch "CalcPixelX" cx;

CalcPixelXInString :: !Int !Int !Int !String !Font -> Int;
CalcPixelXInString id 0 cx str font =  cx;
CalcPixelXInString id cn cx str font
	=  CalcPixelXInString (inc id) (dec cn) (cx + charw) str font;
	where {
	charw	= FontCharWidth (str.[id]) font;
	};

// RWS ...
/* this function is compatible with patchbin in the Unix Clean
   distribution (1.3), variableName should be ' ' (space) padded
   to 10 characters, value should end with a null character
*/ 
PatchableValue :: {#Char} {#Char} -> {#Char};
PatchableValue variableName patchableString
    | patchableString % (0,2) <> "#$@"
        || patchableString % (13,15) <> "%*&"
        || nullIndex >= sizePatchableString
        =   abort ("patchableValue: malformed patchable value " +++ patchableString +++ "\n");
    | patchableString % (3,12) <> variableName
        =   abort ("patchableValue: wrong variableName " +++ patchableString +++ "\n");
    // otherwise
        = patchableString % (16, nullIndex-1);
	where {
		sizePatchableString = size patchableString;
		nullIndex = findNullCharacter 0 sizePatchableString patchableString;
		findNullCharacter i n s
			| i >= n
				=	n;
			| s.[i] == '\0'
				=	i;
			// otherwise
				=  findNullCharacter (i+1) n s;
	}
// ... RWS
