/*
** Copyright 2017-2024,2025 Thomas E. Dickey
** Copyright 1997-2012,2017 Free Software Foundation, Inc.
**
** This file is part of TACK.
**
** TACK is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, version 2.
**
** TACK 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.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with TACK; see the file COPYING.  If not, write to
** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
** Boston, MA 02110-1301, USA
*/

#include <tack.h>

MODULE_ID("$Id: ansi.c,v 1.23 2025/04/26 23:31:35 tom Exp $")

/*
 * Standalone tests for ANSI terminals.  Three entry points:
 * test_ansi_graphics(), test_ansi_reports() and test_ansi_sgr().
 */

/*****************************************************************************
 *
 * Test ANSI status reports
 *
 *****************************************************************************/

/* ASCII control characters */
#define A_DC1 0x11		/* Control Q */
#define A_DC3 0x13		/* Control S */
#define A_ESC 0x1b
#define A_DCS 0x90
#define A_CSI 0x9b
#define A_ST  0x9c

#define MAX_MODES 256

static char default_bank[] = "\033(B\017";
static int private_use, ape, terminal_class;
static short ansi_value[256];
static unsigned char ansi_buf[512], pack_buf[512];

struct ansi_reports {
    int lvl, final;
    const char *text;
    const char *request;
};

static struct ansi_reports report_list[] =
{
    {0, 'c', "(DA) Primary device attributes", "\033[0c"},
    {1, 0, "(DSR) Terminal status", "\033[5n"},
    {1, 'R', "(DSR) Cursor position", "\033[6n"},
    {62, 0, "(DA) Secondary device attributes", "\033[>0c"},
    {62, 0, "(DSR) Printer status", "\033[?15n"},
    {62, 0, "(DSR) Function key definition", "\033[?25n"},
    {62, 0, "(DSR) Keyboard language", "\033[?26n"},
    {63, 0, "(DECRQSS) Data destination", "\033P$q$}\033\\"},
    {63, 0, "(DECRQSS) Status line type", "\033P$q$~\033\\"},
    {63, 0, "(DECRQSS) Erase attribute", "\033P$q\"q\033\\"},
    {63, 0, "(DECRQSS) Personality", "\033P$q\"p\033\\"},
    {63, 0, "(DECRQSS) Top and bottom margins", "\033P$qr\033\\"},
    {63, 0, "(DECRQSS) Character attributes", "\033P$qm\033\\"},
    {63, 0, "(DECRQSS) Illegal request", "\033P$q@\033\\"},
    {63, 0, "(DECRQUPSS) User pref supplemental set", "\033[&u"},
    {63, 0, "(DECRQPSR) Cursor information", "\033[1$w"},
    {63, 0, "(DECRQPSR) Tab stop information", "\033[2$w"},
    {64, 0, "(DA) Tertiary device attributes", "\033[=0c"},
    {64, 0, "(DSR) Extended cursor position", "\033[?6n"},
    {64, 0, "(DSR) Macro space", "\033[?62n"},
    {64, 0, "(DSR) Memory checksum", "\033[?63n"},
    {64, 0, "(DSR) Data integrity", "\033[?75n"},
    {64, 0, "(DSR) Multiple session status", "\033[?85n"},
    {64, 0, "(DECRQSS) Attribute change extent", "\033P$q*x\033\\"},
    {64, 0, "(DECRQSS) Columns per page", "\033P$q$|\033\\"},
    {64, 0, "(DECRQSS) Lines per page", "\033P$qt\033\\"},
    {64, 0, "(DECRQSS) Lines per screen", "\033P$q*|\033\\"},
    {64, 0, "(DECRQSS) Left and right margins", "\033P$qs\033\\"},
    {64, 0, "(DECRQSS) Local functions", "\033P$q+q\033\\"},
    {64, 0, "(DECRQSS) Local function key control", "\033P$q=}\033\\"},
    {64, 0, "(DECRQSS) Select modifier key reporting", "\033P$q+r\033\\"},
    {64, 0, "(DECRQDE) Window report", "\033[\"v"},
    {0, 0, NULL, NULL}
};

struct request_control {
    const char *text;
    const char *expect;
    const char *request;
    const char *set_mode;
    const char *reset_mode;
};

