summaryrefslogtreecommitdiffstats
path: root/src/LYStrings.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:21:21 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:21:21 +0000
commit510ed32cfbffa6148018869f5ade416505a450b3 (patch)
tree0aafabcf3dfaab7685fa0fcbaa683dafe287807e /src/LYStrings.c
parentInitial commit. (diff)
downloadlynx-510ed32cfbffa6148018869f5ade416505a450b3.tar.xz
lynx-510ed32cfbffa6148018869f5ade416505a450b3.zip
Adding upstream version 2.9.0rel.0.upstream/2.9.0rel.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/LYStrings.c')
-rw-r--r--src/LYStrings.c6217
1 files changed, 6217 insertions, 0 deletions
diff --git a/src/LYStrings.c b/src/LYStrings.c
new file mode 100644
index 0000000..2bbd56d
--- /dev/null
+++ b/src/LYStrings.c
@@ -0,0 +1,6217 @@
+/* $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 */