summaryrefslogtreecommitdiffstats
path: root/src/LYJump.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/LYJump.c')
-rw-r--r--src/LYJump.c504
1 files changed, 504 insertions, 0 deletions
diff --git a/src/LYJump.c b/src/LYJump.c
new file mode 100644
index 0000000..877d532
--- /dev/null
+++ b/src/LYJump.c
@@ -0,0 +1,504 @@
+/*
+ * $LynxId: LYJump.c,v 1.52 2018/03/18 19:17:00 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTAlert.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYGlobalDefs.h>
+#include <LYJump.h>
+#include <LYKeymap.h>
+#include <GridText.h>
+
+#include <LYLeaks.h>
+
+#ifdef _WINDOWS
+#include <stdlib.h> /* bsearch() */
+#endif
+
+#ifdef VMS
+#include <fab.h>
+#endif /* VMS */
+
+struct JumpTable *JThead = NULL;
+
+static int LYCompare(const void *e1, const void *e2);
+static unsigned LYRead_Jumpfile(struct JumpTable *jtp);
+
+void LYJumpTable_free(void)
+{
+ struct JumpTable *cur = JThead;
+ struct JumpTable *next;
+
+ while (cur) {
+ next = cur->next;
+ FREE(cur->msg);
+ FREE(cur->file);
+ FREE(cur->shortcut);
+ if (cur->history) {
+ LYFreeStringList(cur->history);
+ cur->history = NULL;
+ }
+ FREE(cur->table);
+ FREE(cur->mp);
+ FREE(cur);
+ cur = next;
+ }
+ JThead = NULL;
+ return;
+}
+
+/*
+ * Utility for listing shortcuts, making any repeated
+ * shortcut the most current in the list. - FM
+ */
+void LYAddJumpShortcut(HTList *historyp, char *shortcut)
+{
+ char *tmp = NULL;
+ char *old;
+ HTList *cur = historyp;
+
+ if (!historyp || isEmpty(shortcut))
+ return;
+
+ StrAllocCopy(tmp, shortcut);
+
+ while (NULL != (old = (char *) HTList_nextObject(cur))) {
+ if (!strcmp(old, tmp)) {
+ HTList_removeObject(historyp, old);
+ FREE(old);
+ break;
+ }
+ }
+ HTList_addObject(historyp, tmp);
+
+ return;
+}
+
+BOOL LYJumpInit(char *config)
+{
+ struct JumpTable *jtp;
+ char *cp;
+
+ /*
+ * Create a JumpTable structure.
+ */
+ jtp = typecalloc(struct JumpTable);
+
+ if (jtp == NULL) {
+ outofmem(__FILE__, "LYJumpInit");
+ }
+
+ /*
+ * config is JUMPFILE:path[:optional_key[:optional_prompt]]
+ *
+ * Skip JUMPFILE.
+ */
+ cp = strtok(config, ":\n");
+ if (!cp) {
+ FREE(jtp);
+ return FALSE;
+ }
+
+ /*
+ * Get the path.
+ */
+ cp = strtok(NULL, ":\n");
+ if (!cp) {
+ FREE(jtp);
+ return FALSE;
+ }
+ StrAllocCopy(jtp->file, cp);
+#ifdef LY_FIND_LEAKS
+ if (!JThead)
+ atexit(LYJumpTable_free);
+#endif /* LY_FIND_LEAKS */
+
+ /*
+ * Get the key, if present.
+ */
+ cp = strtok(NULL, ":\n");
+
+ /*
+ * If no key, check whether we are resetting the default jumps file.
+ */
+ if (!cp && JThead) {
+ struct JumpTable *jtptmp = JThead;
+
+ jumpfile = jtp->file;
+ FREE(jtp);
+ while (jtptmp && jtptmp->key)
+ jtptmp = jtptmp->next;
+ if (!jtptmp)
+ return FALSE;
+ StrAllocCopy(jtptmp->file, jumpfile);
+ StrAllocCopy(jtptmp->msg, jumpprompt);
+ return TRUE;
+ }
+
+ /*
+ * If a key is present and we have no default, create one,
+ * using the path from config, and the current jumpprompt.
+ */
+ if (cp && !JThead) {
+ JThead = jtp;
+ StrAllocCopy(JThead->msg, jumpprompt);
+ if (isEmpty(jumpfile))
+ StrAllocCopy(jumpfile, JThead->file);
+ jtp = typecalloc(struct JumpTable);
+
+ if (jtp == NULL) {
+ outofmem(__FILE__, "LYJumpInit");
+ }
+
+ StrAllocCopy(jtp->file, JThead->file);
+ }
+
+ /*
+ * Complete the initialization of config.
+ */
+ if (cp) {
+ jtp->key = remap(cp, "JUMP", FALSE); /* key is present, (re)map it */
+ cp = strtok(NULL, "\n"); /* get prompt, if present */
+ if (non_empty(cp))
+ StrAllocCopy(jtp->msg, cp); /* prompt is present, load it */
+ else
+ cp = NULL;
+ }
+ if (!cp) /* no prompt, use default */
+ StrAllocCopy(jtp->msg, jumpprompt);
+ if (jtp->msg[strlen(jtp->msg) - 1] != ' ') /* ensure a trailing space */
+ StrAllocCat(jtp->msg, " ");
+ jtp->history = HTList_new();
+ jtp->next = JThead;
+ JThead = jtp;
+ return TRUE;
+}
+
+char *LYJump(int key)
+{
+ static bstring *buf = NULL;
+
+ JumpDatum seeking;
+ JumpDatum *found;
+ char *bp, *cp;
+ struct JumpTable *jtp;
+ int ch;
+ RecallType recall;
+ int ShortcutTotal;
+ int ShortcutNum;
+ BOOLEAN FirstShortcutRecall = TRUE;
+
+ if (!JThead)
+ return NULL;
+ jtp = JThead;
+ while (jtp && jtp->key && jtp->key != key)
+ jtp = jtp->next;
+ if (!jtp) {
+ char *msg = 0;
+
+ HTSprintf0(&msg, KEY_NOT_MAPPED_TO_JUMP_FILE, key);
+ HTAlert(msg);
+ FREE(msg);
+ return NULL;
+ }
+ if (!jtp->table)
+ jtp->nel = LYRead_Jumpfile(jtp);
+ if (jtp->nel == 0)
+ return NULL;
+
+ if (!jump_buffer || isEmpty(jtp->shortcut)) {
+ BStrCopy0(buf, "");
+ } else if (non_empty(jtp->shortcut)) {
+ size_t len = (size_t) BStrLen(buf);
+
+ if (strlen(jtp->shortcut) > len) {
+ jtp->shortcut[len] = '\0';
+ BStrCopy0(buf, jtp->shortcut);
+ }
+ }
+
+ ShortcutTotal = (jtp->history ? HTList_count(jtp->history) : 0);
+ if (jump_buffer && !isBEmpty(buf)) {
+ recall = ((ShortcutTotal > 1) ? RECALL_URL : NORECALL);
+ ShortcutNum = 0;
+ FirstShortcutRecall = FALSE;
+ } else {
+ recall = ((ShortcutTotal >= 1) ? RECALL_URL : NORECALL);
+ ShortcutNum = ShortcutTotal;
+ FirstShortcutRecall = TRUE;
+ }
+
+ statusline(jtp->msg);
+ if ((ch = LYgetBString(&buf, FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the Jump via ^G. - FM
+ */
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+
+ check_recall:
+ bp = buf->str;
+ if (TOUPPER(key) == 'G' && StrNCmp(buf->str, "o ", 2) == 0)
+ bp++;
+ bp = LYSkipBlanks(bp);
+ if (*bp == '\0' &&
+ !(recall && (ch == UPARROW_KEY || ch == DNARROW_KEY))) {
+ /*
+ * User cancelled the Jump via a zero-length string. - FM
+ */
+ BStrCopy0(buf, "");
+ StrAllocCopy(jtp->shortcut, buf->str);
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+#ifdef PERMIT_GOTO_FROM_JUMP
+ if (StrChr(bp, ':') || StrChr(bp, '/')) {
+ char *temp = NULL;
+
+ LYJumpFileURL = FALSE;
+ if (no_goto) {
+ BStrCopy0(buf, "");
+ StrAllocCopy(jtp->shortcut, buf->str);
+ HTUserMsg(RANDOM_URL_DISALLOWED);
+ return NULL;
+ }
+ HTSprintf0(&temp, "Go %s", bp);
+ BStrCopy0(buf, temp);
+ FREE(temp);
+ return (bp = buf->str);
+ }
+#endif /* PERMIT_GOTO_FROM_JUMP */
+
+ if (recall && ch == UPARROW_KEY) {
+ if (FirstShortcutRecall) {
+ /*
+ * Use last Shortcut in the list. - FM
+ */
+ FirstShortcutRecall = FALSE;
+ ShortcutNum = 0;
+ } else {
+ /*
+ * Go back to the previous Shortcut in the list. - FM
+ */
+ ShortcutNum++;
+ }
+ if (ShortcutNum >= ShortcutTotal)
+ /*
+ * Roll around to the last Shortcut in the list. - FM
+ */
+ ShortcutNum = 0;
+ if ((cp = (char *) HTList_objectAt(jtp->history,
+ ShortcutNum)) != NULL) {
+ BStrCopy0(buf, cp);
+ if (jump_buffer && jtp->shortcut &&
+ !strcmp(buf->str, jtp->shortcut)) {
+ _statusline(EDIT_CURRENT_SHORTCUT);
+ } else if ((jump_buffer && ShortcutTotal == 2) ||
+ (!jump_buffer && ShortcutTotal == 1)) {
+ _statusline(EDIT_THE_PREV_SHORTCUT);
+ } else {
+ _statusline(EDIT_A_PREV_SHORTCUT);
+ }
+ if ((ch = LYgetBString(&buf, FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the jump via ^G.
+ */
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+ goto check_recall;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (FirstShortcutRecall) {
+ /*
+ * Use the first Shortcut in the list. - FM
+ */
+ FirstShortcutRecall = FALSE;
+ ShortcutNum = ShortcutTotal - 1;
+ } else {
+ /*
+ * Advance to the next Shortcut in the list. - FM
+ */
+ ShortcutNum--;
+ }
+ if (ShortcutNum < 0)
+ /*
+ * Roll around to the first Shortcut in the list. - FM
+ */
+ ShortcutNum = ShortcutTotal - 1;
+ if ((cp = (char *) HTList_objectAt(jtp->history,
+ ShortcutNum)) != NULL) {
+ BStrCopy0(buf, cp);
+ if (jump_buffer && jtp->shortcut &&
+ !strcmp(buf->str, jtp->shortcut)) {
+ _statusline(EDIT_CURRENT_SHORTCUT);
+ } else if ((jump_buffer && ShortcutTotal == 2) ||
+ (!jump_buffer && ShortcutTotal == 1)) {
+ _statusline(EDIT_THE_PREV_SHORTCUT);
+ } else {
+ _statusline(EDIT_A_PREV_SHORTCUT);
+ }
+ if ((ch = LYgetBString(&buf, FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the jump via ^G.
+ */
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+ goto check_recall;
+ }
+ }
+
+ seeking.key = bp;
+ found = (JumpDatum *) bsearch((char *) &seeking, (char *) jtp->table,
+ (size_t) jtp->nel, sizeof(JumpDatum), LYCompare);
+ if (!found) {
+ user_message("Unknown target '%s'", buf->str);
+ LYSleepAlert();
+ }
+
+ StrAllocCopy(jtp->shortcut, bp);
+ LYAddJumpShortcut(jtp->history, jtp->shortcut);
+ return found ? found->url : NULL;
+}
+
+static unsigned LYRead_Jumpfile(struct JumpTable *jtp)
+{
+ struct stat st;
+ unsigned int nel;
+ char *mp;
+ int fd;
+
+#ifdef VMS
+ int blocksize = 1024;
+ FILE *fp;
+ BOOL IsStream_LF = TRUE;
+#endif /* VMS */
+ char *cp;
+ unsigned i;
+
+ if (isEmpty(jtp->file))
+ return 0;
+
+ CTRACE((tfp, "Read Jumpfile %s\n", jtp->file));
+ if (stat(jtp->file, &st) < 0) {
+ HTAlert(CANNOT_LOCATE_JUMP_FILE);
+ return 0;
+ }
+
+ /* allocate storage to read entire file */
+ if ((mp = typecallocn(char, (size_t) st.st_size + 1)) == NULL) {
+ HTAlert(OUTOF_MEM_FOR_JUMP_FILE);
+ return 0;
+ }
+#ifdef VMS
+ if (st.st_fab_rfm != (char) FAB$C_STMLF) {
+ /** It's a record-oriented file. **/
+ IsStream_LF = FALSE;
+ if ((fp = fopen(jtp->file, "r", "mbc=32")) == NULL) {
+ HTAlert(CANNOT_OPEN_JUMP_FILE);
+ FREE(mp);
+ return 0;
+ }
+ } else if ((fd = open(jtp->file, O_RDONLY, "mbc=32")) < 0)
+#else
+ if ((fd = open(jtp->file, O_RDONLY)) < 0)
+#endif /* VMS */
+ {
+ HTAlert(CANNOT_OPEN_JUMP_FILE);
+ FREE(mp);
+ return 0;
+ }
+#ifdef VMS
+ if (IsStream_LF) {
+ /** Handle as a stream. **/
+#endif /* VMS */
+ if (read(fd, mp, (size_t) st.st_size) != st.st_size) {
+ HTAlert(ERROR_READING_JUMP_FILE);
+ FREE(mp);
+ close(fd);
+ return 0;
+ }
+ mp[st.st_size] = '\0';
+ close(fd);
+#ifdef VMS
+ } else {
+ /** Handle as a series of records. **/
+ if (fgets(mp, blocksize, fp) == NULL) {
+ HTAlert(ERROR_READING_JUMP_FILE);
+ FREE(mp);
+ close(fd);
+ return 0;
+ } else {
+ while (fgets(mp + strlen(mp), blocksize, fp) != NULL) {
+ ;
+ }
+ }
+ LYCloseInput(fp);
+ close(fd);
+ }
+#endif /* VMS */
+
+ /* quick scan for approximate number of entries */
+ nel = 0;
+ cp = mp;
+ while ((cp = StrChr(cp, '\n')) != NULL) {
+ nel++;
+ cp++;
+ }
+
+ jtp->table = (JumpDatum *) malloc((nel + 1) * sizeof(JumpDatum));
+ if (jtp->table == NULL) {
+ HTAlert(OUTOF_MEM_FOR_JUMP_TABLE);
+ FREE(mp);
+ return 0;
+ }
+
+ cp = jtp->mp = mp;
+ for (i = 0; i < nel;) {
+ if (StrNCmp(cp, "<!--", 4) == 0 || StrNCmp(cp, "<dl>", 4) == 0) {
+ cp = StrChr(cp, '\n');
+ if (cp == NULL)
+ break;
+ cp++;
+ continue;
+ }
+ cp = LYstrstr(cp, "<dt>");
+ if (cp == NULL)
+ break;
+ cp += 4;
+ jtp->table[i].key = cp;
+ cp = LYstrstr(cp, "<dd>");
+ if (cp == NULL)
+ break;
+ *cp = '\0';
+ cp += 4;
+ cp = LYstrstr(cp, "href=\"");
+ if (cp == NULL)
+ break;
+ cp += 6;
+ jtp->table[i].url = cp;
+ cp = StrChr(cp, '"');
+ if (cp == NULL)
+ break;
+ *cp = '\0';
+ cp++;
+ cp = StrChr(cp, '\n');
+ if (cp == NULL)
+ break;
+ cp++;
+ CTRACE((tfp, "Read jumpfile[%u] key='%s', url='%s'\n",
+ i, jtp->table[i].key, jtp->table[i].url));
+ i++;
+ }
+
+ return i;
+}
+
+static int LYCompare(const void *e1, const void *e2)
+{
+ return strcasecomp(((const JumpDatum *) e1)->key,
+ ((const JumpDatum *) e2)->key);
+}