/* Request control function selection or setting */
static const struct request_control rqss[] =
{
    {"Data sent to screen", "0", "$}", "\033[0$}", NULL},
    {"Data sent to disabled status line", "0", "$}", NULL, NULL},
    {"\033[0$~\033[1$}", "\033[0$}", NULL, NULL, NULL},
    {"Data sent to enabled status line", "1", "$}", NULL, NULL},
    {"\033[2$~\033[1$}", "\033[0$}", NULL, NULL, NULL},
    {"Disable status line", "0", "$~", "\033[0$~", NULL},
    {"Top status line", "1", "$~", "\033[1$~", NULL},
    {"Bottom status line", "2", "$~", "\033[2$~", NULL},
    {"Erasable character", "0", "\"q", "\033[0\"q", NULL},
    {"Nonerasable character", "1", "\"q", "\033[1\"q", "\033[0\"q"},
    {"Top and bottom margins", "3;10", "r", "\0337\033[3;10r", NULL},
    {"\033[r\0338", NULL, NULL, NULL, NULL},
    {"Top and bottom margins", "default", "r", "\0337\033[r", "\0338"},
    {"Character attributes, dim, bold", "1", "m", "\033[2;1m", "\033[m"},
    {"Character attributes, bold, dim", "2", "m", "\033[1;2m", "\033[m"},
    {"Character attributes, under, rev", "4;7", "m", "\033[4;7m", "\033[m"},
    {"Character attributes, color", "35;42", "m", "\033[35;42m", "\033[m"},
    {"All character attributes", "", "m", "\033[1;2;3;4;5;6;7;8;9m", NULL},
    {"\033[m", NULL, NULL, NULL, NULL},
    {NULL, NULL, NULL, NULL, NULL}
};

/*
**	read_ansi()
**
**	read an ANSI status report from terminal
*/
static void
read_ansi(void)
{
    int ch, i, j, last_escape;

    fflush(stdout);
    read_key((char *) ansi_buf, sizeof(ansi_buf));
    /* Throw away control characters inside CSI sequences.
       Convert two character 7-bit sequences into 8-bit sequences. */
    for (i = j = last_escape = 0; (ch = ansi_buf[i]) != 0; i++) {
	if (ch == A_ESC) {
	    if (last_escape == A_ESC) {
		pack_buf[j++] = A_ESC;
	    }
	    last_escape = A_ESC;
	} else if (last_escape == A_ESC && ch >= '@' && ch <= '_') {
	    last_escape = ch + 0x40;
	    pack_buf[j++] = (unsigned char) last_escape;
	} else if (last_escape != A_CSI || (ch > 0x20 && ch != 0x80)) {
	    if (last_escape == A_ESC) {
		pack_buf[j++] = A_ESC;
	    }
	    if (ch > 0x80 && ch < 0xa0) {
		last_escape = ch;
	    }
	    pack_buf[j++] = (unsigned char) ch;
	}
    }
    if (last_escape == A_ESC) {
	pack_buf[j++] = A_ESC;
    }
    pack_buf[j] = '\0';
    return;
}

/*
**	valid_mode(expected)
**
**	read a terminal mode status report and parse the result
**	Return TRUE if we got the expected terminating character.
*/
static int
valid_mode(int expected)
{
    unsigned char *s;
    int ch, terminator;

    read_ansi();

    ape = 0;
    ch = UChar(pack_buf[0]);
    ansi_value[0] = 0;
    if (ch != A_CSI && ch != A_DCS)
	return FALSE;

    s = pack_buf + 1;
    private_use = 0;
    if ((*s >= '<') & (*s <= '?')) {
	private_use = *s++;
    }
    terminator = 0;
    for (; (ch = *s); s++) {
	if (ch >= '0' && ch <= '9')
	    ansi_value[ape] = (short) (ansi_value[ape] * 10 + ch - '0');
	else if (ch == ';' || ch == ':')
	    ansi_value[++ape] = 0;
	else if (ch >= '<' && ch <= '?')
	    private_use = ch;
	else if (ch >= ' ')
	    terminator = (terminator << 8) | ch;
	else
	    break;
    }
    return terminator == expected;
}

