commit cba4dfd017893694d030202b0c538ce7313dd7ff
parent e9ce7e2eef0036d29e374ee3d6e8acde47d4e745
Author: Ondřej Fiala <ofiala@airmail.cc>
Date: Wed, 27 Dec 2023 03:33:17 +0100
gmnlm: add table of contents & jump to heading commands
FIXME: leaks a lot of memory
Diffstat:
M | src/gmnlm.c | | | 94 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- |
1 file changed, 86 insertions(+), 8 deletions(-)
diff --git a/src/gmnlm.c b/src/gmnlm.c
@@ -35,6 +35,12 @@ struct history {
struct history *prev, *next;
};
+struct headings {
+ int level;
+ char *title;
+ struct headings *next;
+};
+
#define REDIRS_UNLIMITED -1
#define REDIRS_ASK -2
@@ -53,10 +59,13 @@ struct browser {
struct Curl_URL *url;
struct link *links;
struct history *history;
+ struct headings *headings;
bool running;
bool searching;
regex_t regex;
+
+ int target_heading;
};
enum prompt_result {
@@ -86,6 +95,8 @@ const char *help_msg =
"b[N]\r\t\tJump back N entries in history, default is 1\n"
"f[N]\r\t\tJump forward N entries in history, default is 1\n"
"H\r\t\tView all page history\n"
+ "t\r\t\tShow table of contents\n"
+ "#<N>\r\t\tJump to Nth heading\n"
"m [title]\r\t\tSave bookmark\n"
"M\r\t\tBrowse bookmarks\n"
"r\r\t\tReload the page\n"
@@ -497,6 +508,16 @@ out:
return res;
}
+static void
+print_toc(FILE *out, struct headings *first)
+{
+ int nheads = 0;
+ fprintf(out, "Table of contents:\n\n");
+ for (struct headings *p = first; p; p = p->next) {
+ fprintf(out, "%*d. %s\n", (p->level) * 4 - 1, ++nheads, p->title);
+ }
+}
+
static enum prompt_result
do_prompts(const char *prompt, struct browser *browser)
{
@@ -681,6 +702,30 @@ do_prompts(const char *prompt, struct browser *browser)
print_media_parameters(browser->tty, browser->meta
? strchr(browser->meta, ';') : NULL);
goto exit;
+ case 't':
+ if (in[1]) {
+ fprintf(stderr, "Error: unrecognized command.\n");
+ goto exit;
+ }
+ print_toc(browser->tty, browser->headings);
+ goto exit;
+ case '#':
+ if (!in[1]) {
+ fprintf(stderr, "Error: missing argument.\n");
+ goto exit;
+ } else if (!isdigit(in[1])) {
+ fprintf(stderr, "Error: invalid argument.\n");
+ goto exit;
+ }
+ endptr = &in[1];
+ int id = (int)strtol(endptr, &endptr, 10);
+ if (id <= 0 || *endptr) {
+ fprintf(stderr, "Error: invalid argument.\n");
+ goto exit;
+ }
+ browser->target_heading = id;
+ result = PROMPT_ANSWERED;
+ goto exit;
case 'd':
endptr = &in[1];
char *d_url = browser->plain_url;
@@ -843,21 +888,27 @@ static bool
display_document(struct browser *browser, struct gemini_response *resp,
int (*parser_next)(struct gemini_parser *, struct gemini_token *))
{
- int nlinks = 0;
+ int nlinks = 0, nheadings = 0;
struct gemini_parser p;
gemini_parser_init(&p, resp->body);
free(browser->page_title);
browser->page_title = NULL;
struct gemini_token tok;
- struct link **next = &browser->links;
+ struct link **nextlnk = &browser->links;
+ struct headings **nexthd = &browser->headings;
FILE *out = fopen("/dev/null", "w");
while (parser_next(&p, &tok) == 0) {
- if (tok.token != GEMINI_LINK) goto skip;
- *next = calloc(1, sizeof(struct link));
- (*next)->url = strdup(tok.link.url);
- next = &(*next)->next;
-skip:
+ if (tok.token == GEMINI_LINK) {
+ *nextlnk = calloc(1, sizeof(struct link));
+ (*nextlnk)->url = strdup(tok.link.url);
+ nextlnk = &(*nextlnk)->next;
+ } else if(tok.token == GEMINI_HEADING) {
+ *nexthd = calloc(1, sizeof(struct headings));
+ (*nexthd)->level = tok.heading.level;
+ (*nexthd)->title = strdup(tok.heading.title);
+ nexthd = &(*nexthd)->next;
+ }
gemini_token_finish(&tok);
}
fclose(out);
@@ -867,7 +918,7 @@ skip:
char *text = NULL;
int row = 0, col = 0;
bool searching = browser->searching;
- if (searching) {
+ if (searching || browser->target_heading) {
out = fopen("/dev/null", "w+");
}
@@ -936,6 +987,16 @@ repeat:
}
break;
case GEMINI_HEADING:
+ if (browser->target_heading == nheadings + 1) {
+ assert(out != browser->tty);
+ fclose(out);
+ row = col = 0;
+ out = browser->tty;
+ text = NULL;
+ browser->target_heading = 0;
+ goto repeat;
+ }
+ nheadings++;
if (!browser->page_title) {
browser->page_title = strdup(tok.heading.title);
}
@@ -967,6 +1028,10 @@ repeat:
break;
}
+ if (browser->target_heading) {
+ text = NULL;
+ continue;
+ }
if (text && searching) {
int r = regexec(&browser->regex, text, 0, NULL, 0);
if (r != 0) {
@@ -1035,6 +1100,10 @@ repeat:
row = col = 0;
}
}
+ if (browser->target_heading) {
+ fprintf(stderr, "Error: no such heading.\n");
+ browser->target_heading = 0;
+ }
gemini_token_finish(&tok);
return false;
@@ -1262,6 +1331,15 @@ main(int argc, char *argv[])
link = next;
}
browser.links = NULL;
+
+ struct headings *head = browser.headings;
+ while (head) {
+ struct headings *next = head->next;
+ free(head->title);
+ free(head);
+ head = next;
+ }
+ browser.headings = NULL;
}
gemini_tofu_finish(&browser.tofu);