implementation module EdText;

/*	Operations on the Text: A list of lists of lines
	Operations on Lines: lists of strings
	
	To prevent space-leaks strategic strictness annotations are necessary
*/

import StdClass, StdInt, StdString, StdChar, StdBool,StdArray;
from StdMisc import abort;

import EdTextConstants, EdTypes, EdLists;
import UtilDiagnostics;

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

EmptyText	:== EmptyBlock:!Nil;
EmptyBlock	:== EmptyLine:!Nil;
EmptyLine	:== NewlStr:!Nil;

LineNr  id	:== id rem LinesPerBlock;
BlockNr id	:== id / LinesPerBlock;    

/*	Retrieve a line from the text */

Text_GetLine :: !Int !Text -> TLine;
Text_GetLine id text =  Select (Select text (BlockNr id)) (LineNr id);
	
/*	Retrieve a number of lines from the text: arg1:1st linenr, arg2:nr of lines */

Text_GetLines :: !Int !Int !Text -> List TLine;
Text_GetLines id 0	text =  Nil;
Text_GetLines id nr	text =  GetLines (LineNr id) (BlockNr id) nr text;

GetLines :: !Int !Int !Int !Text -> List TLine;
GetLines lid 0		nr (block :! blocks)	=  GetLinesFromBlock lid nr block blocks;
GetLines lid bid	nr (block :! blocks)	=  GetLines lid (dec bid) nr blocks;
GetLines lid bid	nr Nil				=  abort "GetLines: wrong line number";

GetLinesFromBlock	:: !Int !Int !Block !Text -> List TLine;
GetLinesFromBlock 0		nr block		blocks =  GetNrLines nr block blocks;
GetLinesFromBlock lid	nr (line:!lines)	blocks
	=  GetLinesFromBlock (dec lid) nr lines blocks;
GetLinesFromBlock lid nr Nil blocks
	=  abort "GetLinesFromBlock: wrong line number";

GetNrLines	:: !Int !Block !Text -> List TLine;
GetNrLines 0	block			blocks			= Nil;
GetNrLines nr	(line:!lines)	blocks			= line :! GetNrLines (dec nr) lines blocks;
GetNrLines nr	Nil				(block:!blocks)	=  GetNrLines nr block blocks;
GetNrLines nr	block			blocks			=  abort "GetNrLines: wrong number of lines";

/*	Replace a line in the text */

Text_SetLine :: !Int !TLine !Text -> Text;
Text_SetLine id line text =  Update2 text (BlockNr id) (LineNr id) line;

/*	Insert a line in the text */

Text_InsertLine	:: !Int !TLine !Text -> Text;
Text_InsertLine id line text
	=  InsertLine line (LineNr id) (BlockNr id) text;

