/*======================================================================*\
|*		Editor mined						*|
|*		dialog prompt functions					*|
\*======================================================================*/

#include "mined.h"
#include "io.h"
#include "termprop.h"


FLAG char_on_status_line = False; /* is output active on status line ? */
FLAG input_active = False;


/*======================================================================*\
|*			Status and prompt buffer			*|
\*======================================================================*/

/* bottom status contents, kept static for redraw */
static char * bottom_inbuf;
static FLAG bottom_attrib;
static char bottom_status_buf [maxLINE_LEN];
static int bottom_begin_fmt;
static FLAG bottom_utf8_text;

static char * mnemo = NIL_PTR;


/*======================================================================*\
|*			Output functions for status line and prompt	*|
\*======================================================================*/

/* current position on status line */
int lpos = 0;	/* extern only for keyboard mapping menu */
/* Did output on the bottom line wrap and scroll? */
static FLAG pagewrapped = False;
static int bottom_lines = 0;


/**
   Print line-end mark.
 */
static
void
print_mark ()
{
	reverse_off ();
	mark_on ();
	if (cjk_term) {
		put_cjkchar (encodedchar (0x300A));	/* 《 */
		if (cjk_lineend_width == 2) {
			lpos ++;
		}
	} else {
		put_unichar ((character) RET_marker);
	}
	lpos ++;
	mark_off ();
	reverse_on ();
}

/**
   Print a byte to status line.
   Account for column position and possible line-wrap.
 */
static
void
print_byte (c)
  character c;
{
  char_on_status_line = True;

  if (lpos > XBREAK_bottom - 1) {
	putstring ("\r\n");
	lpos = 0;
	pagewrapped = True;
	bottom_lines ++;
  }

  if (c == '\n') {
	print_mark ();
  } else if (iscontrol (c)) {
	putchar ('^');
	lpos ++;
	put_unichar (controlchar (c));
	lpos ++;
  } else {
	putcharacter (c);
	lpos ++;
  }

  char_on_status_line = False;
}

/**
   Print a mapped character to status line.
   Account for column position and possible line-wrap.
   Only called if (mapped_text && ! combining_mode && encoding_has_combining ())
 */
static
void
print_mapped (c, char_begin, line_begin)
  character c;
  char * char_begin;
  char * line_begin;
{
  int width;
  unsigned long unichar = lookup_encodedchar (c);

  char_on_status_line = True;

/*
  This would be generic; for combining_mode == False it's just width = 1; ...

  if (iscombined (unichar, char_begin, line_begin)) {
	width = 0;
  } else {
	width = 1;
  }
*/
  width = 1;

  if (lpos > XBREAK_bottom - 2 + width) {
	putstring ("\r\n");
	lpos = 0;
	pagewrapped = True;
	bottom_lines ++;
  }

  if (c == '\n') {
	print_mark ();
  } else if (iscontrol (c)) {
	putchar ('^');
	lpos ++;
	put_unichar (controlchar (c));
	lpos ++;
  } else {
	if (! combining_mode && combining_screen && iscombined (unichar, char_begin, line_begin)) {
		/* enforce separated display */
		putcharacter (' ');
		putcharacter (c);
	} else {
		putcharacter (c);
	}

	lpos ++;
  }

  char_on_status_line = False;
}

/**
   Print a CJK character to status line.
   Line position and wrapping, as well as status line flag, are handled 
   in input_cjk.
 */
static
void
print_cjk (c, width, char_begin, line_begin)
  unsigned long c;
  int width;
  char * char_begin;
  char * line_begin;
{
  unsigned long unichar = lookup_encodedchar (c);

  if (encoding_has_combining () && iscombined (unichar, char_begin, line_begin)) {
	if (! combining_mode && combining_screen) {
		/* enforce separated display */
	/*	disp_separate_combining = True;	*/
	/*	combiningdisp_on ();		*/

		if (width == 2) {
			put_cjkchar (encodedchar (0x3000));
			put_cjkchar (c);
		} else {
			if (isjoined (unichar, char_begin, line_begin)) {
				/* prevent terminal ligature by 
				   substituting joining character 
				   with its ISOLATED FORM */
				unichar = isolated_alef (unichar);
				c = encodedchar (unichar);
				if (no_char (c)) {
					put_cjkchar (encodedchar (0x2135));
				} else {
					put_cjkchar (c);
				}
			} else {
				put_cjkchar (' ');
				put_cjkchar (c);
			}
		}
	} else if (! combining_screen) {
	/*	disp_separate_combining = True;	*/
	/*	combiningdisp_on ();		*/
		put_cjkchar (c);
	} else {
		put_cjkchar (c);
	}
  } else {
	put_cjkchar (c);
  }
}

/**
   Print a Unicode character to status line.
   Handle line position and wrapping, as well as status line flag.
 */
