// utf-8

/*	Copyright  (c)	Günter Woigk  2002-2003
  					mailto:kio@little-bat.de

	This file is free software

 	This program is distributed in the hope that it will be useful,
 	but WITHOUT ANY WARRANTY; without even the implied warranty of
 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

 	Permission to use, copy, modify, distribute, and sell this software and
 	its documentation for any purpose is hereby granted without fee, provided
 	that the above copyright notice appear in all copies and that both that
 	copyright notice and this permission notice appear in supporting
 	documentation, and that the name of the copyright holder not be used
 	in advertising or publicity pertaining to distribution of the software
 	without specific, written prior permission. The copyright holder makes no
 	representations about the suitability of this software for any purpose.
 	It is provided "as is" without express or implied warranty.

 	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 	PERFORMANCE OF THIS SOFTWARE.

	2002-04-28 kio	moved open and close of file into creator/destructor
	2003-07... kio	changes to use UCS4String library
*/

#define	SAFE	3
#define	LOG 	1

#include "config.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/param.h>
#include <stdlib.h>
#include <sys/uio.h>
#include "Stream.h"
#include "var/var.h"


/* ----	private methods ------------------------------------------------
*/
void Stream::_init ( cString& fname )
{
	fd   		= -1;			// -1 <=> open failed
	path		= fname;
	type		= s_null;		// /dev/null
	openin  	= no;
	openout		= no;
	linebreak	= nl;			// line break := system default  ((kio.h))
	conversion	= utf8;			// tty: charset conversion := utf8
	timeoutin	= 0;			// blocking
	timeoutout	= 0;			// blocking
	getstoptabs	= no;			// Get() stops on tab?			 (dflt: no)
	getstopctls	= no;			// Get() stops on any ctrl code? (dflt: no)
	getstorectls= no;			// Get() stores final ctrl code? (dflt: no)
	error	 	= ok;
//	bufferin	= "";
}



// ============================================================================
// ====			PUBLIC METHODS		===========================================
// ============================================================================


// ============================================================================
// ====			CREATOR / DESTRUCTOR ==========================================
// ============================================================================


/* ----	destructor: close & destroy stream ---------
		close may fail! => check errno
*/
Stream::~Stream ( )
{	
	XXLog("~Stream(%i,\"%s\")\n",fd,CString(path));
	
	if (fd>2) close(fd);	// close except -1 = open failed or 0/1/2 = stdin/out/err
}


/* ----	creator for dummy stream ------------------------
		create a dummy stream with no input or output
*/
Stream::Stream ( )
{	
	XXLog("Stream(dummy)\n");
	_init("null://");
}


/* ----	creator for stdin/out/err ------------------------
		create a stream for stdin, stdout or stderr
*/
Stream::Stream ( int f, cString& name )
{
	XXLog("Stream(%i,\"%s\")\n",f,CString(name));
	XXTRAP( f<0 || f>2 );
	XXTRAP(name=="");

	_init(name);
	fd   	= f;
	type	= ClassifyFile(fd);
	
	openin  = f==0;	
	openout = f>=1 || type==s_tty;
}


/* ----	creator for stream --------------------------------
		open file for stream
*/
Stream::Stream ( cString& file, int flags )
{
	XXLog("Stream(\"%s\",%4x)\n",CString(file),flags);
	
	XXTRAP(file=="");

	_init(file);

	flags &= ~O_NONBLOCK;					// ***TODO*** non-blocking for timeout !!
	fd = open(CString(file),flags,6*(1+8+64));
	if (fd<0) { error = GetError(); return; }

	openin   = (flags&O_RDWR) | (flags&O_RDONLY);
	openout  = (flags&O_RDWR) | (flags&O_WRONLY);

	type	 = ClassifyFile(fd);
}




// ============================================================================
// ====			IO BUFFER =====================================================
// ============================================================================

void Stream::DiscardInput ( )
{
	NIMP();
}

void Stream::DiscardOutput ( )
{
	NIMP();
}

void Stream::SyncOutput ( )
{
	NIMP();
}

bool Stream::IsInAvail ( ) 
{
	NIMP();
	return 1;
}

bool Stream::IsOutFree ( ) 
{
	NIMP();
	return 1;
}

