/* $LynxId: LYStrings.c,v 1.282 2023/10/23 23:41:44 tom Exp $ */
#include <HTUtils.h>
#include <HTCJK.h>
#include <UCAux.h>
#include <LYGlobalDefs.h>
#include <LYUtils.h>
#include <LYStrings.h>
#include <GridText.h>
#include <LYKeymap.h>
#include <LYClean.h>
#include <LYMail.h>
#include <LYNews.h>
#include <LYOptions.h>
#include <LYCharSets.h>
#include <HTAlert.h>
#include <HTString.h>
#include <LYCharUtils.h>
#include <HTList.h>
#include <HTParse.h>
#ifdef USE_MOUSE
#include <LYMainLoop.h>
#endif

#ifdef DJGPP_KEYHANDLER
#include <pc.h>
#include <keys.h>
#endif /* DJGPP_KEYHANDLER */

#ifdef USE_COLOR_STYLE
#include <LYHash.h>
#include <AttrList.h>
#endif

#ifdef USE_SCROLLBAR
#include <LYMainLoop.h>
#endif

#ifdef USE_CMD_LOGGING
#include <LYReadCFG.h>
#include <LYrcFile.h>
#endif

#include <LYShowInfo.h>
#include <LYLeaks.h>

#if defined(WIN_EX)
#undef  BUTTON_CTRL
#define BUTTON_CTRL	0	/* Quick hack */
#endif

#ifdef DEBUG_EDIT
#define CTRACE_EDIT(p) CTRACE(p)
#else
#define CTRACE_EDIT(p)		/*nothing */
#endif

#ifdef SUPPORT_MULTIBYTE_EDIT
#define IsWordChar(c) (isalnum(UCH(c)) || is8bits(c))
#else
#define IsWordChar(c) isalnum(UCH(c))
#endif

/*
 * The edit_history lists allow the user to press tab when entering URL to get
 * the closest match in the closet
 */
#define LYClosetSize 100

static HTList *URL_edit_history;
static HTList *MAIL_edit_history;

/* If you want to add mouse support for some new platform, it's fairly
 * simple to do.  Once you've determined the X and Y coordinates of
 * the mouse event, loop through the elements in the links[] array and
 * see if the coordinates fall within a highlighted link area.	If so,
 * the code must set mouse_link to the index of the chosen link,
 * and return a key value that corresponds to LYK_ACTIVATE.  The
 * LYK_ACTIVATE code in LYMainLoop.c will then check mouse_link
 * and activate that link.  If the mouse event didn't fall within a
 * link, the code should just set mouse_link to -1 and return -1. --AMK
 */

/* The number of the link selected w/ the mouse (-1 if none) */
static int mouse_link = -1;

static int have_levent;

#if defined(USE_MOUSE) && defined(NCURSES)
static MEVENT levent;
#endif

/* Return the value of mouse_link */
int peek_mouse_levent(void)
{
#if defined(USE_MOUSE) && defined(NCURSES)
    if (have_levent > 0) {
	ungetmouse(&levent);
	have_levent--;
	return 1;
    }
#endif
    return 0;
}

/* Return the value of mouse_link, erasing it */
int get_mouse_link(void)
{
    int t;

    t = mouse_link;
    mouse_link = -1;
    if (t < 0)
	t = -1;			/* Backward compatibility. */
    return t;
}

/* Return the value of mouse_link */
int peek_mouse_link(void)
{
    return mouse_link;
}

int fancy_mouse(WINDOW * win, int row,
		int *position)
{
    int cmd = LYK_DO_NOTHING;

#ifdef USE_MOUSE
/*********************************************************************/

#if defined(WIN_EX) && defined(PDCURSES)

    request_mouse_pos();

    if (BUTTON_STATUS(1)
	&& (MOUSE_X_POS >= getbegx(win) &&
	    MOUSE_X_POS < (getbegx(win) + getmaxx(win)))) {
	int mypos = MOUSE_Y_POS - getbegy(win);
	int delta = mypos - row;

	if (mypos + 1 == getmaxy(win)) {
	    /* At the decorative border: scroll forward */
	    if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED)
		cmd = LYK_END;
	    else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED)
		cmd = LYK_NEXT_PAGE;
	    else
		cmd = LYK_NEXT_LINK;
	} else if (mypos >= getmaxy(win)) {
	    if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED))
		cmd = LYK_END;
	    else
		cmd = LYK_NEXT_PAGE;
	} else if (mypos == 0) {
	    /* At the decorative border: scroll back */
	    if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED)
		cmd = LYK_HOME;
	    else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED)
		cmd = LYK_PREV_PAGE;
	    else
		cmd = LYK_PREV_LINK;
	} else if (mypos < 0) {
	    if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED))
		cmd = LYK_HOME;
	    else
		cmd = LYK_PREV_PAGE;
#ifdef KNOW_HOW_TO_TOGGLE
	} else if (BUTTON_STATUS(1) & (BUTTON_CTRL)) {
	    cur_selection += delta;
	    cmd = LYX_TOGGLE;
#endif
	} else if (BUTTON_STATUS(1) & (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL)) {
	    /* Probably some unrelated activity, such as selecting some text.
	     * Select, but do nothing else.
	     */
	    *position += delta;
	    cmd = -1;
	} else {
	    /* No scrolling or overflow checks necessary. */
	    *position += delta;
	    cmd = LYK_ACTIVATE;
	}
    } else if (BUTTON_STATUS(1) & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) {
	cmd = LYK_QUIT;
    }
#else
#if defined(NCURSES)
#define ButtonModifiers (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL)
    MEVENT event;

    getmouse(&event);
    if ((event.bstate & (BUTTON1_CLICKED |
			 BUTTON1_DOUBLE_CLICKED |
			 BUTTON1_TRIPLE_CLICKED))) {
	int mypos = event.y - getbegy(win);
	int delta = mypos - row;

	if ((event.x < getbegx(win) ||
	     event.x >= (getbegx(win) + getmaxx(win)))
	    && !(event.bstate & ButtonModifiers))
	    return LYK_QUIT;	/* User clicked outside, wants to quit? */
	if (mypos + 1 == getmaxy(win)) {
	    /* At the decorative border: scroll forward */
	    if (event.bstate & BUTTON1_TRIPLE_CLICKED)
		cmd = LYK_END;
	    else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
		cmd = LYK_NEXT_PAGE;
	    else
		cmd = LYK_NEXT_LINK;
	} else if (mypos >= getmaxy(win)) {
	    if (event.bstate & (BUTTON1_DOUBLE_CLICKED |
				BUTTON1_TRIPLE_CLICKED))
		cmd = LYK_END;
	    else
		cmd = LYK_NEXT_PAGE;
	} else if (mypos == 0) {
	    /* At the decorative border: scroll back */
	    if (event.bstate & BUTTON1_TRIPLE_CLICKED)
		cmd = LYK_HOME;
	    else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
		cmd = LYK_PREV_PAGE;
	    else
		cmd = LYK_PREV_LINK;
	} else if (mypos < 0) {
	    if (event.bstate & (BUTTON1_DOUBLE_CLICKED |
				BUTTON1_TRIPLE_CLICKED))
		cmd = LYK_HOME;
	    else
		cmd = LYK_PREV_PAGE;
#ifdef KNOW_HOW_TO_TOGGLE
	} else if (event.bstate & (BUTTON_CTRL)) {
	    cur_selection += delta;
	    cmd = LYX_TOGGLE;
#endif
	} else if (event.x <= getbegx(win) + 1 ||
		   event.x >= getbegx(win) + getmaxx(win) - 2) {
	    /* Click on left or right border for positioning without
	     * immediate action: select, but do nothing else.
	     * Actually, allow an error of one position inwards. - kw
	     */
	    *position += delta;
	    cmd = -1;
	} else if (event.bstate & ButtonModifiers) {
	    /* Probably some unrelated activity, such as selecting some text.
	     * Select, but do nothing else.
	     */
	    /* Possibly this is never returned by ncurses, so this case
	     * may be useless depending on situation (kind of mouse support
	     * and library versions). - kw
	     */
	    *position += delta;
	    cmd = -1;
	} else {
	    /* No scrolling or overflow checks necessary. */
	    *position += delta;
	    cmd = LYK_ACTIVATE;
	}
    } else if (event.bstate & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) {
	cmd = LYK_QUIT;
    }
#endif /* NCURSES */
#endif /* PDCURSES */

/************************************************************************/
#endif /* USE_MOUSE */
    (void) win;
    (void) row;
    (void) position;

    return cmd;
}

/*
 * Manage the collection of edit-histories
 */
static HTList *whichRecall(RecallType recall)
{
    HTList **list;

    switch (recall) {
    case RECALL_CMD:
	return LYcommandList();
    case RECALL_MAIL:
	list = &MAIL_edit_history;
	break;
    default:
	list = &URL_edit_history;
	break;
    }
    if (*list == 0)
	*list = HTList_new();
    return *list;
}

/*
 * Remove the oldest item in the closet
 */
static void LYRemoveFromCloset(HTList *list)
{
    void *data = HTList_removeFirstObject(list);

    if (data != 0)
	FREE(data);
}

void LYCloseCloset(RecallType recall)
{
    HTList *list = whichRecall(recall);

    while (!HTList_isEmpty(list)) {
	LYRemoveFromCloset(list);
    }
    HTList_delete(list);	/* should already be empty */
}

/*
 * Strategy:  We begin at the top and search downwards.  We return the first
 * match, i.e., the newest since we search from the top.  This should be made
 * more intelligent, but works for now.
 */
static char *LYFindInCloset(RecallType recall, char *base)
{
    HTList *list = whichRecall(recall);
    char *data;
    size_t len = strlen(base);

    while (!HTList_isEmpty(list)) {
	data = (char *) HTList_nextObject(list);
	if (data != NULL && !StrNCmp(base, data, len))
	    return (data);
    }

    return (0);
}

static void LYAddToCloset(RecallType recall, char *str)
{
    HTList *list = whichRecall(recall);
    char *data = NULL;

    StrAllocCopy(data, str);
    HTList_addObject(list, data);
    while (HTList_count(list) > LYClosetSize)
	LYRemoveFromCloset(list);
}

#ifdef USE_MOUSE
static int XYdist(int x1,
		  int y1,
		  int x2,
		  int y2,
		  int dx2)
{
    int xerr = 3 * (x2 - x1), yerr = 9 * (y2 - y1);

    if (xerr < 0)
	xerr = 3 * (x1 - x2 - dx2) + 1;		/* pos after string not really in it */
    if (xerr < 0)
	xerr = 0;
    if (yerr < 0)
	yerr = -yerr;
    if (!yerr)			/* same line is good */
	return (xerr > 0) ? (xerr * 2 - 1) : 0;
    if (xerr < 9 && yerr)	/* x-dist of 3 cell better than y-dist of 1 cell */
	yerr += (9 - xerr);
    return 2 * xerr + yerr;	/* Subjective factor; ratio -> approx. 6 / 9 */
/*
old: (IZ 1999-07-30)
 3  2  2  2  1  1  1 XX XX XX XX XX  0  1  1  1  2  2  2  3  3
 4\ 3  3  3  2  2  2  2  2  2  2  2  2  2  2  2  3  3  3/ 4  4
 5  4  4  4\ 3  3  3  3  3  3  3  3  3  3  3  3/ 4  4  4  5  5
 6  5  5  5  4  4  4  4  4  4  4  4  4  4  4  4  5  5  5  6  5
now: (kw 1999-10-23)
41 35 29|23 17 11  5 XX XX XX XX XX  1  7 13 19 25|31 37 43 49
   45 39 33\27 24 21 18 18 18 18 18 19 22 25 28/34 40 46 50
      48 42 36 33 30\27 27 27 27 27 28/31 34 37 43 49
         51 45 42 39 36 36 36 36 36 37 40 43 46 49
               51 48 45 45 45 45 45 46 49 52
*/
}

/* Given X and Y coordinates of a mouse event, set mouse_link to the
 * index of the corresponding hyperlink, or set mouse_link to -1 if no
 * link matches the event.  Returns -1 if no link matched the click,
 * or a keycode that must be returned from LYgetch() to activate the
 * link.
 */

static int set_clicked_link(int x,
			    int y,
			    int code,
			    int clicks)
{
    int left = 6;
    int right = LYcolLimit - 5;

    /* yes, I am assuming that my screen will be a certain width. */
    int i;
    int c = -1;

    if (y == (LYlines - 1) || y == 0) {		/* First or last row */
	/* XXXX In fact # is not always at x==0?  KANJI_CODE_OVERRIDE? */
	int toolbar = (y == 0 && HText_hasToolbar(HTMainText));

	mouse_link = -2;
	if (x == 0 && toolbar)	/* On '#' */
	    c = LAC_TO_LKC0(LYK_TOOLBAR);
#if defined(CAN_CUT_AND_PASTE) && defined(USE_COLOR_STYLE)
	else if (y == 0 && x == LYcolLimit && s_hot_paste != NOSTYLE)
	    c = LAC_TO_LKC0(LYK_PASTE_URL);
#endif
	else if (clicks > 1) {
	    if (x < left + toolbar)
		c = (code == FOR_PROMPT && y)
		    ? HOME_KEY : LAC_TO_LKC0(LYK_MAIN_MENU);
	    else if (x > right)
		c = (code == FOR_PROMPT && y)
		    ? END_KEY : LAC_TO_LKC0(LYK_VLINKS);
	    else if (y)		/* Last row */
		c = LAC_TO_LKC0(LYK_END);
	    else		/* First row */
		c = LAC_TO_LKC0(LYK_HOME);
	} else {
	    if (x < left + toolbar)
		c = (code == FOR_PROMPT && y)
		    ? LTARROW_KEY
		    : (
#ifdef USE_COLOR_STYLE
			  (s_forw_backw != NOSTYLE && x - toolbar >= 3)
			  ? LAC_TO_LKC0(LYK_NEXT_DOC)
			  : LAC_TO_LKC0(LYK_PREV_DOC)
#else
			  LAC_TO_LKC0(LYK_NEXT_DOC)
#endif
		    );
	    else if (x > right)
		c = (code == FOR_PROMPT && y)
		    ? RTARROW_KEY : LAC_TO_LKC0(LYK_HISTORY);
	    else if (y)		/* Last row */
		c = LAC_TO_LKC0(LYK_NEXT_PAGE);
	    else		/* First row */
		c = LAC_TO_LKC0(LYK_PREV_PAGE);
	}
#ifdef USE_SCROLLBAR
    } else if (x == (LYcols - 1) && LYShowScrollbar && LYsb_begin >= 0) {
	int h = display_lines - 2 * (LYsb_arrow != 0);

	mouse_link = -2;
	y -= 1 + (LYsb_arrow != 0);
	if (y < 0)
	    return LAC_TO_LKC0(LYK_UP_TWO);
	if (y >= h)
	    return LAC_TO_LKC0(LYK_DOWN_TWO);

	if (clicks >= 2) {
	    double frac = (1. * y) / (h - 1);
	    int l = HText_getNumOfLines() + 1;	/* NOL() off by one? */

	    l -= display_lines;
	    if (l > 0)
		LYSetNewline((int) (frac * l + 1 + 0.5));
	    return LYReverseKeymap(LYK_DO_NOTHING);
	}

	if (y < LYsb_begin)
	    return LAC_TO_LKC0(LYK_PREV_PAGE);
	if (y >= LYsb_end)
	    return LAC_TO_LKC0(LYK_NEXT_PAGE);
	mouse_link = -1;	/* No action in edit fields */
#endif
    } else {
	int mouse_err = 29, /* subjctv-dist better than this for approx stuff */ cur_err;

	/* Loop over the links and see if we can get a match */
	for (i = 0; i < nlinks; i++) {
	    int len, lx = links[i].lx, is_text = 0;
	    int count = 0;
	    const char *text = LYGetHiliteStr(i, count);

	    if (links[i].type == WWW_FORM_LINK_TYPE
		&& F_TEXTLIKE(links[i].l_form->type))
		is_text = 1;

	    /* Check the first line of the link */
	    if (text != NULL) {
		if (is_text)
		    len = links[i].l_form->size;
		else
		    len = (int) LYstrCells(text);
		cur_err = XYdist(x, y, links[i].lx, links[i].ly, len);
		/* Check the second line */
		while (cur_err > 0
		       && (text = LYGetHiliteStr(i, ++count)) != NULL) {
		    /* Note that there is at most one hightext if is_text */
		    int cur_err_2 = XYdist(x, y,
					   LYGetHilitePos(i, count),
					   links[i].ly + count,
					   (int) LYstrCells(text));

		    cur_err = HTMIN(cur_err, cur_err_2);
		}
		if (cur_err > 0 && is_text)
		    cur_err--;	/* a bit of preference for text fields,
				   enter field if hit exactly at end - kw */
		if (cur_err == 0) {
		    int cury, curx;

		    LYGetYX(cury, curx);
		    /* double-click, if we care:
		       submit text submit fields. - kw */
		    if (clicks > 1 && is_text &&
			links[i].l_form->type == F_TEXT_SUBMIT_TYPE) {
			if (code != FOR_INPUT
			/* submit current input field directly */
			    || !(cury == y &&
				 (curx >= lx) &&
				 ((curx - lx) <= len))) {
			    c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT);
			    mouse_link = i;
			} else {
			    c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT);
			    mouse_link = -1;
			}
			mouse_err = 0;
			break;
		    }
		    if (code != FOR_INPUT
		    /* Do not pick up the current input field */
			|| !((cury == y && (curx >= lx) && ((curx - lx) <= len)))) {
			if (is_text) {
			    have_levent = 1;
#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
			    if (x == links[i].lx && y == links[i].ly)
				textinput_redrawn = FALSE;
#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */
			}
			mouse_link = i;
		    } else
			mouse_link = -1;
		    mouse_err = 0;
		    break;
		} else if (cur_err < mouse_err) {
		    mouse_err = cur_err;
		    mouse_link = i;
		}
	    }
	}
	/*
	 * If a link was hit, we must look for a key which will activate
	 * LYK_ACTIVATE We expect to find LYK_ACTIVATE (it's usually mapped to
	 * the Enter key).
	 */
	if (mouse_link >= 0) {
	    if (mouse_err == 0) {
		if (c == -1)
		    c = LAC_TO_LKC0(LYK_ACTIVATE);
	    } else if (mouse_err >= 0)
		c = LAC_TO_LKC0(LYK_CHANGE_LINK);
	} else {
	    if (2 * y > LYlines) {	/* Bottom Half of the screen */
		if (4 * y < 3 * LYlines) {
		    c = LAC_TO_LKC0(LYK_DOWN_TWO);	/* Third quarter */
		} else
		    c = LAC_TO_LKC0(LYK_DOWN_HALF);	/* Fourth quarter */
	    } else {		/* Upper Half of the screen */
		if (4 * y < LYlines) {
		    c = LAC_TO_LKC0(LYK_UP_HALF);	/* First quarter */
		} else
		    c = LAC_TO_LKC0(LYK_UP_TWO);	/* Second quarter */
	    }
	}
    }
    return c;
}
#endif /* USE_MOUSE */

/*
 * LYstrncpy() ensures that the copied strings end with a nul byte.
 * The nul is written to the n+1 position of the target.
 */
char *LYstrncpy(char *target,
		const char *source,
		int n)
{
    char *val = target;
    int len;

    if (source == 0)
	source = "";
    len = (int) strlen(source);

    if (n > 0) {
	if (n > len)
	    n = len;
	(void) memcpy(target, source, (size_t) n);
    } else {
	n = 0;
    }
    target[n] = '\0';
    return val;
}

#define IS_NEW_GLYPH(ch) (utf_flag && (UCH(ch)&0xc0) != 0x80)
#define IS_UTF_EXTRA(ch) (utf_flag && (UCH(ch)&0xc0) == 0x80)

/*
 * LYmbcsstrncpy() terminates strings with a null byte.  It takes account of
 * multibyte characters.  The source string is copied until either end of string
 * or max number of either bytes or glyphs (mbcs sequences) (CJK or UTF8).  The
 * utf_flag argument should be TRUE for UTF8.  - KW & FM
 */
char *LYmbcsstrncpy(char *target,
		    const char *source,
		    int n_bytes,
		    int n_glyphs,
		    int utf_flag)
{
    char *val = target;
    int i_bytes = 0, i_glyphs = 0;

    if (n_bytes < 0)
	n_bytes = 0;
    if (n_glyphs < 0)
	n_glyphs = 0;

    for (; *source != '\0' && i_bytes < n_bytes; i_bytes++) {
	if (IS_NEW_GLYPH(*source)) {
	    if (i_glyphs++ >= n_glyphs) {
		*target = '\0';
		return val;
	    }
	}
	*(target++) = *(source++);
    }
    *target = '\0';

    return val;
}

/*
 * LYmbcs_skip_glyphs() skips a given number of character positions in a string
 * and returns the resulting pointer.  It takes account of UTF-8 encoded
 * characters.  - KW
 */
const char *LYmbcs_skip_glyphs(const char *data,
			       int n_glyphs,
			       int utf_flag)
{
    int i_glyphs = 0;

    if (n_glyphs < 0)
	n_glyphs = 0;

    if (non_empty(data)) {
	if (!utf_flag) {
	    while (n_glyphs-- > 0) {
		if (!*++data)
		    break;
	    }
	} else {
	    while (*data) {
		if (IS_NEW_GLYPH(*data)) {
		    if (i_glyphs++ >= n_glyphs) {
			break;
		    }
		}
		data++;
	    }
	}
    }
    return data;
}

/*
 * LYmbcs_skip_cells() skips a given number of display positions in a string
 * and returns the resulting pointer.  It takes account of UTF-8 encoded
 * characters.  - TD
 */
const char *LYmbcs_skip_cells(const char *data,
			      int n_cells,
			      int utf_flag)
{
    const char *result;
    int actual;
    int target = n_cells;

    do {
	result = LYmbcs_skip_glyphs(data, target--, utf_flag);
	actual = LYstrExtent2(data, (int) (result - data));
    } while ((actual > 0) && (actual > n_cells));
    return result;
}

/*
 * LYmbcsstrlen() returns the printable length of a string that might contain
 * IsSpecial or multibyte (CJK or UTF8) characters.  - FM
 *
 * Counts glyph cells if count_gcells is set.  (Full-width characters in CJK
 * mode count as two.) Counts character glyphs if count_gcells is unset. 
 * (Full- width characters in CJK mode count as one.) - kw
 */
int LYmbcsstrlen(const char *str,
		 int utf_flag,
		 int count_gcells)
{
    int i, j, len = 0;

    if (non_empty(str)) {
#ifdef WIDEC_CURSES
	if (count_gcells) {
	    len = LYstrCells(str);
	} else
#endif
	{
	    for (i = 0; str[i] != '\0'; i++) {
		if (!IsSpecialAttrChar(str[i])) {
		    len++;
		    if (IS_NEW_GLYPH(str[i])) {
			j = 0;
			while (IsNormalChar(str[(i + 1)]) &&
			       j < 5 &&
			       IS_UTF_EXTRA(str[(i + 1)])) {
			    i++;
			    j++;
			}
		    } else if (!utf_flag && IS_CJK_TTY && !count_gcells &&
			       is8bits(str[i]) &&
			       IsNormalChar(str[(i + 1)])) {
			i++;
		    }
		}
	    }
	}
    }
    return (len);
}

#undef GetChar
#undef USE_CURSES_RESIZE

#ifdef USE_SLANG
#if defined(VMS)
#define GetChar() ttgetc()
#elif defined(__DJGPP__)
#define GetChar() getxkey()	/* HTDos.c */
#elif defined(__CYGWIN__)
#define GetChar SLkp_getkey
#else
#define GetChar (int)SLang_getkey
#endif
#else /* curses */
#if defined(DJGPP)
#define GetChar() (djgpp_idle_loop(), wgetch(LYtopwindow()))
#elif defined(NCURSES_VERSION) && defined(__BEOS__)
#define GetChar() myGetCharNodelay()
#endif
#endif