static
void
print_unichar (unichar, width, char_begin, line_begin)
  unsigned long unichar;
  int width;
  char * char_begin;
  char * line_begin;
{
	char_on_status_line = True;

	if (width < 0) {
		width = uniscrwidth (unichar, char_begin, line_begin);
	}

	if (lpos > XBREAK_bottom - 2 + width) {
		putstring ("\r\n");
		lpos = 0;
		pagewrapped = True;
		bottom_lines ++;
	}

	lpos += width;

	if (iscombined (unichar, char_begin, line_begin)) {
		if (! combining_mode && combining_screen) {
		/* enforce separated display */
		/*	disp_separate_combining = True;	*/
		/*	combiningdisp_on ();		*/

			if (iswide (unichar)) {
				put_unichar (0x3000);
				put_unichar (unichar);
			} else {
				if (isjoined (unichar, char_begin, line_begin)) {
					/* prevent terminal ligature by 
					   substituting joining character 
					   with its ISOLATED FORM */
					put_unichar (isolated_alef (unichar));
				} else {
					put_unichar (' ');
					put_unichar (unichar);
				}
			}
		} else if (! combining_screen) {
		/*	disp_separate_combining = True;	*/
		/*	combiningdisp_on ();		*/
			put_unichar (unichar);
		} else {
			put_unichar (unichar);
		}
	} else {
		put_unichar (unichar);
	}

	char_on_status_line = False;
}

/**
   Print a string to status line, limited by screen width if limit > 0.
   int limit: either 0 or XBREAK_bottom
   FLAG attributes = True enables formatting controls in text string:
			unicode indicator (cyan background)
			highlight (red background)
			reverse
			plain (normal)
 */
static
void
printlim_string (text, limit, attributes)
  char * text;
  int limit;
  FLAG attributes;
{
  int utfcount;
  unsigned long unichar;
  unsigned long cjkchar;
  int charwidth;
  char * start = text;

  char_on_status_line = True;
  while (* text != '\0') {
	if (text == mnemo) {
		diagdisp_off ();
	}

	if (utf8_text) {
		utf8_info (text, & utfcount, & unichar);
		charwidth = uniscrwidth (unichar, text, start);
	} else if (iscontrol ((character) * text)) {
		if (attributes && text >= start + bottom_begin_fmt && * text != '\n') {
			charwidth = 0;
		} else {
			charwidth = 2;
		}
	} else if (cjk_text) {
		cjkchar = charvalue (text);
		charwidth = cjkscrwidth (cjkchar, text, start);
	} else {
		charwidth = 1;
	}

	if (limit > 0 && lpos > limit - charwidth) {
		putmark (SHIFT_marker, UTF_SHIFT_marker);
		break;
	}
	if (lpos > XBREAK_bottom - charwidth) {
		putstring ("\r\n");
		lpos = 0;
		pagewrapped = True;
		bottom_lines ++;
	}

	if (* text == '\n') {
		print_mark ();	/* also incrementing lpos */
		text ++;
	} else if (iscontrol ((character) * text)) {
		if (attributes && text >= start + bottom_begin_fmt) {
			switch (* text) {
			case '':	unidisp_on ();
					break;
			case '':	emph_on ();
					menuheader_on ();
					break;
			case '':	disp_normal ();
					break;
			case '':	disp_normal ();
					diagdisp_on ();
					break;
			}
			text ++;
		} else {
			lpos += 2;
			putchar ('^');
			put_unichar (controlchar (* text ++));
		}
	} else if (utf8_text) {
		if (unichar == 0xA0 && attributes && text >= start + bottom_begin_fmt) {
			/* skip NO-BREAK SPACE separator */
		} else {
			/* print and increment lpos */
			print_unichar (unichar, charwidth, text, start);
		}
		text += utfcount;
	} else if (cjk_text) {
		/*int prev_lpos = lpos;*/
		advance_char_scr (& text, & lpos, start);
		/*put_cjkchar (cjkchar, lpos - prev_lpos);*/
		put_cjkchar (cjkchar);
	} else {
		lpos ++;
		putcharacter (* text ++);
	}
  }
  char_on_status_line = False;
}

/**
   Print a string to status line.
 */
static
void
print_string (text)
  register char * text;
{
  printlim_string (text, 0, False);
}


/*======================================================================*\
|*			Terminal dialog					*|
\*======================================================================*/

/*
 * promptyn reads in a 'y' or 'n' character.
 */
character
promptyn ()
{
  register character c;
  while ((c = readcharacter ()) != 'y' && c != 'n' && c != '\033' && ! quit) {
	ring_bell ();
	flush ();
  }
  if (c == '\033') {
	quit = True;
  }
  return c;
}

#ifdef UNUSED
/*
 * In case of a QUIT signal, swallow the dummy char generated by catchquit ()
 * called by re_search () and change ()
 */
void
swallow_dummy_quit_char ()
{
  (void) readcharacter (); /* Swallow away a quit character delivered by QUIT */
/* Not needed because this character is ignored by being the CANCEL command */
}
#endif