bool Stream::IsOutPending ( ) 
{ 
	NIMP();
	return 1;
}

ssize_t Stream::GetInAvail ( ) 
{ 
	NIMP();
	return 1;
}

ssize_t Stream::GetOutFree ( ) 
{ 
	NIMP();
	return 1;
}

ssize_t Stream::GetOutPending ( ) 
{ 
	NIMP();
	return 1;
}



// ============================================================================
// ====			FILE & TTY ====================================================
// ============================================================================

ssize_t Stream::GetFileLength ( )
{ 
	NIMP();
	return 1;
}

void Stream::SetFileLength ( ssize_t )
{ 
	NIMP();
}

ssize_t Stream::GetPosition ( ) 
{ 
	NIMP();
	return 1;
}

void Stream::SetPosition ( ssize_t )
{
	NIMP();
}

void Stream::Append ( )
{
	NIMP();
}

void Stream::Clear ( )
{
	NIMP();
}


/* ----	VT 100 ----
*/
void Stream::Cls ( )				{ XXTRAP(type!=s_tty);PutString("\033[H\033[J"); }
void Stream::HLocate ( uint x )		{ XXTRAP(type!=s_tty);PutString(Using("\033[%uG",x+1)); }
void Stream::VLocate ( uint y )		{ XXTRAP(type!=s_tty);PutString(Using("\033[%ud",y+1)); }
void Stream::CursorLeft ( )			{ XXTRAP(type!=s_tty);PutString("\033[D"); }
void Stream::CursorRight ( )		{ XXTRAP(type!=s_tty);PutString("\033[C"); }
void Stream::CursorUp ( )			{ XXTRAP(type!=s_tty);PutString("\033[A"); }
void Stream::CursorDown ( )			{ XXTRAP(type!=s_tty);PutString("\033[B"); }
void Stream::CursorLeft ( uint n )	{ XXTRAP(type!=s_tty);PutString(Using("\033[%uD",n)); }
void Stream::CursorRight ( uint n )	{ XXTRAP(type!=s_tty);PutString(Using("\033[%uC",n)); }
void Stream::CursorUp ( uint n )	{ XXTRAP(type!=s_tty);PutString(Using("\033[%uA",n)); }
void Stream::CursorDown ( uint n )	{ XXTRAP(type!=s_tty);PutString(Using("\033[%uB",n)); }
void Stream::NL ( )					{ XXTRAP(type!=s_tty);PutString("\012\015"); }
void Stream::Tab ( )				{ XXTRAP(type!=s_tty);PutString("\009"); }
void Stream::Bell ( )				{ XXTRAP(type!=s_tty);PutString("\007"); }
void Stream::ClearRight()			{ XXTRAP(type!=s_tty);PutString("\033[0K"); }	// incl. crsr loc.
void Stream::ClearLeft()			{ XXTRAP(type!=s_tty);PutString("\033[1K"); }	// incl. crsr loc.
void Stream::ClearLine()			{ XXTRAP(type!=s_tty);PutString("\033[2K"); }
void Stream::ClearDown()			{ XXTRAP(type!=s_tty);PutString("\033[0J"); }	// incl. crsr loc.
void Stream::ClearUp()				{ XXTRAP(type!=s_tty);PutString("\033[1J"); }	// incl. crsr loc.
void Stream::ClearScreen()			{ XXTRAP(type!=s_tty);PutString("\033[2J"); }

typedef char escseq[8];
const escseq vt_clearattr    =   "\033[0m";					// reset all attr
const escseq vt_clearfgcolor =   "\033[39m";				// foreground := default color
const escseq vt_clearbgcolor =   "\033[49m";				// background := default color
const escseq vt_bold[2]      = { "\033[22m", "\033[1m" };	// bold off/on
const escseq vt_inverse[2]   = { "\033[27m", "\033[7m" };	// inverse off/on
const escseq vt_underline[2] = { "\033[24m", "\033[4m" };	// underline off/on


