wsh/
wsh/binsrc/
wsh/docs/help/
wsh/docs/old/
wsh/etc/
wsh/src/util/
/* This file contains the terminal "driver" code for splitsh.
   It is designed to keep track of the position of the cursor
   while handling a split window of vt100 emulation.
   All of the routines assume that there is no other output 
   going onto the standard output screen, to mess it up.

   Many thanks to Matt Ostanik who wrote the ANSI Handbook.
*/

#include	<sys/types.h>
#ifdef HAVE_TERMIO_H
#include	<termio.h>		/* Used only for TIOCGWINSZ */
#else
#include	<sys/ioctl.h>		/* Used only for TIOCGWINSZ */
#endif
#include	<stdio.h>
#include	<ctype.h>

/* Function declarations */

extern char *getenv();
static void  vt_goto();			/* Go to a coordinate position */
static void  set_wscroll();		/* Set a scrolling region */
static void  set_uscroll();		/* Set a user scroll region */
static void  end_scroll();		/* End all scrolling regions */
static void  save_cursor();		/* Save the current cursor position */
static void  restor_cursor();		/* Restore previous position */
static void  save_ucursor();		/* Save the current cursor for user */
static void  restor_ucursor();		/* Restore previous cursor for user */
static void  pos_addch();		/* Print a char at a cursor pos */
static void  do_vtesc();		/* Actually do the escape functions */

char *init_vt100();			/* Initialize the screens */
void  set_win();			/* Move the cursor to our win */
int   vt_write();			/* Write a buffer to a screen */
void  vt_addch();			/* Add a character to a screen */
void  end_vt100();			/* End the vt100 scrolling and such */


/* Handy definitions */

#define	ESC	'\033'				/* ^[ */
#define CLEAR	write(1, "\033[;H\033[2J", 8)	/* Go to home and clear */

/* Variables */

typedef struct {  /* Cursor coordinate structure */
	unsigned short x;		/* Line position */
	unsigned short y;		/* Column position */
	} pos;

static int just_init=0;	/* Have we just called vt_init() ? */
static int ul_a, ll_a;	/* The bounds of the upper window */
static int ul_b, ll_b;	/* The bounds of the lower window */
static pos c_a;		/* The current position of the upper window cursor */	
static pos c_b;		/* The current position of the lower window cursor */	
static int scroll_a1=0, scroll_a2=0;	/* The ^[[%d;%dr upper region */
static int scroll_b1=0, scroll_b2=0;	/* The ^[[%d;%dr lower region */
static int lines;	/* The number of lines on the screen */
static int columns;	/* The number of columns on the screen */
static char sep[BUFSIZ];	/* The window separator string */

/* Make these four variables accessable to the calling program */

int UU_lines=0;		/* The user requested lines for the upper window */
int WU_lines=0;		/* The number of lines for the upper window */
int WL_lines=0;		/* The number of lines for the lower window */
int W_columns=0;	/* The number of columns per window */

static int LU_lines;	/* A local copy of UU_lines that is modified */

/* Routine to initialize the vt100 screening, returning an error message,
   or NULL if all went well. */

char *init_vt100()
{
#ifdef TIOCGWINSZ
	struct /* winsize */ {
		unsigned short	ws_row;		/* rows, in characters */
		unsigned short	ws_col;		/* columns, in characters */
		unsigned short	ws_xpixel;	/* horizontal size - not used */
		unsigned short	ws_ypixel;	/* vertical size - not used */
	} mywinz;
#endif
	int i;
	char *ptr;

	lines=0; columns=0;	/* Reset the lines and columns */

	/* Check to make sure it's okay to run */
	if ( ! isatty(0) || ! isatty(1) )
		return("Standard input and output must be a tty");

	if ( ((ptr=getenv("TERM")) == NULL || strncmp(ptr, "vt100", 5)) && 
			/* A vt100 emulation detector -->  */	! vttest() )
		return("Terminal type must be set to vt100");

	if ( (ptr=getenv("LINES")) != NULL )
		lines=atoi(ptr);
	if ( (ptr=getenv("COLUMNS")) != NULL )
		columns=atoi(ptr);

#ifdef TIOCGWINSZ
	if ( ioctl(0, TIOCGWINSZ, &mywinz) == 0 )
	{
		if ( ! lines )
			lines=mywinz.ws_row;
		if ( ! columns )
			columns=mywinz.ws_col;
	}
#endif

	/* Now set defaults if we can't find the window size */
	if ( ! lines )		lines=24;
	if ( ! columns )	columns=80;

	/* Check that each window is at least 3 lines tall */
	if ( lines < 9 )
		return("Screen is not tall enough to split.");

	/* Set the exportable variables */
	if ( UU_lines ) {
		/* Check the user set # of lines */
		if ( UU_lines > (lines-3-3) )
			LU_lines=(lines-3-3);
		else if ( UU_lines < 3 )
			LU_lines=3;
		else
			LU_lines=UU_lines;

		WU_lines=LU_lines;
		WL_lines=(lines-3-LU_lines);
	} else
		WL_lines=WU_lines=((lines-3)/2);
	W_columns=columns;


	/* Set up the separator */
	for ( i=0; (i<columns)&&(i<BUFSIZ); ++i )  sep[i]='=';
	sep[i]='\0';

	/* Clear the screen, and set up the windows */
	CLEAR; 
	printf("%s", sep);			/* Top line - separator */
	ul_a=(1+1);				/* Upper limit of top win */
	ll_a=(1+WU_lines);			/* Lower limit of top win */
	vt_goto((1+WU_lines+1), 1);		/* Move past the top win */
	printf("%s", sep);			/* Print separator */
	ul_b=(1+WU_lines+1+1);			/* Upper limit of bottom win */
	ll_b=(1+WU_lines+1+WL_lines);		/* Lower limit of bottom win */
	vt_goto((1+WU_lines+1+WL_lines+1), 1);	/* Move past bottom win */
	printf("%s", sep);			/* Print separator */
	c_a.x=ul_a; c_a.y=1;			/* Set upper cursor */
	c_b.x=ul_b; c_b.y=1;			/* Set lower cursor */
	fflush(stdout);				/* Update the terminal */

	just_init=1;				/* Set just_init flag */

	return(NULL);
}