/*======================================================================*\
|*			Status line dialog and input			*|
\*======================================================================*/

/*
   Read numeric character value within prompt line.
 */
static
unsigned long
get_num_char (result, maxvalue)
  unsigned long * result;
  unsigned long maxvalue;
{
  unsigned long c;
  unsigned long unichar = 0;
  int base = 16;
  int i = 0;
  unsigned long maxfill = 0;
  FLAG uniinput = False;

  diagdisp_off ();

  while (maxfill < maxvalue && ! quit) {
	flush ();
	c = readcharacter ();

	if (i == 0 && c == '#') {
		base = 8;
	} else if (i == 0 && c == '=') {
		base = 10;
	} else if (i == 0 && (c == 'u' || c == 'U' || c == '+')) {
		uniinput = True;
		maxvalue = 0x10FFFF;
	} else if ((c == '\b' || c == '\177') && i > 0) {
		i --;
		unichar /= base;
		maxfill /= base;
		putstring ("\b \b");
	} else if (c >= '0' && c <= '9' && (base > 8 || c <= '7')) {
		i ++;
		unichar *= base;
		unichar += c - '0';
		maxfill = maxfill * base + base - 1;
		print_byte (c);
	} else if (base == 16 && c >= 'A' && c <= 'F') {
		i ++;
		unichar *= base;
		unichar += c - 'A' + 10;
		maxfill = maxfill * base + base - 1;
		print_byte (c);
	} else if (base == 16 && c >= 'a' && c <= 'f') {
		i ++;
		unichar *= base;
		unichar += c - 'a' + 10;
		maxfill = maxfill * base + base - 1;
		print_byte (c);
	} else if (c == ' ' || c == '\n' || c == '\r') {
		break;
	} else {
		quit = True;
		break;
	}
  }
  if (quit) {
	ring_bell ();
  }
  while (i > 0) {
	putstring ("\b \b");
	i --;
  }

  diagdisp_on ();
  if (uniinput && (cjk_text || mapped_text)) {
	* result = encodedchar (unichar);
  } else {
	* result = unichar;
  }
  return c;
}


/**
   Various auxiliary functions for input ().
 */
static
char *
input_byte (inbuf, ptr, c)
  char * inbuf;
  char * ptr;
  character c;
{
  if (c > '\0' && (ptr - inbuf) < maxLINE_LEN) {
	if (mapped_text && ! combining_mode && encoding_has_combining ()) {
		print_mapped (c, ptr, inbuf);
	} else {
		print_byte (c);
	}
	putstring (" \b");
	* ptr ++ = c;
	* ptr = '\0';
  } else {
	ring_bell ();
  }

  if (mapped_text && combining_mode && encoding_has_combining ()) {
	if (iscombining (lookup_encodedchar (c))) {
		/* refresh display from base char, needed on xterm */
		unsigned long uc;
		char * refreshptr = ptr;
		do {
			precede_char (& refreshptr, inbuf);
			c = charvalue (refreshptr);
			uc = lookup_encodedchar (c);
		} while (refreshptr != inbuf && iscombining (uc));
		if (! iscombining (uc)) {
			putstring ("\b");
		}
		/* Now refresh the whole combined character */
		while (refreshptr < ptr) {
			c = charvalue (refreshptr ++);
			put_unichar (lookup_encodedchar (c));
		}
	}
  }

  return ptr;
}

static
char *
input_cjk (inbuf, ptr, c)
  char * inbuf;
  char * ptr;
  unsigned long c;
{
  character cjkbytes [5];
  int len;
  int scrlen;
  character * cp;

	len = cjkencode (c, cjkbytes);
	if (len > 0 && (ptr - inbuf - 1 + len) < maxLINE_LEN) {
		scrlen = cjkscrwidth (c, ptr, inbuf);

		char_on_status_line = True;

		if (lpos > XBREAK_bottom - scrlen) {
			putstring ("\r\n");
			lpos = 0;
			pagewrapped = True;
			bottom_lines ++;
		}

		print_cjk (c, scrlen, ptr, inbuf);

		cp = cjkbytes;
		while (* cp != '\0') {
			* ptr ++ = * cp;
			cp ++;
		}
		* ptr = '\0';

		lpos += scrlen;

		char_on_status_line = False;
		putstring (" \b");
	} else {
		ring_bell ();
	}

	if (combining_mode && encoding_has_combining ()) {
		if (iscombining (lookup_encodedchar (c))) {
			/* refresh display from base char, needed on xterm */
			unsigned long uc;
			char * refreshptr = ptr;
			do {
				precede_char (& refreshptr, inbuf);
				c = charvalue (refreshptr);
				uc = lookup_encodedchar (c);
			} while (refreshptr != inbuf && iscombining (uc));
			if (! iscombining (uc)) {
				if (cjkscrwidth (c, refreshptr, inbuf) == 2) {
					putstring ("\b\b");
				} else {
					putstring ("\b");
				}
			}
			/* Now refresh the whole combined character */
			while (refreshptr < ptr) {
				c = charvalue (refreshptr);
				put_cjkchar (c);
				advance_char (& refreshptr);
			}
		}
	}

	return ptr;
}