/* ---------------------------------------------------------------
		set'n'clear attributes functions for printing strings
		the following functions frame the passed string with
		control sequences which set the desired attributes
		and clear those attributes after printing.
		note:
			there is no possibility to test whether some of the
			desired attributes were already set before printing
		1) 	so if they are set they are set before and always reset after printing
		2)	but if they are not set they are not reset before nor after printing
*/
String VT_Bold      ( cString& s )	{ return String(vt_bold[1])      +s +vt_bold[0];      }
String VT_Inverse   ( cString& s )	{ return String(vt_inverse[1])   +s +vt_inverse[0];   }
String VT_Underline ( cString& s )	{ return String(vt_underline[1]) +s +vt_underline[0]; }

String VT_FGColor ( cString& s, VT_Color c )
{
	bool bright = c&VT_bright;
	c &= 15; if (c==VT_normal) return s;	// note: VT_normal: fg color is not set nor reset

	escseq e = "\033[39m";
	if (bright) e[2] = '9';
	e[3] = '0' -VT_black +c;
	return String(e)+s+"\033[39m";
}

String VT_BGColor ( cString& s, VT_Color c )
{
	bool bright = c&VT_bright;
	c &= 15; if (c==VT_normal) return s;	// note: VT_normal: bg color is not set nor reset

	escseq bu[2] = { "\033[49m", "\033[100m" };
	ptr e = bu[0];		// bg normal
	e = bu[bright];
	e[3+bright] = '0' - VT_black +c;
	return String(e)+s+"\033[49m";
}

String VT_Attr ( cString& s, VT_Color fg, VT_Color bg )
{
	String z = s;
	if (fg&VT_bold)			z = VT_Bold(z);
	if (fg&VT_inverse)		z = VT_Inverse(z);
	if (fg&VT_underline)	z = VT_Underline(z);
	if ((fg&15)!=VT_normal)	z = VT_FGColor(z,fg);
	if ((bg&15)!=VT_normal)	z = VT_BGColor(z,bg);
	return z;
}


/* -------------------------------------------------
		set and clear attributes for stream
		these functions set or clear the desired attributes
		they silently do nothing if the stream is no tty
*/
void Stream::ClearAttr( )			{ if (type==s_tty) PutString("\033[0m"/*"\033[39m\033[49m"*/); }
void Stream::SetBold ( bool f )		{ if (type==s_tty) PutString(vt_bold[f]); }
void Stream::SetInverse ( bool f )	{ if (type==s_tty) PutString(vt_inverse[f]); }
void Stream::SetUnderline ( bool f ){ if (type==s_tty) PutString(vt_underline[f]); }

void Stream::SetFGColor (VT_Color c)
{
	if (type!=s_tty) return;

	bool bright = c&VT_bright;
	c &= 15;

	escseq e = "\033[39m";		// fg normal
	if( c!=VT_normal )
	{
		if (bright) e[2] = '9';
		e[3] = '0' -VT_black +c;
	}
	PutString(e);
}

void Stream::SetBGColor (VT_Color c)
{
	if (type!=s_tty) return;

	bool bright = c&VT_bright;
	c &= 15;

	escseq bu[2] = { "\033[49m", "\033[100m" };
	ptr e = bu[0];		// bg normal
	if( c!=VT_normal )
	{
		e = bu[bright];
		e[3+bright] = '0' - VT_black +c;
	}
	PutString(e);
}

void Stream::SetAttr ( VT_Color fg, VT_Color bg )
{
	if (type!=s_tty) return;

	ClearAttr();			// löscht auch fg/bg color
	if ((fg&15)!=VT_normal)	SetFGColor(fg);
	if ((bg&15)!=VT_normal)	SetBGColor(bg);
	if (fg&VT_bold)         SetBold();
	if (fg&VT_underline)    SetUnderline();
	if (fg&VT_inverse)      SetInverse();
}




/* ----	get cursor position ------------------------------------
		top/left corner = (0,0)
*/
void Stream::GetPosition ( uint& row, uint& col )
{
	XXTRAP(type!=s_tty);

	PutString("\033[6n");
	String z;
	UCS4Char c;

a:	row=0;col=0;
	int a=z.Len(); if(z.Len()>20) return;
	c=GetChar(); if(error)return;
	z+=c; if(c!=27)goto a;
	c=GetChar(); if(error)return;
	z+=c; if(c!='[')goto a;
b:	c=GetChar(); if(error)return;
	z+=c; if(c==';')goto c;
	if(_no_dec_digit(c))goto a;
	row=row*10+_digit_val(c); goto b;
c:	c=GetChar(); if(error)return;
	z+=c; if(c=='R')goto d;
	if(_no_dec_digit(c))goto a;
	col=col*10+_digit_val(c); goto c;
d:	if(a>0)PutbackString(z.LeftString(a));
	row-=1;col-=1;
}