/*
**	read_reports()
**
**	read all the reports in the ANSI report structure
*/
static int
read_reports(void)
{
    int i, tc, lc;

    lc = 5;
    terminal_class = tc = 0;
    for (i = 0; report_list[i].text; i++, lc++) {
	int j, vcr;
	char *s;

	if (terminal_class < report_list[i].lvl &&
	    tc < report_list[i].lvl) {
	    put_crlf();
	    menu_prompt();
	    ptext("/status [q] > ");
	    j = wait_here();
	    if (j != 'n' && j != 'N')
		return 0;
	    tc = report_list[i].lvl;
	    lc = 1;
	} else if (lc + 2 >= lines) {
	    put_crlf();
	    ptext("Hit any key to continue ");
	    (void) wait_here();
	    lc = 1;
	}
	sprintf(temp, "%s (%s) ", report_list[i].text,
		expand_command(report_list[i].request));
	ptext(temp);
	for (j = (int) strlen(temp); j < 49; j++)
	    putchp(' ');
	tc_putp(report_list[i].request);
	vcr = 0;
	if (report_list[i].final == 0) {
	    read_ansi();
	} else if (valid_mode(report_list[i].final))
	    switch (report_list[i].final) {
	    case 'c':
		terminal_class = ansi_value[0];
		break;
	    case 'R':
		vcr = TRUE;
		break;
	    }
	j = UChar(pack_buf[0]);
	if (j != A_CSI && j != A_DCS) {
	    const char *t;

	    put_crlf();
	    t = "*** The above request gives illegal response ***";
	    ptext(t);
	    for (j = (int) strlen(t); j < 49; j++)
		putchp(' ');
	}
	s = expand((const char *) ansi_buf);
	if (char_count + expand_chars >= columns) {
	    put_str("\r\n        ");
	    lc++;
	}
	putln(s);
	if (vcr) {		/* find out how big the screen is */
	    int k;

	    tc_putp(report_list[i].request);
	    if (!valid_mode('R'))
		continue;
	    j = ansi_value[0];
	    k = ansi_value[1];
	    tc_putp("\033[255B\033[255C\033[6n");
	    if (!valid_mode('R'))
		continue;
	    sprintf(temp, "\033[%d;%dH", j, k);
	    tc_putp(temp);
	    ptext("(DSR) Screen size (CSI 6 n)");
	    for (j = char_count; j < 50; j++)
		putchp(' ');
	    sprintf(temp, "%d x %d", ansi_value[1], ansi_value[0]);
	    ptextln(temp);

	}
    }
    menu_prompt();
    ptext("/status r->repeat test, <return> to continue > ");
    return wait_here();
}

/*
**	request_cfss()
**
**	Request Control function selection or settings
*/
static int
request_cfss(void)
{
    int i, j, k, l, ch;
    char *s;

    put_clear();
    ptextln("Request                         Expected  Received");
    put_crlf();
    for (i = 0; rqss[i].text; i++) {
	ptext(rqss[i].text);
	if (rqss[i].expect == NULL)
	    continue;
	j = (int) (strlen(rqss[i].text) + strlen(rqss[i].expect));
	putchp(' ');
	for (j++; j < 40; j++)
	    putchp(' ');
	ptext(rqss[i].expect);
	putchp(' ');
	tc_putp(rqss[i].set_mode);
	sprintf(temp, "\033P$q%s\033\\", rqss[i].request);
	tc_putp(temp);
	read_ansi();
	tc_putp(rqss[i].reset_mode);
	putchp(' ');
	for (j = 0; ansi_buf[j]; j++) {
	    if (ansi_buf[j] == 'r') {
		for (k = j++; (ch = UChar(ansi_buf[k])) != 0; k++)
		    if (ch == A_ESC) {
			break;
		    } else if (ch == A_ST) {
			break;
		    }
		ansi_buf[k] = '\0';
		s = expand((const char *) &ansi_buf[j]);
		if (char_count + expand_chars >= columns)
		    put_str("\r\n        ");
		put_str(s);
	    }
	}
	put_crlf();
    }
    /* calculate the valid attributes */
    ptext("Valid attributes:         0");
    j = 0;
    for (i = 1; i < 20; i++) {
	sprintf(temp, "\033[0;%dm\033P$qm\033\\", i);
	tc_putp(temp);
	(void) valid_mode('m');
	if (ape > 0) {
	    j = i;
	    sprintf(temp, "\033[0m; %d", i);
	    tc_putp(temp);
	}
    }
    put_crlf();
    /* calculate how many parameters can be sent */
    ptext("Max number of parameters: ");
    sprintf(temp, "%dm\033P$qm\033\\", j);
    l = -1;
    if (j > 0)
	for (l = 1; l < 33; l++) {
	    tc_putp("\033[0");
	    for (ch = 1; ch <= l; ch++)
		put_this(';');
	    tc_putp(temp);
	    (void) valid_mode('m');
	    if (ape == 0)
		break;
	}
    tc_putp("\033[m");
    if (l >= 0) {
	sprintf(temp, "%d", l);
	ptext(temp);
    } else
	ptext("unknown");
    put_crlf();
    return wait_here();
}

