#include #include #include #include #include /* errors */ void perr(const char *s) { fprintf(stderr, "%s\n", s); } /* misc */ int running; int quit(const char *_) { puts("goodbye!"); running = 0; return 0; } /* documents */ #define TXT_BUF_SIZE (1024 * 1024) struct { char buf[TXT_BUF_SIZE]; size_t n; } text; struct doc { struct { size_t ofs, len, idx; } txt; }; #define DOC_MAX 32 struct doc docv[DOC_MAX] = { { .txt = { .ofs = 0, .len = 0, .idx = 0 } } }; size_t docn = 0, doci = 0; void doc_pop(void) { if (docn > 0) { docn--; if (doci > 0) doci--; } } void doc_shift(void) { if (docn > 0) { docn--; memmove(text.buf, text.buf + docv[0].txt.len, text.n - docv[0].txt.len); memmove(&docv[0], &docv[1], docn * sizeof(struct doc)); docv[0].txt.ofs = 0; for (int i = 1; i < docn; i++) { docv[i].txt.idx = docv[i - 1].txt.ofs + (docv[i].txt.idx - docv[i].txt.ofs); docv[i].txt.ofs = docv[i - 1].txt.ofs + docv[i - 1].txt.len; } if (docn > 0 && doci >= docn) doci = docn - 1; } } int doc_new(void) { if (doci < docn) { text.n = docv[doci].txt.ofs; docn = doci; } if (docn >= DOC_MAX) return -1; memset(&docv[docn], 0, sizeof(struct doc)); docv[docn].txt.ofs = text.n; docv[docn].txt.idx = text.n; doci = docn++; return 0; } int doc_add(const char *buf, size_t n) { if (text.n + n > TXT_BUF_SIZE) { return -1; } memcpy(&text.buf[text.n], buf, n); docv[doci].txt.len += n; text.n += n; return 0; } int doc_adds(const char *s) { return doc_add(s, strlen(s)); } void doc_prev(void) { if (doci > 0) doci--; } void doc_next(void) { if (doci < docn) doci++; } void doc_back_line(void) { if (doci >= docn) return; struct doc *d = &docv[doci]; size_t i = d->txt.idx; while (i > 0 && text.buf[d->txt.idx] != '\n') i--; if (i > 0) i--; while (i > 0 && text.buf[d->txt.idx] != '\n') i--; if (i > 0) i++; d->txt.idx = i; } void doc_print_line(void) { if (doci >= docn) return; struct doc *d = &docv[doci]; size_t i = d->txt.idx; size_t n = d->txt.ofs + d->txt.len; while (i < n && text.buf[d->txt.idx] != '\n') { putchar(text.buf[i++]); } if (i < n) i++; d->txt.idx = i; } /* pagination */ size_t pg_lines() { return 24; } int pg_down(const char *_) { size_t lines = pg_lines(); while (lines--) doc_print_line(); return 0; } int pg_up(const char *_) { size_t lines = pg_lines() << 1; while (lines--) doc_back_line(); pg_down(_); return 0; } int pg_redraw(const char *_) { size_t lines = pg_lines(); while (lines--) doc_back_line(); pg_down(_); return 0; } /* navigation */ #define HOST_MAX 255 #define PATH_MAX 255 enum doc_type { TYPE_UNKNOWN, TYPE_GOPHERDOC, TYPE_GEMTEXT, TYPE_PLAIN, }; enum protocol { PROT_UNKNOWN, PROT_FILE, PROT_GOPHER, PROT_GEMINI, }; struct addr { char host[HOST_MAX]; char path[PATH_MAX]; size_t host_len, path_len; enum protocol prot; enum doc_type type; }; int url_to_addr(const char *url, struct addr *adr, enum protocol prot_default) { adr->prot = prot_default; adr->type = TYPE_PLAIN; adr->host_len = 0; size_t n = strlen(url); if (n > PATH_MAX) return -1; adr->path_len = n; memcpy(adr->path, url, n); return 0; } int nav_to(const char *url) { struct addr a; static enum protocol prot_default = PROT_FILE; /* change to gopher later */ if (url_to_addr(url, &a, prot_default)) { return -1; } prot_default = a.prot; return 0; } int nav_link_nr(unsigned long link_nr) { return 0; } /* commands */ struct cmd { char ch; int (*fn)(const char *); }; int addtxt(const char *s) { return doc_add(s, strlen(s)) || doc_add("\n", 1); } struct cmd cmd_tbl[] = { { 'g', nav_to }, { '\n', pg_down }, { 'b', pg_up }, { 'r', pg_redraw }, { 'a', addtxt }, { 'q', quit } }; /* cmd is mutated when trimming strings */ void cmd_do(char *cmd) { if (isdigit(*cmd)) { errno = 0; unsigned long n = strtoul(cmd, NULL, 10); if (errno) { perr("invalid link number"); } else if (nav_link_nr(n)) { perr("navigation failure"); } } else { for (size_t i = 0; i < sizeof cmd_tbl / sizeof(struct cmd); i++) { if (cmd_tbl[i].ch == *cmd) { while (isspace(*++cmd)); for (int j = strlen(cmd) - 1; j > 0 && isspace(cmd[j]); j--) { cmd[j] = 0; } if (cmd_tbl[i].fn(cmd)) { perr("failed"); } goto found; } } perr("?"); found: ; } } int cmd_get(char *buf, size_t n) { fputs("* ", stdout); return !!fgets(buf, n, stdin); } int main(void) { char cmd_buf[1024]; running = 1; doc_new(); doc_adds("Press ? for help.\n"); while (running && cmd_get(cmd_buf, sizeof cmd_buf)) { cmd_do(cmd_buf); } return 0; }