/* ----	set cursor to position ------------------------------------
		top/left corner = (0,0)
*/
void Stream::SetPosition ( uint row, uint col )
{
	XXTRAP(type!=s_tty);
	XXTRAP((col>>10)||(row>>10));
	PutString( Using("\033[%u;%uH",row+1,col+1) );
}

void Stream::SavePosition ( )
{
	XXTRAP(type!=s_tty);

	PutString("\033""7"); 		// VT100
}

void Stream::RestorePosition ( )
{
	XXTRAP(type!=s_tty);

	PutString("\033""8"); 		// VT100
}

void Stream::GetWindowSize ( uint& rows, uint& cols )
{
	XXTRAP(type!=s_tty);

	TerminalSize(fd,rows,cols);
}

void Stream::SetWindowSize ( uint , uint  )
{
	Log("\nStream::SetWindowSize(int,int) not yet implemented. ");
	// ***TODO***
}



// ============================================================================
// ====			INPUT / OUTPUT ================================================
// ============================================================================


/* ----	write binary data -----------------------------------------------------
		blocking
		timeout applied		***TODO***
		in: if 'error' is already set, nothing is done.
		returns number of bytes actually written
		on error 'error' is set
*/
ssize_t Stream::Write ( const void* p, ssize_t n )
{
	if ( error || n==0 ) return 0;	
	XXTRAP ( !p );
	XXTRAP ( n<0 );
	if ( !openout ) { error=outputnotpossible; return 0; }
	XXXTRAP ( fd<0 );

	/*Log("«");*/

	ssize_t sum = 0;
a:	ssize_t m = write(fd,ptr(p)+sum,n-sum);
	if (m!=-1)
	{
		sum += m;
		XXXTRAP(sum>n);
		if (sum==n) { /*Log("»");*/ return n; }
		else goto a;
	}

	if (GetError()==EINTR||GetError()==EAGAIN) goto a;

	error=GetError();
	return sum;
}

	
/* ----	read binary data -----------------------------------------------------
		blocking 
		timeout applied		***TODO***
		in: if 'error' is already set, nothing is done.
		returns number of bytes actually read
		on error 'error' is set
*/
ssize_t Stream::Read ( void* p, ssize_t n )
{
	if ( error || n==0 ) return 0;	
	XXTRAP ( !p );
	XXTRAP ( n<0 );
	if (!openin) { error=inputnotpossible; return 0; }
	XXXTRAP(fd<0);

	ssize_t sum = 0;

	if(bufferin.Len())
	{
		sum = bufferin.Len();
		if (sum>=n) 
		{
			memcpy(p,bufferin.Text(),n); 
			bufferin=bufferin.MidString(n); 
			return n; 
		}
		memcpy(p,bufferin.Text(),sum);
		bufferin.Clear();
	}

	/*Log("<");*/

a:	ssize_t m = read(fd,ptr(p)+sum,n-sum);
	if (m!=-1)
	{
		sum += m;
		XXXTRAP(sum>n);
		if (sum==n) { /*Log(">");*/ return n; }
		goto a;
	}

	if (GetError()==EINTR||GetError()==EAGAIN) goto a;

	Log("\nStream::Read(): %s ",ErrorStr());

	error=GetError();
	return sum;
}


/* ----	put back a single character -----------------------------
		the valid utf-8 character is stored back into the inbuffer
		character encoding conversion applied
*/
void Stream::PutbackChar ( UCS4Char c )
{
	bufferin = String(c).ConvertedTo(conversion) + bufferin;
}

void Stream::PutbackString ( cString& s )
{
	bufferin = s.ConvertedTo(conversion) + bufferin;
}