/* Write to the specified screen, handling ^[[ escape codes */

static char t_1[12]={'\0'}, t_2[12]={'\0'};
static int ti_1=0, ti_2=0;

/* State definitions */
#define NORMAL	0
#define AT_ESC	1
#define	AT_NUM1	2
#define	AT_NUM2	3
#define AT_QNUM	4
static int state=NORMAL;

int vt_write(win, buf, len)
int win;			/* The window; 0 if top, 1 if bottom */
char *buf;			/* The data to write */
int len;			/* The amount of data to write */
{
	char *ptr;
	pos *cursor;
	int i, *ul, *ll;

	/* Set the current window variables */
	if ( win == 0 )
	{
		cursor=(&c_a);
		ul=(&ul_a);
	}
	else
	{
		cursor=(&c_b);
		ll=(&ll_b);
	}

	/* Move the cursor into position if we need to */
	set_win(win);

	for ( ptr=buf, i=0; i<len; ++i, ++ptr )
	{
		switch (state)
		{
			case NORMAL:	if ( *ptr == ESC )
						state=AT_ESC;
					else
						vt_addch(win, *ptr);
					break;
			case AT_ESC:	if ( *ptr == '[' )
						state=AT_NUM1;
					else if ( *ptr == 'D' )
					{  /* Scroll text up. */
						printf("\033D");
						fflush(stdout);
						state=NORMAL;
					}
					else if ( *ptr == 'M' )
					{  /* Scroll text down */
						printf("\033M");
						fflush(stdout);
						state=NORMAL;
					}
					else if ( *ptr == '7' )
					{  /* Save cursor. */
						save_ucursor(win);
						state=NORMAL;
					}
					else if ( *ptr == '8' )
					{  /* Restore cursor */
						restor_ucursor(win);
						state=NORMAL;
					}
					else	
						state=NORMAL;
					break;
			case AT_NUM1:	if ( isdigit(*ptr) )
						t_1[ti_1++]=(*ptr);
					else if ( *ptr == '?' )
					{
						state=AT_QNUM;
					}
					else
					{
						t_1[ti_1]='\0';
						ti_1=0;
						if ( *ptr == ';' )
							state=AT_NUM2;
						else
						{
							do_vtesc(win, *ptr);
							state=NORMAL;
						}
					}
					if ( ti_1 > 10 )
					{  /* Way too many digits */
						ti_1=0;
						state=NORMAL;
					}
					break;
			case AT_NUM2:	if ( isdigit(*ptr) )
						t_2[ti_2++]=(*ptr);
					else 
					{
						t_2[ti_2]='\0';
						ti_2=0;
						do_vtesc(win, *ptr);
						state=NORMAL;
					}
					if ( ti_2 > 10 )
					{  /* Way too many digits */
						ti_2=0;
						state=NORMAL;
					}
					break;
			case AT_QNUM:	if ( isdigit(*ptr) )
						t_1[ti_1++]=(*ptr);
					else
					{ /* Write the escape code */
						t_1[ti_1]='\0';
						ti_1=0;
						printf("\033[?%s%c", t_1, *ptr);	
						fflush(stdout);
						t_1[0]='\0';
						state=NORMAL;
					}
					break;
			default:	state=NORMAL;	/* Say what?? */
		}
	}
	return(i);
}
					