InsertLine :: !TLine !Int !Int !Text -> Text;
InsertLine line 0	0	Nil						= (line:!Nil) :! Nil;
InsertLine line lid	0	(block:!blocks)	| save	= block` :! blocks;
												= block` :! InsertLine carry 0 0 blocks;
	where {
	(save,block`,carry)= InsertInBlock line lid (dec LinesPerBlock) block;
	};
InsertLine line lid	bid	(block:!blocks)			= block :! InsertLine line lid (dec bid) blocks;
InsertLine line lid	bid	Nil						= abort "InsertLine: wrong line number";

InsertInBlock :: !TLine !Int !Int !Block -> (!Bool,!Block,!TLine);
InsertInBlock line 0	max block			=  (save, line:!block`, carry);
	where {
	(save,block`,carry)	= IsBlockTooLong max block;
	};
InsertInBlock line lid	max (this:!lines)	=  (save, this:!block, carry);
	where {
	(save,block,carry)	= InsertInBlock line (dec lid) (dec max) lines;
	};
InsertInBlock line lid	max Nil				=  abort "InsertInBlock: wrong line number";

IsBlockTooLong :: !Int !Block -> (!Bool,!Block,!TLine);
IsBlockTooLong n Nil			=  (True, Nil, EmptyLine);
IsBlockTooLong 0 (line:!Nil)		=  (False, Nil, line);
IsBlockTooLong n (line:!lines)	=  (save, line:!block, carry);
	where {
	(save,block,carry)= IsBlockTooLong (dec n) lines;
	};

/*	Remove a line from a text */

Text_RemoveLine	:: !Int !Text -> Text;
Text_RemoveLine 0	((line:!Nil):!Nil)	=  EmptyText;
Text_RemoveLine id	text				=  RemoveLine (LineNr id) (BlockNr id) text;

RemoveLine :: !Int !Int !Text -> Text;
RemoveLine 0   0	((line:!Nil):!Nil)
	= Nil;
RemoveLine lid 0	(block:!Nil)
	= RemoveFromBlock lid block :! Nil;
RemoveLine lid 0	( block :! blocks=:((line:!lines) :! rest) )
	= RemoveFromBlockAndAppend lid line block :! RemoveLine 0 0 blocks;
RemoveLine lid bid	(block:!blocks)
	= block :! RemoveLine lid (dec bid) blocks;
RemoveLine lid bid 	Nil
	= abort "RemoveLine: wrong line number";

RemoveFromBlock	:: !Int !Block -> Block;
RemoveFromBlock 0	(line:!lines)	= lines;
RemoveFromBlock lid (line:!lines)	= line :! RemoveFromBlock (dec lid) lines;
RemoveFromBlock lid	Nil				= abort "RemoveFromBlock: wrong line number";

RemoveFromBlockAndAppend :: !Int !TLine !Block -> Block;
RemoveFromBlockAndAppend 0		line (this:!lines)
	= Append lines line;
RemoveFromBlockAndAppend lid	line (this:!lines)
	= this :! RemoveFromBlockAndAppend (dec lid) line lines;
RemoveFromBlockAndAppend lid	line Nil		
	=  abort "RemoveFromBlockAndAppend: wrong line number";

/*	Cut a Selection from the Text to the Clipboard */

Text_CutSelection :: !PartTSel !Text -> (!Clipboard,!Text);
Text_CutSelection tsel=:{l1,c1,l2,c2} text
	| l1 == l2	=  (Nil,text);	// Clearing in one line only affects current line
				=  CutSelection (LineNr l1) (BlockNr l1)
		           				(LineNr l2) (BlockNr l2) c1 c2 text;

CutSelection :: !Int !Int !Int !Int !Int !Int !Text -> (!Clipboard,!Text);
CutSelection li1 0 li2 bi2 cn1 cn2 (block:!blocks)
	= (clip, LinesToText lines);
	where {
	(clip,lines)= CutSelFromBlock li1 li2 bi2 cn1 cn2 block blocks;
	};
CutSelection li1 bi1 li2 bi2 cn1 cn2 (block:!blocks)
	= (clip, block :! rest);
	where {
	(clip,rest)= CutSelection li1 (dec bi1) li2 (dec bi2) cn1 cn2 blocks;
	};
CutSelection _ _ _ _ _ _ blocks
	=	RuleDoesn`tMatch "CutSelection" (Nil, blocks);

CutSelFromBlock	:: !Int !Int !Int !Int !Int !Block !Text -> (!Clipboard,!List TLine);
CutSelFromBlock 0 li2 bi2 cn1 cn2 (line:!lines) blocks
	= (Line_LineToString last :! clip, Line_GlueLine first second :! rest);
	where {
	(clip,second,rest)	= CutRestSelection li2` bi2 cn2 lines blocks;
	(first,last)		= Line_SplitLine cn1 line;
	li2` | bi2 == 0		= dec li2;
						= li2;
	};
CutSelFromBlock li1 li2 bi2 cn1 cn2 (line:!lines) blocks
	=  (clip, line :! rest);
	where {
	(clip,rest)		= CutSelFromBlock (dec li1) li2` bi2 cn1 cn2 lines blocks;
	li2` | bi2 == 0	= dec li2;
					= li2;
	};
CutSelFromBlock _ _ _ _ _ lines _
	=	RuleDoesn`tMatch "CutSelFromBlock" (Nil, lines);

CutRestSelection :: !Int !Int !Int !Block !Text -> (!Clipboard,!TLine,!List TLine);
CutRestSelection 0		0	cn2 (line:!lines)	blocks
	= (str:!Nil, last, Lines_and_BlocksToLines lines blocks);
	where {
	str				= Line_LineToString (Reverse2 first Nil);
	(first,last)	= Line_SplitLine cn2 line;
	};
CutRestSelection li2	0	cn2 (line:!lines)	blocks
	= (Line_LineToString line :! clip, last, rest);
	where {
	(clip,last,rest)	= CutRestSelection (dec li2) 0 cn2 lines blocks;
	};
CutRestSelection li2	bi2	cn2 lines			blocks
	=  CutBlock li2 bi2 cn2 lines blocks;

CutBlock :: !Int !Int !Int !Block !Text -> (!Clipboard,!TLine,!List TLine);
CutBlock li2 bi2 cn2 Nil (block:!blocks)
	=  CutRestSelection li2 (dec bi2) cn2 block blocks;
CutBlock li2 bi2 cn2 (line:!lines) blocks
	= (Line_LineToString line :! clip, last, rest);
	where {
	(clip,last,rest)	= CutBlock li2 bi2 cn2 lines blocks;
	};
CutBlock li2 bi2 cn2 lines blocks
	=  abort "CutBlock: wrong selection";

/*	Copy a Selection to the Clipboard */

Text_CopySelection :: !PartTSel !Text -> Clipboard;
Text_CopySelection tsel=:{l1,c1,l2,c2} text
	| l1 == l2	= line % (c1, dec c2) :! Nil;
				= PreciseClipboard c1 c2 lines;
	where {
	line 	= Line_LineToString (Text_GetLine l1 text);
	lines	= Text_GetLines l1 (inc (l2 - l1)) text;
	};

PreciseClipboard	:: !Int !Int !(List TLine) -> Clipboard;
PreciseClipboard cn1 cn2 (line:!lines)
	=  str % (cn1, dec (size str)) :! TransformToClip cn2 lines;
	where {
	str	= Line_LineToString line;
	};
PreciseClipboard cn1 cn2 Nil
	=  abort "PreciseClipboard: wrong selection";

TransformToClip	:: !Int !(List TLine) -> Clipboard;
TransformToClip cn2 (line:!Nil)
	= (Line_LineToString line) % (0, dec cn2) :! Nil;
TransformToClip cn2 (line:!lines)
	= Line_LineToString line :! TransformToClip cn2 lines;
TransformToClip cn2 Nil
	=  abort "TransformToClip: wrong selection";

/*	Paste the Clipboard into the Text */

Text_PasteClipboard	:: !Clipboard !Int !Int !Text -> Text;
Text_PasteClipboard Nil lnr cnr text
	=  abort "Text_PasteClipboard: empty clipboard";
Text_PasteClipboard (str:!Nil) lnr cnr text
	=  text;				// Pasting of one string only affects current line
Text_PasteClipboard clip lnr cnr text
	=  InsertClipboard clip (LineNr lnr) (BlockNr lnr) cnr text;
	
InsertClipboard	:: !Clipboard !Int !Int !Int !Text -> Text;
InsertClipboard clip lid 0 cnr (block:!blocks)
	=  LinesToText (InsertClipInBlock clip lid cnr block blocks);
InsertClipboard clip lid bid cnr (block:!blocks)
	=  block :! InsertClipboard clip lid (dec bid) cnr blocks;
InsertClipboard clip lid bid cnr Nil
	=  abort "InsertClipboard: wrong line number";

InsertClipInBlock :: !Clipboard !Int !Int !Block !Text -> List TLine;
InsertClipInBlock (str:!rest) 0 cnr (line:!lines) blocks
	= Line_MakeLine (part1 +++ str) :! InsertClipInLine rest part2 lines blocks;
	where {
	(part1,part2)= ChopLine cnr line;
	};
InsertClipInBlock clip lid cnr (line:!lines) blocks
	= line :! InsertClipInBlock clip (dec lid) cnr lines blocks;
InsertClipInBlock clip lid cnr Nil blocks
	=  abort "InsertClipInBlock: wrong line number";

InsertClipInLine	:: !Clipboard !String !Block !Text -> List TLine;
InsertClipInLine (str:!Nil) last lines blocks
	= Line_MakeLine (str +++ last) :! Lines_and_BlocksToLines lines blocks;
InsertClipInLine (str:!rest) last lines blocks
	= Line_MakeLine str :! InsertClipInLine rest last lines blocks;
InsertClipInLine Nil last lines blocks
	=  abort "InsertClipInLine: wrong clipboard";

Lines_and_BlocksToLines	:: !Block !Text -> List TLine;
Lines_and_BlocksToLines Nil				blocks =  BlocksToLines blocks;
Lines_and_BlocksToLines (line:!lines)	blocks = line :! Lines_and_BlocksToLines lines blocks;
	
BlocksToLines	:: !Text -> List TLine;
BlocksToLines Nil				=  Nil;
BlocksToLines (block:!blocks)	= Lines_and_BlocksToLines block blocks;

ChopLine	:: !Int !TLine -> (!String,!String);
ChopLine cnr line
	| cnr == 0	=  ("",str);
				=  (str % (0, dec cnr), str % (cnr, dec (size str)));
	where {
	str	= Line_LineToString line;
	};

/*	Transform a Clipboard into a Text */

Text_ClipboardToText :: !Clipboard -> (!Text,!NrLines);
Text_ClipboardToText Nil	=  (EmptyText,1);
Text_ClipboardToText clip	=  ClipboardToText clip;
	
ClipboardToText	:: !Clipboard -> (!Text,!NrLines);
ClipboardToText Nil		=  (Nil, 0);
ClipboardToText clip	=  (block :! rest, nrblock + nrrest);
	where {
	(block,restclip,nrblock)	= SplitTake_and_TransformClip LinesPerBlock clip;
	(rest,nrrest)				= ClipboardToText restclip;
	};
	
SplitTake_and_TransformClip	:: !Int !Clipboard -> (!List TLine,!Clipboard,!NrLines);
SplitTake_and_TransformClip n Nil		=  (Nil,Nil,0);
SplitTake_and_TransformClip 0 list		=  (Nil,list,0);
SplitTake_and_TransformClip n (str:!r)	=  (line:!more,rest,inc nrmore);
	where {
	(more,rest,nrmore)	= SplitTake_and_TransformClip (dec n) r;
	line				= MakeLine 0 (size str`) str`;
	str`				= str +++ NewlStr;
	};

/*	Transform a list of STRINGs into a Text */

Text_StringsToText	:: !(List String) -> Text;
Text_StringsToText Nil		=  EmptyText;
Text_StringsToText strings	=  StringsToText strings;

StringsToText	:: !(List String) -> Text;
StringsToText Nil		= Nil;
StringsToText strings	= block :! StringsToText rest;
	where {
	(block,rest)	= SplitTake_and_Transform LinesPerBlock strings;
	};

SplitTake_and_Transform	:: !Int !(List String) -> (!List TLine,!List String);
SplitTake_and_Transform n Nil		=  (Nil,Nil);
SplitTake_and_Transform 0 list		=  (Nil,list);
SplitTake_and_Transform n (str:!r)	=  (line:!more, rest);
	where {
	(more,rest)	= SplitTake_and_Transform (dec n) r;
	line		= MakeLine 0 (size str) str;
	};
	
/* Append two Text objects */

Text_AppendText	:: !Text !Text -> Text;
Text_AppendText Nil				text2		= Text_AppendNewLine text2;
Text_AppendText EmptyText		text2		= Text_AppendNewLine text2;
Text_AppendText text1			Nil			= Text_AppendNewLine text1;
Text_AppendText text1			EmptyText	= Text_AppendNewLine text1;
Text_AppendText text1			text2		= Text_AppendText2 text1 (Text_AppendNewLine text2);

Text_AppendText2 :: !Text !Text -> Text;
Text_AppendText2 Nil				text2	= text2;
Text_AppendText2 (block:!Nil)		text2	= AppendText block text2;
Text_AppendText2 (block:!blocks)	text2	= block :! Text_AppendText2 blocks text2;
	
AppendText :: !Block !Text -> Text;
AppendText Nil		Nil		= Nil;
AppendText Nil		text	= text;
AppendText block	Nil		= block :! Nil;
AppendText block	text	= block` :! AppendText restblock text`;
	where {
	(block`,restblock,text`)	= Split_and_TakeBlock True LinesPerBlock block text;
	};
	
Split_and_TakeBlock	:: !Bool !Int !Block !Text -> (!Block,!Block,!Text);
Split_and_TakeBlock last 0 block			text					= (Nil,block,text);
Split_and_TakeBlock last n Nil				Nil						= (Nil,Nil,Nil);
Split_and_TakeBlock last n Nil				(block:!blocks)			= Split_and_TakeBlock False n block blocks;
Split_and_TakeBlock last n EmptyBlock		Nil				| last	= (Nil,Nil,Nil);
Split_and_TakeBlock last n EmptyBlock		(block:!blocks)	| last	= Split_and_TakeBlock False n block blocks;
Split_and_TakeBlock last n (line:!lines)	text					= (line:!block,restblock,text`);
	where {
	(block,restblock,text`)	= Split_and_TakeBlock last (dec n) lines text;
	};
	