#ifdef USE_CURSES_RESIZE
static int myGetCharResize(void)
{
    int c;
    WINDOW *win = LYtopwindow();

    do {
	wtimeout(win, 20);
	c = wgetch(win);
	wtimeout(win, -1);
	CheckScreenSize();
    } while (c <= 0);
    return c;
}
#define GetChar() myGetCharResize()
#endif

#ifdef USE_CURSES_NODELAY
/* PDCurses - until version 2.7 in 2005 - defined ERR as 0, unlike other
 * versions of curses.  Generally both EOF and ERR are defined as -1's. 
 * However, there is a special case (see HTCheckForInterrupt()) to handle a
 * case where no select() function is used in the win32 environment.
 *
 * HTCheckForInterrupt() uses nodelay() in this special case to check for
 * pending input.  That normally returns ERR.  But LYgetch_for() checks the
 * return value of this function for EOF (to handle some antique runtime
 * libraries which did not set the state for feof/ferror).  Returning a zero
 * (0) is safer since normally that is not mapped to any commands, and will be
 * ignored by lynx.
 */
static int myGetCharNodelay(void)
{
    int c = wgetch(LYwin);

    if (c == -1)
	c = 0;
    CheckScreenSize();

    return c;
}
#else
#define myGetCharNodelay() wgetch(LYwin)
#endif

#if !defined(GetChar) && defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
/* PDCurses sends back key-modifiers that we don't use, but would waste time
 * upon, e.g., repainting the status line
 */
static int myGetChar(void)
{
    int c;
    BOOL done = FALSE;

    do {
	switch (c = myGetCharNodelay()) {
	case KEY_SHIFT_L:
	case KEY_SHIFT_R:
	case KEY_CONTROL_L:
	case KEY_CONTROL_R:
	case KEY_ALT_L:
	case KEY_ALT_R:
	case KEY_RESIZE:
	    break;
	default:
	    done = TRUE;
	    break;
	}
    } while (!done);

    return c;
}
#define GetChar() myGetChar()
#endif

#if !defined(GetChar) && defined(VMS)
#define GetChar() ttgetc()
#endif

#if !defined(GetChar)
#ifdef HAVE_KEYPAD
#define GetChar() getch()
#else
#ifndef USE_GETCHAR
#define USE_GETCHAR
#endif /* !USE_GETCHAR */
#define GetChar() getchar()	/* used to be "getc(stdin)" and "getch()" */
#endif /* HAVE_KEYPAD */
#endif /* !defined(GetChar) */

#if defined(USE_SLANG) && defined(USE_MOUSE)
static int sl_parse_mouse_event(int *x, int *y, int *button)
{
    /* "ESC [ M" has already been processed.  Three more characters are
     * expected:  BUTTON X Y
     */
    *button = (int) SLang_getkey();
    switch (*button) {
    case 040:			/* left button */
    case 041:			/* middle button */
    case 042:			/* right button */
	*button -= 040;
	break;

    default:			/* Hmmm.... */
	SLang_flush_input();
	return -1;
    }

    *x = (int) SLang_getkey();
    if (*x == CH_ESC)		/* Undo 7-bit replace for large x - kw */
	*x = (int) SLang_getkey() + 64 - 33;
    else
	*x -= 33;
    *y = (int) SLang_getkey();
    if (*y == CH_ESC)		/* Undo 7-bit replace for large y - kw */
	*y = (int) SLang_getkey() + 64 - 33;
    else
	*y -= 33;
    return 0;
}

static int sl_read_mouse_event(int code)
{
    int mouse_x, mouse_y, button;

    mouse_link = -1;
    if (-1 != sl_parse_mouse_event(&mouse_x, &mouse_y, &button)) {
	if (button == 0)	/* left */
	    return set_clicked_link(mouse_x, mouse_y, FOR_PANEL, 1);

	if (button == 1)	/* middle */
	    return LYReverseKeymap(LYK_VIEW_BOOKMARK);

	if (button == 2)	/* right */
	{
	    /* Right button: go back to prev document.
	     * The problem is that we need to determine
	     * what to return to achieve this.
	     */
	    return LYReverseKeymap(LYK_PREV_DOC);
	}
    }
    if (code == FOR_INPUT || code == FOR_PROMPT)
	return DO_NOTHING;
    else
	return -1;
}
#endif /* USE_SLANG and USE_MOUSE */

static BOOLEAN csi_is_csi = TRUE;
void ena_csi(int flag)
{
    csi_is_csi = (BOOLEAN) flag;
}

#if defined(USE_KEYMAPS)

#ifdef USE_SLANG
#define define_key(string, code) \
	SLkm_define_keysym ((SLFUTURE_CONST char*)(string), \
			    (unsigned) code, \
			    Keymap_List)
#if SLANG_VERSION < 20000
#define expand_substring(target, first, last, final) \
 	(SLexpand_escaped_string(target, \
				 DeConst(first), \
				 DeConst(last), 1)
static int SLang_get_error(void)
{
    return SLang_Error;
}
#else
int LY_Slang_UTF8_Mode = 0;

#define expand_substring(target, first, last, final) \
	(SLexpand_escaped_string(target, \
				 DeConst(first), \
				 DeConst(last), \
				 LY_Slang_UTF8_Mode), 1)
#endif

static SLKeyMap_List_Type *Keymap_List;

/* This value should be larger than anything in LYStrings.h */
#define MOUSE_KEYSYM 0x0400
#endif

/*
 * For ncurses, we use the predefined keysyms, since that lets us also reuse
 * the CSI logic and other special cases for VMS, NCSA telnet, etc.
 */
#ifdef USE_SLANG
# ifdef VMS
#  define EXTERN_KEY(string,string1,lynx,curses) {string,lynx}
# else
#  define EXTERN_KEY(string,string1,lynx,curses) {string,lynx},{string1,lynx}
# endif
# define INTERN_KEY(string,lynx,curses)          {string,lynx,lynx}
#else
# define INTERN_KEY(string,lynx,curses)          {string,curses,lynx}
# define EXTERN_KEY(string,string1,lynx,curses)  {string,curses,lynx}
#endif

typedef struct {
    const char *string;
    int value;
    LYExtraKeys internal;
} Keysym_String_List;
/* *INDENT-OFF* */
static Keysym_String_List Keysym_Strings [] =
{
    INTERN_KEY( "UPARROW",	UPARROW_KEY,	KEY_UP ),
    INTERN_KEY( "DNARROW",	DNARROW_KEY,	KEY_DOWN ),
    INTERN_KEY( "RTARROW",	RTARROW_KEY,	KEY_RIGHT ),
    INTERN_KEY( "LTARROW",	LTARROW_KEY,	KEY_LEFT ),
    INTERN_KEY( "PGDOWN",	PGDOWN_KEY,	KEY_NPAGE ),
    INTERN_KEY( "PGUP",		PGUP_KEY,	KEY_PPAGE ),
    INTERN_KEY( "HOME",		HOME_KEY,	KEY_HOME ),
    INTERN_KEY( "END",		END_KEY,	KEY_END ),
    INTERN_KEY( "F1",		F1_KEY,		KEY_F(1) ),
    INTERN_KEY( "F2",		F2_KEY,		KEY_F(2) ),
    INTERN_KEY( "F3",		F3_KEY,		KEY_F(3) ),
    INTERN_KEY( "F4",		F4_KEY,		KEY_F(4) ),
    INTERN_KEY( "F5",		F5_KEY,		KEY_F(5) ),
    INTERN_KEY( "F6",		F6_KEY,		KEY_F(7) ),
    INTERN_KEY( "F7",		F7_KEY,		KEY_F(7) ),
    INTERN_KEY( "F8",		F8_KEY,		KEY_F(8) ),
    INTERN_KEY( "F9",		F9_KEY,		KEY_F(9) ),
    INTERN_KEY( "F10",		F10_KEY,	KEY_F(10) ),
    INTERN_KEY( "F11",		F11_KEY,	KEY_F(11) ),
    INTERN_KEY( "F12",		F12_KEY,	KEY_F(12) ),
    INTERN_KEY( "DO_KEY",	DO_KEY,		KEY_F(16) ),
    INTERN_KEY( "FIND_KEY",	FIND_KEY,	KEY_FIND ),
    INTERN_KEY( "SELECT_KEY",	SELECT_KEY,	KEY_SELECT ),
    INTERN_KEY( "INSERT_KEY",	INSERT_KEY,	KEY_IC ),
    INTERN_KEY( "REMOVE_KEY",	REMOVE_KEY,	KEY_DC ),
    INTERN_KEY( "DO_NOTHING",	DO_NOTHING,	DO_NOTHING|LKC_ISLKC ),
    INTERN_KEY( "BACKTAB_KEY",	BACKTAB_KEY,	BACKTAB_KEY ),
    INTERN_KEY( NULL,		UNKNOWN_KEY,	ERR )
};
/* *INDENT-ON* */

#ifdef NCURSES_VERSION
/*
 * Ncurses stores the termcap/terminfo names in arrays sorted to match the
 * array of strings in the TERMTYPE struct.
 */
static int lookup_tiname(char *name, NCURSES_CONST char *const *names)
{
    int code;

    for (code = 0; names[code] != 0; code++)
	if (!strcmp(names[code], name))
	    return code;
    return -1;
}

static const char *expand_tiname(const char *first, size_t len, char **result, char *final)
{
    char name[BUFSIZ];
    int code;
    TERMTYPE *tp = (TERMTYPE *) (cur_term);

    LYStrNCpy(name, first, len);
    **result = '\0';
    if ((code = lookup_tiname(name, strnames)) >= 0
	|| (code = lookup_tiname(name, strfnames)) >= 0) {
	if (tp->Strings[code] != 0) {
	    LYStrNCpy(*result, tp->Strings[code], (final - *result));
	    (*result) += strlen(*result);
	}
    }
    return first + len;
}

static const char *expand_tichar(const char *first, char **result, char *final)
{
    int ch;
    int limit = 0;
    int radix = 0;
    int value = 0;
    const char *name = 0;

    switch (ch = *first++) {
    case 'E':
    case 'e':
	value = 27;
	break;
    case 'a':
	name = "bel";
	break;
    case 'b':
	value = '\b';
	break;
    case 'f':
	value = '\f';
	break;
    case 'n':
	value = '\n';
	break;
    case 'r':
	value = '\r';
	break;
    case 't':
	value = '\t';
	break;
    case 'v':
	value = '\v';
	break;
    case 'd':
	radix = 10;
	limit = 3;
	break;
    case 'x':
	radix = 16;
	limit = 2;
	break;
    default:
	if (isdigit(ch)) {
	    radix = 8;
	    limit = 3;
	    first--;
	} else {
	    value = *first;
	}
	break;
    }

    if (radix != 0) {
	char *last = 0;
	char tmp[80];

	LYStrNCpy(tmp, first, limit);
	value = (int) strtol(tmp, &last, radix);
	if (last != 0 && last != tmp)
	    first += (last - tmp);
    }

    if (name != 0) {
	(void) expand_tiname(name, strlen(name), result, final);
    } else {
	**result = (char) value;
	(*result) += 1;
    }

    return first;
}

static BOOLEAN expand_substring(char *target,
				const char *first,
				const char *last,
				char *final)
{
    int ch;

    while (first < last) {
	switch (ch = *first++) {
	case ESCAPE:
	    first = expand_tichar(first, &target, final);
	    break;
	case '^':
	    ch = *first++;
	    if (ch == LPAREN) {
		const char *s = StrChr(first, RPAREN);
		char *was = target;

		if (s == 0)
		    s = first + strlen(first);
		first = expand_tiname(first, (size_t) (s - first), &target, final);
		if (target == was)
		    return FALSE;
		if (*first)
		    first++;
	    } else if (ch == '?') {	/* ASCII delete? */
		*target++ = 127;
	    } else if ((ch & 0x3f) < 0x20) {	/* ASCII control char? */
		*target++ = (char) (ch & 0x1f);
	    } else {
		*target++ = '^';
		first--;	/* not legal... */
	    }
	    break;
	case 0:		/* convert nulls for terminfo */
	    ch = 0200;
	    /* FALLTHRU */
	default:
	    *target++ = (char) ch;
	    break;
	}
    }
    *target = '\0';
    return TRUE;
}
#endif

static void unescaped_char(const char *parse, int *keysym)
{
    size_t len = strlen(parse);
    char buf[BUFSIZ];

    if (len >= 3) {
	(void) expand_substring(buf,
				parse + 1,
				parse + len - 1,
				buf + sizeof(buf) - 1);
	if (strlen(buf) == 1)
	    *keysym = *buf;
    }
}

static BOOLEAN unescape_string(char *source, char *target, char *final)
{
    BOOLEAN ok = FALSE;

    if (*source == SQUOTE) {
	int keysym = -1;

	unescaped_char(source, &keysym);
	if (keysym >= 0) {
	    target[0] = (char) keysym;
	    target[1] = '\0';
	    ok = TRUE;
	}
    } else if (*source == DQUOTE) {
	if (expand_substring(target, source + 1, source + strlen(source) - 1, final))
	    ok = TRUE;
	(void) final;
    }
    return ok;
}

static Keysym_String_List *lookupKeysymByName(const char *name)
{
    Keysym_String_List *k;
    Keysym_String_List *result = 0;

    k = Keysym_Strings;
    while (k->string != NULL) {
	if (0 == strcasecomp(k->string, name)) {
	    result = k;
	    break;
	}
	k++;
    }
    return result;
}

int map_string_to_keysym(const char *str, int *keysym, int internal)
{
    int modifier = 0;

    *keysym = -1;

    if (strncasecomp(str, "LAC:", 4) == 0) {
	char *other = StrChr(str + 4, ':');

	if (other) {
	    int othersym = lecname_to_lec(other + 1);
	    char buf[BUFSIZ];

	    if (othersym >= 0 && other - str - 4 < BUFSIZ) {
		LYStrNCpy(buf, str + 4, (other - str - 4));
		*keysym = lacname_to_lac(buf);
		if (*keysym >= 0) {
		    *keysym = LACLEC_TO_LKC0(*keysym, othersym);
		    return (*keysym);
		}
	    }
	}
	*keysym = lacname_to_lac(str + 4);
	if (*keysym >= 0) {
	    *keysym = LAC_TO_LKC0(*keysym);
	    return (*keysym);
	}
    } else if (strncasecomp(str, "Meta-", 5) == 0) {
	str += 5;
	modifier = LKC_MOD2;
	if (*str) {
	    size_t len = strlen(str);

	    if (len == 1) {
		return (*keysym = (UCH(str[0])) | modifier);
	    } else if (len == 2 && str[0] == '^' &&
		       (isalpha(UCH(str[1])) ||
			(TOASCII(str[1]) >= '@' && TOASCII(str[1]) <= '_'))) {
		return (*keysym = FROMASCII(UCH(str[1] & 0x1f)) | modifier);
	    } else if (len == 2 && str[0] == '^' &&
		       str[1] == '?') {
		return (*keysym = CH_DEL | modifier);
	    }
	    if (*str == '^' || *str == '\\') {
		char buf[BUFSIZ];

		(void) expand_substring(buf,
					str,
					str + HTMIN(len, 28),
					buf + sizeof(buf) - 1);
		if (strlen(buf) <= 1)
		    return (*keysym = (UCH(buf[0])) | modifier);
	    }
	}
    } else if (*str == SQUOTE) {
	unescaped_char(str, keysym);
    } else if (isdigit(UCH(*str))) {
	char *tmp;
	long value = strtol(str, &tmp, 0);

	if (!isalnum(UCH(*tmp))) {
	    *keysym = (int) value;
#ifndef USE_SLANG
	    if (*keysym > 255)
		*keysym |= LKC_ISLKC;	/* caller should remove this flag - kw */
#endif
	}
    } else {
	Keysym_String_List *k = lookupKeysymByName(str);

	if (k != 0) {
	    *keysym = (internal
		       ? k->internal
		       : k->value);
	}
    }

    if (*keysym >= 0)
	*keysym |= modifier;
    return (*keysym);
}

LYExtraKeys LYnameToExtraKeys(const char *name)
{
    Keysym_String_List *k = lookupKeysymByName(name);
    LYExtraKeys result = UNKNOWN_KEY;

    if (k != 0)
	result = k->internal;
    return result;
}

const char *LYextraKeysToName(LYExtraKeys code)
{
    Keysym_String_List *k;
    const char *result = 0;

    k = Keysym_Strings;
    while (k->string != NULL) {
	if (k->internal == code) {
	    result = k->string;
	    break;
	}
	k++;
    }
    return result;
}

/*
 * Starting at a nonblank character, skip over a token, counting quoted and
 * escaped characters.
 */
static char *skip_keysym(char *parse)
{
    int quoted = 0;
    int escaped = 0;

    while (*parse) {
	if (escaped) {
	    escaped = 0;
	} else if (quoted) {
	    if (*parse == ESCAPE) {
		escaped = 1;
	    } else if (*parse == quoted) {
		quoted = 0;
	    }
	} else if (*parse == ESCAPE) {
	    escaped = 1;
	} else if (*parse == DQUOTE || *parse == SQUOTE) {
	    quoted = *parse;
	} else if (isspace(UCH(*parse))) {
	    break;
	}
	parse++;
    }
    return (quoted || escaped) ? 0 : parse;
}

/*
 * The first token is the string to define, the second is the name (of the
 * keysym) to define it to.
 */
#define MY_TRACE(p) CTRACE2(TRACE_CFG, p)

static int setkey_cmd(char *parse)
{
    char *s, *t;
    int keysym;
    char buf[BUFSIZ];

    MY_TRACE((tfp, "KEYMAP(PA): in=%s", parse));	/* \n-terminated */
    if ((s = skip_keysym(parse)) != 0) {
	if (isspace(UCH(*s))) {
	    *s++ = '\0';
	    s = LYSkipBlanks(s);
	    if ((t = skip_keysym(s)) == 0) {
		MY_TRACE((tfp, "KEYMAP(SKIP) no key expansion found\n"));
		return -1;
	    }
	    if (t != s)
		*t = '\0';
	    if (map_string_to_keysym(s, &keysym, FALSE) >= 0) {
		if (!unescape_string(parse, buf, buf + sizeof(buf) - 1)) {
		    MY_TRACE((tfp, "KEYMAP(SKIP) could unescape key\n"));
		    return 0;	/* Trace the failure and continue. */
		}
		if (LYTraceLogFP == 0) {
		    MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x\n",
			      (unsigned) keysym));
		} else {
		    MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x, seq='%s'\n",
			      (unsigned) keysym, buf));
		}
		return define_key(buf, keysym);
	    } else {
		MY_TRACE((tfp, "KEYMAP(SKIP) could not map to keysym\n"));
	    }
	} else {
	    MY_TRACE((tfp, "KEYMAP(SKIP) junk after key description: '%s'\n", s));
	}
    } else {
	MY_TRACE((tfp, "KEYMAP(SKIP) no key description\n"));
    }
    return -1;
}
#undef MY_TRACE

static int unsetkey_cmd(char *parse)
{
    char *s = skip_keysym(parse);

    if (s != parse) {
	*s = '\0';
#ifdef NCURSES_VERSION
	/*
	 * This won't work with Slang.  Remove the definition for the given
	 * keysym.
	 */
	{
	    int keysym;

	    if (map_string_to_keysym(parse, &keysym, FALSE) >= 0)
		define_key((char *) 0, keysym);
	}
#endif
#ifdef USE_SLANG
	/* Slang implements this, for undefining the string which is associated
	 * with a keysym (the reverse of what we normally want, but may
	 * occasionally find useful).
	 */
	SLang_undefine_key(parse, Keymap_List);
	if (SLang_get_error())
	    return -1;
#endif
    }
    return 0;
}

#ifdef FNAMES_8_3
#define FNAME_LYNX_KEYMAPS "_lynxkey.map"
#else
#define FNAME_LYNX_KEYMAPS ".lynx-keymaps"
#endif /* FNAMES_8_3 */

static int read_keymap_file(void)
{
    /* *INDENT-OFF* */
    static struct {
	const char *name;
	int (*func) (char *s);
    } table[] = {
	{ "setkey",   setkey_cmd },
	{ "unsetkey", unsetkey_cmd },
    };
    /* *INDENT-ON* */

    char *line = NULL;
    FILE *fp;
    char file[LY_MAXPATH];
    int linenum;
    size_t n;

    LYAddPathToHome(file, sizeof(file), FNAME_LYNX_KEYMAPS);

    if ((fp = fopen(file, "r")) == 0)
	return 0;

    CTRACE((tfp, "read_keymap_file %s\n", file));
    linenum = 0;
    while (LYSafeGets(&line, fp) != 0) {
	char *s = LYSkipBlanks(line);

	linenum++;

	if ((*s == 0) || (*s == '#'))
	    continue;

	for (n = 0; n < TABLESIZE(table); n++) {
	    size_t len = strlen(table[n].name);

	    if (strlen(s) > len && !StrNCmp(s, table[n].name, len)
		&& (*(table[n].func)) (LYSkipBlanks(s + len)) < 0)
		fprintf(stderr, FAILED_READING_KEYMAP, linenum, file);
	}
    }
    FREE(line);
    LYCloseInput(fp);
    return 0;
}