/* ----	write a single character ---------------------------------------------
		blocking (timeout applied)
		char(0) is a valid character!
*/
void Stream::PutChar ( UCS4Char n )
{	char c;

	switch(conversion)
	{
		case ucs4:
		{
			UCS4Char m = UCS4CharFromUCS4(n);
			Write(&m,sizeof(m)); return;
		}
		case ucs2:
		{
			UCS2Char m = UCS2CharFromUCS4(n);
			Write(&m,sizeof(m)); return;
		}
		case ucs1:
		{
			UCS1Char m = UCS1CharFromUCS4(n);
			Write(&m,sizeof(m)); return;
		}
		case utf8:
		{
			char bu[6], *p=bu;
			UCS4CharToUTF8(n,p);
			Write(bu,p-bu);
		//	UTF8Char s = UTF8CharFromUCS4(n);
		//	Write(s,UTF8CharNominalSize(s));
			return;
		}
		case ascii_us:	c = UCS4CharToAsciiUS(n);  goto r;
		case ascii_ger:	c = UCS4CharToAsciiGer(n); goto r;
		case cp_437:	c = UCS4CharToCp437(n);    goto r;
		case mac_roman:	c = UCS4CharToMacRoman(n); goto r;
		case atari_st:	c = UCS4CharToAtariST(n);  goto r;
		case rtos:		c = UCS4CharToRtos(n);     r: Write(&c,1); return;

		default:
		{
			SetError(illegalconversion);
			c = n>=0x80 ? '?' : n; goto r;
		}
	}
}


/* ----	read a single character ----------------------------------------------------
		blocking		***TO DO*** should be non blocking
*/
UCS4Char Stream::GetChar ( )
{
	XXXLogIn("Stream::GetChar()");

	char bu[20]; ssize_t bsz=0;
	UCS4Char n;

a:	Read(bu+bsz,1); if(error) { n=UCS4ReplacementChar; goto x; }
	bsz+=1; ClearError();

	switch(conversion)
	{
	case ucs1:		n = bu[0]; break;
	case ucs2:		if(bsz<2) goto a; n = *(UCS2Char*)bu; break;
	case ucs4:		if(bsz<4) goto a; n = *(UCS4Char*)bu; break;
	case utf8:		if(bsz<UTF8CharNominalSize(bu[0])) goto a; n = UCS4CharFromUTF8(bu); break;
	case ascii_us:	n = UCS2CharFromAsciiUS(bu[0]); break;
	case ascii_ger:	n = UCS2CharFromAsciiGer(bu[0]);break;
	case cp_437:	n = UCS2CharFromCp437(bu[0]);   break;
	case mac_roman:	n = UCS2CharFromMacRoman(bu[0]);break;
	case atari_st:	n = UCS2CharFromAtariST(bu[0]); break;
	case rtos:		n = UCS2CharFromRtos(bu[0]);    break;
	default:		SetError(illegalconversion); n = bu[0]; if(n>>7) n='?';break;
	}

	error=GetError();

x:	XXXLogOut("= %i",(int)n);
	return n;
}


/* ----	write ucs-4 text string -----------------------------------
*/
void Stream::PutString ( cString& s )
{
	XXXLogIn("PutString()");

/*	this looks pretty bogus:
	switch(conversion)
	{
	case ucs4:
		Write(s.Text(),s.Len()*sizeof(UCS4Char));
		break;
	case ucs2:
		{	String z = s.ConvertedTo(ucs2);
			XXXTRAP(z.Csz()!=2);
			Write(z.Text(),z.Len()*sizeof(UCS2Char));
		}	break;
	default:
*/		{	String z = s.ConvertedTo(conversion);
			XXXTRAP(z.Csz()!=1);
			Write(z.Text(),z.Len());
		}
/*		break;
	}
*/

	XXXLogOut("");
}