static
char *
add_input_buf (ptr, c)
  char * ptr;
  character c;
{
  * ptr ++ = c;
  return ptr;
}

static
char *
input_unicode (inbuf, ptr, unichar)
  char * inbuf;
  char * ptr;
  unsigned long unichar;
{
  int utfcount;
  char * inputptr = ptr;

	if (unichar < 0x80) {
	    return input_byte (inbuf, ptr, unichar);
	} else if (unichar < 0x800) {
	    if ((ptr - inbuf) + 2 > maxLINE_LEN) {
		ring_bell ();
	    } else {
		print_unichar (unichar, -1, inputptr, inbuf);
		ptr = add_input_buf (ptr, 0xC0 | (unichar >> 6));
		ptr = add_input_buf (ptr, 0x80 | (unichar & 0x3F));
		* ptr = '\0';
	    }
	} else if (unichar < 0x10000) {
	    if ((ptr - inbuf) + 3 > maxLINE_LEN) {
		ring_bell ();
	    } else {
		print_unichar (unichar, -1, inputptr, inbuf);
		ptr = add_input_buf (ptr, 0xE0 | (unichar >> 12));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 6) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | (unichar & 0x3F));
		* ptr = '\0';
	    }
	} else if (unichar < 0x200000) {
	    if ((ptr - inbuf) + 4 > maxLINE_LEN) {
		ring_bell ();
	    } else {
		print_unichar (unichar, -1, inputptr, inbuf);
		ptr = add_input_buf (ptr, 0xF0 | (unichar >> 18));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 12) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 6) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | (unichar & 0x3F));
		* ptr = '\0';
	    }
	} else if (unichar < 0x4000000) {
	    if ((ptr - inbuf) + 5 > maxLINE_LEN) {
		ring_bell ();
	    } else {
		print_unichar (unichar, -1, inputptr, inbuf);
		ptr = add_input_buf (ptr, 0xF8 | (unichar >> 24));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 18) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 12) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 6) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | (unichar & 0x3F));
		* ptr = '\0';
	    }
	} else if (unichar < 0x80000000) {
	    if ((ptr - inbuf) + 6 > maxLINE_LEN) {
		ring_bell ();
	    } else {
		print_unichar (unichar, -1, inputptr, inbuf);
		ptr = add_input_buf (ptr, 0xFC | (unichar >> 30));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 24) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 18) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 12) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | ((unichar >> 6) & 0x3F));
		ptr = add_input_buf (ptr, 0x80 | (unichar & 0x3F));
		* ptr = '\0';
	    }
	} else {
		ring_bell ();
	}

	if (combining_mode) {
		if (iscombining (unichar)) {
			/* refresh display from base char, needed on xterm */
			do {
				precede_char (& inputptr, inbuf);
				utf8_info (inputptr, & utfcount, & unichar);
			} while (inputptr != inbuf && iscombining (unichar));
			if (! iscombining (unichar)) {
				if (iswide (unichar)) {
					putstring ("\b\b");
				} else {
					putstring ("\b");
				}
			}
			/* This also assumes utf8_screen == True 
			   because otherwise combining_mode would be False
			 */
			putstring (inputptr);
		}
	}

	putstring (" \b");
	return ptr;
}

static
char *
input_character (inbuf, ptr, c)
  char * inbuf;
  char * ptr;
  unsigned long c;
{
	if (no_char (c)) {
		ring_bell ();
		return ptr;
	} else if (c < 0x80) {
		return input_byte (inbuf, ptr, c);
	} else if (utf8_text) {
		return input_unicode (inbuf, ptr, c);
	} else if (cjk_text) {
		return input_cjk (inbuf, ptr, c);
	} else if (c < 0x100) {
		return input_byte (inbuf, ptr, c);
	} else {
		ring_bell ();
		return ptr;
	}
}

void
redraw_prompt ()
{
	set_cursor (0, - MENU);
	while (bottom_lines > 0) {
		scroll_reverse ();
		bottom_lines --;
	}
	if (MENU && ! can_add_line) {
		displaymenuline ();
	}
	rd_bottom_line ();
	if (can_clear_eol) {
		clear_eol ();
	}
}