static void setup_vtXXX_keymap(void)
{
    /* *INDENT-OFF* */
    static Keysym_String_List table[] = {
	INTERN_KEY( "\033[A",	UPARROW_KEY,	KEY_UP ),
	INTERN_KEY( "\033OA",	UPARROW_KEY,	KEY_UP ),
	INTERN_KEY( "\033[B",	DNARROW_KEY,	KEY_DOWN ),
	INTERN_KEY( "\033OB",	DNARROW_KEY,	KEY_DOWN ),
	INTERN_KEY( "\033[C",	RTARROW_KEY,	KEY_RIGHT ),
	INTERN_KEY( "\033OC",	RTARROW_KEY,	KEY_RIGHT ),
	INTERN_KEY( "\033[D",	LTARROW_KEY,	KEY_LEFT ),
	INTERN_KEY( "\033OD",	LTARROW_KEY,	KEY_LEFT ),
	INTERN_KEY( "\033[1~",	FIND_KEY,	KEY_FIND ),
	INTERN_KEY( "\033[2~",	INSERT_KEY,	KEY_IC ),
	INTERN_KEY( "\033[3~",	REMOVE_KEY,	KEY_DC ),
	INTERN_KEY( "\033[4~",	SELECT_KEY,	KEY_SELECT ),
	INTERN_KEY( "\033[5~",	PGUP_KEY,	KEY_PPAGE ),
	INTERN_KEY( "\033[6~",	PGDOWN_KEY,	KEY_NPAGE ),
	INTERN_KEY( "\033[7~",	HOME_KEY,	KEY_HOME),
	INTERN_KEY( "\033[8~",	END_KEY,	KEY_END ),
	INTERN_KEY( "\033[11~",	F1_KEY,		KEY_F(1) ),
	INTERN_KEY( "\033[28~",	F1_KEY,		KEY_F(1) ),
	INTERN_KEY( "\033OP",	F1_KEY,		KEY_F(1) ),
	INTERN_KEY( "\033[OP",	F1_KEY,		KEY_F(1) ),
	INTERN_KEY( "\033[29~",	DO_KEY,		KEY_F(16) ),
#if defined(USE_SLANG)
#if defined(__WIN32__) || defined(__MINGW32__)
	INTERN_KEY( "\xE0H",	UPARROW_KEY,	KEY_UP ),
	INTERN_KEY( "\xE0P",	DNARROW_KEY,	KEY_DOWN ),
	INTERN_KEY( "\xE0M",	RTARROW_KEY,	KEY_RIGHT ),
	INTERN_KEY( "\xE0K",	LTARROW_KEY,	KEY_LEFT ),
	INTERN_KEY( "\xE0R",	INSERT_KEY,	KEY_IC ),
	INTERN_KEY( "\xE0S",	REMOVE_KEY,	KEY_DC ),
	INTERN_KEY( "\xE0I",	PGUP_KEY,	KEY_PPAGE ),
	INTERN_KEY( "\xE0Q",	PGDOWN_KEY,	KEY_NPAGE ),
	INTERN_KEY( "\xE0G",	HOME_KEY,	KEY_HOME),
	INTERN_KEY( "\xE0O",	END_KEY,	KEY_END ),
#endif
#if !defined(VMS)
	INTERN_KEY(	"^(ku)", UPARROW_KEY,	KEY_UP ),
	INTERN_KEY(	"^(kd)", DNARROW_KEY,	KEY_DOWN ),
	INTERN_KEY(	"^(kr)", RTARROW_KEY,	KEY_RIGHT ),
	INTERN_KEY(	"^(kl)", LTARROW_KEY,	KEY_LEFT ),
	INTERN_KEY(	"^(@0)", FIND_KEY,	KEY_FIND ),
	INTERN_KEY(	"^(kI)", INSERT_KEY,	KEY_IC ),
	INTERN_KEY(	"^(kD)", REMOVE_KEY,	KEY_DC ),
	INTERN_KEY(	"^(*6)", SELECT_KEY,	KEY_SELECT ),
	INTERN_KEY(	"^(kP)", PGUP_KEY,	KEY_PPAGE ),
	INTERN_KEY(	"^(kN)", PGDOWN_KEY,	KEY_NPAGE ),
	INTERN_KEY(	"^(@7)", END_KEY,	KEY_END ),
	INTERN_KEY(	"^(kh)", HOME_KEY,	KEY_HOME),
	INTERN_KEY(	"^(k1)", F1_KEY,	KEY_F(1) ),
	INTERN_KEY(	"^(k2)", F2_KEY,	KEY_F(2) ),
	INTERN_KEY(	"^(k3)", F3_KEY,	KEY_F(3) ),
	INTERN_KEY(	"^(k4)", F4_KEY,	KEY_F(4) ),
	INTERN_KEY(	"^(k5)", F5_KEY,	KEY_F(5) ),
	INTERN_KEY(	"^(k6)", F6_KEY,	KEY_F(6) ),
	INTERN_KEY(	"^(k7)", F7_KEY,	KEY_F(7) ),
	INTERN_KEY(	"^(k8)", F8_KEY,	KEY_F(8) ),
	INTERN_KEY(	"^(k9)", F9_KEY,	KEY_F(9) ),
	INTERN_KEY(	"^(k;)", F10_KEY,	KEY_F(10) ),
	INTERN_KEY(	"^(F1)", F11_KEY,	KEY_F(11) ),
	INTERN_KEY(	"^(F2)", F12_KEY,	KEY_F(12) ),
	INTERN_KEY(	"^(F6)", DO_KEY,	KEY_F(16) ),
#endif /* !VMS */
#endif /* SLANG */
    };
    /* *INDENT-ON* */

    size_t n;

    for (n = 0; n < TABLESIZE(table); n++)
	define_key(table[n].string, table[n].value);
}

int lynx_initialize_keymaps(void)
{
#ifdef USE_SLANG
    int i;
    char keybuf[2];

    /* The escape sequences may contain embedded termcap strings.  Make
     * sure the library is initialized for that.
     */
    SLtt_get_terminfo();

    if (NULL == (Keymap_List = SLang_create_keymap("Lynx", NULL)))
	return -1;

    keybuf[1] = 0;
    for (i = 1; i < 256; i++) {
	keybuf[0] = (char) i;
	define_key(keybuf, i);
    }

    setup_vtXXX_keymap();
    define_key("\033[M", MOUSE_KEYSYM);

    if (SLang_get_error())
	SLang_exit_error("Unable to initialize keymaps");
#else
    setup_vtXXX_keymap();
#endif
    return read_keymap_file();
}

#endif /* USE_KEYMAPS */

#if defined(USE_MOUSE) && (defined(NCURSES))
static int LYmouse_menu(int x, int y, int atlink, int code)
{
#define ENT_ONLY_DOC	1
#define ENT_ONLY_LINK	2
    /* *INDENT-OFF* */
    static const struct {
	const char *txt;
	int  action;
	unsigned int  flag;
    } possible_entries[] = {
	{"Quit",			LYK_ABORT,		ENT_ONLY_DOC},
	{"Home page",			LYK_MAIN_MENU,		ENT_ONLY_DOC},
	{"Previous document",		LYK_PREV_DOC,		ENT_ONLY_DOC},
	{"Beginning of document",	LYK_HOME,		ENT_ONLY_DOC},
	{"Page up",			LYK_PREV_PAGE,		ENT_ONLY_DOC},
	{"Half page up",		LYK_UP_HALF,		ENT_ONLY_DOC},
	{"Two lines up",		LYK_UP_TWO,		ENT_ONLY_DOC},
	{"History",			LYK_HISTORY,		ENT_ONLY_DOC},
	{"Help",			LYK_HELP,		0},
	{"Do nothing (refresh)",	LYK_REFRESH,		0},
	{"Load again",			LYK_RELOAD,		ENT_ONLY_DOC},
	{"Edit Doc URL and load",	LYK_ECGOTO,		ENT_ONLY_DOC},
	{"Edit Link URL and load",	LYK_ELGOTO,		ENT_ONLY_LINK},
	{"Show info",			LYK_INFO,		0},
	{"Search",			LYK_WHEREIS,		ENT_ONLY_DOC},
	{"Print",			LYK_PRINT,		ENT_ONLY_DOC},
	{"Two lines down",		LYK_DOWN_TWO,		ENT_ONLY_DOC},
	{"Half page down",		LYK_DOWN_HALF,		ENT_ONLY_DOC},
	{"Page down",			LYK_NEXT_PAGE,		ENT_ONLY_DOC},
	{"End of document",		LYK_END,		ENT_ONLY_DOC},
	{"Bookmarks",			LYK_VIEW_BOOKMARK,	ENT_ONLY_DOC},
	{"Cookie jar",			LYK_COOKIE_JAR,		ENT_ONLY_DOC},
#ifdef USE_CACHEJAR
	{"Cache jar",			LYK_CACHE_JAR,		ENT_ONLY_DOC},
#endif
	{"Search index",		LYK_INDEX_SEARCH,	ENT_ONLY_DOC},
	{"Set Options",			LYK_OPTIONS,		ENT_ONLY_DOC},
	{"Activate this link",		LYK_MOUSE_SUBMIT,	ENT_ONLY_LINK},
	{"Download",			LYK_DOWNLOAD,		ENT_ONLY_LINK}
    };
    /* *INDENT-ON* */

#define TOTAL_MENUENTRIES	TABLESIZE(possible_entries)
    const char *choices[TOTAL_MENUENTRIES + 1];
    int actions[TOTAL_MENUENTRIES];

    int c, c1, retlac;
    unsigned filter_out = (unsigned) (atlink ? ENT_ONLY_DOC : ENT_ONLY_LINK);

    c = c1 = 0;
    while (c < (int) TOTAL_MENUENTRIES) {
	if (!(possible_entries[c].flag & filter_out)) {
	    choices[c1] = possible_entries[c].txt;
	    actions[c1++] = possible_entries[c].action;
	}
	c++;
    }
    choices[c1] = NULL;

    /* Somehow the mouse is over the number instead of being over the
       name, so we decrease x. */
    c = LYChoosePopup((atlink ? 2 : 10) - 1, y, (x > 5 ? x - 5 : 1),
		      choices, c1, FALSE, TRUE);

    /*
     * LYhandlePopupList() wasn't really meant to be used outside of old-style
     * Options menu processing.  One result of mis-using it here is that we
     * have to deal with side-effects regarding SIGINT signal handler and the
     * term_options global variable.  - kw
     */
    if (term_options) {
	retlac = LYK_DO_NOTHING;
	term_options = FALSE;
    } else {
	retlac = actions[c];
    }

    if (code == FOR_INPUT && mouse_link == -1) {
	switch (retlac) {
	case LYK_ABORT:
	    retlac = LYK_QUIT;	/* a bit softer... */
	    /* fall through */
	case LYK_MAIN_MENU:
	case LYK_PREV_DOC:
	case LYK_HOME:
	case LYK_PREV_PAGE:
	case LYK_UP_HALF:
	case LYK_UP_TWO:
	case LYK_HISTORY:
	case LYK_HELP:
/*	    case LYK_REFRESH:*/
	case LYK_RELOAD:
	case LYK_ECGOTO:
	case LYK_INFO:
	case LYK_WHEREIS:
	case LYK_PRINT:
	case LYK_DOWN_TWO:
	case LYK_DOWN_HALF:
	case LYK_NEXT_PAGE:
	case LYK_END:
	case LYK_VIEW_BOOKMARK:
	case LYK_COOKIE_JAR:
#ifdef USE_CACHEJAR
	case LYK_CACHE_JAR:
#endif
	case LYK_INDEX_SEARCH:
	case LYK_OPTIONS:
	    mouse_link = -3;	/* so LYgetch_for() passes it on - kw */
	}
    }
    if (retlac == LYK_DO_NOTHING ||
	retlac == LYK_REFRESH) {
	mouse_link = -1;	/* mainloop should not change cur link - kw */
    }
    if (code == FOR_INPUT && retlac == LYK_DO_NOTHING) {
	repaint_main_statusline(FOR_INPUT);
    }
    return retlac;
}
#endif /* USE_MOUSE && (NCURSES || PDCURSES) */

#if defined(USE_KEYMAPS) && defined(USE_SLANG)
/************************************************************************/

static int current_sl_modifier = 0;

/* We cannot guarantee the type for 'GetChar', and should not use a cast. */
static int myGetChar(void)
{
    int i = GetChar();

    if (i == 0)			/* trick to get NUL char through - kw */
	current_sl_modifier = LKC_ISLKC;
    return i;
}

static int LYgetch_for(int code)
{
    SLang_Key_Type *key;
    int keysym;

    current_sl_modifier = 0;

    key = SLang_do_key(Keymap_List, myGetChar);
    if ((key == NULL) || (key->type != SLKEY_F_KEYSYM)) {
#if defined(__WIN32__) || defined(__MINGW32__)
	if ((key == NULL) && (current_sl_modifier == LKC_ISLKC)) {
	    key = SLang_do_key(Keymap_List, myGetChar);
	    keysym = key->f.keysym;
	    switch (keysym) {
	    case 'H':
		keysym = UPARROW_KEY;
		break;
	    case 'P':
		keysym = DNARROW_KEY;
		break;
	    case 'M':
		keysym = RTARROW_KEY;
		break;
	    case 'K':
		keysym = LTARROW_KEY;
		break;
	    case 'R':
		keysym = INSERT_KEY;
		break;
	    case 'S':
		keysym = REMOVE_KEY;
		break;
	    case 'I':
		keysym = PGUP_KEY;
		break;
	    case 'Q':
		keysym = PGDOWN_KEY;
		break;
	    case 'G':
		keysym = HOME_KEY;
		break;
	    case 'O':
		keysym = END_KEY;
		break;
	    case ';':
		keysym = F1_KEY;
		break;
	    }
	    return (keysym);
	}
#endif
	return (current_sl_modifier ? 0 : DO_NOTHING);
    } else {
	keysym = (int) key->f.keysym;

#if defined (USE_MOUSE)
	if (keysym == MOUSE_KEYSYM)
	    return sl_read_mouse_event(code);
#endif

	if (keysym < 0) {
	    return 0;

	} else if (keysym & (LKC_ISLECLAC | LKC_ISLAC)) {
	    return (keysym);
	} else {
	    current_sl_modifier = 0;
	    if (LKC_HAS_ESC_MOD(keysym)) {
		current_sl_modifier = LKC_MOD2;
		keysym &= LKC_MASK;
	    }

	    if (keysym + 1 >= KEYMAP_SIZE) {
		return 0;
	    } else {
		return (keysym | current_sl_modifier);
	    }
	}
    }
}

/************************************************************************/
#else /* NOT  defined(USE_KEYMAPS) && defined(USE_SLANG) */

/*
 * LYgetch() translates some escape sequences and may fake noecho.
 */
#define found_CSI(first,second) ((second) == '[' || (first) == 155)
#define found_TLD(value)	((value) == '~')