/* ----	read ucs-4 text string from tty -----------------------------------
		blocking
		stops at most ctls
		in: expects text cursor to sit at idx
*/
UCS4Char Stream::TTYGetString ( String& string, long& idx )
{
	XXTRAP( type!=s_tty );

	uint z,col0,cols;
	GetWindowSize(z,cols);					// cols = window width
	GetPosition(z,col0);
	col0 = (col0 -idx%cols +cols) % cols;	// col0 = start column of input text

	while ( !error )
	{
		UCS4Char c = GetChar(); if(error) break;

	// printable character?
		if ( UCS4CharIsPrintable(c) )
		{
			string = LeftString(string,idx)+String(c)+MidString(string,idx);
			PutChar(c); idx++;
			if (idx>=string.Len()) continue;
			SavePosition();
			PutString ( MidString(string,idx) );
			RestorePosition();
			continue;
		}

	// 8=DEL or 127=RUBOUT
		if ( c==8 || c==0x7f )
		{
			if(idx==0) continue;
			if ((idx+col0)%cols) CursorLeft();
			else { CursorUp(); HLocate(cols-1); }
			idx--;
d8:			string = LeftString(string,idx) + MidString(string,(idx+1));
			SavePosition();
			PutString ( MidString(string,idx)+" " );
			RestorePosition();
			continue;
		}

	// input end?
	// leave cursor at current position
	// return unknown code
		if (c!=27)
		{
			return c;
		}

// ---- escape sequence

		c = GetChar(); if(error) return 27;

		if (c!='[')	// unknown esc. seq.
		{
			if (c=='O')	// Oh (not null): test for "cursor keys in application mode"
			{
				UCS4Char d = GetChar();
				if(!error) switch(d)
				{
					case 'A': goto cu; case 'B': goto cd;
					case 'C': goto cr; case 'D': goto cl;
				}
				PutbackChar(d);
			}

			PutbackChar(c);
			return 27;
		}

// ---- esc [ sequence

		c = GetChar(); if(error) { PutbackChar('['); return 27; }

		if(c=='C')		// cursor right
		{
cr:			if(idx>=string.Len()) continue;
			PutChar ( string[idx] );
			idx++;
			continue;
		}

		if(c=='B')		// cursor down
		{
cd:			uint n = string.Len()-idx; if(n>cols) n=cols;
			PutString( string.MidString(idx,n) );
			idx+=n;
			continue;
		}

		if(c=='F')		// Ende
		{
			PutString ( MidString(string,idx) );
			idx = string.Len();
			continue;
		}

		if(c=='D')		// cursor left
		{
cl:			if(idx==0) continue;
			if ((idx+col0)%cols) CursorLeft();
			else { CursorUp(); HLocate(cols-1); }
			idx--;
			continue;
		}

		if(c=='A') 		// cursor up
		{
cu:			if((uint)idx>=cols){idx-=cols;CursorUp();continue;}
			goto p1;
		}

		if(c=='H')		// Pos1
		{
p1:			long n = (idx+col0)/cols; if (n) CursorUp( n );
			HLocate(col0);
			idx = 0;
			continue;
		}

		if(c=='3')		// Entf
		{
			GetChar(); if(error)break;		// expect '~'
			if(idx>=string.Len()) continue;
			goto d8;
		}

	// unknown esc. seq.
		PutbackString(String("[")+c);
		return 27;

	}	// while noerror

	return UCS4ReplacementChar;
}


/* ----	line editor with cursor movement and history ---------------------------
		if idx==0 then a new line of text is entered
		else the last entry from history is edited,
		starting up with cursor in column idx.
*/
String Stream::Input ( Var& history, long idx )
{
	XXTRAP(type!=s_tty);

	if (history.IsNoList()) history.SetList();				// might be initally supplied
	if (history.ListSize()>50) history.ResizeList(50);		// truncate list
	if (idx==0||history.ListSize()==0) history[history.ListSize()] = emptyString;// append new item

	long const h_start = 0;
	long const h_end   = history.ListSize() -1;
	long       h_index = h_end;

	String string = history[h_index].Text();

	if ( idx && (ulong)idx<(ulong)string.Len() )
	{
		PutString(string.LeftString(idx));
		SavePosition();
		PutString(string.MidString(idx));
		RestorePosition();
	}
	else
	{
		idx = string.Len();
		PutString(string);
	}

// getstoptabs:	 ignored
// getstopctls:	 ignored
// getstorectls: ignored
	ulong stops = (1<<3)|(1<<4)|(1<<0)|(1<<10)|(1<<13)|(1<<12);
	UCS4Char c  = 0;

	while ( !error )
	{
		c = TTYGetString(string,idx);				// tty line editor
		if (error) break;							// abort
		if ( (1<<c)&stops ) break;					// input end; e.g. return

	// now handle history:  pg_up = esc [ 5 ~  and  pg_dn = esc [ 6 ~

		if ( c!=27 )	// esc => unknown => display code
		{
			PutbackString ( String("{$") + HexString(c) + "}" );
			continue;
		}

		c = GetChar(); if (error) break;
		if (c!='[') { PutbackChar(c); PutbackString("{$1B}"); continue; }

		c = GetChar(); if (error) break;
		if (c!='5'&&c!='6') { PutbackChar(c); PutbackString("{$1B}["); continue; }

		GetChar(); if (error) break;				// '~'

		if (c=='5'&&h_index==h_start) continue;		// top of history
		if (c=='6'&&h_index==h_end  ) continue;		// bottom of history

		PutbackString("\033[H\001");	// Pos1 + stopper
		TTYGetString(string,idx);		// perform Pos1, return stopper
		ClearDown();					// clear to end of screen

		history[h_index].Text() = string;
		if (c=='5') h_index--; else h_index++;
		string = history[h_index].Text();

		PutString(string);
		idx = string.Len();
	}

	if (error) bufferin.Clear();

	PutString ( string.MidString(idx) );
	if (!error && (c==3||c==4)) error = endoffile;
	if (string!=SpaceString(string.Len()))
		 history[h_end].Text() = string;		// remember text in history
	else history.DeleteLastItem();				// except if empty
	for (int i=h_end-1;i>=0;i--) if (history[i].Text()==string) history.DeleteItem(i);
	return string;
}