/* Add a character to the requested screen, updating screen position */
void vt_addch(win, ch)
int win;			/* The window; 0 if top, 1 if bottom */
char ch;
{
	pos *cursor;
	int *ll;

	/* Set the proper current window */
	if ( win == 0 )
	{
		cursor=(&c_a);
		ll=(&ll_a);
	}
	else
	{
		cursor=(&c_b);
		ll=(&ll_b);
	}

	switch (ch)
	{
		case '\n':	if ( cursor->x != *ll )
					++(cursor->x);
				write(1, "\n", 1);
				break;
		case '\r':	cursor->y = 1;
				write(1, "\r", 1);
				break;
		case '\b':	do_vtesc(win, 'D');	/* Move left */
				break;
		default:	if ( cursor->y > columns )	/* Wrap */
				{
					if ( cursor->x != *ll )
						++(cursor->x);
					write(1, "\r\n", 2);
					cursor->y=1;
				}
				else
					++(cursor->y);
				write(1, &ch, 1);
				break;
	}
	return;
}

					
/* This is the function that processes the vt100 escape codes */
static void do_vtesc(win, esc)
int win;			/* The window; 0 if top, 1 if bottom */
char esc;			/* The actual escape code */
{
	pos *cursor;
	int i, *ul, *ll;
	int *scroll_1, *scroll_2;
	int count1, count2;

	/* Set the proper current window */
	if ( win == 0 )
	{
		cursor=(&c_a);
		ul=(&ul_a);
		ll=(&ll_a);
		scroll_1=(&scroll_a1);
		scroll_2=(&scroll_a2);
	}
	else
	{
		cursor=(&c_b);
		ul=(&ul_b);
		ll=(&ll_b);
		scroll_1=(&scroll_a1);
		scroll_2=(&scroll_a2);
	}

	/* Set the numeric parameters */
	count1=( *t_1 ? atoi(t_1) : 0 );
	count2=( *t_2 ? atoi(t_2) : 0 );
	t_1[0]='\0'; t_2[0]='\0';
	
	/* Execute the escape */
	switch (esc)
	{
		/* Go to position */
		case 'H':	if ( count1 == 0 )
					count1=1;
				if ( count2 == 0 )
					count2=1;
				cursor->x=((*ul)+(count1-1));
				cursor->y=count2;
				if ( cursor->x < *ul )
					cursor->x=(*ul);
				else if ( cursor->x > *ll )
					cursor->x=(*ll);
				if ( cursor->y > columns )
					cursor->y=columns;
				else if ( cursor->y < 1 )
					cursor->y=1;
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow up */
		case 'A':	if ( count1 == 0 )
					count1=1;
				cursor->x=((cursor->x)-(count1));
				if ( cursor->x < *ul )
					cursor->x=(*ul);
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow down */
		case 'B':	if ( count1 == 0 )
					count1=1;
				cursor->x=((cursor->x)+(count1));
				if ( cursor->x > *ll )
					cursor->x=(*ll);
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow right */
		case 'C':	if ( count1 == 0 )
					count1=1;
				cursor->y=((cursor->y)+(count1));
				if ( cursor->y > columns )
					cursor->y=columns;
				vt_goto(cursor->x, cursor->y);
				break;
		/* Arrow left */
		case 'D':	if ( count1 == 0 )
					count1=1;
				cursor->y=((cursor->y)-(count1));
				if ( cursor->y < 1 )
					cursor->y=1;
				vt_goto(cursor->x, cursor->y);
				break;
		/* Delete to the end of the line */
		case 'K':	printf("\033[K");
				fflush(stdout);
				break;
		/* Clear the screen.. Whew. */
		case 'J':	if ( count1 == 2 ) /* Clear whole screen */
				{
					save_cursor(win);
					vt_goto(*ul, 1);
					for ( i=(*ul); i<(*ll); ++i )
						printf("\033[K\033[B");
					fflush(stdout);
					restor_cursor(win);
				}
				else	/* Clear to the bottom */
				{
					save_cursor(win);
					for ( i=(cursor->x); i<(*ll); ++i )
						printf("\033[K\033[B");
					fflush(stdout);
					restor_cursor(win);
				}
				break;	
		/* Set a user defined scrolling region for the window */
		case 'r':	if ( ! count1 && ! count2 )
				{  /* Unset the user scrolling region */
					if ( *scroll_1 || *scroll_2 )
					{
						*scroll_1=0;
						*scroll_2=0;
						set_wscroll(win);
					}
					vt_goto(*ul, *ll);
					break;
				}	
				*scroll_1=(*ul+(count1 ? count1 : 1)-1);
				if ( *scroll_1 > *ll )
					*scroll_1=(*ll);
				*scroll_2=(*ul+(count2 ? count2 : 1)-1);
				if ( *scroll_2 > *ll )
					*scroll_2=(*ll);

				if ( (*scroll_1 == *ul && *scroll_2 == *ll) ||
				     (*scroll_1 > *scroll_2) )
				{  /* Invalid scroll region */
					if ( *scroll_1 || *scroll_2 )
					{
						*scroll_1=0;
						*scroll_2=0;
						set_wscroll(win);
					}
					vt_goto(*ul, *ll);
					break;
				}
				/* Yay! User scroll region! */
				set_uscroll(*scroll_1, *scroll_2);
				vt_goto(*ul, *ll);
				break;
		/* Set graphics color mode */
		case 'm':	if ( count2 > 0 )
					printf("\033[%d;%dm", count1, count2);
				else if ( count1 > 0 )
					printf("\033[%dm", count1 );
				else 
					printf("\033[m");
				fflush(stdout);
				break;
		default:	break;
	}
	count1=0; count2=0;
	return;
}