static
char *
erase1 (inbuf, ptr, all_combined)
  char * inbuf;
  char * ptr;
  FLAG all_combined;
{
  FLAG need_refresh = False;

	diagdisp_off ();
	precede_char (& ptr, inbuf);
	if (iscontrol ((character) * ptr)) {
	    if (* ptr == '\n' && (cjk_lineend_width == 1 || ! cjk_term)) {
		/* single-width newline indication */
		putstring (" \b\b \b");
		lpos = lpos - 1;
	    } else {
		putstring (" \b\b\b  \b\b");
		lpos = lpos - 2;
	    }
	} else {
	    unsigned long charval = charvalue (ptr);
	    int charwidth;

	    if (combining_mode && encoding_has_combining ()) {
		if (all_combined) {
			while (ptr != inbuf && iscombined (unicode (charval), ptr, inbuf)) {
				precede_char (& ptr, inbuf);
				charval = charvalue (ptr);
			}
		} else if (iscombined (unicode (charval), ptr, inbuf)) {
			/* workaround for refresh of base character 
			   without or with fewer combining accents */
			need_refresh = True;
		}
	    }

	    if (utf8_text) {
		charwidth = uniscrwidth (charval, ptr, inbuf);
	    } else if (cjk_text) {
		charwidth = cjkscrwidth (charval, ptr, inbuf);
	    } else if (mapped_text && combining_mode && encoding_has_combining ()
			&& iscombined (lookup_encodedchar (charval), ptr, inbuf)) {
		charwidth = 0;
	    } else {
		charwidth = 1;
	    }

	    if (charwidth == 2) {
		putstring (" \b\b\b  \b\b");
		lpos = lpos - 2;
	    } else if (charwidth == 1) {
		putstring (" \b\b \b");
		lpos = lpos - 1;
	    }
	}
	diagdisp_on ();
	putstring (" \b");
	* ptr = '\0';

	if (need_refresh) {
		redraw_prompt ();
	}

	return ptr;
}

static
char *
input_prefix (inbuf, ptr, ps)
  char * inbuf;
  char * ptr;
  struct prefixspec * ps;
{
  unsigned long c = readcharacter_unicode_mapped ();

  if (command (c) == DPC) {
	if (mnemo == NIL_PTR ? ptr > inbuf : (char *) ptr > mnemo) {
		return erase1 (inbuf, ptr, False);
	} else {
		ring_bell ();
		return ptr;
	}
  } else if ((command (c) == CTRLINS && (c = readcharacter_unicode_mapped ()) == FUNcmd && (keyshift |= ctrl_mask))
	     || c == FUNcmd) {
	struct prefixspec * ps2 = lookup_prefix (keyproc, keyshift);
	if (ps2) {
		c = readcharacter_unicode_mapped ();
		if ((command (c) == CTRLINS && (c = readcharacter_unicode_mapped ()) == FUNcmd && (keyshift |= ctrl_mask))
		    || c == FUNcmd) {
			struct prefixspec * ps3 = lookup_prefix (keyproc, keyshift);
			if (ps3) {
				c = readcharacter_unicode_mapped ();
				c = compose_patterns (c, ps, ps2, ps3);
				return input_character (inbuf, ptr, c);
			} else {
				ring_bell ();
				return ptr;
			}
		} else {
			c = compose_patterns (c, ps, ps2, 0);
			return input_character (inbuf, ptr, c);
		}
	} else {
		ring_bell ();
		return ptr;
	}
  } else {
	c = compose_patterns (c, ps, 0, 0);
	return input_character (inbuf, ptr, c);
  }
}

static
voidfunc
command1 (c)
  unsigned long c;
{
  voidfunc c0 = command (c);
  if (c0 == F12 && (keyshift & altshift_mask) == alt_mask) {
	return toggleKEYMAP;
  } else if (c0 == F12 && (keyshift & ctrlshift_mask) == ctrl_mask) {
	return setupKEYMAP;
  } else {
	return c0;
  }
}

/**
   Read prompt input.
 * Input () reads a string and echoes it on the prompt line.
 * Return values:
 *	when QUIT character typed => ERRORS
 *	when empty input and clearfl == True: NO_INPUT
 *	otherwise: FINE
 */