static int LYgetch_for(int code)
{
    int a, b, c, d = -1;
    int current_modifier = 0;
    BOOLEAN done_esc = FALSE;

    (void) code;

    have_levent = 0;

  re_read:
#if !defined(UCX) || !defined(VAXC)	/* errno not modifiable ? */
    if (errno == EINTR)
	set_errno(0);		/* reset - kw */
#endif /* UCX && VAXC */
#ifndef USE_SLANG
    clearerr(stdin);		/* needed here for ultrix and SOCKETSHR, but why? - FM */
#endif /* !USE_SLANG */
#if !defined(USE_SLANG) || defined(VMS) || defined(DJGPP_KEYHANDLER)
    c = GetChar();
    lynx_nl2crlf(FALSE);
#else
    if (LYCursesON) {
	c = GetChar();
	lynx_nl2crlf(FALSE);
    } else {
	c = getchar();
	if (c == EOF && errno == EINTR)		/* Ctrl-Z causes EINTR in getchar() */
	    clearerr(stdin);
	if (feof(stdin) || ferror(stdin) || c == EOF) {
#ifdef IGNORE_CTRL_C
	    if (sigint)
		sigint = FALSE;
#endif /* IGNORE_CTRL_C */
	    CTRACE((tfp, "GETCH: Translate ^C to ^G.\n"));
	    return (LYCharINTERRUPT2);	/* use ^G to cancel whatever called us. */
	}
    }
#endif /* !USE_SLANG || VMS */

    CTRACE((tfp, "GETCH%d: Got %#x.\n", code, (unsigned) c));
    if (LYNoZapKey > 1 && errno != EINTR &&
	(c == EOF
#ifdef USE_SLANG
	 || (c == 0xFFFF)
#endif
	)) {

	CTRACE((tfp,
		"nozap: Got EOF, curses %s, stdin is %p, LYNoZapKey reduced from %d to 0.\n",
		LYCursesON ? "on" : "off", (void *) stdin, LYNoZapKey));
	LYNoZapKey = 0;		/* 2 -> 0 */
	if (LYReopenInput() > 0) {
	    if (LYCursesON) {
		stop_curses();
		start_curses();
		LYrefresh();
	    }
	    goto re_read;
	}
    }
#ifdef USE_GETCHAR
    if (c == EOF && errno == EINTR)	/* Ctrl-Z causes EINTR in getchar() */
	goto re_read;
#else
    if (c == EOF && errno == EINTR) {

	CTRACE((tfp, "Got EOF with EINTR, recent_sizechange is %d\n",
		recent_sizechange));
#if defined(HAVE_SIZECHANGE) || defined(USE_SLANG)
	CheckScreenSize();
	if (!recent_sizechange) {	/* not yet detected by ourselves */
	    size_change(0);
	    CTRACE((tfp, "Now recent_sizechange is %d\n", recent_sizechange));
	}
#elif !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */
	set_errno(0);		/* reset - kw */
#endif /* UCX && VAXC */
	return (DO_NOTHING);
    }
#endif /* USE_GETCHAR */

#ifdef USE_SLANG
    if (c == 0xFFFF && LYCursesON) {
#ifdef IGNORE_CTRL_C
	if (sigint) {
	    sigint = FALSE;
	    goto re_read;
	}
#endif /* IGNORE_CTRL_C */
	return (LYCharINTERRUPT2);	/* use ^G to cancel whatever called us. */
    }
#else /* not USE_SLANG: */
    if (feof(stdin) || ferror(stdin) || c == EOF) {
	CheckScreenSize();
	if (recent_sizechange)
	    return (LYCharINTERRUPT2);	/* use ^G to cancel whatever called us. */
#ifdef IGNORE_CTRL_C
	if (sigint) {
	    sigint = FALSE;
	    /* clearerr(stdin);  don't need here if stays above - FM */
	    goto re_read;
	}
#endif /* IGNORE_CTRL_C */
#if !defined(USE_GETCHAR) && !defined(VMS) && !defined(NCURSES)
	if (c == ERR && errno == EINTR)		/* may have been handled signal - kw */
	    goto re_read;
#endif /* USE_GETCHAR */

	cleanup();
	exit_immediately(EXIT_SUCCESS);
    }
#endif /* USE_SLANG */

    if (!escape_bound
	&& (c == CH_ESC || (csi_is_csi && c == UCH(CH_ESC_PAR)))) {
	/* handle escape sequence  S/390 -- gil -- 2024 */
	done_esc = TRUE;	/* Flag: we did it, not keypad() */
	b = GetChar();

	if (b == '[' || b == 'O') {
	    a = GetChar();
	} else {
	    a = b;
	}

	switch (a) {
	case 'A':
	    c = UPARROW_KEY;
	    break;
	case 'B':
	    c = DNARROW_KEY;
	    break;
	case 'C':
	    c = RTARROW_KEY;
	    break;
	case 'D':
	    c = LTARROW_KEY;
	    break;
	case 'q':		/* vt100 application keypad 1 */
	    c = END_KEY;
	    break;
	case 'r':		/* vt100 application keypad 2 */
	    c = DNARROW_KEY;
	    break;
	case 's':		/* vt100 application keypad 3 */
	    c = PGDOWN_KEY;
	    break;
	case 't':		/* vt100 application keypad 4 */
	    c = LTARROW_KEY;
	    break;
	case 'v':		/* vt100 application keypad 6 */
	    c = RTARROW_KEY;
	    break;
	case 'w':		/* vt100 application keypad 7 */
	    c = HOME_KEY;
	    break;
	case 'x':		/* vt100 application keypad 8 */
	    c = UPARROW_KEY;
	    break;
	case 'y':		/* vt100 application keypad 9 */
	    c = PGUP_KEY;
	    break;
	case 'M':
#if defined(USE_SLANG) && defined(USE_MOUSE)
	    if (found_CSI(c, b)) {
		c = sl_read_mouse_event(code);
	    } else
#endif
		c = '\n';	/* keypad enter on pc ncsa telnet */
	    break;

	case 'm':
#ifdef VMS
	    if (b != 'O')
#endif /* VMS */
		c = '-';	/* keypad on pc ncsa telnet */
	    break;
	case 'k':
	    if (b == 'O')
		c = '+';	/* keypad + on my xterminal :) */
	    else
		done_esc = FALSE;	/* we have another look below - kw */
	    break;
	case 'l':
#ifdef VMS
	    if (b != 'O')
#endif /* VMS */
		c = '+';	/* keypad on pc ncsa telnet */
	    break;
	case 'P':
#ifdef VMS
	    if (b != 'O')
#endif /* VMS */
		c = F1_KEY;
	    break;
	case 'u':
#ifdef VMS
	    if (b != 'O')
#endif /* VMS */
		c = F1_KEY;	/* macintosh help button */
	    break;
	case 'p':
#ifdef VMS
	    if (b == 'O')
#endif /* VMS */
		c = '0';	/* keypad 0 */
	    break;
	case '1':		/* VTxxx  Find  */
	    if (found_CSI(c, b) && found_TLD(d = GetChar()))
		c = FIND_KEY;
	    else
		done_esc = FALSE;	/* we have another look below - kw */
	    break;
	case '2':
	    if (found_CSI(c, b)) {
		if (found_TLD(d = GetChar()))	/* VTxxx Insert */
		    c = INSERT_KEY;
		else if ((d == '8' ||
			  d == '9') &&
			 found_TLD(GetChar())) {
		    if (d == '8')	/* VTxxx   Help */
			c = F1_KEY;
		    else if (d == '9')	/* VTxxx    Do  */
			c = DO_KEY;
		    d = -1;
		}
	    } else
		done_esc = FALSE;	/* we have another look below - kw */
	    break;
	case '3':			     /** VTxxx Delete **/
	    if (found_CSI(c, b) && found_TLD(d = GetChar()))
		c = REMOVE_KEY;
	    else
		done_esc = FALSE;	/* we have another look below - kw */
	    break;
	case '4':			     /** VTxxx Select **/
	    if (found_CSI(c, b) && found_TLD(d = GetChar()))
		c = SELECT_KEY;
	    else
		done_esc = FALSE;	/* we have another look below - kw */
	    break;
	case '5':			     /** VTxxx PrevScreen **/
	    if (found_CSI(c, b) && found_TLD(d = GetChar()))
		c = PGUP_KEY;
	    else
		done_esc = FALSE;	/* we have another look below - kw */
	    break;
	case '6':			     /** VTxxx NextScreen **/
	    if (found_CSI(c, b) && found_TLD(d = GetChar()))
		c = PGDOWN_KEY;
	    else
		done_esc = FALSE;	/* we have another look below - kw */
	    break;
	case '[':			     /** Linux F1-F5: ^[[[A etc. **/
	    if (found_CSI(c, b)) {
		if ((d = GetChar()) == 'A')
		    c = F1_KEY;
		break;
	    }
	    /* FALLTHRU */
	default:
	    if (c == CH_ESC && a == b && !found_CSI(c, b)) {
		current_modifier = LKC_MOD2;
		c = a;
		/* We're not yet done if ESC + curses-keysym: */
		done_esc = (BOOL) ((a & ~0xFF) == 0);
		break;
	    }
	    CTRACE((tfp, "Unknown key sequence: %d:%d:%d\n", c, b, a));
	    CTRACE_SLEEP(MessageSecs);
	    break;
	}
	if (isdigit(a) && found_CSI(c, b) && d != -1 && !found_TLD(d))
	    d = GetChar();
	if (!done_esc && (a & ~0xFF) == 0) {
	    if (a == b && !found_CSI(c, b) && c == CH_ESC) {
		current_modifier = LKC_MOD2;
		c = a;
		done_esc = TRUE;
	    } else {
		done_esc = TRUE;
	    }
	}
    }
#ifdef USE_KEYMAPS
    /* Extract a single code if two are merged: */
    if (c >= 0 && (c & LKC_ISLECLAC)) {
	if (!(code == FOR_INPUT || code == FOR_PROMPT))
	    c = LKC2_TO_LKC(c);
    } else if (c >= 0 && (c & LKC_ISLKC)) {
	c &= ~LKC_ISLKC;
	done_esc = TRUE;	/* already a lynxkeycode, skip keypad switches - kw */
    }
    if (c >= 0 && LKC_HAS_ESC_MOD(c)) {
	current_modifier = LKC_MOD2;
	c &= LKC_MASK;
    }
    if (c >= 0 && (c & (LKC_ISLECLAC | LKC_ISLAC))) {
	done_esc = TRUE;	/* already a lynxactioncode, skip keypad switches - iz */
    }
#endif
    if (done_esc) {
	/* don't do keypad() switches below, we already got it - kw */
    } else {
#ifdef HAVE_KEYPAD
	/*
	 * Convert keypad() mode keys into Lynx defined keys.
	 */
	switch (c) {
	case KEY_DOWN:		/* The four arrow keys ... */
	    c = DNARROW_KEY;
	    break;
	case KEY_UP:
	    c = UPARROW_KEY;
	    break;
	case KEY_LEFT:
	    c = LTARROW_KEY;
	    break;
	case KEY_RIGHT:	/* ... */
	    c = RTARROW_KEY;
	    break;
#if defined(PDCURSES)		/* for NEC PC-9800 1998/08/30 (Sun) 21:50:35 */
	case KEY_C2:
	    c = DNARROW_KEY;
	    break;
	case KEY_A2:
	    c = UPARROW_KEY;
	    break;
	case KEY_B1:
	    c = LTARROW_KEY;
	    break;
	case KEY_B3:
	    c = RTARROW_KEY;
	    break;
	case PAD0:		/* PC-9800 Ins */
	    c = INSERT_KEY;
	    break;
	case PADSTOP:		/* PC-9800 DEL */
	    c = REMOVE_KEY;
	    break;
#endif /* PDCURSES */
	case KEY_HOME:		/* Home key (upward+left arrow) */
	    c = HOME_KEY;
	    break;
	case KEY_CLEAR:	/* Clear screen */
	    c = 18;		/* CTRL-R */
	    break;
	case KEY_NPAGE:	/* Next page */
	    c = PGDOWN_KEY;
	    break;
	case KEY_PPAGE:	/* Previous page */
	    c = PGUP_KEY;
	    break;
	case KEY_LL:		/* home down or bottom (lower left) */
	    c = END_KEY;
	    break;
#if defined(KEY_A1) && defined(KEY_C3)
	    /* The keypad is arranged like this: */
	    /*    a1    up    a3   */
	    /*   left   b2  right  */
	    /*    c1   down   c3   */
	case KEY_A1:		/* upper left of keypad */
	    c = HOME_KEY;
	    break;
	case KEY_A3:		/* upper right of keypad */
	    c = PGUP_KEY;
	    break;
	case KEY_B2:		/* center of keypad */
	    c = DO_NOTHING;
	    break;
	case KEY_C1:		/* lower left of keypad */
	    c = END_KEY;
	    break;
	case KEY_C3:		/* lower right of keypad */
	    c = PGDOWN_KEY;
	    break;
#endif /* defined(KEY_A1) && defined(KEY_C3) */
#ifdef KEY_ENTER
	case KEY_ENTER:	/* enter/return      */
	    c = '\n';
	    break;
#endif /* KEY_ENTER */
#ifdef PADENTER			/* PDCURSES */
	case PADENTER:
	    c = '\n';
	    break;
#endif /* PADENTER */
#ifdef KEY_END
	case KEY_END:		/* end key           001 */
	    c = END_KEY;
	    break;
#endif /* KEY_END */
#ifdef KEY_HELP
	case KEY_HELP:		/* help key          001 */
	    c = F1_KEY;
	    break;
#endif /* KEY_HELP */
#ifdef KEY_BACKSPACE
	case KEY_BACKSPACE:
	    c = CH_DEL;		/* backspace key (delete, not Ctrl-H)  S/390 -- gil -- 2041 */
	    break;
#endif /* KEY_BACKSPACE */
	case KEY_F(1):
	    c = F1_KEY;		/* VTxxx Help */
	    break;
#if defined(KEY_F) && !defined(__DJGPP__) && !defined(_WINDOWS)
	case KEY_F(16):
	    c = DO_KEY;		/* VTxxx Do */
	    break;
#endif /* KEY_F */
#ifdef KEY_REDO
	case KEY_REDO:		/* VTxxx Do */
	    c = DO_KEY;
	    break;
#endif /* KEY_REDO */
#ifdef KEY_FIND
	case KEY_FIND:
	    c = FIND_KEY;	/* VTxxx Find */
	    break;
#endif /* KEY_FIND */
#ifdef KEY_SELECT
	case KEY_SELECT:
	    c = SELECT_KEY;	/* VTxxx Select */
	    break;
#endif /* KEY_SELECT */
#ifdef KEY_IC
	case KEY_IC:
	    c = INSERT_KEY;	/* VTxxx Insert */
	    break;
#endif /* KEY_IC */
#ifdef KEY_DC
	case KEY_DC:
	    c = REMOVE_KEY;	/* VTxxx Remove */
	    break;
#endif /* KEY_DC */
#ifdef KEY_BTAB
	case KEY_BTAB:
	    c = BACKTAB_KEY;	/* Back tab, often Shift-Tab */
	    break;
#endif /* KEY_BTAB */
#ifdef KEY_RESIZE
	case KEY_RESIZE:
	    c = DO_NOTHING;
	    break;
#endif /* KEY_RESIZE */

/* The following maps PDCurses keys away from lynx reserved values */
#if (defined(_WINDOWS) || defined(__DJGPP__)) && !defined(USE_SLANG)
	case KEY_F(2):
	    c = 0x213;
	    break;
	case KEY_F(3):
	    c = 0x214;
	    break;
	case KEY_F(4):
	    c = 0x215;
	    break;
	case KEY_F(5):
	    c = 0x216;
	    break;
	case KEY_F(6):
	    c = 0x217;
	    break;
	case KEY_F(7):
	    c = 0x218;
	    break;
#endif /* PDCurses */

#if defined(USE_MOUSE)
/********************************************************************/

#if defined(NCURSES) || defined(PDCURSES)
	case KEY_MOUSE:
	    CTRACE((tfp, "KEY_MOUSE\n"));
	    if (code == FOR_CHOICE) {
		c = MOUSE_KEY;	/* Will be processed by the caller */
	    }
#if defined(NCURSES)
	    else if (code == FOR_SINGLEKEY) {
		MEVENT event;

		getmouse(&event);	/* Completely ignore event - kw */
		c = DO_NOTHING;
	    }
#endif
	    else {
#if defined(NCURSES)
		MEVENT event;
		int err;
		int lac = LYK_UNKNOWN;

		c = -1;
		mouse_link = -1;
		err = getmouse(&event);
		if (err != OK) {
		    CTRACE((tfp, "Mouse error: no event available!\n"));
		    return (code == FOR_PANEL ? 0 : DO_NOTHING);
		}
		levent = event;	/* Allow setting pos in entry fields */
		if (event.bstate & BUTTON1_CLICKED) {
		    c = set_clicked_link(event.x, event.y, code, 1);
		} else if (event.bstate & BUTTON1_DOUBLE_CLICKED) {
		    c = set_clicked_link(event.x, event.y, code, 2);
		    if (c == LAC_TO_LKC0(LYK_MOUSE_SUBMIT) &&
			code == FOR_INPUT)
			lac = LYK_MOUSE_SUBMIT;
		} else if (event.bstate & BUTTON3_CLICKED) {
		    c = LAC_TO_LKC0(LYK_PREV_DOC);
		} else if (code == FOR_PROMPT
		    /* Cannot ignore: see LYCurses.c */
			   || (event.bstate &
			       (BUTTON1_PRESSED | BUTTON1_RELEASED
				| BUTTON2_PRESSED | BUTTON2_RELEASED
				| BUTTON3_PRESSED | BUTTON3_RELEASED))) {
		    /* Completely ignore - don't return anything, to
		       avoid canceling the prompt - kw */
		    goto re_read;
		} else if (event.bstate & BUTTON2_CLICKED) {
		    int atlink;

		    c = set_clicked_link(event.x, event.y, code, 1);
		    atlink = (c == LAC_TO_LKC0(LYK_ACTIVATE));
		    if (!atlink)
			mouse_link = -1;	/* Forget about approx stuff. */

		    lac = LYmouse_menu(event.x, event.y, atlink, code);
		    if (lac == LYK_MOUSE_SUBMIT) {
			if (mouse_link == -1)
			    lac = LYK_ACTIVATE;
#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
			else if (mouse_link >= 0 &&
				 textfields_need_activation &&
				 links[mouse_link].type == WWW_FORM_LINK_TYPE &&
				 F_TEXTLIKE(links[mouse_link].l_form->type))
			    lac = LYK_ACTIVATE;
#endif
		    }
		    if (lac == LYK_ACTIVATE && mouse_link == -1) {
			HTAlert(gettext("No link chosen"));
			lac = LYK_REFRESH;
		    }
		    c = LAC_TO_LKC(lac);
		}
#if NCURSES_MOUSE_VERSION > 1
		else if (event.bstate & BUTTON4_PRESSED) {
		    c = LAC_TO_LKC(LYK_UP_HALF);
		} else if (event.bstate & BUTTON5_PRESSED) {
		    c = LAC_TO_LKC(LYK_DOWN_HALF);
		}
#endif
		if (code == FOR_INPUT && mouse_link == -1 &&
		    lac != LYK_REFRESH &&
		    lac != LYK_MOUSE_SUBMIT) {
		    ungetmouse(&event);		/* Caller will process this. */
		    wgetch(LYwin);	/* ungetmouse puts KEY_MOUSE back */
		    c = MOUSE_KEY;
		}
#else /* pdcurses version */

#define H_CMD_AREA	6
#define HIST_CMD_2	12
#define V_CMD_AREA	1

		int left = H_CMD_AREA;
		int right = (LYcolLimit - H_CMD_AREA - 1);

		/* yes, I am assuming that my screen will be a certain width. */

		int tick_count;
		char *p = NULL;
		char mouse_info[128];
		static int old_click = 0;	/* [m Sec] */

		c = -1;
		mouse_link = -1;

		if (!system_is_NT) {
		    tick_count = GetTickCount();

		    /* Guard Mouse button miss click */
		    if ((tick_count - old_click) < 700) {
			c = DO_NOTHING;
			break;
		    } else {
			old_click = tick_count;
		    }
		}
		request_mouse_pos();

		if (BUTTON_STATUS(1) & BUTTON_PRESSED) {
		    if (MOUSE_Y_POS > (LYlines - V_CMD_AREA - 1)) {
			/* Screen BOTTOM */
			if (MOUSE_X_POS < left) {
			    c = LTARROW_KEY;
			    p = "<-";
			} else if (MOUSE_X_POS < HIST_CMD_2) {
			    c = RTARROW_KEY;
			    p = "->";
			} else if (MOUSE_X_POS > right) {
			    c = 'z';
			    p = "Cancel";
			} else {
			    c = PGDOWN_KEY;
			    p = "PGDOWN";
			}
		    } else if (MOUSE_Y_POS < V_CMD_AREA) {
			/* Screen TOP */
			if (MOUSE_X_POS < left) {
			    c = LTARROW_KEY;
			    p = "<-";
			} else if (MOUSE_X_POS < HIST_CMD_2) {
			    c = RTARROW_KEY;
			    p = "->";
			} else if (MOUSE_X_POS > right) {
			    c = 'z';
			    p = "Cancel";
			} else {
			    c = PGUP_KEY;
			    p = "PGUP";
			}
		    } else {
			c = set_clicked_link(MOUSE_X_POS,
					     MOUSE_Y_POS,
					     FOR_PANEL, 1);
		    }
		}
		if (p && c != -1) {
		    sprintf(mouse_info, "Mouse = 0x%x, [%s]", c, p);
		    SetConsoleTitle(mouse_info);
		}
#endif /* !(WIN_EX) */
		if ((c + 1) >= KEYMAP_SIZE && (c & LKC_ISLAC))
		    return (c);
	    }
	    break;
#endif /* NCURSES || PDCURSES */

/********************************************************************/
#endif /* USE_MOUSE */

	}
#endif /* HAVE_KEYPAD */
#ifdef DJGPP_KEYHANDLER
	switch (c) {
	case K_Down:		/* The four arrow keys ... */
	case K_EDown:
	    c = DNARROW_KEY;
	    break;
	case K_Up:
	case K_EUp:
	    c = UPARROW_KEY;
	    break;
	case K_Left:
	case K_ELeft:
	    c = LTARROW_KEY;
	    break;
	case K_Right:		/* ... */
	case K_ERight:
	    c = RTARROW_KEY;
	    break;
	case K_Home:		/* Home key (upward+left arrow) */
	case K_EHome:
	    c = HOME_KEY;
	    break;
	case K_PageDown:	/* Next page */
	case K_EPageDown:
	    c = PGDOWN_KEY;
	    break;
	case K_PageUp:		/* Previous page */
	case K_EPageUp:
	    c = PGUP_KEY;
	    break;
	case K_End:		/* home down or bottom (lower left) */
	case K_EEnd:
	    c = END_KEY;
	    break;
	case K_F1:		/* F1 key */
	    c = F1_KEY;
	    break;
	case K_Insert:		/* Insert key */
	case K_EInsert:
	    c = INSERT_KEY;
	    break;
	case K_Delete:		/* Delete key */
	case K_EDelete:
	    c = REMOVE_KEY;
	    break;
	case K_Alt_Escape:	/* Alt-Escape */
	    c = 0x1a7;
	    break;
	case K_Control_At:	/* CTRL-@ */
	    c = 0x1a8;
	    break;
	case K_Alt_Backspace:	/* Alt-Backspace */
	    c = 0x1a9;
	    break;
	case K_BackTab:	/* BackTab */
	    c = BACKTAB_KEY;
	    break;
	}
#endif /* DGJPP_KEYHANDLER */
#if defined(USE_SLANG) && (defined(__DJGPP__) || defined(__CYGWIN__)) && !defined(DJGPP_KEYHANDLER)  && !defined(USE_KEYMAPS)
	switch (c) {
	case SL_KEY_DOWN:	/* The four arrow keys ... */
	    c = DNARROW_KEY;
	    break;
	case SL_KEY_UP:
	    c = UPARROW_KEY;
	    break;
	case SL_KEY_LEFT:
	    c = LTARROW_KEY;
	    break;
	case SL_KEY_RIGHT:	/* ... */
	    c = RTARROW_KEY;
	    break;
	case SL_KEY_HOME:	/* Home key (upward+left arrow) */
	case SL_KEY_A1:	/* upper left of keypad */
	    c = HOME_KEY;
	    break;
	case SL_KEY_NPAGE:	/* Next page */
	case SL_KEY_C3:	/* lower right of keypad */
	    c = PGDOWN_KEY;
	    break;
	case SL_KEY_PPAGE:	/* Previous page */
	case SL_KEY_A3:	/* upper right of keypad */
	    c = PGUP_KEY;
	    break;
	case SL_KEY_END:	/* home down or bottom (lower left) */
	case SL_KEY_C1:	/* lower left of keypad */
	    c = END_KEY;
	    break;
	case SL_KEY_F(1):	/* F1 key */
	    c = F1_KEY;
	    break;
	case SL_KEY_IC:	/* Insert key */
	    c = INSERT_KEY;
	    break;
	case SL_KEY_DELETE:	/* Delete key */
	    c = REMOVE_KEY;
	    break;
	}
#endif /* USE_SLANG && __DJGPP__ && !DJGPP_KEYHANDLER && !USE_KEYMAPS */
    }

    if (c & (LKC_ISLAC | LKC_ISLECLAC)) {
	return (c);
    } else if ((c + 1) >= KEYMAP_SIZE) {
	/*
	 * Don't return raw values for KEYPAD symbols which we may have missed
	 * in the switch above if they are obviously invalid when used as an
	 * index into (e.g.) keypad[].  - KW
	 */
	return (0);
    } else {
	return (c | current_modifier);
    }
}

/************************************************************************/
#endif /* NOT  defined(USE_KEYMAPS) && defined(USE_SLANG) */

int LYgetch(void)
{
    return LYReadCmdKey(FOR_PANEL);
}

/*
 * Read a single keystroke, allows mouse-selection.
 */
int LYgetch_choice(void)
{
    int ch = LYReadCmdKey(FOR_CHOICE);

    if (ch == LYCharINTERRUPT1)
	ch = LYCharINTERRUPT2;	/* treat ^C the same as ^G */
    return ch;
}

/*
 * Read a single keystroke, allows mouse events.
 */
int LYgetch_input(void)
{
    int ch = LYReadCmdKey(FOR_INPUT);

    if (ch == LYCharINTERRUPT1)
	ch = LYCharINTERRUPT2;	/* treat ^C the same as ^G */
    return ch;
}

/*
 * Read a single keystroke, ignoring case by translating it to uppercase.
 * Ignore mouse events, if any.
 */
int LYgetch_single(void)
{
    int ch = LYReadCmdKey(FOR_SINGLEKEY);

    if (ch == LYCharINTERRUPT1)
	ch = LYCharINTERRUPT2;	/* treat ^C the same as ^G */
    else if (ch > 0 && ch < 256)
	ch = TOUPPER(ch);	/* will ignore case of result */
    return ch;
}

/*
 * Convert a null-terminated string to lowercase
 */
void LYLowerCase(char *arg_buffer)
{
    register unsigned char *buffer = (unsigned char *) arg_buffer;
    size_t i;

    for (i = 0; buffer[i]; i++) {
#ifdef SUPPORT_MULTIBYTE_EDIT	/* 1998/11/23 (Mon) 17:04:55 */
	if ((buffer[i] & 0x80) != 0
	    && buffer[i + 1] != 0) {
	    if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) {
		continue;
	    }
	    i++;
	} else {
	    buffer[i] = UCH(TOLOWER(buffer[i]));
	}
#else
	buffer[i] = TOLOWER(buffer[i]);
#endif
    }
}

/*
 * Convert a null-terminated string to uppercase
 */
void LYUpperCase(char *arg_buffer)
{
    register unsigned char *buffer = (unsigned char *) arg_buffer;
    size_t i;

    for (i = 0; buffer[i]; i++) {
#ifdef SUPPORT_MULTIBYTE_EDIT	/* 1998/11/23 (Mon) 17:05:10 */
	if ((buffer[i] & 0x80) != 0
	    && buffer[i + 1] != 0) {
	    if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) {
		continue;
	    }
	    i++;
	} else {
	    buffer[i] = UCH(TOUPPER(buffer[i]));
	}
#else
	buffer[i] = UCH(TOUPPER(buffer[i]));
#endif
    }
}

/*
 * Remove newlines from a string, returning true if we removed any.
 */
BOOLEAN LYRemoveNewlines(char *buffer)
{
    BOOLEAN result = FALSE;

    if (buffer != 0) {
	register char *buf = buffer;

	for (; *buf && *buf != '\n' && *buf != '\r'; buf++) ;
	if (*buf) {
	    /* runs very seldom */
	    char *old = buf;

	    for (; *old; old++) {
		if (*old != '\n' && *old != '\r')
		    *buf++ = *old;
	    }
	    *buf = '\0';
	    result = TRUE;
	}
    }
    return result;
}

/*
 * Remove leading/trailing whitespace from a string, reduce runs of embedded
 * whitespace to single blanks.
 */
char *LYReduceBlanks(char *buffer)
{
    if (non_empty(buffer)) {
	LYTrimLeading(buffer);
	LYTrimTrailing(buffer);
	convert_to_spaces(buffer, TRUE);
    }
    return buffer;
}

/*
 * Remove ALL whitespace from a string (including embedded blanks), and returns
 * a pointer to the end of the trimmed string.
 */
char *LYRemoveBlanks(char *buffer)
{
    char *result = NULL;

    if (buffer != 0) {
	register char *buf = buffer;

	for (; *buf && !isspace(UCH(*buf)); buf++) ;
	if (*buf) {
	    /* runs very seldom */
	    char *old = buf;

	    for (; *old; old++) {
		if (!isspace(UCH(*old)))
		    *buf++ = *old;
	    }
	    *buf = '\0';
	}
	result = buf;
    }
    return result;
}

/*
 * Skip whitespace
 */
char *LYSkipBlanks(char *buffer)
{
    if (buffer != NULL) {
	while (isspace(UCH((*buffer))))
	    buffer++;
    }
    return buffer;
}

/*
 * Skip non-whitespace
 */
char *LYSkipNonBlanks(char *buffer)
{
    if (buffer != NULL) {
	while (*buffer != 0 && !isspace(UCH((*buffer))))
	    buffer++;
    }
    return buffer;
}

/*
 * Skip const whitespace
 */
const char *LYSkipCBlanks(const char *buffer)
{
    while (isspace(UCH((*buffer))))
	buffer++;
    return buffer;
}

/*
 * Skip const non-whitespace
 */
const char *LYSkipCNonBlanks(const char *buffer)
{
    while (*buffer != 0 && !isspace(UCH((*buffer))))
	buffer++;
    return buffer;
}

/*
 * Trim leading blanks from a string
 */
void LYTrimLeading(char *buffer)
{
    char *skipped = LYSkipBlanks(buffer);

    while ((*buffer++ = *skipped++) != 0) ;
}

/*
 * Trim trailing newline(s) from a string
 */
char *LYTrimNewline(char *buffer)
{
    size_t i = strlen(buffer);

    while (i != 0 && (buffer[i - 1] == '\n' || buffer[i - 1] == '\r'))
	buffer[--i] = 0;
    return buffer;
}

/*
 * Trim trailing blanks from a string
 */
void LYTrimTrailing(char *buffer)
{
    size_t i = strlen(buffer);

    while (i != 0 && isspace(UCH(buffer[i - 1])))
	buffer[--i] = 0;
}

/* 1997/11/10 (Mon) 14:26:10, originally string_short() in LYExterns.c, but
 * moved here because LYExterns is not always configured.
 */
char *LYElideString(char *str,
		    int cut_pos)
{
    char buff[MAX_LINE], *s, *d;
    static char s_str[MAX_LINE];
    int len;

    LYStrNCpy(buff, str, sizeof(buff) - 1);
    len = (int) strlen(buff);
    if (len > (LYcolLimit - 9)) {
	buff[cut_pos] = '.';
	buff[cut_pos + 1] = '.';
	for (s = (buff + len) - (LYcolLimit - 9) + cut_pos + 1,
	     d = (buff + cut_pos) + 2;
	     s >= buff &&
	     d >= buff &&
	     d < buff + LYcols &&
	     (*d++ = *s++) != 0;) ;
	buff[LYcols] = 0;
    }
    strcpy(s_str, buff);
    return (s_str);
}

/*
 * Trim a startfile, returning true if it looks like one of the Lynx tags.
 */
BOOLEAN LYTrimStartfile(char *buffer)
{
    BOOLEAN result = FALSE;

    LYTrimHead(buffer);
    if (isLYNXEXEC(buffer) ||
	isLYNXPROG(buffer)) {
	/*
	 * The original implementations of these schemes expected white space
	 * without hex escaping, and did not check for hex escaping, so we'll
	 * continue to support that, until that code is redone in conformance
	 * with SGML principles.  - FM
	 */
	HTUnEscapeSome(buffer, " \r\n\t");
	convert_to_spaces(buffer, TRUE);
	result = TRUE;
    }
    return result;
}

/*
 * Escape unsafe characters in startfile, except for lynx internal URLs.
 */
void LYEscapeStartfile(char **buffer)
{
    if (!LYTrimStartfile(*buffer)) {
	char *escaped = HTEscapeUnsafe(*buffer);

	StrAllocCopy(*buffer, escaped);
	FREE(escaped);
    }
}

/*
 * Trim all blanks from startfile, except for lynx internal URLs.
 */
void LYTrimAllStartfile(char *buffer)
{
    if (!LYTrimStartfile(buffer)) {
	LYRemoveBlanks(buffer);
    }
}

/*
 * Display the current value of the string and allow the user to edit it.
 */

/*
 * Shorthand to get rid of the "edit->suchandsos".
 */
#define IsDirty   edit->efIsDirty
#define IsHidden  edit->efIsMasked
#define StartX    edit->efStartX
#define StartY    edit->efStartY
#define Buffer    edit->efBuffer
#define EditAt    edit->efEditAt	/* current editing position (bytes) */
#define BufInUse  edit->efBufInUse	/* length (bytes) */
#define BufAlloc  edit->efBufAlloc
#define BufLimit  edit->efBufLimit
#define DpyWidth  edit->efWidth
#define DpyStart  edit->efDpyStart	/* display-start (columns) */
#define PanMargin edit->efPanMargin
#define IsPanned  edit->efIsPanned
#define PadChar   edit->efPadChar
#ifdef ENHANCED_LINEEDIT
#define EditMark  edit->efEditMark
#endif
#define InputMods edit->efInputMods
#define Offs2Col  edit->efOffs2Col

#define enableEditMark() \
	if (EditMark < 0) \
	    EditMark = -(1 + EditMark)