/* Clean up the screen and clear the scrolling regions */
void end_vt100()
{
	end_scroll();
	vt_goto(lines, 1);
	printf("\033[K");
	fflush(stdout);
}

/* Move the cursor to the current position on the requested window */
static int lastwin=(-1);	/* The last window we switched to */
void set_win(win)
int win;			/* The window; 0 if top, 1 if bottom */
{
	pos *cursor;
	int *scroll_1, *scroll_2;

	if ( lastwin != win || just_init )
	{
		lastwin=win;
		if ( win == 0 )
		{
			scroll_1=(&scroll_a1);
			scroll_2=(&scroll_a2);
			cursor=(&c_a);
		}
		else
		{
			scroll_1=(&scroll_b1);
			scroll_2=(&scroll_b2);
			cursor=(&c_b);
		}
		if ( *scroll_1 || *scroll_2 )		/* Set scrolling */
			set_uscroll(*scroll_1, *scroll_2);
		else
			set_wscroll(win);
		vt_goto(cursor->x, cursor->y);		/* Restore cursor */

		just_init=0;				/* Reset flag */
	}
}

static void vt_goto(line, col)
unsigned short line, col;
{
	printf("\033[%d;%dH", line, col);
	fflush(stdout);
}

/* Sets the cursor at the home position as a sideffect */
static void set_wscroll(win)
int win;		/* 0 for the top window, 1 for the bottom */
{
	if ( win == 0 )
		printf("\033[%d;%dr", ul_a, ll_a);
	else
		printf("\033[%d;%dr", ul_b, ll_b);
	fflush(stdout);
}

/* Sets the cursor at the home position as a sideffect */
static void set_uscroll(start, stop)
int start;		/* The top X coordinate */
int stop;		/* The bottom X coordinate */
{
	printf("\033[%d;%dr", start, stop);
	fflush(stdout);
}

/* Sets the cursor at the home position as a sideffect */
static void end_scroll()
{
	printf("\033[;r");
	fflush(stdout);
}

static pos s_a={0,0}, s_b={0,0};		/* Places to save cursor */
static void save_cursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	if ( win == 0 )
	{
		s_a.x=c_a.x;
		s_a.y=c_a.y;
	}
	else
	{
		s_b.x=c_b.x;
		s_b.y=c_b.y;
	}
}

static void restor_cursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	pos *cursor, *saved;

	if ( win == 0 )
	{
		cursor=(&c_a);
		saved=(&s_a);
	}
	else
	{
		cursor=(&c_b);
		saved=(&s_b);
	}
	if ( saved->x == 0 )
		return;		/* No previous cursor saved */

	cursor->x=saved->x;
	cursor->y=saved->y;
	vt_goto(cursor->x, cursor->y);
}

static pos us_a={0,0}, us_b={0,0};		/* Places to save cursor */
static void save_ucursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	if ( win == 0 )
	{
		us_a.x=c_a.x;
		us_a.y=c_a.y;
	}
	else
	{
		us_b.x=c_b.x;
		us_b.y=c_b.y;
	}
}

static void restor_ucursor(win)
int win;		/* 0 for top window, 1 for bottom window */
{
	pos *cursor, *saved;

	if ( win == 0 )
	{
		cursor=(&c_a);
		saved=(&us_a);
	}
	else
	{
		cursor=(&c_b);
		saved=(&us_b);
	}
	if ( saved->x == 0 )
		return;		/* No previous cursor saved */

	cursor->x=saved->x;
	cursor->y=saved->y;
	vt_goto(cursor->x, cursor->y);
}

static void pos_addch(position, ch)
pos *position;
char ch;
{
	vt_goto(position->x, position->y);
	write(1, &ch, 1);
}