/*
**	mode_display(puc, mode, initial, set, reset)
**
**	print the mode display entry
*/
static void
mode_display(const char *p, int n, int c, int s, int r)
{
    int k;

    sprintf(temp, "%s%d (%c, %c, %c)", p, n, c, s, r);
    k = (int) strlen(temp);
    if (char_count + k >= columns)
	put_crlf();
    for (; k < 14; k++)
	putchp(' ');
    put_str(temp);
}

/*
**	terminal_state()
**
**	test DECRQM status reports
*/
static void
terminal_state(void)
{
    int modes_found;
    char tms[256];

    ptext("Testing terminal mode status. (CSI 0 $ p)");
    tc_putp("\033[0$p");
    modes_found = 0;
    tms[0] = '\0';

    if (valid_mode(('$' << 8) | 'y')) {
	static const char *puc[] =
	{"", "<", "=", ">", "?", NULL};

	int i, j, k, l;
	char *s;
	char buf[256];

	int mode_puc[MAX_MODES];
	int mode_number[MAX_MODES];

	char set_value[MAX_MODES];
	char reset_value[MAX_MODES];
	char current_value[MAX_MODES];

	for (i = 0; puc[i]; i++) {
	    put_crlf();
	    if (i) {
		sprintf(temp, "Private use: %c", puc[i][0]);
	    } else {
		strcpy(temp, "Standard modes:");
	    }
	    k = (int) strlen(temp);
	    ptext(temp);
	    for (j = 0; j < (int) sizeof(buf); buf[j++] = ' ') ;
	    for (j = l = 0; j < 255 && j - l < 50; j++) {
		sprintf(temp, "\033[%s%d$p", puc[i], j);
		tc_putp(temp);
		if (!valid_mode(('$' << 8) | 'y')) {
		    /* not valid, save terminating value */
		    s = expand((const char *) ansi_buf);
		    sprintf(tms + strlen(tms), "%s%d %s  ",
			    puc[i], j, s);
		    break;
		}
		if (private_use != puc[i][0])
		    break;
		if (ansi_value[0] != j)
		    break;
		if (ansi_value[1]) {
		    l = j;
		    if (k > 70) {
			buf[k] = '\0';
			put_crlf();
			ptextln(buf);
			for (k = 0; k < (int) sizeof(buf);) {
			    buf[k++] = ' ';
			}
			k = 0;
		    }
		    sprintf(temp, " %d", j);
		    ptext(temp);
		    k += (int) strlen(temp);
		    buf[k - 1] = (char) (ansi_value[1] + '0');
		    if (modes_found >= MAX_MODES)
			continue;
		    current_value[modes_found] =
			(char) (ansi_value[1] + '0');
		    /* some modes never return */
		    if ((i == 0 && j == 13)	/* control execution */
			||(puc[i][0] == '?' && j == 2))		/* VT52 */
			set_value[modes_found] =
			    reset_value[modes_found] = '-';
		    else
			set_value[modes_found] =
			    reset_value[modes_found] = ' ';
		    mode_puc[modes_found] = i;
		    mode_number[modes_found++] = j;
		}
	    }
	    buf[k] = '\0';
	    if (buf[k - 1] != ' ') {
		put_crlf();
		ptext(buf);
	    }
	}

	if ((i = modes_found) != 0) {
	    put_crlf();
	    put_crlf();
	    if (tms[0]) {
		ptextln(tms);
	    }
	    ptext("Hit 'Y' to test mode set/reset states: ");
	    i = wait_here();
	}
	if (i == 'y' || i == 'Y')
	    while (1) {
#ifdef STATUSFIX
		FILE *fp;

#ifdef TEDANSI
		fp = fopen("ted.ansi", "w");
#else
		fp = fopen("/dev/console", "w");
#endif
#endif
		for (i = j = 0; j < modes_found; j = ++i >> 1) {
		    if (set_value[j] == '-')
			continue;
		    k = (current_value[j] ^ i) & 1;
		    sprintf(temp, "\033[%s%d%c\033[%s%d$p",
			    puc[mode_puc[j]], mode_number[j],
			    k ? 'l' : 'h',
			    puc[mode_puc[j]], mode_number[j]);
#ifdef STATUSFIX
		    if (fp) {
			fprintf(fp, "%s\n", expand(temp));
			fflush(fp);
		    }
#endif
		    tc_putp(temp);
		    if (!valid_mode(('$' << 8) | 'y'))
			continue;
		    if (k) {
			reset_value[j] = (char) (ansi_value[1] + '0');
		    } else {
			set_value[j] = (char) (ansi_value[1] + '0');
		    }
		}
		put_str("\033[30l");	/* added for GORT bug
					   (WY-185) */
#ifdef STATUSFIX
		if (fp)
		    fclose(fp);
#endif
		tty_set();
		/* print the results */
		put_clear();
		putln("mode (initial, set, reset)");
		for (j = 0; j < modes_found; j++) {
		    mode_display(puc[mode_puc[j]], mode_number[j],
				 current_value[j], set_value[j], reset_value[j]);
		}
		ptext("\n\nHit 'R' to repeat test.  'S' to sort results: ");
		i = wait_here();
		if (i == 's' || i == 'S') {	/* print the same stuff,
						   sorted by
						   current_value */
		    put_crlf();
		    for (i = '1'; i <= '4'; i++) {
			for (j = 0; j < modes_found; j++) {
			    if (current_value[j] == i)
				mode_display(puc[mode_puc[j]],
					     mode_number[j], current_value[j],
					     set_value[j], reset_value[j]);
			}
		    }
		    ptext("\n\nHit 'R' to repeat test: ");
		    i = wait_here();
		}
		if (i != 'r' && i != 'R')
		    break;
		tty_raw(1, char_mask);
	    }
    } else {
	tty_set();
    }
}