#define disableEditMark() \
	if (EditMark >= 0) \
	    EditMark = -(1 + EditMark)

#ifdef ENHANCED_LINEEDIT
static bstring *killbuffer;
#endif

static void updateMargin(FieldEditor * edit)
{
    if ((int) BufAlloc > DpyWidth) {	/* Need panning? */
	if (DpyWidth > 4)
	    IsPanned = TRUE;

	/*
	 * Figure out margins.  If too big, we do a lot of unnecessary
	 * scrolling.  If too small, user doesn't have sufficient look-ahead. 
	 * Let's say 25% for each margin, upper bound is 10 columns.
	 */
	PanMargin = DpyWidth / 4;
	if (PanMargin > 10)
	    PanMargin = 10;
    }
}

/*
 * Before using an array position, make sure that the array is long enough.
 * Reallocate if needed.
 */
static void ExtendEditor(FieldEditor * edit, int position)
{
    size_t need = (size_t) (++position);

    if (need >= BufAlloc && (BufLimit == 0 || need < BufLimit)) {
	CTRACE((tfp, "ExtendEditor from %lu to %lu\n",
		(unsigned long) BufAlloc,
		(unsigned long) need));
	Buffer = typeRealloc(char, Buffer, need);
	Offs2Col = typeRealloc(int, Offs2Col, need + 1);

	BufAlloc = need;
	updateMargin(edit);
    }
}

void LYFinishEdit(FieldEditor * edit)
{
    CTRACE((tfp, "LYFinishEdit:%s\n", NonNull(Buffer)));

    FREE(Buffer);
    FREE(Offs2Col);
}

void LYSetupEdit(FieldEditor * edit, char *old_value, unsigned buffer_limit, int display_limit)
{
    CTRACE((tfp, "LYSetupEdit buffer %lu, display %d:%s\n",
	    (unsigned long) buffer_limit,
	    display_limit,
	    old_value));

    BufLimit = buffer_limit;
    if (buffer_limit == 0)
	buffer_limit = (unsigned) strlen(old_value) + 1;

    /*
     * Initialize edit record
     */
    LYGetYX(StartY, StartX);
    PadChar = ' ';
    IsDirty = TRUE;
    IsPanned = FALSE;
    InputMods = 0;

    BufAlloc = buffer_limit;
    DpyWidth = display_limit;
    PanMargin = 0;
    EditAt = (int) strlen(old_value);
#ifdef ENHANCED_LINEEDIT
    EditMark = -1;		/* pos=0, but do not show it yet */
#endif
    DpyStart = 0;

    updateMargin(edit);

    BufInUse = strlen(old_value);
    Buffer = typecallocn(char, BufAlloc + 1);

    if (Buffer == 0)
	outofmem(__FILE__, "LYSetupEdit");

    LYStrNCpy(Buffer, old_value, buffer_limit);
    Offs2Col = typecallocn(int, BufAlloc + 1);

    if (Offs2Col == 0)
	outofmem(__FILE__, "LYSetupEdit");
}

#ifdef SUPPORT_MULTIBYTE_EDIT

/*
 * MBCS positioning routines below are specific to SUPPORT_MULTIBYTE_EDIT code.
 * Currently they handle UTF-8 and (hopefully) CJK.
 * Current encoding is recognized using defines below.
 *
 * LYmbcs* functions don't look very convenient to use here...
 * Do we really need utf_flag as an argument?
 *
 * It is set (see IS_UTF8_TTY) for every invocation out there, and they use
 * HTCJK flag internally anyway.  Something like LYmbcsstrnlen == mbcs_glyphs
 * would be useful to work with string slices -Sergej Kvachonok 
 */

#define IS_UTF8_EXTRA(x) (((unsigned char)(x) & 0300) == 0200)

/*
 * Counts glyphs in a multibyte (sub)string s of length len.
 */
static int mbcs_glyphs(char *s, int len)
{
    int glyphs = 0;
    int i;

    if (IS_UTF8_TTY) {
	for (i = 0; s[i] && i < len; i++)
	    if (!IS_UTF8_EXTRA(s[i]))
		glyphs++;
    } else if (IS_CJK_TTY) {
	for (i = 0; s[i] && i < len; i++, glyphs++)
	    if (is8bits(s[i]))
		i++;
    } else {
	glyphs = len;
    }
    return glyphs;
}

/*
 * Check if there are no continuation bytes in the multibyte (sub)string of
 * length len.
 */
static int mbcs_valid(char *s, int len, int limit)
{
    int i;
    int result = FALSE;

    if (IS_UTF8_TTY) {
	for (i = 0; s[i] && i < limit; i++) {
	    if (!IS_UTF8_EXTRA(s[i])) {
		if ((i + 1) == len) {
		    result = TRUE;
		    break;
		}
	    }
	}
    } else if (IS_CJK_TTY) {
	for (i = 0; s[i] && i < limit; i++) {
	    if (!is8bits(s[i])) {
		if ((i + 1) == len) {
		    result = TRUE;
		    break;
		}
	    }
	}
    } else {
	result = TRUE;
    }
    return result;
}

/*
 * Calculates offset in bytes of a glyph at cell position pos.
 */
static int mbcs_skip(char *s, int pos)
{
    int p, i;

    if (IS_UTF8_TTY) {
	for (i = 0, p = 0; s[i]; i++) {
	    if (!IS_UTF8_EXTRA(s[i]))
		p++;
	    if (p > pos)
		break;
	}
    } else if (IS_CJK_TTY) {
	for (p = i = 0; s[i] && p < pos; p++, i++)
	    if (is8bits(s[i]))
		i++;
    } else {
	i = pos;
    }

    return i;
}

/*
 * Given a string that would display (at least) the given number of cells,
 * determine the number of multibyte characters that comprised those cells.
 */
static int cell2char(char *s, int cells)
{
    int result = 0;
    int len = (int) strlen(s);
    int pos;
    int have;

    CTRACE_EDIT((tfp, "cell2char(%d) %d:%s\n", cells, len, s));
    if (len != 0) {
	int best = -1;

	for (pos = 0; pos <= len; ++pos) {
	    have = LYstrExtent2(s, pos);
	    CTRACE_EDIT((tfp, "  %2d:%2d:%.*s\n", pos, have, pos, s));
	    if (have >= cells) {
		if (cells <= 0)
		    break;
		/* the best solution is the one with the most bytes */
		best = pos;
		if (mbcs_valid(s, pos, len))
		    break;
	    }
	}
	if (best >= 0)
	    pos = best;
	if (pos > len)
	    pos = len;
    } else {
	pos = 0;
    }
    result = mbcs_glyphs(s, pos);
    CTRACE_EDIT((tfp, "->%d\n", result));
    return result;
}

#endif /* SUPPORT_MULTIBYTE_EDIT */

#ifdef EXP_KEYBOARD_LAYOUT
static int map_active = 0;

#else
#define map_active 0
#endif

int LYEditInsert(FieldEditor * edit, unsigned const char *s,
		 int len,
		 int map GCC_UNUSED,
		 int maxMessage)
{
    int length = (int) strlen(Buffer);
    int remains = (int) BufAlloc - (length + len);
    int edited = 0, overflow = 0;

    /*
     * ch is (presumably) printable character.
     */
    if (remains < 0) {
	overflow = 1;
	len = 0;
	if ((int) BufAlloc > length)	/* Insert as much as we can */
	    len = (int) BufAlloc - length;
	else
	    goto finish;
    }
    ExtendEditor(edit, length + len);
    Buffer[length + len] = '\0';
    for (; length >= EditAt; length--)	/* Make room */
	Buffer[length + len] = Buffer[length];
#ifdef EXP_KEYBOARD_LAYOUT
    if (map < 0)
	map = map_active;
    if (map && IS_UTF8_TTY) {
	int off = EditAt;
	unsigned const char *e = s + len;
	char *tail = 0;

	while (s < e) {
	    char utfbuf[8];
	    int l = 1;

	    utfbuf[0] = (char) *s;
	    if (*s < 128 && LYKbLayouts[current_layout][*s]) {
		UCode_t ucode = LYKbLayouts[current_layout][*s];

		if (ucode > 127) {
		    if (UCConvertUniToUtf8(ucode, utfbuf)) {
			l = (int) strlen(utfbuf);
			remains -= l - 1;
			if (remains < 0) {
			    if (tail)
				strcpy(Buffer + off, tail);
			    FREE(tail);
			    len = off;
			    overflow = 1;
			    goto finish;
			}
			if (l > 1 && !tail)
			    StrAllocCopy(tail, Buffer + EditAt + len);
		    } else
			utfbuf[0] = '?';
		} else
		    utfbuf[0] = (char) ucode;
	    }
	    if ((size_t) (off + l) <= BufAlloc) {
		memcpy(Buffer + off, utfbuf, (size_t) l);
		edited = 1;
		off += l;
	    }
	    s++;
	}
	if (tail)
	    strcpy(Buffer + off, tail);
	len = off - EditAt;
	FREE(tail);
    } else if (map) {
	unsigned const char *e = s + len;
	unsigned char *t = (unsigned char *) Buffer + EditAt;

	while (s < e) {
	    int ch;

	    if (*s < 128 && LYKbLayouts[current_layout][*s]) {
		ch = UCTransUniChar((UCode_t) LYKbLayouts[current_layout][*s],
				    current_char_set);
		if (ch < 0)
		    ch = '?';
	    } else
		ch = *s;
	    *t = UCH(ch);
	    t++, s++;
	}
	edited = 1;
    } else
#endif /* defined EXP_KEYBOARD_LAYOUT */
    {
	StrNCpy(Buffer + EditAt, (const char *) s, len);
	edited = 1;
    }

  finish:
    EditAt += len;
    BufInUse += (size_t) len;
    if (edited)
	IsDirty = TRUE;
    if (overflow && maxMessage)
	_statusline(MAXLEN_REACHED_DEL_OR_MOV);
#ifdef ENHANCED_LINEEDIT
    if (EditMark > EditAt)
	EditMark += len;
    else if (EditMark < -(1 + EditAt))
	EditMark -= len;
    disableEditMark();
#endif
    return edited;
}

/*
 * Do one edit-operation, given the input 'ch' and working buffer 'edit'.
 *
 * If the input is processed, returns zero.
 * If the action should be performed outside of line-editing mode, return -ch.
 * Otherwise, e.g., returns 'ch'.
 */
int LYDoEdit(FieldEditor * edit, int ch,
	     int action,
	     int maxMessage)
{
    int i;
    int length;
    unsigned char uch;
    int offset;

    if ((int) BufAlloc <= 0)
	return (0);		/* Be defensive */

    BufInUse = strlen(&Buffer[0]);
    length = (int) BufInUse;

    switch (action) {
#ifdef EXP_KEYBOARD_LAYOUT
    case LYE_SWMAP:
	/*
	 * Turn input character mapping on or off.
	 */
	map_active = ~map_active;
	break;
#endif
#ifndef CJK_EX
    case LYE_AIX:
	/*
	 * Handle CJK characters, or as a valid character in the current
	 * display character set.  Otherwise, we treat this as LYE_ENTER.
	 */
	if (!IS_CJK_TTY && LYlowest_eightbit[current_char_set] > 0x97)
	    return (ch);
#endif
	/* FALLTHRU */
    case LYE_CHAR:
	uch = UCH(ch);
	LYEditInsert(edit, &uch, 1, map_active, maxMessage);
	return 0;		/* All changes already registered */

    case LYE_C1CHAR:
	/*
	 * ch is the second part (in most cases, a capital letter) of a 7-bit
	 * replacement for a character in the 8-bit C1 control range.
	 *
	 * This is meant to undo transformations like 0x81 -> 0x1b 0x41 (ESC A)
	 * etc., done by slang on Unix and possibly some comm programs.  It's
	 * an imperfect workaround that doesn't work for all such characters.
	 */
	ch &= 0xFF;
	if (ch + 64 >= LYlowest_eightbit[current_char_set])
	    ch += 64;

	if (EditAt <= ((int) BufAlloc) && BufInUse < BufAlloc) {
#ifdef ENHANCED_LINEEDIT
	    if (EditMark > EditAt)
		EditMark++;
	    else if (EditMark < -(1 + EditAt))
		EditMark--;
	    disableEditMark();
#endif
	    ExtendEditor(edit, length + 1);
	    for (i = length; i >= EditAt; i--)	/* Make room */
		Buffer[i + 1] = Buffer[i];
	    Buffer[length + 1] = '\0';
	    Buffer[EditAt] = (char) ch;
	    EditAt++;
	} else {
	    if (maxMessage) {
		_statusline(MAXLEN_REACHED_DEL_OR_MOV);
	    }
	    return (ch);
	}
	break;

    case LYE_BACKW:		/* go backward one word */
	while (EditAt && !IsWordChar(Buffer[EditAt - 1]))
	    EditAt--;
	while (EditAt && IsWordChar(UCH(Buffer[EditAt - 1])))
	    EditAt--;
	break;

    case LYE_FORWW:		/* go forward one word */
	while (IsWordChar(UCH(Buffer[EditAt])))
	    EditAt++;
	while (!IsWordChar(Buffer[EditAt]) && Buffer[EditAt])
	    EditAt++;
	break;

    case LYE_ERASE:		/* erase the line */
	Buffer[0] = '\0';
#ifdef ENHANCED_LINEEDIT
	EditMark = -1;		/* Do not show the mark */
#endif
	/* FALLTHRU */

    case LYE_BOL:		/* go to beginning of line  */
	EditAt = 0;
	break;

    case LYE_EOL:		/* go to end of line  */
	EditAt = length;
	break;

    case LYE_DELNW:		/* delete next word  */
	offset = EditAt;
	LYDoEdit(edit, 0, LYE_FORWW, FALSE);
	offset = EditAt - offset;
	EditAt -= offset;

	goto shrink;		/* right below */

    case LYE_DELPW:		/* delete previous word  */
	offset = EditAt;
	LYDoEdit(edit, 0, LYE_BACKW, FALSE);
	offset -= EditAt;

      shrink:
	for (i = EditAt; i < length - offset + 1; i++)
	    Buffer[i] = Buffer[i + offset];
#ifdef ENHANCED_LINEEDIT
	disableEditMark();
	if (EditMark <= -(1 + EditAt + offset))
	    EditMark += offset;	/* Shift it */
	if (-(1 + EditAt + offset) < EditMark && EditMark < -(1 + EditAt))
	    EditMark = -(1 + EditAt);	/* Set to the current position */
#endif

	break;

    case LYE_DELBL:		/* delete from cursor to beginning of line */
	for (i = EditAt; i < length + 1; i++)
	    Buffer[i - EditAt] = Buffer[i];

#ifdef ENHANCED_LINEEDIT
	disableEditMark();
	if (EditMark <= -(1 + EditAt))
	    EditMark += EditAt;	/* Shift it */
	else
	    EditMark = -1;	/* Reset it */
#endif
	EditAt = 0;
	break;

    case LYE_DELEL:		/* delete from cursor to end of line */
	Buffer[EditAt] = '\0';
#ifdef ENHANCED_LINEEDIT
	disableEditMark();
	if (EditMark <= -(1 + EditAt))
	    EditMark = -1;	/* Reset it */
#endif
	break;

    case LYE_DELN:		/* delete next character */
	if (EditAt >= length)
	    break;
#ifndef SUPPORT_MULTIBYTE_EDIT
	EditAt++;
#else
	EditAt += mbcs_skip(Buffer + EditAt, 1);
#endif
	/* FALLTHRU */

    case LYE_DELP:		/* delete previous character */
	if (length == 0 || EditAt == 0)
	    break;

#ifndef SUPPORT_MULTIBYTE_EDIT
#ifdef ENHANCED_LINEEDIT
	disableEditMark();
	if (EditMark <= -(1 + EditAt))
	    EditMark++;
#endif
	EditAt--;
	for (i = EditAt; i < length; i++)
	    Buffer[i] = Buffer[i + 1];
#else /* SUPPORT_MULTIBYTE_EDIT */
	offset = EditAt - mbcs_skip(Buffer, mbcs_glyphs(Buffer, EditAt) - 1);
	EditAt -= offset;
	for (i = EditAt; i < length - offset + 1; i++)
	    Buffer[i] = Buffer[i + offset];

#ifdef ENHANCED_LINEEDIT
	disableEditMark();
	if (EditMark <= -(1 + EditAt))
	    EditMark += offset;	/* Shift it */
#endif

#endif /* SUPPORT_MULTIBYTE_EDIT */
	break;

    case LYE_FORW_RL:
    case LYE_FORW:		/* move cursor forward */
#ifndef SUPPORT_MULTIBYTE_EDIT
	if (EditAt < length)
	    EditAt++;
#else
	if (EditAt < length)
	    EditAt += mbcs_skip(Buffer + EditAt, 1);
#endif
	else if (action == LYE_FORW_RL)
	    return -ch;
	break;

    case LYE_BACK_LL:
    case LYE_BACK:		/* move cursor backward */
#ifndef SUPPORT_MULTIBYTE_EDIT
	if (EditAt > 0)
	    EditAt--;
#else
	if (EditAt > 0)
	    EditAt = mbcs_skip(Buffer, mbcs_glyphs(Buffer, EditAt) - 1);
#endif
	else if (action == LYE_BACK_LL)
	    return -ch;
	break;

#ifdef ENHANCED_LINEEDIT
    case LYE_TPOS:
	/*
	 * Transpose characters - bash or ksh(emacs not gmacs) style
	 */

#ifdef SUPPORT_MULTIBYTE_EDIT
	if (IS_UTF8_TTY || IS_CJK_TTY)
	    break;		/* Can't help it now */
#endif

	if (length <= 1 || EditAt == 0)
	    return (ch);
	if (EditAt == length)
	    EditAt--;
	enableEditMark();
	if (EditMark == EditAt || EditMark == EditAt + 1)
	    EditMark = EditAt - 1;
	disableEditMark();
	if (Buffer[EditAt - 1] == Buffer[EditAt]) {
	    EditAt++;
	    break;
	}
	i = Buffer[EditAt - 1];
	Buffer[EditAt - 1] = Buffer[EditAt];
	Buffer[EditAt++] = (char) i;
	break;

    case LYE_SETMARK:		/* Emacs-like set-mark-command */
	EditMark = EditAt;
	return (0);

    case LYE_XPMARK:		/* Emacs-like exchange-point-and-mark */
	enableEditMark();
	if (EditMark == EditAt)
	    return (0);
	i = EditAt;
	EditAt = EditMark;
	EditMark = i;
	break;

    case LYE_KILLREG:		/* Emacs-like kill-region */
	enableEditMark();
	if (EditMark == EditAt) {
	    BStrFree(killbuffer);
	    return (0);
	}
	if (EditMark > EditAt)
	    LYDoEdit(edit, 0, LYE_XPMARK, FALSE);
	{
	    int reglen = EditAt - EditMark;

	    BStrCopy1(killbuffer, Buffer + EditMark, reglen);
	    for (i = EditMark; Buffer[i + reglen]; i++)
		Buffer[i] = Buffer[i + reglen];
	    Buffer[i] = Buffer[i + reglen];	/* terminate */
	    EditAt = EditMark;
	}
	disableEditMark();
	break;

    case LYE_YANK:		/* Emacs-like yank */
	if (!killbuffer) {
	    EditMark = -(1 + EditAt);
	    return (0);
	} else {
	    int yanklen = killbuffer->len;

	    if ((EditAt + yanklen) <= (int) BufAlloc &&
		BufInUse + (size_t) yanklen <= BufAlloc) {

		ExtendEditor(edit, EditAt + yanklen);

		EditMark = -(1 + EditAt);

		for (i = length; i >= EditAt; i--)	/* Make room */
		    Buffer[i + yanklen] = Buffer[i];
		for (i = 0; i < yanklen; i++)
		    Buffer[EditAt++] = killbuffer->str[i];

	    } else if (maxMessage) {
		_statusline(MAXLEN_REACHED_DEL_OR_MOV);
	    }
	}
	break;

#endif /* ENHANCED_LINEEDIT */

    case LYE_UPPER:
	LYUpperCase(Buffer);
	break;

    case LYE_LOWER:
	LYLowerCase(Buffer);
	break;

    default:
	return (ch);
    }
    IsDirty = TRUE;
    BufInUse = strlen(&Buffer[0]);
    return (0);
}

/*
 *  This function prompts for a choice or page number.
 *  If a 'g' or 'p' suffix is included, that will be
 *  loaded into c.  Otherwise, c is zeroed. - FM & LE
 */
int get_popup_number(const char *msg,
		     int *c,
		     int *rel)
{
    bstring *temp = NULL;
    int result = 0;

    /*
     * Load the c argument into the prompt buffer.
     */
    BStrCopy0(temp, "?");
    temp->str[0] = (char) *c;

    _statusline(msg);

    /*
     * Get the number, possibly with a suffix, from the user.
     */
    if (LYgetBString(&temp, FALSE, 0, NORECALL) < 0 || isBEmpty(temp)) {
	HTInfoMsg(CANCELLED);
	*c = '\0';
	*rel = '\0';
    } else {
	char *p = temp->str;

	*rel = '\0';
	result = atoi(p);
	while (isdigit(UCH(*p)))
	    ++p;
	switch (*p) {
	case '+':
	case '-':
	    /* 123+ or 123- */
	    *rel = *p++;
	    *c = *p;
	    break;
	default:
	    *c = *p++;
	    *rel = *p;
	    break;
	case 0:
	    break;
	}

	/*
	 * If we had a 'g' or 'p' suffix, load it into c.  Otherwise, zero c.  Then
	 * return the number.
	 */
	if (*p == 'g' || *p == 'G') {
	    *c = 'g';
	} else if (*p == 'p' || *p == 'P') {
	    *c = 'p';
	} else {
	    *c = '\0';
	}
	if (*rel != '+' && *rel != '-')
	    *rel = 0;
    }
    BStrFree(temp);
    return result;
}

#ifdef USE_COLOR_STYLE
#  define TmpStyleOn(s)		curses_style((s), STACK_ON)
#  define TmpStyleOff(s)	curses_style((s), STACK_OFF)
#else
#  define TmpStyleOn(s)
#  define TmpStyleOff(s)
#endif /* defined USE_COLOR_STYLE */

static void remember_column(FieldEditor * edit, int offset)
{
    int y0, x0;

#if defined(USE_SLANG)
    y0 = 0;
    x0 = SLsmg_get_column();
#elif defined(USE_CURSES_PADS)
    getyx(LYwin, y0, x0);
#else
    getyx(stdscr, y0, x0);
#endif
    Offs2Col[offset] = x0;

    (void) y0;
    (void) x0;
}

static void fill_edited_line(int prompting GCC_UNUSED, int length, int ch)
{
    int i;

    TmpStyleOn(prompting ? s_prompt_edit_pad : s_aedit_pad);

    for (i = 0; i < length; i++) {
	LYaddch(UCH(ch));
    }

    TmpStyleOff(prompting ? s_prompt_edit_pad : s_aedit_pad);
}

/*
 * Multibyte string display subroutine.
 * FieldEditor fields retain their values as byte offsets.
 * All external logic still works fine with byte values.
 */