/* Append an empty line to the end of the text, if it is not already there */

Text_AppendNewLine :: !Text -> Text;
Text_AppendNewLine Nil						= EmptyText;
Text_AppendNewLine EmptyText				= EmptyText;
Text_AppendNewLine text			| has_nl	= text;
											= AppendNewLine2 text;
	where {
	has_nl		= LLength lastline == 1 && Select lastline 0 == NewlStr;
	lastblock	= Select text (dec (LLength text));
	lastline	= Select lastblock (dec (LLength lastblock));
	};

AppendNewLine2 :: !Text -> Text;
AppendNewLine2 Nil				= Nil;
AppendNewLine2 (block:!Nil)	= (block` :! text);
	where {
	(block`,text)	= DoAppend LinesPerBlock block;
	};
AppendNewLine2 (block:!blocks)	= block :! AppendNewLine2 blocks;

DoAppend :: !Int !Block -> (!Block,!Text);
DoAppend 0 block			= (Nil,EmptyText);
DoAppend n Nil				= (EmptyBlock, Nil);
DoAppend n (line:!lines)	= (line:!block,text);
	where {
	(block,text)	= DoAppend (dec n) lines;
	};
	
/*	Determine the length of a text */

Text_NrLines :: !Text -> Int;
Text_NrLines Nil	= 0;
Text_NrLines text	= LinesPerBlock * nrblocks + LLength (Select text nrblocks);
	where {
	nrblocks	= dec (LLength text);
	};
	