/*
**	ansi_report_help()
**
**	Display the informational data for the ANSI report test.
*/
static void
ansi_report_help(void)
{
    ptext("Begin ANSI status report testing. ");
    ptext(" Parity bit set will be displayed in reverse video. ");
    ptext(" If the terminal hangs, hit any alphabetic key. ");
    ptextln(" Use n to continue testing.  Use q to quit.");
    put_crlf();
}

/*
**	test_ansi_reports()
**
**	Test the ANSI status report functions
*/
void
tools_status(
		TestList * t GCC_UNUSED,
		int *state GCC_UNUSED,
		int *ch)
{
    int i;

    put_clear();
    ansi_report_help();
    tty_raw(1, char_mask);

    do {
	i = read_reports();
	if (i != 'r' && i != 'R') {
	    *ch = i;
	    break;
	}
    } while (i);

    /* VT320, VT420, etc. */
    if (terminal_class >= 63) {
	do {
	    i = request_cfss();
	} while (i == 'r' || i == 'R');
	*ch = i;
	terminal_state();
    } else {
	tty_set();
    }
}

#define MAX_SGR 120
#define LEN_SGR 10
#define FMT_SGR "Mode %3d "

/*
**	display_sgr()
**
**	Test a range of ANSI sgr attributes
**	puc -> Private Use Character
*/
static void
display_sgr(int puc)
{
    int k;

    temp[0] = (char) puc;
    temp[1] = '\0';
    for (k = 0; k < MAX_SGR; k++) {
	if (char_count + LEN_SGR > 80)
	    put_crlf();
	else if (char_count + LEN_SGR > columns)
	    put_crlf();
	else if (k > 0)
	    printf(" ");
	printf("\033[%s%dm" FMT_SGR "\033[0m", temp, k, k);
	char_count += LEN_SGR;
	if (puc == '\0') {
	    if (k == 19)
		printf("\033[10m");
	    if (k == 39)
		printf("\033[37m");
	    if (k == 49)
		printf("\033[40m");
	}
    }
    put_crlf();
    if (puc == '<')
	printf("\033[<1m");
    else if (puc)
	printf("\033[%s0m", temp);
    set_attr(0);
}

/*
**	print_sgr20(on, off)
**
**	print the sgr line for sgr20()
*/
static void
print_sgr20(int on, int off)
{
    if (char_count > columns - 13) {
	put_crlf();
    } else if (char_count) {
	put_str("  ");
    }
    char_count += 11;
    printf("%d/%d \033[%dmon\033[%dm off\033[0m", on, off, on, off);
}