static
int
input (inbuf, clearfl, term_input)
  char * inbuf;
  FLAG clearfl;
  char * term_input;
{
  char * ptr;
  unsigned long c;
  unsigned long mnemoc;
  char * term_chars;

  ptr = inbuf;
  * ptr = '\0';
  bottom_lines = 0;

  while (! quit) {
     if (lpos >= XBREAK_bottom) {
	pagewrapped = True;
     }
     if (lpos < 0) {
	redraw_prompt ();
     }

     while (True) {
	if (mnemo != NIL_PTR) {
		diagdisp_off ();
		putstring (" \b");
		flush ();
		c = readcharacter ();
	} else {
		flush ();
		c = readcharacter_mapped ();
	}
	if (no_char (c)) {
		ring_bell ();
	} else {
		break;
	}
     }

     /* special mapping */
     if (c == ' ' && (keyshift & ctrlshift_mask) == ctrlshift_mask) {
	c = 0xA0;	/* NO-BREAK SPACE */
     }

     if (command (c) == SNL || command (c) == Popmark) {
	c = '\r';
     }

     for (term_chars = term_input; * term_chars != '\0'; term_chars ++) {
	if (c == * term_chars) {
		c = '\n';
	}
     }

     if (mnemo != NIL_PTR &&
         (c == ' ' || c == '\n' || c == '\r' || c == quit_char || c == '\033')) {
	mnemoc = compose_mnemonic (mnemo);

	diagdisp_on ();

	/* restore prompt display, remove mnemonic */
	while ((char *) ptr > mnemo) {
		ptr = erase1 (inbuf, ptr, True);
	}

	mnemo = NIL_PTR;

	if (c != quit_char && c != '\033') {
		ptr = input_character (inbuf, ptr, mnemoc);
	}
     } else if (c == quit_char) {
	ring_bell ();
	quit = True;
     } else if (allow_keymap && command1 (c) == toggleKEYMAP) {
	reverse_off ();
	toggleKEYMAP ();
	if (flags_changed) {
		displayflags ();
	}
	redraw_prompt ();
     } else if (allow_keymap && command1 (c) == setupKEYMAP) {
	reverse_off ();
	setupKEYMAP ();
	if (flags_changed) {
		displayflags ();
	}
	redraw_prompt ();
     } else if (command (c) == DPC) {
	if (mnemo == NIL_PTR ? ptr > inbuf : (char *) ptr > mnemo) {
		ptr = erase1 (inbuf, ptr, ! (keyshift & ctrl_mask));
	} else {
		ring_bell ();
	}
     } else if (command (c) == CTRLINS) {
	c = readcharacter_unicode ();
	if (c == FUNcmd) {
		struct prefixspec * ps;
		keyshift |= ctrl_mask;
		ps = lookup_prefix (keyproc, keyshift);
		if (ps) {
			ptr = input_prefix (inbuf, ptr, ps);
		} else {
			ring_bell ();
		}
	} else if (c == '#') {
	    unsigned long cx;
	    do {
		cx = get_num_char (& c, max_char_value ());
		if (quit) {
			quit = False;
		} else {
			ptr = input_character (inbuf, ptr, c);
		}
	    } while (cx == ' ');
	} else if (c == ' ') {
		/* mark mnemonic start position 
		   in input buffer */
		mnemo = (char *) ptr;
		diagdisp_off ();
	} else if (c > ' ' && c != 0x7F) {
		c = compose (c, readcharacter_unicode ());
		if (no_char (c)) {
			ring_bell ();
		} else {
			ptr = input_character (inbuf, ptr, c);
		}
	} else {
		ptr = input_character (inbuf, ptr, c);
	}
     } else if (c == '\033' || command (c) == MOUSEescape) {
	quit = True;
     } else if (c == '\n' || c == '\r') {
	/* if inbuf is empty clear status line */
	return (ptr == inbuf && clearfl) ? NO_INPUT : FINE;
     } else if (c == FUNcmd) {
	struct prefixspec * ps = lookup_prefix (keyproc, keyshift);
	if (ps) {
		ptr = input_prefix (inbuf, ptr, ps);
	} else {
		ring_bell ();
	}
     } else {
	ptr = input_character (inbuf, ptr, c);
     }
  }
  quit = False;
  return ERRORS;
}

/*
 * Show concatenation of s1 and s2 on the status line (bottom of screen)
   Set stat_visible only if bottom_line is visible.
   The return value is FINE except for get_string, where it is taken 
   from the call to input ().
status_line (str1, str2) is (void) bottom_line (True, (str1), (str2), NIL_PTR, False, "")
status_msg (str)	 is status_line (str, NIL_PTR)
status_beg (str)	 is (void) bottom_line (True, (str), NIL_PTR, NIL_PTR, True, "")
error (str1)		 is (void) bottom_line (True, (str1), NIL_PTR, NIL_PTR, False, "")
error2 (str1, str2)	 is (void) bottom_line (True, (str1), (str2), NIL_PTR, False, "")
clear_status ()		 is (void) bottom_line (False, NIL_PTR, NIL_PTR, NIL_PTR, False, "")
get_string (str1, str2, fl, term_chars)
			 is bottom_line (True, (str1), NIL_PTR, (str2), fl, term_chars)
 */

void
status_uni (msg)
  char * msg;
{
  FLAG save_utf8_text = utf8_text;
  utf8_text = True;
  /* don't need to save and set utf16_file = False; */

  bottom_line (VALID, msg, NIL_PTR, NIL_PTR, False, "");

  utf8_text = save_utf8_text;
}

/**
   get_string_uni prompts a string like get_string_nokeymap
   but uses Unicode input
   BEWARE: conflicts with RDwin!
 */
int
get_string_uni (prompt, inbuf, statfl, term_input)
  char * prompt;
  char * inbuf;
  FLAG statfl;
  char * term_input;
{
  int ret;

  /* push text encoding, temp. set to utf8 */
  FLAG save_utf8_text = utf8_text;
  FLAG save_cjk_text = cjk_text;
  FLAG save_mapped_text = mapped_text;
  utf8_text = True;
  cjk_text = False;
  mapped_text = False;

  ret = get_string_nokeymap (prompt, inbuf, statfl, term_input);

  /* pop text encoding */
  utf8_text = save_utf8_text;
  cjk_text = save_cjk_text;
  mapped_text = save_mapped_text;

  return ret;
}