void LYRefreshEdit(FieldEditor * edit)
{
    /* bytes and characters are not the same thing */
#if defined(DEBUG_EDIT)
    int all_bytes;
#endif
    int pos_bytes = EditAt;
    int dpy_bytes;
    int lft_bytes;		/* base of string which is displayed */

    /* cells refer to display-columns on the screen */
    int all_cells;		/* total of display-cells in Buffer */
    int dpy_cells;		/* number of cells which are displayed */
    int lft_cells;		/* number of cells before display (on left) */
    int pos_cells;		/* number of display-cells up to EditAt */

#if defined(SUPPORT_MULTIBYTE_EDIT)
    int dpy_chars;
    int lft_chars;

#if defined(DEBUG_EDIT)
    int all_chars;
    int pos_chars;
#endif
#endif

    /* other data */
    int i;
    int padsize;
    char *str;
    int lft_shift = 0;
    int rgt_shift = 0;

#ifdef USE_COLOR_STYLE
    int estyle;
#endif
    int prompting = 0;

    (void) pos_bytes;

    /*
     * If we've made no changes, or if there is nothing to display, just leave.
     */
    if (!IsDirty || (DpyWidth == 0))
	return;

    CTRACE((tfp, "LYRefreshEdit:%s\n", Buffer));

    IsDirty = FALSE;

    BufInUse = strlen(&Buffer[0]);

    all_cells = LYstrCells(Buffer);
    pos_cells = LYstrExtent2(Buffer, EditAt);

#if defined(SUPPORT_MULTIBYTE_EDIT) && defined(DEBUG_EDIT)
    all_bytes = (int) BufInUse;
    lft_chars = mbcs_glyphs(Buffer, DpyStart);
    pos_chars = mbcs_glyphs(Buffer, EditAt);
    all_chars = mbcs_glyphs(Buffer, all_bytes);
#endif

    /*
     * Now we have:
     *                .--DpyWidth--.
     *      +---------+=============+-----------+
     *      |         |M           M|           |   (M=PanMargin)
     *      +---------+=============+-----------+
     *      0         DpyStart                   BufInUse
     *
     * Insertion point can be anywhere between 0 and stringlength.  Calculate
     * a new display starting point.
     *
     * First, make Lynx scroll several columns at a time as needed when
     * extending the string.   Doing this helps with lowspeed connections.
     */

    lft_bytes = DpyStart;
    lft_cells = LYstrExtent2(Buffer, DpyStart);

    if ((lft_cells + DpyWidth) <= all_cells) {
	if (pos_cells >= (lft_cells + DpyWidth) - PanMargin) {
	    lft_cells = (pos_cells - DpyWidth) + PanMargin;
#ifdef SUPPORT_MULTIBYTE_EDIT
	    lft_chars = cell2char(Buffer, lft_cells);
	    lft_bytes = mbcs_skip(Buffer, lft_chars);
#else
	    lft_bytes = lft_cells;
#endif /* SUPPORT_MULTIBYTE_EDIT */
	}
    }

    if (pos_cells < lft_cells + PanMargin) {
	lft_cells = pos_cells - PanMargin;
	if (lft_cells < 0)
	    lft_cells = 0;
#ifdef SUPPORT_MULTIBYTE_EDIT
	lft_chars = cell2char(Buffer, lft_cells);
	lft_bytes = mbcs_skip(Buffer, lft_chars);
#else
	lft_bytes = lft_cells;
#endif /* SUPPORT_MULTIBYTE_EDIT */
    }

    LYmove(StartY, StartX);

    /*
     * Draw the left scrolling-indicator now, to avoid the complication of
     * overwriting part of a multicolumn character which may lie in the first
     * position.
     */
    if (IsPanned && lft_cells) {
	CTRACE_EDIT((tfp, "Draw left scroll-indicator\n"));
	TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr);
	LYmove(StartY, StartX);
	LYaddch(ACS_LARROW);
	TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr);
	lft_shift = 1;
    }

    str = &Buffer[lft_bytes];
    DpyStart = lft_bytes;

    dpy_cells = all_cells - lft_cells;
    CTRACE_EDIT((tfp, "Comparing dpy_cells %d > (%d - %d)\n",
		 dpy_cells, DpyWidth, lft_shift));
    if (dpy_cells > (DpyWidth - lft_shift)) {
	rgt_shift = 1;
	dpy_cells = (DpyWidth - lft_shift - rgt_shift);
    }
    for (;;) {
#ifdef SUPPORT_MULTIBYTE_EDIT
	dpy_chars = cell2char(str, dpy_cells);
	dpy_bytes = mbcs_skip(str, dpy_chars);
#else
	dpy_bytes = dpy_cells;
#endif /* SUPPORT_MULTIBYTE_EDIT */
	/*
	 * The last character on the display may be multicolumn, and if we take
	 * away a single cell for the right scroll-indicator, that would force
	 * us to display fewer characters.  Check for that, and recompute.
	 */
	if (rgt_shift && *str) {
	    int old_cells = dpy_cells;

	    dpy_cells = LYstrExtent2(str, dpy_bytes);
	    if (dpy_cells > old_cells)
		dpy_cells = old_cells - 1;

	    CTRACE_EDIT((tfp, "Comparing cells %d vs %d\n", dpy_cells, old_cells));
	    if (dpy_cells < old_cells) {
		CTRACE_EDIT((tfp, "Recomputing...\n"));
		continue;
	    }
	}
	break;
    }

    CTRACE_EDIT((tfp, "BYTES left %2d pos %2d dpy %2d all %2d\n",
		 lft_bytes, pos_bytes, dpy_bytes, all_bytes));
    CTRACE_EDIT((tfp, "CELLS left %2d pos %2d dpy %2d all %2d\n",
		 lft_cells, pos_cells, dpy_cells, all_cells));
    CTRACE_EDIT((tfp, "CHARS left %2d pos %2d dpy %2d all %2d\n",
		 lft_chars, pos_chars, dpy_chars, all_chars));

#ifdef USE_COLOR_STYLE
    /*
     * If this is the last screen line, set attributes to normal, should only
     * be needed for color styles.  The curses function may be used directly to
     * avoid complications.  - kw
     */
    if (StartY == (LYlines - 1))
	prompting = 1;
    if (prompting) {
	estyle = s_prompt_edit;
    } else {
	estyle = s_aedit;
    }
    CTRACE2(TRACE_STYLE,
	    (tfp, "STYLE.getstr: switching to <edit.%s>.\n",
	     prompting ? "prompt" : "active"));
    if (estyle != NOSTYLE) {
	curses_style(estyle, STACK_ON);
    } else {
	(void) wattrset(LYwin, A_NORMAL);	/* need to do something about colors? */
    }
#endif
    if (IsHidden) {
	BOOL utf_flag = IS_UTF8_TTY;
	int cell = 0;

	fill_edited_line(0, dpy_cells, '*');

	i = 0;
	do {
	    const char *last = str + i;
	    const char *next = LYmbcs_skip_glyphs(last, 1, utf_flag);
	    int j = (int) (next - str);

	    while (i < j) {
		Offs2Col[i++] = cell + StartX;
	    }
	    cell += LYstrExtent2(last, (int) (next - last));
	} while (i < dpy_bytes);
	Offs2Col[i] = cell + StartX;
    } else {
#if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
	if (EditMark >= 0 && DpyStart > EditMark)
	    TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel);
#endif
	remember_column(edit, 0);
	for (i = 0; i < dpy_bytes; i++) {
#if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
	    if (EditMark >= 0 && ((DpyStart + i == EditMark && EditAt > EditMark)
				  || (DpyStart + i == EditAt && EditAt < EditMark)))
		TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel);
	    if (EditMark >= 0 && ((DpyStart + i == EditMark && EditAt < EditMark)
				  || (DpyStart + i == EditAt && EditAt > EditMark)))
		TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel);
#endif
	    if (str[i] == 1 || str[i] == 2 ||
		(UCH(str[i]) == 160 &&
		 !(HTPassHighCtrlRaw || IS_CJK_TTY ||
		   (LYCharSet_UC[current_char_set].enc != UCT_ENC_8859 &&
		    !(LYCharSet_UC[current_char_set].like8859
		      & UCT_R_8859SPECL))))) {
		LYaddch(' ');
	    } else if (str[i] == '\t') {
		int col = Offs2Col[i] - StartX;

		/*
		 * Like LYwaddnstr(), expand tabs from the beginning of the
		 * field.
		 */
		while (++col % 8)
		    LYaddch(' ');
		LYaddch(' ');
	    } else {
		LYaddch(UCH(str[i]));
	    }
	    remember_column(edit, i + 1);
	}
#if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
	if (EditMark >= 0 &&
	    ((DpyStart + dpy_bytes <= EditMark && DpyStart + dpy_bytes > EditAt)
	     || (DpyStart + dpy_bytes > EditMark
		 && DpyStart + dpy_bytes <= EditAt))) {
	    TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel);
	}
#endif
    }

    /*
     * Erase rest of input area.
     */
    padsize = DpyWidth - (Offs2Col[dpy_bytes] - StartX);
    fill_edited_line(prompting, padsize, PadChar);

    /*
     * Scrolling indicators.
     */
    if (IsPanned && dpy_bytes && rgt_shift) {
	CTRACE((tfp, "Draw right-scroller offset (%d + %d)\n",
		dpy_cells, lft_shift));
	TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr);
	LYmove(StartY, StartX + dpy_cells + lft_shift);
	LYaddch(ACS_RARROW);
	TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr);
    }

    /*
     * Finally, move the cursor to the point where the next edit will occur.
     */
    LYmove(StartY, Offs2Col[EditAt - DpyStart]);

#ifdef USE_COLOR_STYLE
    if (estyle != NOSTYLE)
	curses_style(estyle, STACK_OFF);
#endif
    LYrefresh();
}

static void reinsertEdit(FieldEditor * edit, char *result)
{
    if (result != 0) {
	LYDoEdit(edit, '\0', LYE_ERASE, FALSE);
	while (*result != '\0') {
	    LYLineEdit(edit, (int) (*result), FALSE);
	    result++;
	}
    }
}

static int caselessCmpList(const void *a,
			   const void *b)
{
    return strcasecomp(*(STRING2PTR) a, *(STRING2PTR) b);
}

static int normalCmpList(const void *a,
			 const void *b)
{
    return strcmp(*(STRING2PTR) a, *(STRING2PTR) b);
}

static char **sortedList(HTList *list, int ignorecase)
{
    size_t count = (unsigned) HTList_count(list);
    size_t j = 0;
    size_t k, jk;
    char **result = typecallocn(char *, count + 1);

    if (result == 0)
	outofmem(__FILE__, "sortedList");

    while (!HTList_isEmpty(list))
	result[j++] = (char *) HTList_nextObject(list);

    if (count > 1) {
	qsort((char *) result, count, sizeof(*result),
	      ignorecase ? caselessCmpList : normalCmpList);

	/* remove duplicate entries from the sorted index */
	for (j = 0; result[j] != 0; j++) {
	    k = j;
	    while (result[k] != 0
		   && !strcmp(result[j], result[k])) {
		k++;
	    }
	    k--;
	    if (j != k) {
		for (jk = j;; jk++) {
		    result[jk] = result[jk + k - j];
		    if (result[jk] == 0)
			break;
		}
	    }
	}
    }

    return result;
}

int LYarrayLength(STRING2PTR list)
{
    int result = 0;

    while (*list++ != 0)
	result++;
    return result;
}

int LYarrayWidth(STRING2PTR list)
{
    int result = 0;
    int check;

    while (*list != 0) {
	check = (int) strlen(*list++);
	if (check > result)
	    result = check;
    }
    return result;
}

static void FormatChoiceNum(char *target,
			    int num_choices,
			    int choice,
			    const char *value)
{
    if (num_choices >= 0) {
	int digits = (num_choices > 9) ? 2 : 1;

	sprintf(target, "%*d: %.*s",
		digits, (choice + 1),
		MAX_LINE - 9 - digits, value);
    } else {
	LYStrNCpy(target, value, MAX_LINE - 1);
    }
}

static unsigned options_width(STRING2PTR list)
{
    unsigned width = 0;
    int count = 0;

    while (list[count] != 0) {
	unsigned ncells = (unsigned) LYstrCells(list[count]);

	if (ncells > width) {
	    width = ncells;
	}
	count++;
    }
    return width;
}

static void draw_option(WINDOW * win, int entry,
			int width,
			int reversed,
			int num_choices,
			int number,
			const char *value)
{
    char Cnum[MAX_LINE];

    (void) width;

    FormatChoiceNum(Cnum, num_choices, number, "");
#ifdef USE_SLANG
    SLsmg_gotorc(win->top_y + entry, (win->left_x + 2));
    LYaddstr(Cnum);
    if (reversed)
	SLsmg_set_color(2);
    SLsmg_write_nstring((SLFUTURE_CONST char *) value, (unsigned) win->width);
    if (reversed)
	SLsmg_set_color(0);
#else
    wmove(win, entry, 1);
    LynxWChangeStyle(win, s_menu_entry, STACK_ON);
    waddch(win, ' ');
    LynxWChangeStyle(win, s_menu_entry, STACK_OFF);
    LynxWChangeStyle(win, s_menu_number, STACK_ON);
    waddstr(win, Cnum);
    LynxWChangeStyle(win, s_menu_number, STACK_OFF);
#ifdef USE_COLOR_STYLE
    LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_ON);
#else
    if (reversed)
	wstart_reverse(win);
#endif
    LYpaddstr(win, width, value);
#ifdef USE_COLOR_STYLE
    LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_OFF);
#else
    if (reversed)
	wstop_reverse(win);
#endif
    LynxWChangeStyle(win, s_menu_entry, STACK_ON);
    waddch(win, ' ');
    LynxWChangeStyle(win, s_menu_entry, STACK_OFF);
#endif /* USE_SLANG */
}

static void show_popup_status(int cur_choice,
			      STRING2PTR choices,
			      int disabled,
			      int for_mouse)
{
    if (disabled) {
	_statusline(CHOICE_LIST_UNM_MSG);
    } else if (!for_mouse) {
	if (fields_are_named()) {
	    char *status_msg = NULL;

	    HTSprintf0(&status_msg, CHOICE_LIST_ADV_MSG, choices[cur_choice]);
	    _statusline(status_msg);
	    FREE(status_msg);
	} else {
	    _statusline(CHOICE_LIST_MESSAGE);
	}
#if defined(USE_MOUSE) && (defined(NCURSES) || defined(PDCURSES))
    } else {
	_statusline(MOUSE_CHOICE_MESSAGE);
#endif
    }
}

#define SHOW_POPUP_STATUS() show_popup_status(cur_choice, choices, disabled, for_mouse)

/*
 * This function offers the choices for values of an option via a popup window
 * which functions like that for selection of options in a form.  - FM
 *
 * Also used for mouse popups with ncurses; this is indicated by for_mouse.
 */
int LYhandlePopupList(int cur_choice,
		      int ly,
		      int lx,
		      STRING2PTR choices,
		      int width,
		      int i_length,
		      int disabled,
		      int for_mouse)
{
    BOOLEAN numbered = (BOOLEAN) (keypad_mode != NUMBERS_AS_ARROWS);
    int c = 0, cmd = 0, i = 0, j = 0, rel = 0;
    int orig_choice;
    WINDOW *form_window;
    int num_choices = 0;
    int max_choices = 0;
    int top, bottom, length = -1;
    int window_offset = 0;
    int lines_to_show;
    char Cnum[64];
    int Lnum;
    int npages;
    static bstring *prev_target = NULL;		/* Search string buffer */
    static bstring *next_target = NULL;		/* Next search buffer */
    static BOOL first = TRUE;
    char *cp;
    int ch = 0;
    RecallType recall;
    int QueryTotal;
    int QueryNum;
    BOOLEAN FirstRecall = TRUE;
    BOOLEAN ReDraw = FALSE;
    int number;
    char buffer[MAX_LINE];
    STRING2PTR Cptr = NULL;

#define CAN_SCROLL_DOWN	1
#define CAN_SCROLL_UP	2
#define CAN_SCROLL	4
    int can_scroll = 0, can_scroll_was = 0;

    orig_choice = cur_choice;
    if (cur_choice < 0)
	cur_choice = 0;

    /*
     * Initialize the search string buffer. - FM
     */
    if (first) {
	BStrCopy0(next_target, "");
	first = FALSE;
    }
    BStrCopy0(prev_target, "");
    QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
    recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL);
    QueryNum = QueryTotal;

    /*
     * Count the number of choices to be displayed, where num_choices ranges
     * from 0 to n, and set width to the longest choice string length.  Also
     * set Lnum to the length for the highest choice number, then decrement
     * num_choices so as to be zero-based.  The window width will be based on
     * the sum of width and Lnum.  - FM
     */
    num_choices = LYarrayLength(choices) - 1;
    if (width <= 0)
	width = (int) options_width(choices);
    if (numbered) {
	sprintf(Cnum, "%d: ", num_choices);
	Lnum = (int) strlen(Cnum);
	max_choices = num_choices;
    } else {
	Lnum = 0;
	max_choices = -1;
    }

    /*
     * Let's assume for the sake of sanity that ly is the number corresponding
     * to the line the choice is on.
     *
     * Let's also assume that cur_choice is the number of the item that should
     * be initially selected, as 0 being the first item.
     *
     * So what we have, is the top equal to the current screen line subtracting
     * the cur_choice + 1 (the one must be for the top line we will draw in a
     * box).  If the top goes under 0, consider it 0.
     */
    top = ly - (cur_choice + 1);
    if (top < 0)
	top = 0;

    /*
     * Check and see if we need to put the i_length parameter up to the number
     * of real choices.
     */
    if (i_length < 1) {
	i_length = num_choices;
    } else {
	/*
	 * Otherwise, it is really one number too high.
	 */
	i_length--;
    }

    /*
     * The bottom is the value of the top plus the number of options to view
     * plus 3 (one for the top line, one for the bottom line, and one to offset
     * the 0 counted in the num_choices).
     */
    bottom = top + i_length + 3;

    /*
     * Set lines_to_show based on the user_mode global.
     */
    if (user_mode == NOVICE_MODE)
	lines_to_show = LYlines - 4;
    else
	lines_to_show = LYlines - 2;

    if (for_mouse && user_mode == NOVICE_MODE && lines_to_show > 2)
	lines_to_show--;

    /*
     * Hmm...  If the bottom goes beyond the number of lines available,
     */
    if (bottom > lines_to_show) {
	/*
	 * Position the window at the top if we have more choices than will fit
	 * in the window.
	 */
	if ((i_length + 3) > lines_to_show) {
	    top = 0;
	    bottom = (top + (i_length + 3));
	    if (bottom > lines_to_show)
		bottom = (lines_to_show + 1);
	} else {
	    /*
	     * Try to position the window so that the selected choice will
	     * appear where the selection box currently is positioned.  It
	     * could end up too high, at this point, but we'll move it down
	     * latter, if that has happened.
	     */
	    top = (lines_to_show + 1) - (i_length + 3);
	    bottom = (lines_to_show + 1);
	}
    }

    /*
     * This is really fun, when the length is 4, it means 0 to 4, or 5.
     */
    length = (bottom - top) - 2;
    if (length <= num_choices)
	can_scroll = CAN_SCROLL;

    /*
     * Move the window down if it's too high.
     */
    if (bottom < ly + 2) {
	bottom = ly + 2;
	if (bottom > lines_to_show + 1)
	    bottom = lines_to_show + 1;
	top = bottom - length - 2;
    }

    if (for_mouse) {
	int check = (Lnum + (int) width + 4);
	int limit = LYcols;

	/* shift horizontally to lie within screen width, if possible */
	if (check < limit) {
	    if (lx - 1 + check > limit)
		lx = limit + 1 - check;
	    else if (lx <= 0)
		lx = 1;
	}
    }

    /*
     * Set up the overall window, including the boxing characters ('*'), if it
     * all fits.  Otherwise, set up the widest window possible.  - FM
     */
    width += Lnum;
    bottom -= top;

    if (num_choices <= 0
	|| cur_choice > num_choices
	|| (form_window = LYstartPopup(&top,
				       &lx,
				       &bottom,
				       &width)) == 0)
	return (orig_choice);

    width -= Lnum;
    bottom += top;

    SHOW_POPUP_STATUS();

    /*
     * Set up the window_offset for choices.
     * cur_choice ranges from 0...n
     * length ranges from 0...m
     */
    if (cur_choice >= length) {
	window_offset = cur_choice - length + 1;
    }

    /*
     * Compute the number of popup window pages.  - FM
     */
    npages = ((num_choices + 1) > length) ?
	(((num_choices + 1) + (length - 1)) / (length))
	: 1;
    /*
     * OH!  I LOVE GOTOs!  hack hack hack
     */
  redraw:

    /*
     * Display the boxed choices.
     */
    for (i = 0; i <= num_choices; i++) {
	if (i >= window_offset && i - window_offset < length) {
	    draw_option(form_window, ((i + 1) - window_offset), width, FALSE,
			max_choices, i, choices[i]);
	}
    }
    LYbox(form_window, !numbered);
    Cptr = NULL;

    /*
     * Loop on user input.
     */
    while (cmd != LYK_ACTIVATE) {
	int row = ((i + 1) - window_offset);

	/* Show scroll indicators. */
	if (can_scroll) {
	    can_scroll = ((window_offset ? CAN_SCROLL_UP : 0)
			  | (num_choices - window_offset >= length
			     ? CAN_SCROLL_DOWN : 0));
	    if (~can_scroll & can_scroll_was) {		/* Need to redraw */
		LYbox(form_window, !numbered);
		can_scroll_was = 0;
	    }
	    if (can_scroll & ~can_scroll_was & CAN_SCROLL_UP) {
		wmove(form_window, 1, Lnum + width + 3);
		LynxWChangeStyle(form_window, s_menu_sb, STACK_ON);
		waddch(form_window, ACS_UARROW);
		LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF);
	    }
	    if (can_scroll & ~can_scroll_was & CAN_SCROLL_DOWN) {
		wmove(form_window, length, Lnum + width + 3);
		LynxWChangeStyle(form_window, s_menu_sb, STACK_ON);
		waddch(form_window, ACS_DARROW);
		LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF);
	    }
	}

	/*
	 * Unreverse cur choice.
	 */
	if (Cptr != NULL) {
	    draw_option(form_window, row, width, FALSE,
			max_choices, i, Cptr[i]);
	}
	Cptr = choices;
	i = cur_choice;
	row = ((cur_choice + 1) - window_offset);
	draw_option(form_window, row, width, TRUE,
		    max_choices, cur_choice, Cptr[cur_choice]);
	LYstowCursor(form_window, row, 1);

	c = LYgetch_choice();
	if (term_options || LYCharIsINTERRUPT(c)) {	/* Control-C or Control-G */
	    cmd = LYK_QUIT;
#ifndef USE_SLANG
	} else if (c == MOUSE_KEY) {
	    if ((cmd = fancy_mouse(form_window, row, &cur_choice)) < 0)
		goto redraw;
	    if (cmd == LYK_ACTIVATE)
		break;
#endif
	} else {
	    cmd = LKC_TO_LAC(keymap, c);
	}
#ifdef VMS
	if (HadVMSInterrupt) {
	    HadVMSInterrupt = FALSE;
	    cmd = LYK_QUIT;
	}