/*	Transform a list of Lines into a Text */

LinesToText	:: !(List TLine) -> Text;
LinesToText Nil		=  Nil;
LinesToText lines	= block :! LinesToText rest;
	where {
	(block,rest)	= SplitTake LinesPerBlock lines;
	};

SplitTake :: !Int !(List TLine) -> (!List TLine,!List TLine);
SplitTake n Nil			= (Nil,Nil);
SplitTake 0 list		= (Nil,list);
SplitTake n (line:!r)	= (line:!more, rest);
	where {
	(more,rest)	= SplitTake (dec n) r;
	};

/*	Transform a STRING into a TLine */

Line_MakeLine :: !String -> TLine;
Line_MakeLine str =  MakeLine 0 (size str) str;

MakeLine :: !Int !Int !String -> TLine;
MakeLine i len string
	| i >= len	= Nil;
				= str :! MakeLine newi len string;
	where {
	(str,newi)	= UptoTab i len string;
	};

UptoTab	:: !Int !Int !String -> (!String,!Int);
UptoTab i len string
	|  string.[i] == TabChar	=  (TabStr, inc i);
								=  UptoNextTab i i len string;

UptoNextTab	:: !Int !Int !Int !String -> (!String,!Int);
UptoNextTab beginIndex i len string
// RWS ...
//	| string.[i] == '\n' && i <> len-1
//		=	UptoNextTab beginIndex (inc i) len string ->> "UptoNextTab: illegal newline\n";
// ... RWS
	| i >= len || string.[i] == TabChar
		| i == beginIndex
			=	("",i);
		// otherwise
			=	(string % (beginIndex, i-1), i);
	=  UptoNextTab beginIndex (inc i) len string;