void
rd_bottom_line ()
{
  set_cursor (0, YMAX);
  diagdisp_on ();
  if (bottom_inbuf == NIL_PTR) {
	lpos = 0;
	if (bottom_utf8_text && ! utf8_text) {
		utf8_text = True;
		printlim_string (bottom_status_buf, XBREAK_bottom, bottom_attrib == VALID);
		utf8_text = False;
	} else {
		printlim_string (bottom_status_buf, XBREAK_bottom, bottom_attrib == VALID);
	}
  } else {
	lpos = 0;
	print_string (bottom_status_buf);
	print_string (bottom_inbuf);
  }
  if (! input_active) {
	diagdisp_off ();
	set_cursor_xy ();	/* Set cursor back to old position */
  }
  flush ();	/* Perform the actual screen output */
}

/**
   Clear or display status line, read prompt if desired.
   s1, s2: strings to display
   inbuf: buffer for prompt to read
   attrib: True: turn on reverse video on both strings
           False: don't (clear_status)
           VALID: interpret control chars in string for attributes
			(at beginning) suppress embedding spaces
		plus printlim_string controls:
			unicode indicator (cyan background)
			highlight (red background)
			reverse
			plain (normal)
 */
int
bottom_line (attrib, s1, s2, inbuf, statfl, term_input)
  FLAG attrib;
  char * s1;
  char * s2;
  char * inbuf;
  FLAG statfl;
  char * term_input;
{
  int ret = FINE;

  if (inbuf != NIL_PTR) {
	* inbuf = '\0';
  }

  if (pagewrapped) {
	bottom_status_buf [0] = '\0';
	RD ();
	pagewrapped = False;
  }

  /* set static status buffer (for redrawing):
     - input buffer
     - markup attributre
     - UTF-8 flag
     - buffer contents
     - begin of formatting marker
   */
  bottom_inbuf = inbuf;
  bottom_attrib = attrib;
  bottom_utf8_text = utf8_text;
  bottom_begin_fmt = 0;
  if (attrib == VALID && s1 && * s1 == '') {
	build_string (bottom_status_buf, "%s%s", unnull (s1), unnull (s2));
	if (s2 != NIL_PTR) {
		bottom_begin_fmt = strlen (unnull (s1));
	}
  } else {
	build_string (bottom_status_buf, " %s%s ", unnull (s1), unnull (s2));
	if (s2 != NIL_PTR) {
		bottom_begin_fmt = strlen (unnull (s1)) + 1;
	}
  }

  if (attrib != False && stat_visible) {
	set_cursor (0, YMAX);
	clear_lastline ();
  }

  set_cursor (0, YMAX);
  if (attrib != False) {	/* Print rev. start sequence */
	diagdisp_on ();
	stat_visible = True;
  } else {			/* Used as clear_status () */
	diagdisp_off ();
	stat_visible = False;
  }
  if (inbuf == NIL_PTR) {
	lpos = 0;
	printlim_string (bottom_status_buf, XBREAK_bottom, attrib == VALID);
  } else {
	lpos = 0;
	print_string (bottom_status_buf);
	input_active = True;
	ret = input (inbuf, statfl, term_input);
	input_active = False;
  }

  /* Print normal video */
  diagdisp_off ();
  /* set_cursor (lpos, YMAX); not needed; wouldn't work proportional */
  if (can_clear_eol) {
	clear_eol ();
  } else {
	put_blanks (XMAX - 1 - lpos);
	if (proportional) {
		set_cursor (0, YMAX);
		diagdisp_on ();
		if (bottom_inbuf == NIL_PTR) {
			lpos = 0;
			printlim_string (bottom_status_buf, XBREAK_bottom, attrib == VALID);
		} else {
			lpos = 0;
			print_string (bottom_status_buf);
			print_string (bottom_inbuf);
		}
		/* pagewrapped ? diagdisp_off ()? */
	} else {
		set_cursor (lpos, YMAX);
	}
  }

  if (inbuf != NIL_PTR) {
	set_cursor (0, YMAX);
  } else if (statfl) {
	diagdisp_on ();
  } else {
	set_cursor_xy ();	/* Set cursor back to old position */
  }

  flush ();	/* Perform the actual screen output */
  if (ret != FINE) {
	clear_status ();
  }
  return ret;
}

/*
 * Get_file () reads a filename from the terminal.
 */
int
get_filename (message, file)
  char * message;
  char * file;
{
  int ret = get_string (message, file, True, "");
#ifndef msdos
  char * filei;
  char file1 [maxLINE_LEN];

  if (file [0] == '~' && (file [1] == '/' || file [1] == '\0')) {
	filei = file; filei ++;
	build_string (file1, "%s%s", envvar ("HOME"), filei);
	strcpy (file, file1);
  }
#endif
  return ret;
}