#endif /* VMS */

	switch (cmd) {
	case LYK_F_LINK_NUM:
	    c = '\0';
	    /* FALLTHRU */
	case LYK_1:		/* FALLTHRU */
	case LYK_2:		/* FALLTHRU */
	case LYK_3:		/* FALLTHRU */
	case LYK_4:		/* FALLTHRU */
	case LYK_5:		/* FALLTHRU */
	case LYK_6:		/* FALLTHRU */
	case LYK_7:		/* FALLTHRU */
	case LYK_8:		/* FALLTHRU */
	case LYK_9:
	    /*
	     * Get a number from the user, possibly with a 'g' or 'p' suffix
	     * (which will be loaded into c).  - FM & LE
	     */
	    number = get_popup_number(SELECT_OPTION_NUMBER, &c, &rel);

	    /* handle + or - suffix */
	    CTRACE((tfp, "got popup option number %d, ", number));
	    CTRACE((tfp, "rel='%c', c='%c', cur_choice=%d\n",
		    rel, c, cur_choice));
	    if (c == 'p') {
		int curpage = ((cur_choice + 1) > length) ?
		(((cur_choice + 1) + (length - 1)) / (length))
		: 1;

		CTRACE((tfp, "  curpage=%d\n", curpage));
		if (rel == '+')
		    number = curpage + number;
		else if (rel == '-')
		    number = curpage - number;
	    } else if (rel == '+') {
		number = cur_choice + number + 1;
	    } else if (rel == '-') {
		number = cur_choice - number + 1;
	    }
	    if (rel)
		CTRACE((tfp, "new number=%d\n", number));
	    /*
	     * Check for a 'p' suffix.  - FM
	     */
	    if (c == 'p') {
		/*
		 * Treat 1 or less as the first page.  - FM
		 */
		if (number <= 1) {
		    if (window_offset == 0) {
			HTUserMsg(ALREADY_AT_OPTION_BEGIN);
			SHOW_POPUP_STATUS();
			break;
		    }
		    window_offset = 0;
		    cur_choice = 0;
		    SHOW_POPUP_STATUS();
		    goto redraw;
		}

		/*
		 * Treat a number equal to or greater than the number of pages
		 * as the last page.  - FM
		 */
		if (number >= npages) {
		    if (window_offset >= ((num_choices - length) + 1)) {
			HTUserMsg(ALREADY_AT_OPTION_END);
			SHOW_POPUP_STATUS();
			break;
		    }
		    window_offset = ((npages - 1) * length);
		    if (window_offset > (num_choices - length)) {
			window_offset = (num_choices - length + 1);
		    }
		    if (cur_choice < window_offset)
			cur_choice = window_offset;
		    SHOW_POPUP_STATUS();
		    goto redraw;
		}

		/*
		 * We want an intermediate page.  - FM
		 */
		if (((number - 1) * length) == window_offset) {
		    char *msg = 0;

		    HTSprintf0(&msg, ALREADY_AT_OPTION_PAGE, number);
		    HTUserMsg(msg);
		    FREE(msg);
		    SHOW_POPUP_STATUS();
		    break;
		}
		cur_choice = window_offset = ((number - 1) * length);
		SHOW_POPUP_STATUS();
		goto redraw;

	    }

	    /*
	     * Check for a positive number, which signifies that a choice
	     * should be sought.  - FM
	     */
	    if (number > 0) {
		/*
		 * Decrement the number so as to correspond with our cur_choice
		 * values.  - FM
		 */
		number--;

		/*
		 * If the number is in range and had no legal suffix, select
		 * the indicated choice.  - FM
		 */
		if (number <= num_choices && c == '\0') {
		    cur_choice = number;
		    cmd = LYK_ACTIVATE;
		    break;
		}

		/*
		 * Verify that we had a 'g' suffix, and act on the number.  -
		 * FM
		 */
		if (c == 'g') {
		    if (cur_choice == number) {
			/*
			 * The choice already is current.  - FM
			 */
			char *msg = 0;

			HTSprintf0(&msg, OPTION_ALREADY_CURRENT, (number + 1));
			HTUserMsg(msg);
			FREE(msg);
			SHOW_POPUP_STATUS();
			break;
		    }

		    if (number <= num_choices) {
			/*
			 * The number is in range and had a 'g' suffix, so make
			 * it the current option, scrolling if needed.  - FM
			 */
			j = (number - cur_choice);
			cur_choice = number;
			if ((j > 0) &&
			    (cur_choice - window_offset) >= length) {
			    window_offset += j;
			    if (window_offset > (num_choices - length + 1))
				window_offset = (num_choices - length + 1);
			} else if ((cur_choice - window_offset) < 0) {
			    window_offset -= abs(j);
			    if (window_offset < 0)
				window_offset = 0;
			}
			SHOW_POPUP_STATUS();
			goto redraw;
		    }

		    /*
		     * Not in range.  - FM
		     */
		    HTUserMsg(BAD_OPTION_NUM_ENTERED);
		}
	    }

	    /*
	     * Restore the popup statusline.  - FM
	     */
	    SHOW_POPUP_STATUS();
	    break;

	case LYK_PREV_LINK:
	case LYK_LPOS_PREV_LINK:
	case LYK_FASTBACKW_LINK:
	case LYK_UP_LINK:

	    if (cur_choice > 0)
		cur_choice--;

	    /*
	     * Scroll the window up if necessary.
	     */
	    if ((cur_choice - window_offset) < 0) {
		window_offset--;
		goto redraw;
	    }
	    break;

	case LYK_NEXT_LINK:
	case LYK_LPOS_NEXT_LINK:
	case LYK_FASTFORW_LINK:
	case LYK_DOWN_LINK:
	    if (cur_choice < num_choices)
		cur_choice++;

	    /*
	     * Scroll the window down if necessary
	     */
	    if ((cur_choice - window_offset) >= length) {
		window_offset++;
		goto redraw;
	    }
	    break;

	case LYK_NEXT_PAGE:
	    /*
	     * Okay, are we on the last page of the list?  If not then,
	     */
	    if (window_offset != (num_choices - length + 1)) {
		/*
		 * Modify the current choice to not be a coordinate in the
		 * list, but a coordinate on the item selected in the window.
		 */
		cur_choice -= window_offset;

		/*
		 * Page down the proper length for the list.  If simply to far,
		 * back up.
		 */
		window_offset += length;
		if (window_offset > (num_choices - length)) {
		    window_offset = (num_choices - length + 1);
		}

		/*
		 * Readjust the current selection to be a list coordinate
		 * rather than window.  Redraw this thing.
		 */
		cur_choice += window_offset;
		goto redraw;
	    } else if (cur_choice < num_choices) {
		/*
		 * Already on last page of the list so just redraw it with the
		 * last item selected.
		 */
		cur_choice = num_choices;
	    }
	    break;

	case LYK_PREV_PAGE:
	    /*
	     * Are we on the first page of the list?  If not then,
	     */
	    if (window_offset != 0) {
		/*
		 * Modify the current selection to not be a list coordinate,
		 * but a window coordinate.
		 */
		cur_choice -= window_offset;

		/*
		 * Page up the proper length.  If too far, back up.
		 */
		window_offset -= length;
		if (window_offset < 0) {
		    window_offset = 0;
		}

		/*
		 * Readjust the current choice.
		 */
		cur_choice += window_offset;
		goto redraw;
	    } else if (cur_choice > 0) {
		/*
		 * Already on the first page so just back up to the first item.
		 */
		cur_choice = 0;
	    }
	    break;

	case LYK_HOME:
	    cur_choice = 0;
	    if (window_offset > 0) {
		window_offset = 0;
		goto redraw;
	    }
	    break;

	case LYK_END:
	    cur_choice = num_choices;
	    if (window_offset != (num_choices - length + 1)) {
		window_offset = (num_choices - length + 1);
		goto redraw;
	    }
	    break;

	case LYK_DOWN_TWO:
	    cur_choice += 2;
	    if (cur_choice > num_choices)
		cur_choice = num_choices;

	    /*
	     * Scroll the window down if necessary.
	     */
	    if ((cur_choice - window_offset) >= length) {
		window_offset += 2;
		if (window_offset > (num_choices - length + 1))
		    window_offset = (num_choices - length + 1);
		goto redraw;
	    }
	    break;

	case LYK_UP_TWO:
	    cur_choice -= 2;
	    if (cur_choice < 0)
		cur_choice = 0;

	    /*
	     * Scroll the window up if necessary.
	     */
	    if ((cur_choice - window_offset) < 0) {
		window_offset -= 2;
		if (window_offset < 0)
		    window_offset = 0;
		goto redraw;
	    }
	    break;

	case LYK_DOWN_HALF:
	    cur_choice += (length / 2);
	    if (cur_choice > num_choices)
		cur_choice = num_choices;

	    /*
	     * Scroll the window down if necessary.
	     */
	    if ((cur_choice - window_offset) >= length) {
		window_offset += (length / 2);
		if (window_offset > (num_choices - length + 1))
		    window_offset = (num_choices - length + 1);
		goto redraw;
	    }
	    break;

	case LYK_UP_HALF:
	    cur_choice -= (length / 2);
	    if (cur_choice < 0)
		cur_choice = 0;

	    /*
	     * Scroll the window up if necessary.
	     */
	    if ((cur_choice - window_offset) < 0) {
		window_offset -= (length / 2);
		if (window_offset < 0)
		    window_offset = 0;
		goto redraw;
	    }
	    break;

	case LYK_REFRESH:
	    lynx_force_repaint();
	    LYrefresh();
	    break;

	case LYK_NEXT:
	    if (recall && isBEmpty(next_target)) {
		/*
		 * We got a 'n'ext command with no prior query specified within
		 * the popup window.  See if one was entered when the popup was
		 * retracted, and if so, assume that's what's wanted.  Note
		 * that it will become the default within popups, unless
		 * another is entered within a popup.  If the within popup
		 * default is to be changed at that point, use WHEREIS ('/')
		 * and enter it, or the up- or down-arrow keys to seek any of
		 * the previously entered queries, regardless of whether they
		 * were entered within or outside of a popup window.  - FM
		 */
		if ((cp = (char *) HTList_objectAt(search_queries,
						   0)) != NULL) {
		    BStrCopy0(next_target, cp);
		    QueryNum = 0;
		    FirstRecall = FALSE;
		}
	    }
	    BStrCopy(prev_target, next_target);
	    /* FALLTHRU */
	case LYK_WHEREIS:
	    if (isBEmpty(prev_target)) {
		_statusline(ENTER_WHEREIS_QUERY);
		if ((ch = LYgetBString(&prev_target, FALSE, 0, recall)) < 0) {
		    /*
		     * User cancelled the search via ^G.  - FM
		     */
		    HTInfoMsg(CANCELLED);
		    goto restore_popup_statusline;
		}
	    }

	  check_recall:
	    if (isBEmpty(prev_target) &&
		!(recall && (ch == UPARROW_KEY || ch == DNARROW_KEY))) {
		/*
		 * No entry.  Simply break.  - FM
		 */
		HTInfoMsg(CANCELLED);
		goto restore_popup_statusline;
	    }

	    if (recall && ch == UPARROW_KEY) {
		if (FirstRecall) {
		    /*
		     * Use the current string or last query in the list.  - FM
		     */
		    FirstRecall = FALSE;
		    if (!isBEmpty(next_target)) {
			for (QueryNum = (QueryTotal - 1);
			     QueryNum > 0; QueryNum--) {
			    if ((cp = (char *) HTList_objectAt(search_queries,
							       QueryNum))
				!= NULL &&
				!strcmp(next_target->str, cp)) {
				break;
			    }
			}
		    } else {
			QueryNum = 0;
		    }
		} else {
		    /*
		     * Go back to the previous query in the list.  - FM
		     */
		    QueryNum++;
		}
		if (QueryNum >= QueryTotal) {
		    /*
		     * Roll around to the last query in the list.  - FM
		     */
		    QueryNum = 0;
		}
		if ((cp = (char *) HTList_objectAt(search_queries,
						   QueryNum)) != NULL) {
		    BStrCopy0(prev_target, cp);
		    if (!isBEmpty(next_target) &&
			!strcmp(next_target->str, prev_target->str)) {
			_statusline(EDIT_CURRENT_QUERY);
		    } else if ((!isBEmpty(next_target) && QueryTotal == 2) ||
			       (isBEmpty(next_target) && QueryTotal == 1)) {
			_statusline(EDIT_THE_PREV_QUERY);
		    } else {
			_statusline(EDIT_A_PREV_QUERY);
		    }
		    if ((ch = LYgetBString(&prev_target,
					   FALSE, 0, recall)) < 0) {
			/*
			 * User cancelled the search via ^G.  - FM
			 */
			HTInfoMsg(CANCELLED);
			goto restore_popup_statusline;
		    }
		    goto check_recall;
		}
	    } else if (recall && ch == DNARROW_KEY) {
		if (FirstRecall) {
		    /*
		     * Use the current string or first query in the list.  - FM
		     */
		    FirstRecall = FALSE;
		    if (!isBEmpty(next_target)) {
			for (QueryNum = 0;
			     QueryNum < (QueryTotal - 1); QueryNum++) {
			    if ((cp = (char *) HTList_objectAt(search_queries,
							       QueryNum))
				!= NULL &&
				!strcmp(next_target->str, cp)) {
				break;
			    }
			}
		    } else {
			QueryNum = (QueryTotal - 1);
		    }
		} else {
		    /*
		     * Advance to the next query in the list.  - FM
		     */
		    QueryNum--;
		}
		if (QueryNum < 0) {
		    /*
		     * Roll around to the first query in the list.  - FM
		     */
		    QueryNum = (QueryTotal - 1);
		}
		if ((cp = (char *) HTList_objectAt(search_queries,
						   QueryNum)) != NULL) {
		    BStrCopy0(prev_target, cp);
		    if (isBEmpty(next_target) &&
			!strcmp(next_target->str, prev_target->str)) {
			_statusline(EDIT_CURRENT_QUERY);
		    } else if ((!isBEmpty(next_target) && QueryTotal == 2) ||
			       (isBEmpty(next_target) && QueryTotal == 1)) {
			_statusline(EDIT_THE_PREV_QUERY);
		    } else {
			_statusline(EDIT_A_PREV_QUERY);
		    }
		    if ((ch = LYgetBString(&prev_target,
					   FALSE, 0, recall)) < 0) {
			/*
			 * User cancelled the search via ^G. - FM
			 */
			HTInfoMsg(CANCELLED);
			goto restore_popup_statusline;
		    }
		    goto check_recall;
		}
	    }
	    /*
	     * Replace the search string buffer with the new target.  - FM
	     */
	    BStrCopy(next_target, prev_target);
	    HTAddSearchQuery(next_target->str);

	    /*
	     * Start search at the next choice.  - FM
	     */
	    for (j = 1; Cptr[i + j] != NULL; j++) {
		FormatChoiceNum(buffer, max_choices, (i + j), Cptr[i + j]);
		if (LYcase_sensitive) {
		    if (strstr(buffer, next_target->str) != NULL)
			break;
		} else {
		    if (LYstrstr(buffer, next_target->str) != NULL)
			break;
		}
	    }
	    if (Cptr[i + j] != NULL) {
		/*
		 * We have a hit, so make that choice the current.  - FM
		 */
		cur_choice += j;
		/*
		 * Scroll the window down if necessary.
		 */
		if ((cur_choice - window_offset) >= length) {
		    window_offset += j;
		    if (window_offset > (num_choices - length + 1))
			window_offset = (num_choices - length + 1);
		    ReDraw = TRUE;
		}
		goto restore_popup_statusline;
	    }

	    /*
	     * If we started at the beginning, it can't be present.  - FM
	     */
	    if (cur_choice == 0) {
		HTUserMsg2(STRING_NOT_FOUND, next_target->str);
		goto restore_popup_statusline;
	    }

	    /*
	     * Search from the beginning to the current choice.  - FM
	     */
	    for (j = 0; j < cur_choice; j++) {
		FormatChoiceNum(buffer, max_choices, (j + 1), Cptr[j]);
		if (LYcase_sensitive) {
		    if (strstr(buffer, next_target->str) != NULL)
			break;
		} else {
		    if (LYstrstr(buffer, next_target->str) != NULL)
			break;
		}
	    }
	    if (j < cur_choice) {
		/*
		 * We have a hit, so make that choice the current.  - FM
		 */
		j = (cur_choice - j);
		cur_choice -= j;
		/*
		 * Scroll the window up if necessary.
		 */
		if ((cur_choice - window_offset) < 0) {
		    window_offset -= j;
		    if (window_offset < 0)
			window_offset = 0;
		    ReDraw = TRUE;
		}
		goto restore_popup_statusline;
	    }

	    /*
	     * Didn't find it in the preceding choices either.  - FM
	     */
	    HTUserMsg2(STRING_NOT_FOUND, next_target->str);

	  restore_popup_statusline:
	    /*
	     * Restore the popup statusline and reset the search variables.  -
	     * FM
	     */
	    SHOW_POPUP_STATUS();
	    BStrCopy0(prev_target, "");
	    QueryTotal = (search_queries ? HTList_count(search_queries)
			  : 0);
	    recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL);
	    QueryNum = QueryTotal;
	    if (ReDraw == TRUE) {
		ReDraw = FALSE;
		goto redraw;
	    }
	    break;

	case LYK_QUIT:
	case LYK_ABORT:
	case LYK_PREV_DOC:
	case LYK_INTERRUPT:
	    cur_choice = orig_choice;
	    cmd = LYK_ACTIVATE;	/* to exit */
	    break;
	}
    }
    LYstopPopup();

    return (disabled ? orig_choice : cur_choice);
}

/*
 * Allow the user to edit a string.
 */
int LYgetBString(bstring **inputline,
		 int hidden,
		 unsigned max_cols,
		 RecallType recall)
{
    int x, y;
    int ch;
    int xlec = -2;
    int last_xlec = -1;
    int last_xlkc = -1;
    FieldEditor MyEdit, *edit = &MyEdit;

#ifdef SUPPORT_MULTIBYTE_EDIT
    BOOL refresh_mb = TRUE;
#endif /* SUPPORT_MULTIBYTE_EDIT */
    BOOL done = FALSE;
    int result = -1;

    CTRACE((tfp, "called LYgetBString hidden %d, recall %d\n", hidden, (int) recall));

    LYGetYX(y, x);		/* Use screen from cursor position to eol */

    (void) y;
    (void) x;

    if (*inputline == NULL)	/* caller may not have initialized this */
	BStrCopy0(*inputline, "");

    LYSetupEdit(edit, (*inputline)->str, max_cols, LYcolLimit - x);
    IsHidden = (BOOL) hidden;
#ifdef FEPCTRL
    fep_on();
#endif

    while (!done) {
      beginning:
#ifndef SUPPORT_MULTIBYTE_EDIT
	LYRefreshEdit(edit);
#else /* SUPPORT_MULTIBYTE_EDIT */
	if (refresh_mb)
	    LYRefreshEdit(edit);
#endif /* SUPPORT_MULTIBYTE_EDIT */
	ch = LYReadCmdKey(FOR_PROMPT);
#ifdef SUPPORT_MULTIBYTE_EDIT
#ifdef CJK_EX			/* for SJIS code */
	if (!refresh_mb
	    && (EditBinding(ch) != LYE_CHAR))
	    goto beginning;
#else
	if (!refresh_mb
	    && (EditBinding(ch) != LYE_CHAR)
	    && (EditBinding(ch) != LYE_AIX))
	    goto beginning;
#endif
#endif /* SUPPORT_MULTIBYTE_EDIT */

	if (term_letter || term_options
#ifdef VMS
	    || HadVMSInterrupt
#endif /* VMS */
#ifndef DISABLE_NEWS
	    || term_message
#endif
	    ) {
#ifdef VMS
	    HadVMSInterrupt = FALSE;
#endif /* VMS */
	    ch = LYCharINTERRUPT2;
	}

	if (recall != NORECALL && (ch == UPARROW_KEY || ch == DNARROW_KEY)) {
	    BStrCopy0(*inputline, Buffer);
	    LYAddToCloset(recall, Buffer);
	    CTRACE((tfp, "LYgetstr(%s) recall\n", (*inputline)->str));
#ifdef FEPCTRL
	    fep_off();
#endif
	    LYFinishEdit(edit);
	    result = ch;
	    done = TRUE;
	    continue;
	}
	ch |= InputMods;
	InputMods = 0;
	if (last_xlkc != -1) {
	    if (ch == last_xlkc)
		ch |= LKC_MOD3;
	    last_xlkc = -1;	/* consumed */
	}
#ifndef WIN_EX
	if (LKC_TO_LAC(keymap, ch) == LYK_REFRESH)
	    goto beginning;
#endif
	last_xlec = xlec;
	xlec = EditBinding(ch);
	if ((xlec & LYE_DF) && !(xlec & LYE_FORM_LAC)) {
	    last_xlkc = ch;
	    xlec &= ~LYE_DF;
	} else {
	    last_xlkc = -1;
	}
	switch (xlec) {
	case LYE_SETM1:
	    InputMods |= LKC_MOD1;
	    break;
	case LYE_SETM2:
	    InputMods |= LKC_MOD2;
	    break;
	case LYE_TAB:
	    if (xlec == last_xlec && recall != NORECALL) {
		HTList *list = whichRecall(recall);

		if (!HTList_isEmpty(list)) {
		    char **data = sortedList(list, (BOOL) (recall == RECALL_CMD));
		    int old_y, old_x;
		    int cur_choice = 0;
		    int num_options = LYarrayLength((STRING2PTR) data);

		    while (cur_choice < num_options
			   && strcasecomp(data[cur_choice], Buffer) < 0)
			cur_choice++;

		    LYGetYX(old_y, old_x);
		    cur_choice = LYhandlePopupList(cur_choice,
						   0,
						   old_x,
						   (STRING2PTR) data,
						   -1,
						   -1,
						   FALSE,
						   FALSE);
		    if (cur_choice >= 0) {
			if (recall == RECALL_CMD)
			    _statusline(": ");
			reinsertEdit(edit, data[cur_choice]);
		    }
		    LYmove(old_y, old_x);
		    FREE(data);
		}
	    } else {
		reinsertEdit(edit, LYFindInCloset(recall, Buffer));
	    }
	    break;

#ifndef CJK_EX
	case LYE_AIX:
	    /*
	     * Handle CJK characters, or as a valid character in the current
	     * display character set.  Otherwise, we treat this as LYE_ENTER.
	     */
	    if (ch != '\t' &&
		(IS_CJK_TTY ||
		 LYlowest_eightbit[current_char_set] <= 0x97)) {
		LYLineEdit(edit, ch, FALSE);
		break;
	    }
#endif
	    /* FALLTHRU */
	case LYE_ENTER:
	    BStrCopy0(*inputline, Buffer);
	    if (!hidden)
		LYAddToCloset(recall, Buffer);
	    CTRACE((tfp, "LYgetstr(%s) LYE_ENTER\n", (*inputline)->str));
#ifdef FEPCTRL
	    fep_off();
#endif
	    LYFinishEdit(edit);
	    result = ch;
	    done = TRUE;
	    continue;

#ifdef CAN_CUT_AND_PASTE
	case LYE_PASTE:
	    {
		unsigned char *s = (unsigned char *) get_clip_grab(), *e;
		size_t len;

		if (!s)
		    break;
		len = strlen((const char *) s);
		e = s + len;

		if (len != 0) {
		    unsigned char *e1 = s;

		    while (e1 < e) {
			if (*e1 < ' ') {	/* Stop here? */
			    if (e1 > s)
				LYEditInsert(edit, s, (int) (e1 - s),
					     map_active, TRUE);
			    s = e1;
			    if (*e1 == '\t') {	/* Replace by space */
				LYEditInsert(edit,
					     (unsigned const char *) " ",
					     1,
					     map_active,
					     TRUE);
				s = ++e1;
			    } else {
				break;
			    }
			} else {
			    ++e1;
			}
		    }
		    if (e1 > s) {
			LYEditInsert(edit, s, (int) (e1 - s), map_active, TRUE);
		    }
		}
		get_clip_release();
		break;
	    }
#endif

	case LYE_ABORT:
	    CTRACE((tfp, "LYgetstr LYE_ABORT\n"));
#ifdef FEPCTRL
	    fep_off();
#endif
	    LYFinishEdit(edit);
	    BStrCopy0(*inputline, "");
	    done = TRUE;
	    continue;

	case LYE_STOP:
	    CTRACE((tfp, "LYgetstr LYE_STOP\n"));
#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
	    textfields_need_activation = TRUE;
	    LYFinishEdit(edit);
	    BStrCopy0(*inputline, "");
	    done = TRUE;
	    continue;
#else
#ifdef ENHANCED_LINEEDIT
	    disableEditMark();
#endif
	    break;
#endif

	case LYE_LKCMD:
	    /*
	     * Used only in form_getstr() for invoking the LYK_F_LINK_NUM
	     * prompt when in form text fields.  - FM
	     */
	    break;

	case LYE_FORM_PASS:
	    /*
	     * Used in form_getstr() to end line editing and pass on the input
	     * char/lynxkeycode.  Here it is just ignored.  - kw
	     */
	    break;

	default:
	    if (xlec & LYE_FORM_LAC) {
		/*
		 * Used in form_getstr() to end line editing and pass on the
		 * lynxkeycode already containing a lynxactioncode.  Here it is
		 * just ignored.  - kw
		 */
		break;
	    }
#ifndef SUPPORT_MULTIBYTE_EDIT
	    LYLineEdit(edit, ch, FALSE);
#else /* SUPPORT_MULTIBYTE_EDIT */
	    if (LYLineEdit(edit, ch, FALSE) == 0) {
		if (refresh_mb && IS_CJK_TTY && (0x81 <= ch) && (ch <= 0xfe))
		    refresh_mb = FALSE;
		else
		    refresh_mb = TRUE;
	    } else {
		if (!refresh_mb) {
		    LYDoEdit(edit, 0, LYE_DELP, FALSE);
		}
	    }
#endif /* SUPPORT_MULTIBYTE_EDIT */
	}
    }
    return result;
}

