gmni

a gemini line mode client
git clone https://git.clttr.info/gmni.git
Log (Feed) | Files | Refs (Tags) | README | LICENSE

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:
Msrc/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);