[comp.os.minix] ANSI escape sequences

context@uw-june.UUCP (Ronald Blanford) (09/25/87)

This is a summary of responses received to my proposal of a week ago
to change the format of the Minix cursor positioning sequence.  The
essence of that proposal was:

> I would like to propose that the Minix cursor positioning escape
> sequence be changed from ESC x y to ESC y x.  This change would make
> it possible to add support for ANSI escape sequences which begin with
> ESC [. 

There were 9 responses.  I've summarized their comments and added my
own.

> Timothy L. Kay <tim@csvax.caltech.edu> expressed support for the
> proposed change. 

> Bill Gray <bgray@mu.edu>, Martin Minow <decvax!minow>, Gordon W. Ross
> <gwr%linus@mitre-bedford.ARPA>, and Thos Sumner <thos@cca.ucsf.edu>
> suggested eliminating the current scheme entirely in favor of ANSI
> sequences. 

My feeling is that Minix was intended to be an experimental/teaching
system, so that the exploration of alternatives to accepted practice
should be encouraged.  While the introduction of yet another system of
nonstandard terminal controls in the face of existing standards seems
to me to be counterproductive, I did not wish to usurp the
prerogatives of the original implementors by eliminating their method
altogether.  Therefore I proposed what seemed to be the minimal
modification to the existing method which would allow implementation
of the increased functionality and standardization of ANSI sequences. 

> Vincent P. Broman <broman@cod.nosc.mil> suggested changing the Minix
> row numbering in addition to the argument order. 

If support for ANSI sequences were to be added, programs wishing a
top-down row numbering scheme could choose the ANSI alternative.  To
modify the Minix interpretation of row numbers exceeds the demands of
minimal change. 

> Eric Gisin <egisin@orchid.waterloo.edu> points out that ESC ~ is also
> used (at least on DEC terminals) to map G1 into GR. 

> Andrew S. Tanenbaum <mcvax!cs.vu.nl!ast@uunet.UU.NET> supports a move
> to ANSI standards, but suggests modifying the current method to 
> ESC # x y. 

The ESC # prefix proposed by Tanenbaum is also currently in use by DEC
to generate double-width/height lines and alignment patterns.  My
proposal did not address these potential sources of conflict. 

> Kok Chen <sun!imagen!kchen> suggests the use of a special sequence to
> switch between Minix and ANSI modes. 

This would certainly take care of any possible conflicts between
native and ANSI escape sequences.  Unfortunately, such a switch
sequence would either require the programmer to be aware of the
environment in which the program is executing, or else require the
user to bracket the program with echo commands to switch the modes. 

Now, my conclusions and revised proposal:

None of the responses offered any support of the existing scheme.  My
own conclusion from these responses, especially Tanenbaum's, is that
the existing escape sequences were hacked together with minimal
forethought just to allow Mined to be ported.  I feel that the
experiment they represent has been adequately demonstrated, and that
there is no further reason not to adopt ANSI sequences as the native
mode. 

In fact there are great imperatives why the existing Minix sequences
should be eliminated.  First of all, the ESC z attribute sequence
represents a continued dependence on the IBM PC hardware
configuration.  How can this be condoned in an operating system that
supports remote terminals and runs on non-IBM 32000 and 68000 systems
as well? 

Secondly, the existing set is simply not powerful enough for many
applications.  Kok Chen indicated that he was writing an application
requiring insertion/deletion of characters and lines, and that it had
become necessary to bypass the tty driver and manipulate screen memory
directly, presumably through /dev/mem.  Rather than comment on his
solution, let's just say that it well illustrates the problem.  Any
effort spent to increase the power of expression would be best spent
in conjunction with standardization. 

Consequently, I have changed all programs currently using escape
sequences to conform to ANSI standards.  These are: mm/main.c,
mined1.c, clr.c, and more.c.

I have changed tty.c to recognize the ANSI equivalents in lieu of the
sequences which were previously implemented (position cursor, reverse
index, clear to end of screen, and set graphic rendition).  While I
spent some effort to make these changes easily extensible, I believe
that the current code for ANSI parsing and command execution will need
to be replaced when a full parser is implemented.  The current changes
will minimize and localize to the kernel the necessary modifications
when that time comes.  Eric Gisin has already supplied me with
skeleton code for a full parser, and Gordon Ross has offered to help
with the implementation. 

The following shell archive contains the diffs engendered by these
changes.  The diffs are relative to the 1.2 release. 

------------------------- CUT HERE ----------------------------------
echo x - clr.dif
gres '^X' '' > clr.dif << '/'
X7,9c7,9
X<   prints("\033 8\033~0");
X<   exit(0);
X< }
X---
X>   prints("\033[H\033[J");
X>   exit(0);
X> }
/
echo x - mined1.dif
gres '^X' '' > mined1.dif << '/'
X840,842c840,843
X<   string_print(pos_string);
X<   putchar(X_PLUS + nx);
X<   putchar(Y_PLUS + YMAX - ny);/* Driver has (0,0) at lower left corner */
X---
X>   char text_buffer[10];
X> 
X>   build_string(text_buffer, pos_string, ny+1, nx+1);
X>   string_print(text_buffer);
X1330,1334c1331,1335
X< char   *enter_string = "\033 8\033~0";	/* String printed on entering mined */
X< char   *pos_string = "\033";		/* Absolute cursor position */
X< char   *rev_scroll = "\033~1";		/* String for reverse scrolling */
X< char   *rev_video = "\033z\160";	/* String for starting reverse video */
X< char   *normal_video = "\033z\007";	/* String for leaving reverse video */
X---
X> char   *enter_string = "\033[H\033[J";	/* String printed on entering mined */
X> char   *pos_string = "\033[%d;%dH";	/* Absolute cursor position */
X> char   *rev_scroll = "\033M";		/* String for reverse scrolling */
X> char   *rev_video = "\033[7m";		/* String for starting reverse video */
X> char   *normal_video = "\033[m";	/* String for leaving reverse video */
/
echo x - mm_main.dif
gres '^X' '' > mm_main.dif << '/'
X165c165
X<   printf("%c 8%c~0",033, 033);	/* go to top of screen and clear screen */
X---
X>   printf("%c[H%c[J",033, 033);	/* go to top of screen and clear screen */
/
echo x - more.dif
gres '^X' '' > more.dif << '/'
X9,11c9,11
X< #define reverse()	write(1, "\033z\160", 3)	/* reverse video */
X< #define normal()	write(1, "\033z\7", 3)		/* undo reverse() */
X< #define clearln()	write(1, "\r\033~0", 4)		/* clear line */
X---
X> #define reverse()	write(1, "\033[7m", 4)		/* reverse video */
X> #define normal()	write(1, "\033[m", 3)		/* undo reverse() */
X> #define clearln()	write(1, "\r\033[J", 4)		/* clear line */
/
echo x - tty.dif
gres '^X' '' > tty.dif << '/'
X64a65
X> #define MAX_ESC_PARMS      2	/* number of escape sequence parameters allowed */
X96,97c97,100
X<   char tty_esc_state;		/* 0=normal, 1 = ESC seen, 2 = ESC + x seen */
X<   char tty_echar;		/* first character following an ESC */
X---
X>   char tty_esc_state;		/* 0=normal, 1=ESC, 2=ESC[ */
X>   char tty_esc_intro;		/* Distinguishing character following ESC */
X>   int tty_esc_parmv[MAX_ESC_PARMS];	/* list of escape parameters */
X>   int *tty_esc_parmp;		/* pointer to current escape parameter */
X104c107
X<   int tty_row;			/* current row (0 at bottom of screen) */
X---
X>   int tty_row;			/* current row (0 at top of screen) */
X957,967c960,963
X< /* Output a character on the console. Check for escape sequences, including
X<  *   ESC 32+x 32+y to move cursor to (x, y)
X<  *   ESC ~ 0       to clear from cursor to end of screen
X<  *   ESC ~ 1       to reverse scroll the screen 1 line
X<  *   ESC z x       to set the attribute byte to x (z is a literal here)
X<  */
X< 
X<   /* Check to see if we are part way through an escape sequence. */
X<   if (tp->tty_esc_state == 1) {
X< 	tp->tty_echar = c;
X< 	tp->tty_esc_state = 2;
X---
X> /* Output a character on the console.  Check for escape sequences first. */
X> 
X>   if (tp->tty_esc_state > 0) {
X> 	parse_escape(tp, c);
X971,976d966
X<   if (tp->tty_esc_state == 2) {
X< 	escape(tp, tp->tty_echar, c);
X< 	tp->tty_esc_state = 0;
X< 	return;
X<   }
X< 
X984c974
X< 		move_to(tp, tp->tty_column, tp->tty_row + 1);
X---
X> 		move_to(tp, tp->tty_column, tp->tty_row - 1);
X1001,1004c991,994
X< 		if (tp->tty_row == 0) 
X< 			scroll_screen(tp, GO_FORWARD);
X< 		else
X< 			tp->tty_row--;
X---
X> 		if (tp->tty_row == SCR_LINES-1) 
X> 			scroll_screen(tp, GO_FORWARD);
X> 		else
X> 			tp->tty_row++;
X1030c1020
X< 		tp->tty_ramqueue[tp->tty_rwords++] = tp->tty_attribute | c;
X---
X> 		tp->tty_ramqueue[tp->tty_rwords++] = tp->tty_attribute | (c & BYTE);
X1083c1073
X< int y;				/* row (0 <= y <= 24, 0 at bottom) */
X---
X> int y;				/* row (0 <= y <= 24, 0 at top) */
X1091c1081
X<   tp->tty_vid = (tp->tty_org + 2*(SCR_LINES-1-y)*LINE_WIDTH + 2*x);
X---
X>   tp->tty_vid = (tp->tty_org + 2*y*LINE_WIDTH + 2*x);
X1099,1135c1089,1199
X< PRIVATE escape(tp, x, y)
X< register struct tty_struct *tp;	/* pointer to tty struct */
X< char x;				/* escape sequence is ESC x y; this is x */
X< char y;				/* escape sequence is ESC x y; this is y */
X< {
X< /* Handle an escape sequence. */
X< 
X<   int n, ct, vx;
X< 
X< 
X<   /* Check for ESC z attribute - used to change attribute byte. */
X<   if (x == 'z') {
X< 	/* Set attribute byte */
X< 	tp->tty_attribute = y << 8;
X< 	return;
X<   }
X<   /* Check for ESC ~ n -  used for clear screen, reverse scroll. */
X<   if (x == '~') {
X< 	if (y == '0') {
X< 		/* Clear from cursor to end of screen */
X< 		n = 2 * LINE_WIDTH * (tp->tty_row + 1) - 2 * tp->tty_column;
X< 		vx = tp->tty_vid;
X< 		while (n > 0) {
X< 			ct = MIN(n, vid_retrace);
X< 			vid_copy(NIL_PTR, vid_base, vx, ct/2);
X< 			vx += ct;
X< 			n -= ct;
X< 		}
X< 	} else if (y == '1') {
X< 		/* Reverse scroll. */
X< 		scroll_screen(tp, GO_BACKWARD);
X< 	}
X< 	return;
X<   }
X< 
X<   /* Must be cursor movement (or invalid). */
X<   move_to(tp, x - 32, y - 32);
X---
X> 
X> PRIVATE parse_escape(tp, c)
X> register struct tty_struct *tp;	/* pointer to tty struct */
X> char c;				/* next character in escape sequence */
X> {
X> /* The following ANSI escape sequences are currently supported:
X>  *   ESC M         to reverse index the screen
X>  *   ESC [ y ; x H to move cursor to (x, y) [default (1,1)]
X>  *   ESC [ 0 J     to clear from cursor to end of screen
X>  *   ESC [ n m     to set the screen rendition
X>  *			n: 0 = normal [default]
X>  *			   7 = reverse
X>  */
X> 
X>   switch (tp->tty_esc_state) {
X> 	case 1: 		/* ESC seen */
X> 		tp->tty_esc_intro = '\0';
X> 		tp->tty_esc_parmp = tp->tty_esc_parmv;
X> 		tp->tty_esc_parmv[0] = tp->tty_esc_parmv[1] = 0;
X> 		switch (c) {
X> 		  case '[': 	/* Control Sequence Introducer */
X> 			tp->tty_esc_intro = c;
X> 			tp->tty_esc_state = 2; 
X> 			break;
X> 		  case 'M': 	/* Reverse Index */
X> 			do_escape(tp, c);
X> 			break;
X> 		  default: 
X> 			tp->tty_esc_state = 0; 
X> 			break;
X> 		}
X> 		break;
X> 
X> 	case 2: 		/* ESC [ seen */
X> 		if (c >= '0' && c <= '9') {
X> 			if (tp->tty_esc_parmp 
X> 					< tp->tty_esc_parmv + MAX_ESC_PARMS)
X> 				*tp->tty_esc_parmp =
X> 				  *tp->tty_esc_parmp * 10 + (c - '0');
X> 			break;
X> 		}
X> 		else if (c == ';') {
X> 			if (++tp->tty_esc_parmp 
X> 					< tp->tty_esc_parmv + MAX_ESC_PARMS)
X> 				*tp->tty_esc_parmp = 0;
X> 			break;
X> 		}
X> 		else {
X> 			do_escape(tp, c);
X> 		}
X> 		break;
X> 	default:		/* illegal state */
X> 		tp->tty_esc_state = 0;
X> 		break;
X>   }
X> }
X> 
X> PRIVATE do_escape(tp, c)
X> register struct tty_struct *tp;	/* pointer to tty struct */
X> char c;				/* next character in escape sequence */
X> {
X>   int n, ct, vx;
X> 
X>   /* Handle a sequence beginning with just ESC */
X>   if (tp->tty_esc_intro == '\0') {
X>     switch (c) {
X> 	case 'M':		/* Reverse Index */
X> 		if (tp->tty_row == 0)
X> 			scroll_screen(tp, GO_BACKWARD);
X> 		else
X> 			tp->tty_row--;
X> 		move_to(tp, tp->tty_column, tp->tty_row);
X> 		break;
X> 	default: break;
X>     }
X>   }
X>   else
X>   /* Handle a sequence beginning with ESC [ and parameters */
X>   if (tp->tty_esc_intro == '[') {
X>     switch (c) {
X> 	case 'H':		/* Position cursor */
X> 		move_to(tp, 
X> 			MAX(1, MIN(LINE_WIDTH, tp->tty_esc_parmv[1])) - 1,
X> 			MAX(1, MIN(SCR_LINES, tp->tty_esc_parmv[0])) - 1 );
X> 		break;
X> 	case 'J':		/* Clear from cursor to end of screen */
X> 		if (tp->tty_esc_parmv[0] == 0) {
X> 			n = 2 * ((SCR_LINES - (tp->tty_row + 1)) * LINE_WIDTH
X> 				+ LINE_WIDTH - (tp->tty_column + 1));
X> 			vx = tp->tty_vid;
X> 			while (n > 0) {
X> 				ct = MIN(n, vid_retrace);
X> 				vid_copy(NIL_PTR, vid_base, vx, ct/2);
X> 				vx += ct;
X> 				n -= ct;
X> 			}
X> 		}
X> 		break;
X> 	case 'm':		/* Set graphic rendition */
X> 		switch (tp->tty_esc_parmv[0]) {
X> 			case 7:	tp->tty_attribute = 0160 << 8;
X> 				break;
X> 			default: tp->tty_attribute = 0007 << 8;
X> 				break;
X> 		}
X> 		break;
X> 	default:
X> 		break;
X>     }
X>   }
X>   tp->tty_esc_state = 0;
X1234c1298
X<   move_to(&tty_struct[0], 0, 0);	/* move cursor to lower left corner */
X---
X>   move_to(&tty_struct[0], 0, SCR_LINES-1); /* move cursor to lower left corner */
/