/*	Transform a TLine into a STRING */

Line_LineToString :: !TLine -> String;
Line_LineToString Nil			=  "";
Line_LineToString (str:!rest)	=  str +++  Line_LineToString rest ;

/*	Split a line into a part before (reversed) and a part after the cursor */

Line_SplitLine	:: !Int !TLine -> (!TLine,!TLine);
Line_SplitLine cursor line
	| common == ""	=  (before,after);
					= (befcom :! before, aftcom :! after);
	where {
	(before,after,common,sofar)	= PrimitiveSplit 0 cursor line Nil;
	befcom						= common % (0, dec comcur);
	aftcom						= common % (comcur, dec (size common));
	comcur						= cursor - sofar;
	};

PrimitiveSplit :: !Int !Int !TLine !TLine -> (!TLine,!TLine,!String,!Int);
PrimitiveSplit sofar cursor Nil before
	| sofar == cursor	=  (before,Nil,"",sofar);
						=  abort "PrimitiveSplit: cursor not in line";
PrimitiveSplit sofar cursor after=:(str:!rest) before
	| sofar == cursor	=  (before,after,"",sofar);
	| newsof > cursor	=  (before,rest,str,sofar);
						=  PrimitiveSplit newsof cursor rest (str:!before);
	where {
	newsof	= sofar + size str ;
	};

/*	Glue a a part before (reversed) and a part after the cursor into one line */

Line_GlueLine :: !TLine !TLine -> TLine;
Line_GlueLine bef aft =  GlueLine (Reverse2 bef Nil) aft;