/*
 * Use this for fixed-buffer edits which have not been converted to use
 * LYgetBString().
 */
int LYgetstr(char *inputline,	/* fixed-size buffer for input/output */
	     int hidden,	/* true to suppress from command-history */
	     unsigned bufsize,	/* sizeof(inputline) */
	     RecallType recall)	/* type of command-history */
{
    int ch;
    bstring *my_bstring = NULL;

    BStrCopy0(my_bstring, inputline);
    if (my_bstring != 0) {
	ch = LYgetBString(&my_bstring, hidden, bufsize, recall);
	if (ch >= 0 && my_bstring != 0)
	    LYStrNCpy(inputline, my_bstring->str, bufsize);
	BStrFree(my_bstring);
    } else {
	ch = -1;
    }
    return ch;
}

const char *LYLineeditHelpURL(void)
{
    static int lasthelp_lineedit = -1;
    static char helpbuf[LY_MAXPATH] = "\0";
    static char *phelp = &helpbuf[0];
    const char *result = NULL;

    if (lasthelp_lineedit == current_lineedit) {
	result = helpbuf;
    } else {
	const char *source = LYLineeditHelpURLs[current_lineedit];
	size_t available;

	if (lasthelp_lineedit == -1) {
	    LYStrNCpy(helpbuf, helpfilepath, sizeof(helpbuf) - 1);
	    phelp += strlen(helpbuf);
	}
	available = (sizeof(helpbuf) - (size_t) (phelp - helpbuf));
	if (non_empty(source) &&
	    (strlen(source) <= available)) {
	    LYStrNCpy(phelp, source, available);
	    lasthelp_lineedit = current_lineedit;
	    result = helpbuf;
	}
    }
    return result;
}

/*
 * Wrapper for sscanf to ensure that lynx can "always" read a POSIX float.
 * In some locales, the decimal point changes.
 */
int LYscanFloat2(const char **source, float *result)
{
    int count = 0;
    char *temp;
    const char *src = *source;

    src = LYSkipCBlanks(src);
    *result = 0.0;
    if (StrChr(src, '.') != 0) {
	long frc_part = 0;
	float scale = 1.0;

	if (*src != '.') {
	    temp = NULL;
	    frc_part = strtol(src, &temp, 10);
	    *result = (float) frc_part;
	    src = temp;
	}
	if (src != 0 && *src == '.') {
	    ++src;
	    if (isdigit(UCH(*src))) {
		temp = NULL;
		frc_part = strtol(src, &temp, 10);
		if (temp != 0) {
		    int digits = (int) (temp - src);

		    while (digits-- > 0)
			scale *= (float) 10.0;
		    *result += ((float) frc_part / scale);
		}
		src = temp;
	    }
	}
	if (src != 0 && *src != '\0' && StrChr(" \t+", *src) == 0) {
	    char *extra = (char *) malloc(2 + strlen(src));

	    if (extra != 0) {
		extra[0] = '1';
		strcpy(extra + 1, src);
		if (sscanf(extra, "%f", &scale) == 1) {
		    *result *= scale;
		}
		FREE(extra);
		src = LYSkipCNonBlanks(src);
	    } else {
		src = 0;
	    }
	}
	if (src != 0)
	    count = 1;
    } else {
	count = sscanf(src, "%f", result);
	src = LYSkipCNonBlanks(src);
    }
    CTRACE2(TRACE_CFG,
	    (tfp, "LYscanFloat \"%s\" -> %f (%s)\n",
	     *source, *result,
	     count ? "ok" : "error"));
    *source = src;
    return count;
}

int LYscanFloat(const char *source, float *result)
{
    const char *temp = source;

    return LYscanFloat2(&temp, result);
}

/*
 * A replacement for 'strsep()'
 */
char *LYstrsep(char **stringp,
	       const char *delim)
{
    char *marker;
    char *result = 0;

    if (non_empty(stringp)) {
	result = *stringp;	/* will return the old value */
	marker = strpbrk(*stringp, delim);
	if (marker) {
	    *marker = '\0';	/* terminate the substring */
	    *stringp = ++marker;	/* point to the next substring */
	} else {
	    *stringp = 0;	/* this was the last */
	}
    }
    return result;
}

/*
 * LYstrstr finds the first occurrence of the string pointed to by needle
 * in the string pointed to by haystack.
 *
 * It returns NULL if the string is not found.
 *
 * It is a case insensitive search.
 */
char *LYstrstr(char *haystack,
	       const char *needle)
{
    int len = (int) strlen(needle);
    char *result = NULL;

    for (; *haystack != '\0'; haystack++) {
	if (0 == UPPER8(*haystack, *needle)) {
	    if (0 == strncasecomp8(haystack + 1, needle + 1, len - 1)) {
		result = haystack;
		break;
	    }
	}
    }

    return (result);
}

#define SkipSpecialChars(p) \
	while (IsSpecialAttrChar(*p) && *p != '\0') \
	    p++

/*
 * LYno_attr_char_case_strstr finds the first occurrence of the
 * string pointed to by needle in the string pointed to by haystack.
 *
 * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
 *
 * It is a case insensitive search.
 */
const char *LYno_attr_char_case_strstr(const char *haystack,
				       const char *needle)
{
    const char *refptr, *tstptr;
    const char *result = NULL;

    if (haystack != NULL && needle != NULL) {

	SkipSpecialChars(haystack);

	for (; *haystack != '\0' && (result == NULL); haystack++) {
	    if (0 == UPPER8(*haystack, *needle)) {
		refptr = haystack + 1;
		tstptr = needle + 1;

		if (*tstptr == '\0') {
		    result = haystack;
		    break;
		}

		while (1) {
		    if (!IsSpecialAttrChar(*refptr)) {
			if (0 != UPPER8(*refptr, *tstptr))
			    break;
			refptr++;
			tstptr++;
		    } else {
			refptr++;
		    }
		    if (*tstptr == '\0') {
			result = haystack;
			break;
		    }
		    if (*refptr == '\0')
			break;
		}
	    }
	}
    }

    return (result);
}

/*
 * LYno_attr_char_strstr finds the first occurrence of the
 * string pointed to by needle in the string pointed to by haystack.
 * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
 *
 * It is a case sensitive search.
 */
const char *LYno_attr_char_strstr(const char *haystack,
				  const char *needle)
{
    const char *refptr, *tstptr;
    const char *result = NULL;

    if (haystack != NULL && needle != NULL) {

	SkipSpecialChars(haystack);

	for (; *haystack != '\0' && (result == NULL); haystack++) {
	    if ((*haystack) == (*needle)) {
		refptr = haystack + 1;
		tstptr = needle + 1;

		if (*tstptr == '\0') {
		    result = haystack;
		    break;
		}

		while (1) {
		    if (!IsSpecialAttrChar(*refptr)) {
			if ((*refptr) != (*tstptr))
			    break;
			refptr++;
			tstptr++;
		    } else {
			refptr++;
		    }
		    if (*tstptr == '\0') {
			result = haystack;
			break;
		    } else if (*refptr == '\0') {
			break;
		    }
		}
	    }
	}
    }

    return (result);
}

/*
 * LYno_attr_mbcs_case_strstr finds the first occurrence of the string pointed
 * to by needle in the string pointed to by haystack.  It takes account of
 * MultiByte Character Sequences (UTF8).  The physical lengths of the displayed
 * string up to the start and end (= next position after) of the target string
 * are returned in *nstartp and *nendp if the search is successful.
 *
 * These lengths count glyph cells if count_gcells is set.  (Full-width
 * characters in CJK mode count as two.) Normally that's what we want.  They
 * count actual glyphs if count_gcells is unset.  (Full-width characters in CJK
 * mode count as one.)
 *
 * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
 *
 * It assumes UTF8 if utf_flag is set.
 *
 * It is a case insensitive search.
 */
const char *LYno_attr_mbcs_case_strstr(const char *haystack,
				       const char *needle,
				       int utf_flag,
				       int count_gcells,
				       int *nstartp,
				       int *nendp)
{
    const char *refptr;
    const char *tstptr;
    int len = 0;
    int offset;
    const char *result = NULL;

    if (haystack != NULL && needle != NULL) {

	SkipSpecialChars(haystack);

	for (; *haystack != '\0' && (result == NULL); haystack++) {
	    if ((!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
		 *haystack == *needle &&
		 IsNormalChar(*(haystack + 1))) ||
		(0 == UPPER8(*haystack, *needle))) {
		int tarlen = 0;

		offset = len;
		len++;

		refptr = (haystack + 1);
		tstptr = (needle + 1);

		if (*tstptr == '\0') {
		    if (nstartp)
			*nstartp = offset;
		    if (nendp)
			*nendp = len;
		    result = haystack;
		    break;
		}
		if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
		    *haystack == *needle &&
		    IsNormalChar(*refptr)) {
		    /* handle a CJK multibyte string */
		    if (*refptr == *tstptr) {
			refptr++;
			tstptr++;
			if (count_gcells)
			    tarlen++;
			if (*tstptr == '\0') {
			    if (nstartp)
				*nstartp = offset;
			    if (nendp)
				*nendp = len + tarlen;
			    result = haystack;
			    break;
			}
		    } else {
			/* not a match */
			haystack++;
			if (count_gcells)
			    len++;
			continue;
		    }
		}
		/* compare the remainder of the string */
		while (1) {
		    if (!IsSpecialAttrChar(*refptr)) {
			if (!utf_flag && IS_CJK_TTY && is8bits(*refptr)) {
			    if (*refptr == *tstptr &&
				*(refptr + 1) == *(tstptr + 1) &&
				!IsSpecialAttrChar(*(refptr + 1))) {
				refptr++;
				tstptr++;
				if (count_gcells)
				    tarlen++;
			    } else {
				break;
			    }
			} else if (0 != UPPER8(*refptr, *tstptr)) {
			    break;
			}

			if (!IS_UTF_EXTRA(*tstptr)) {
			    tarlen++;
			}
			refptr++;
			tstptr++;

		    } else {
			refptr++;
		    }

		    if (*tstptr == '\0') {
			if (nstartp)
			    *nstartp = offset;
			if (nendp)
			    *nendp = len + tarlen;
			result = haystack;
			break;
		    }
		    if (*refptr == '\0')
			break;
		}
	    } else if (!(IS_UTF_EXTRA(*haystack) ||
			 IsSpecialAttrChar(*haystack))) {
		if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
		    IsNormalChar(*(haystack + 1))) {
		    haystack++;
		    if (count_gcells)
			len++;
		}
		len++;
	    }
	}
    }

    return (result);
}

/*
 * LYno_attr_mbcs_strstr finds the first occurrence of the string pointed
 * to by needle in the string pointed to by haystack.
 *
 * It takes account of CJK and MultiByte Character Sequences (UTF8).  The
 * physical lengths of the displayed string up to the start and end (= next
 * position after) the target string are returned in *nstartp and *nendp if the
 * search is successful.
 *
 * These lengths count glyph cells if count_gcells is set.  (Full-width
 * characters in CJK mode count as two.) Normally that's what we want.  They
 * count actual glyphs if count_gcells is unset.  (Full-width characters in CJK
 * mode count as one.)
 *
 * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
 *
 * It assumes UTF8 if utf_flag is set.
 *
 * It is a case sensitive search.
 */
const char *LYno_attr_mbcs_strstr(const char *haystack,
				  const char *needle,
				  int utf_flag,
				  int count_gcells,
				  int *nstartp,
				  int *nendp)
{
    const char *refptr;
    const char *tstptr;
    int len = 0;
    int offset;
    const char *result = NULL;

    if (haystack != NULL && needle != NULL) {

	SkipSpecialChars(haystack);

	for (; *haystack != '\0' && (result == NULL); haystack++) {
	    if ((*haystack) == (*needle)) {
		int tarlen = 0;

		offset = len;
		len++;

		refptr = (haystack + 1);
		tstptr = (needle + 1);

		if (*tstptr == '\0') {
		    if (nstartp)
			*nstartp = offset;
		    if (nendp)
			*nendp = len;
		    result = haystack;
		    break;
		} else if (!utf_flag &&
			   IS_CJK_TTY &&
			   is8bits(*haystack) &&
			   IsNormalChar(*refptr)) {
		    /* handle a CJK multibyte string */
		    if (*refptr == *tstptr) {
			/* found match */
			refptr++;
			tstptr++;
			if (count_gcells)
			    tarlen++;
			if (*tstptr == '\0') {
			    if (nstartp)
				*nstartp = offset;
			    if (nendp)
				*nendp = len + tarlen;
			    result = haystack;
			    break;
			}
		    } else {
			/* not a match - restart comparison */
			haystack++;
			if (count_gcells)
			    len++;
			continue;
		    }
		}
		/* compare the remainder of the string */
		while (1) {
		    if (!IsSpecialAttrChar(*refptr)) {
			if (!utf_flag && IS_CJK_TTY && is8bits(*refptr)) {
			    if (*refptr == *tstptr &&
				*(refptr + 1) == *(tstptr + 1) &&
				!IsSpecialAttrChar(*(refptr + 1))) {
				refptr++;
				tstptr++;
				if (count_gcells)
				    tarlen++;
			    } else {
				break;
			    }
			} else if ((*refptr) != (*tstptr)) {
			    break;
			}

			if (!IS_UTF_EXTRA(*tstptr)) {
			    tarlen++;
			}
			refptr++;
			tstptr++;
		    } else {
			refptr++;
		    }

		    if (*tstptr == '\0') {
			if (nstartp)
			    *nstartp = offset;
			if (nendp)
			    *nendp = len + tarlen;
			result = haystack;
			break;
		    }
		    if (*refptr == '\0')
			break;
		}
	    } else if (!(IS_UTF_EXTRA(*haystack) ||
			 IsSpecialAttrChar(*haystack))) {
		if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
		    IsNormalChar(*(haystack + 1))) {
		    haystack++;
		    if (count_gcells)
			len++;
		}
		len++;
	    }
	}
    }
    return (result);
}

/*
 * Allocate and return a copy of a string.
 * see StrAllocCopy
 */
char *SNACopy(char **target,
	      const char *source,
	      size_t n)
{
    FREE(*target);
    if (source) {
	*target = typeMallocn(char, n + 1);

	if (*target == NULL) {
	    CTRACE((tfp, "Tried to malloc %lu bytes\n", (unsigned long) n));
	    outofmem(__FILE__, "SNACopy");
	}
	LYStrNCpy(*target, source, n);
    }
    return *target;
}

/*
 * Combine string allocation and concatenation.
 * see StrAllocCat
 */
char *SNACat(char **target,
	     const char *source,
	     size_t n)
{
    if (non_empty(source)) {
	if (*target) {
	    size_t length = strlen(*target);

	    *target = typeRealloc(char, *target, length + n + 1);

	    if (*target == NULL)
		outofmem(__FILE__, "SNACat");
	    LYStrNCpy(*target + length, source, n);
	} else {
	    *target = typeMallocn(char, n + 1);

	    if (*target == NULL)
		outofmem(__FILE__, "SNACat");
	    MemCpy(*target, source, n);
	    (*target)[n] = '\0';	/* terminate */
	}
    }
    return *target;
}

#include <caselower.h>

/*
 * Returns lowercase equivalent for unicode,
 * transparent output if no equivalent found.
 */
static long UniToLowerCase(long upper)
{
    size_t i, high, low;
    long diff = 0;
    long result = upper;

    if (upper > 0) {
	/*
	 * Try unicode_to_lower_case[].
	 */
	low = 0;
	high = TABLESIZE(unicode_to_lower_case);
	while (low < high) {
	    /*
	     * Binary search.
	     */
	    i = (low + (high - low) / 2);
	    diff = (unicode_to_lower_case[i].upper - upper);
	    if (diff < 0) {
		low = i + 1;
	    } else if (diff > 0) {
		high = i;
	    } else if (diff == 0) {
		result = unicode_to_lower_case[i].lower;
		break;
	    }
	}
    }

    return result;
}

/*
 *   UPPER8 ?
 *   it was "TOUPPER(a) - TOUPPER(b)" in its previous life...
 *
 *   It was realized that case-insensitive user search
 *   got information about upper/lower mapping from TOUPPER
 *   (precisely from "(TOUPPER(a) - TOUPPER(b))==0")
 *   and depends on locale in its 8bit mapping. -
 *   Usually fails with DOS/WINDOWS display charsets
 *   as well as on non-UNIX systems.
 *
 *   So use unicode case mapping.
 */
int UPPER8(int ch1, int ch2)
{
    int result = 0;

    if (ch1 == ch2) {
	result = 0;
    } else if (!ch2) {
	result = UCH(ch1);
    } else if (!ch1) {
	result = -UCH(ch2);
    } else if (UCH(TOASCII(ch1)) < 128 && UCH(TOASCII(ch2)) < 128) {
	/* case-insensitive match for us-ascii */
	result = (TOUPPER(ch1) - TOUPPER(ch2));
    } else if (UCH(TOASCII(ch1)) > 127 &&
	       UCH(TOASCII(ch2)) > 127) {
	/* case-insensitive match for upper half */
	if (DisplayCharsetMatchLocale) {
	    result = (TOUPPER(ch1) - TOUPPER(ch2));	/* old-style */
	} else {
	    long uni_ch2 = UCTransToUni((char) ch2, current_char_set);
	    long uni_ch1;

	    if (uni_ch2 < 0) {
		result = UCH(ch1);
	    } else {
		uni_ch1 = UCTransToUni((char) ch1, current_char_set);
		result = (int) (UniToLowerCase(uni_ch1) - UniToLowerCase(uni_ch2));
	    }
	}
    } else {
	result = -10;		/* mismatch */
    }

    return result;
}

/*
 * Replaces 'fgets()' calls into a fixed-size buffer with reads into a buffer
 * that is allocated.  When an EOF or error is found, the buffer is freed
 * automatically.
 */
char *LYSafeGets(char **target,
		 FILE *fp)
{
    char buffer[BUFSIZ];
    char *result = 0;

    if (target != 0)
	result = *target;
    if (result != 0)
	*result = 0;

    while (fgets(buffer, (int) sizeof(buffer), fp) != NULL) {
	if (*buffer)
	    result = StrAllocCat(result, buffer);
	if (StrChr(buffer, '\n') != 0)
	    break;
    }
    if (ferror(fp)) {
	FREE(result);
    } else if (feof(fp) && result && *result == '\0') {
	/*
	 * If the file ends in the middle of a line, return the partial line;
	 * if another call is made after this, it will return NULL.  - kw
	 */
	FREE(result);
    }
    if (target != 0)
	*target = result;
    return result;
}

#ifdef USE_CMD_LOGGING
static FILE *cmd_logfile;
static FILE *cmd_script;

void LYOpenCmdLogfile(int argc,
		      char **argv)
{
    int n;

    if (non_empty(lynx_cmd_logfile)) {
	cmd_logfile = LYNewTxtFile(lynx_cmd_logfile);
	if (cmd_logfile != 0) {
	    fprintf(cmd_logfile, "# Command logfile created by %s %s (%s)\n",
		    LYNX_NAME, LYNX_VERSION, LYVersionDate());
	    for (n = 0; n < argc; n++) {
		fprintf(cmd_logfile, "# Arg%d = %s\n", n, argv[n]);
	    }
	}
    }
}

BOOL LYHaveCmdScript(void)
{
    return (BOOL) (cmd_script != 0);
}

void LYOpenCmdScript(void)
{
    if (non_empty(lynx_cmd_script)) {
	cmd_script = fopen(lynx_cmd_script, TXT_R);
	CTRACE((tfp, "LYOpenCmdScript(%s) %s\n",
		lynx_cmd_script,
		cmd_script != 0 ? "SUCCESS" : "FAIL"));
    }
}

int LYReadCmdKey(int mode)
{
    int ch = -1;

    if (cmd_script != 0) {
	char *buffer = 0;
	char *src;
	char *tmp;

	while ((ch < 0) && LYSafeGets(&buffer, cmd_script) != 0) {
	    LYTrimTrailing(buffer);
	    src = LYSkipBlanks(buffer);
	    tmp = LYSkipNonBlanks(src);
	    switch ((unsigned) (tmp - src)) {
	    case 4:
		if (!strncasecomp(src, "exit", 4))
		    exit_immediately(EXIT_SUCCESS);
		break;
	    case 3:
		if (!strncasecomp(src, "key", 3)) {
		    ch = LYStringToKeycode(LYSkipBlanks(tmp));
		} else if (!strncasecomp(src, "set", 3)) {
		    src = LYSkipBlanks(tmp);
		    tmp = src;
		    while (*tmp != '\0') {
			if (isspace(UCH(*tmp)) || *tmp == '=')
			    break;
			++tmp;
		    }
		    if (*tmp != '\0') {
			*tmp++ = '\0';
			tmp = LYSkipBlanks(tmp);
		    }
		    if (LYSetConfigValue(src, tmp)) {
			CTRACE((tfp, "LYSetConfigValue(%s, %s)\n", src, tmp));
		    } else if (LYsetRcValue(src, tmp)) {
			CTRACE((tfp, "LYsetRcValue(%s, %s)\n", src, tmp));
		    } else {
			CTRACE((tfp, "?? set ignored %s\n", src));
		    }
		}
		break;
	    }
	}
	if (feof(cmd_script)) {
	    fclose(cmd_script);
	    cmd_script = 0;
	}
	if (ch >= 0) {
	    LYSleepReplay();
	    LYrefresh();
	}
	FREE(buffer);
    } else {
	ch = LYgetch_for(mode);
    }
    CTRACE((tfp, "LYReadCmdKey(%d) ->%s (%#x)\n",
	    mode, LYKeycodeToString(ch, TRUE), (unsigned) ch));
    LYWriteCmdKey(ch);
    return ch;
}

/*
 * Write a LYKeymapCode 'ch' to the logfile.
 */
void LYWriteCmdKey(int ch)
{
    if (cmd_logfile != 0) {
	fprintf(cmd_logfile, "key %s\n", LYKeycodeToString(ch, FALSE));
    }
}

void LYCloseCmdLogfile(void)
{
    if (cmd_logfile != 0) {
	LYCloseOutput(cmd_logfile);
	cmd_logfile = 0;
    }
    if (cmd_script != 0) {
	LYCloseInput(cmd_script);
	cmd_script = 0;
    }
    FREE(lynx_cmd_logfile);
    FREE(lynx_cmd_script);
}
#endif /* USE_CMD_LOGGING */