/*======================================================================*\
|*				Number input				*|
\*======================================================================*/

/*
 * get_number () reads a number from the terminal.
 * prompt_num_char () prompts for a numeric character from the terminal.
 * If the parameter firstdigit is a valid digit, empty input (return) 
 * will yield a valid number; if it is '\0', it won't.
 * The last character typed in is returned.
 * ERRORS is returned on a bad number or on interrupted input.
 * The resulting number is put into the integer the argument points to.
 */
long
get_number (message, firstdigit, result)
  char * message;
  char firstdigit;
  int * result;
{
  long ch;
  int value;
  int i = 0;

  status_beg (message);

  if (firstdigit > '\0') {
	ch = firstdigit;
  } else {
	ch = readcharacter_mapped ();
  }

  if (ch == quit_char || ch == '\033') {
	quit = True;
  }
  if (! quit && (ch < '0' || ch > '9')) {
	error ("Bad number");
	return ERRORS;
  }

/* Convert input to a decimal number */
  value = 0;
  while (! quit && ((ch >= '0' && ch <= '9')
			   || ch == '\b' || ch == '\177')) {
	if (ch == '\b' || ch == '\177') {
		if (i > 0) {
			i --;
			value /= 10;
			putstring ("\b \b"); flush ();
			if (lpos >= XBREAK_bottom) {
				pagewrapped = True;
			}
			lpos --;
		}
	} else {
		i ++;
		print_byte (ch); flush ();
		if (lpos >= XBREAK_bottom) {
			pagewrapped = True;
		}
		value *= 10;
		value += ch - '0';
	}
	ch = readcharacter_mapped ();
	if (ch == quit_char || ch == '\033') {
		quit = True;
	}
  }

  clear_status ();
  if (quit) {
	clear_status ();
	return ERRORS;
  }
  * result = value;
  return ch;
}

/*
   Prompt for hex or octal character value.
 */
int
prompt_num_char (result, maxvalue)
  unsigned long * result;
  unsigned long maxvalue;
{
  unsigned long ch;
  int value = 0;
  int i;
  FLAG ok = False;
  FLAG uniinput = False;
  int base = 16;
  unsigned long maxfill = 0;

  if (cjk_text || mapped_text) {
	status_beg ("Enter character code value (hex / # oct / = dec / u unicode) ...");
  } else {
	status_beg ("Enter character code value (hex / # oct / = dec) ...");
  }

  i = 0;
  while (maxfill < maxvalue && ! ok) {
	ch = readcharacter ();
	if (i == 0) {
		if (ch == '#') {
			base = 8;
			print_string ("(oct) "); flush ();
			continue;
		} else if (ch == '=') {
			base = 10;
			print_string ("(dec) "); flush ();
			continue;
		} else if (ch == 'u' || ch == 'U' || ch == '+') {
			uniinput = True;
			maxvalue = 0x10FFFF;
			print_string ("(uni) "); flush ();
			continue;
		}
	}

	if (ch >= '0' && ch <= '9' && (ch <= '7' || base > 8)) {
		i ++;
		value *= base;
		value += ch - '0';
		maxfill = maxfill * base + base - 1;
		print_byte (ch); flush ();
	} else if (base == 16 && ch >= 'A' && ch <= 'F') {
		i ++;
		value *= base;
		value += ch - 'A' + 10;
		maxfill = maxfill * base + base - 1;
		print_byte (ch); flush ();
	} else if (base == 16 && ch >= 'a' && ch <= 'f') {
		i ++;
		value *= base;
		value += ch - 'a' + 10;
		maxfill = maxfill * base + base - 1;
		ch = ch - 'a' + 'A';
		print_byte (ch); flush ();
	} else if (ch == '\b' || ch == '\177') {
		if (i > 0) {
			i --;
			value /= base;
			maxfill /= base;
			putstring ("\b \b"); flush ();
			if (lpos >= XBREAK_bottom) {
				pagewrapped = True;
			}
			lpos --;
		}
	} else if (ch == '\033' || ch == quit_char) {
		clear_status ();
		return ERRORS;
	} else if (ch == ' ' || ch == '\n' || ch == '\r') {
		if (i == 0) {
			clear_status ();
			return ERRORS;
		}
		ok = True;
	} else {
		quit = True;
		ok = True;
	}

	if (lpos >= XBREAK_bottom) {
		pagewrapped = True;
	}
  }

  clear_status ();
  if (quit) {
	if (base == 16) {
		error ("Bad hex value");
	} else if (base == 8) {
		error ("Bad octal value");
	} else {
		error ("Bad decimal value");
	}
	quit = False;
	return ERRORS;
  }

  if (uniinput && (cjk_text || mapped_text)) {
	* result = encodedchar (value);
  } else {
	* result = value;
  }
  return ch;
}


/*======================================================================*\
|*				End					*|
\*======================================================================*/