GlueLine :: !TLine !TLine -> TLine;
GlueLine Nil				aft					= aft;
GlueLine before=:(bstr:!Nil)	Nil					= before;
GlueLine (bstr:!Nil)			after=:(astr:!rest)
	| bstr == TabStr || astr == TabStr		= bstr :! after;
												= (bstr +++ astr) :! rest;
GlueLine (bstr:!rest)		aft					= bstr :! GlueLine rest aft;

/*	Glue a part before and a part after the cursor into one reversed line */

Line_GlueAfter :: !TLine !TLine -> TLine;
Line_GlueAfter bef aft = Reverse_and_RemoveNl (Line_GlueLine bef aft) Nil;

Reverse_and_RemoveNl :: !TLine !TLine -> TLine;
Reverse_and_RemoveNl (str:!Nil) rev
	| str == NewlStr	= rev;
						= str % (0, size str  - 2) :! rev;
Reverse_and_RemoveNl (str:!rest) rev
						=  Reverse_and_RemoveNl rest (str:!rev);
Reverse_and_RemoveNl Nil _
	=	RuleDoesn`tMatch "Reverse_and_RemoveNl" Nil;

/*	Calculate the nr of chars in a line */

Line_NrChars :: !TLine -> Int;
Line_NrChars Nil										= 0;
Line_NrChars (str:!Nil)	|  str .[ last]  == NewlChar	= last;
														= inc last;
	where {
	last	= dec (size str);
		
	};
Line_NrChars (str:!rest)									= size str  +  Line_NrChars rest ;

/*	Functions to change the current line */

LineToBefore :: !TLine !TLine -> TLine;
LineToBefore Nil		before									= before;
LineToBefore (str:!Nil)	before	| last < 1						= before;
								|  str .[last]  <> NewlChar	= str :! before;
																= str % (0, dec last) :! before;
	where {
	last= dec (size str);
	};
LineToBefore (str:!rest)	before									= LineToBefore rest (str:!before);

BeforeToLine :: !TLine -> TLine;
BeforeToLine Nil					=  NewlStr:!Nil;
BeforeToLine before=:(TabStr:!rest)	=  Reverse2 (NewlStr :! before) Nil;
BeforeToLine (str:!rest)				=  Reverse2 ((str +++ NewlStr) :! rest) Nil;

/*	Cut a small Selection from the current line to the Clipboard */

CutFromCurLine :: !PartTSel !CurLine -> (!Clipboard,!TLine,!TLine);
CutFromCurLine tsel=:{l1,c1,l2,c2} {CurLine | before,after,cnr}
	| c2 == cnr	= (clip1:!Nil,bef,after);
	| c1 == cnr	= (clip2:!Nil,before,aft);
				= (clip3:!Nil,bef`,aft`);
	where {
	(clip1,bef)		= CutFromBefore (c2 - c1) before "";
	(clip2,aft)		= CutFromAfter (c2 - c1) after "";
	(clip3,aft`)	= CutFromAfter (c2 - c1) after` "";
	(bef`,after`)	= Line_SplitLine c1 (Line_GlueLine before after);
	};
	
CutFromBefore :: !Int !TLine !String -> (!String,!TLine);
CutFromBefore 0 before			clip	= (clip,before);
CutFromBefore n (TabStr:!rest)	clip	= CutFromBefore (dec n) rest (TabStr +++ clip);
CutFromBefore n (str:!rest)		clip
	| n > len	= CutFromBefore (n - len) rest (str +++ clip);
	| n == len	= (str +++ clip, rest);
				= (str % (i, dec len) +++ clip, str % (0, dec i) :! rest);
	where {
	i	=	len - n;
	len	=	size str;
	};
CutFromBefore _ Nil clip
	=	RuleDoesn`tMatch "CutFromBefore" (clip,Nil);

CutFromAfter :: !Int !TLine !String -> (!String,!TLine);
CutFromAfter 0 after			clip	= (clip,after);
CutFromAfter n (TabStr:!rest)	clip	= CutFromAfter (dec n) rest (clip +++ TabStr);
CutFromAfter n (str:!rest)		clip
	| n > len	= CutFromAfter (n - len) rest (clip +++ str);
	| n == len	= (clip +++ str, rest);
				= (clip +++  str % (0, dec n) , str % (n, dec len) :! rest);
	where {
	len	=	size str;
	};
CutFromAfter _ Nil clip
	=	RuleDoesn`tMatch "CutFromAfter" (clip,Nil);