/*
**	sgr20(void)
**
**	display the enter/exit attributes 1-9 and 20-29
*/
static void
sgr20(void)
{
    int k;

    put_crlf();
    ptextln("Test enter/exit attributes 1-9 and 21-29.");
    for (k = 1; k < 10; k++) {
	print_sgr20(k, k + 20);
    }
    print_sgr20(1, 22);		/* bold */
    print_sgr20(2, 22);		/* dim */
    print_sgr20(8, 22);		/* blank */
    printf("\033[0m");
    set_attr(0);
}

/*
**	tools_sgr(testlist, state, ch)
**
**	Run the ANSI graphics rendition mode tool
**	Return the last character typed.
*/
void
tools_sgr(
	     TestList * t GCC_UNUSED,
	     int *state GCC_UNUSED,
	     int *ch)
{
    int k;

    put_clear();
    for (k = 0;;) {
	display_sgr(k);
	put_crlf();
	menu_prompt();
	ptext("/sgr Enter =><?r [<cr>] > ");
	k = wait_here();
	if ((k == 'r') || (k == 'R')) {
	    k = 0;
	} else if ((k < '<') || (k > '?')) {
	    break;
	}
    }
    sgr20();

    put_newlines(2);
    *ch = REQUEST_PROMPT;
}

/*****************************************************************************
 *
 * Test ANSI graphics
 *
 *****************************************************************************/
/*
**	select_bank(bank)
**
**	select a graphics character set for ANSI terminals
*/
static void
select_bank(const char *bank)
{
    tc_putp(bank);
    switch (bank[1] & 3) {
    case 0:
	putchp('O' & 0x1f);	/* control O */
	break;
    case 1:
	putchp('N' & 0x1f);	/* control N */
	tc_putp("\033~");
	break;
    case 2:
	tc_putp("\033n\033}");
	break;
    case 3:
	tc_putp("\033o\033|");
	break;
    }
}

/*
**	show_characters(bank, bias)
**
**	print the ANSI graphics characters
*/
static void
show_characters(char *bank, int bias)
{
    int i;

    sprintf(temp, "G%d G%c   ", bank[1] & 3, bias ? 'R' : 'L');
    ptext(temp);
    select_bank(bank);
    for (i = ' '; i < 0x80; i++) {
	if (char_count >= columns ||
	    (i != ' ' && (i & 31) == 0))
	    put_str("\n        ");
	putchp(i + bias);
    }
    select_bank(default_bank);
    put_str("   DEL <");
    select_bank(bank);
    putchp(0x7f + bias);
    select_bank(default_bank);
    putchp('>');
    put_crlf();
    put_crlf();
}

/* ANSI graphics test
        94     96   character sets
   G0   (      ,
   G1   )      -
   G2   *      .
   G3   +      /

Standard Definitions
   A    UK
   B    US ASCII

Dec extended definitions
   0    Special graphics

 */

/*
**	tools_charset(testlist, state, ch)
**
**	Run the ANSI alt-charset mode tool
*/
void
tools_charset(
		 TestList * t GCC_UNUSED,
		 int *state GCC_UNUSED,
		 int *chp GCC_UNUSED)
{
    int j, ch;
    char bank[32];

    put_clear();
    ptext("Enter the bank ()*+,-./ followed by the character set");
    ptext(" 0123456789:;<=>? for private use, and");
    ptextln(" @A...Z[\\]^_`a...z{|}~ for standard sets.");
    strcpy(bank, "\033)0");
    for (; bank[0];) {
	put_crlf();
	show_characters(bank, 0);

	/* G0 will not print in GR */
	if (bank[1] & 3) {
	    show_characters(bank, 0x80);
	}
	ptext("bank+set> ");
	for (j = 1; (ch = getchp(char_mask)); j++) {
	    if (ch == EOF)
		break;
	    putchp(ch);
	    if (j == 1 && ch > '/')
		j++;
	    bank[j] = (char) ch;
	    if (ch < ' ' || ch > '/')
		break;
	    if (j + 2 >= (int) sizeof(bank))
		break;
	}
	if (j == 1)
	    break;
	if (bank[j] < '0' || bank[j] > '~')
	    break;
	bank[j + 1] = '\0';
    }
    put_crlf();
}