/* ----	read ucs-4 text string -----------------------------------
		blocking
		***TODO*** timeout
*/
String Stream::GetString ( )
{	String s;	// input string

// getstoptabs:	 shall get() stop on 'tab' in addition to cr,lf,ff,eof	dflt: no
// getstopctls:	 shall get() stop on any ctl code? (tty: no editor!)	dflt: no
// getstorectls: shall get() store the final ctl code? (or drop it)		dflt: no
	ulong stops = getstopctls ? ~0 : (1<<3)|(1<<4)|(1<<0)|(1<<10)|(1<<13)|(1<<12) | (getstoptabs<<9);

//	no tty: no echo, no line editor
	if(type!=s_tty||getstopctls)
	{
		XXXLogIn("Stream::GetString(!tty)");

		uchar bu[101], c=0; ssize_t n; int i;

	// read data:
	a:	n = Read(bu,NELEM(bu)-1);
		XXXTRAP(n<0&&!error);
		XXXTRAP(n>0&&error);
		if(error) goto x;

	// search for control code:
		for ( i=0;i<n;i++ )
		{
			c=bu[i]; if(c==0x7f) c=8;
			if (c>=0x20) continue;			// printable
			if ( (1<<c) & stops ) break;	// stopper control code
			else ; 							// other control codes are left in string
		}

	// no control code found?
		if (i>=n) { s += String(bu,n); goto a; }

	// store data:
		s += String(bu,i+(getstorectls)); i++;

	// special care for dos line breaks:
		if ((c==10||c==13) && !getstopctls )
		{
			if (i==n) n += Read(bu+n,1)==1;
			i += c+bu[i]==23;
		}

	// file:	reposition file pointer: read_after_write consistency!
	// default:	putback remaining data
		if (i<n)
		{
			error=ok;		// <- clear pot. error from dos linebreak handling
			if(type==s_file) { if (lseek(fd,i-n,SEEK_CUR)==-1) error=GetError(); }
			else			 { bufferin = String(bu+i,n-i) + bufferin; }
		}

	x:	XXXLogOut("= %s",CString(s.ConvertedFrom(conversion)));
		return s.ConvertedFrom(conversion);
	}


// tty: echo, line editor
	else
	{
		XXXLogIn("Stream::GetString(tty)");

		long idx = 0;		// cursor position

		while ( !error )
		{
			UCS4Char c = TTYGetString(s,idx);
			if (error) { bufferin.Clear(); break; }

			if ( (1<<c)&stops )
			{
				PutString ( MidString(s,idx) );
				if(getstorectls) s += String(c);
				if (!error && (c==3||c==4)) error = endoffile;
				break;
			}

		// unknown control code
			PutbackString ( String("{$") + HexString(c) + "}" );
			continue;
		}

		XXXLogOut("= %s",CString(s));
		return s;
	}
}


















