2 * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * doc-email: Present an email message as its intended content, with
6 * part recognition and decoding etc.
8 * A multipart document is created from sets of three documents.
10 * - The first is the original email, overlayed with 'crop' to select
11 * one section, then overlayed with handlers for the transfer-encoding
12 * and (optionally) charset. There will be one for the headers
13 * and either one for the body, or one for each part of the body
14 * is multipart. All nested multiparts have their parts linearized.
15 * - The second is a scratch text document which can contain a transformed
16 * copy of the content when the tranformation is too complex for an
17 * overlay layer. This includes HTML and PDF which can be converted
18 * to text, but the conversion is complex, and the headers, which need to be
19 * re-ordered as well as filtered and decoded. For images, this document has
20 * trivial content but specifier a rendering function that displays the image.
21 * - The final section is a 'spacer' which has fixed content and displays
22 * as a summary line for the part (e.g. MIME-type, file name) and
23 * provides active buttons for acting on the content (save, external viewer
26 * The middle part has attributes set on the document which can be
27 * accessed from the spacer using "multipart-prev:"
28 * - email:path identify the part in the nexted multipart struture
29 * e.g. "header", "body", "body,multipart/mixed:0,mulitpart/alternate:1"
30 * - email:actions a ':' separated list of buttons. "hide:save:view"
31 * - email:content-type the MIME type of the content. "image/png"
35 #define _GNU_SOURCE /* for asprintf */
44 #define PANE_DATA_TYPE struct email_view
45 #define DOC_NEXT email_next
46 #define DOC_PREV email_prev
55 #include "core-pane.h"
57 static inline bool is_orig(int p)
59 return p >= 0 && p % 3 == 0;
62 static inline bool is_transformed(int p)
64 return p >= 0 && p % 3 == 1;
67 static inline bool is_spacer(int p)
69 return p >= 0 && p % 3 == 2;
72 static inline int to_orig(int p)
79 static bool handle_content(struct pane *p safe,
80 char *type, char *xfer, char *disp,
81 struct mark *start safe, struct mark *end safe,
82 struct pane *mp safe, struct pane *spacer safe,
84 static bool handle_rfc822(struct pane *email safe,
85 struct mark *start safe, struct mark *end safe,
86 struct pane *mp safe, struct pane *spacer safe,
89 static bool cond_append(struct buf *b safe, char *txt safe, char *tag safe,
90 int offset, int *pos safe)
92 char *tagf = "action-activate:email-";
93 int prelen = 1 + strlen(tagf) + strlen(tag) + 1 + 1;
95 int len = prelen + strlen(txt) + postlen;
96 if (offset >= 0 && offset <= b->len + len)
104 buf_concat(b, "]</>");
108 static bool is_attr(char *a safe, char *attrs safe)
111 if (strncmp(a, attrs, l) != 0)
113 if (attrs[l] == ':' || attrs[l] == '\0')
118 DEF_CMD(email_spacer)
124 struct mark *m = ci->mark;
125 struct mark *pm = ci->mark2;
136 /* Count the number of chars before the cursor.
137 * This tells us which button to highlight.
141 while (!mark_ordered_or_same(pm, m)) {
142 doc_prev(ci->focus, pm);
149 buf_concat(&b, "<fg:red>");
151 attr = pane_mark_attr(ci->home, m, "multipart-prev:email:path");
153 buf_concat(&b, attr);
157 attr = pane_mark_attr(ci->focus, m, "email:visible");
158 if (attr && strcmp(attr, "none") == 0)
160 if (attr && strcmp(attr, "orig") == 0)
162 attr = attr_find(ci->home->attrs, "email:extra-headers");
163 extras = attr && *attr;
165 attr = pane_mark_attr(ci->home, m, "multipart-prev:email:actions");
169 while (ok && attr && *attr) {
171 char *a = strchr(attr, ':');
174 if (is_attr("hide", attr))
175 ok = cond_append(&b, visible ? "HIDE" : "SHOW", attr,
177 else if (is_attr("full", attr))
178 ok = cond_append(&b, orig ? "BRIEF" : "FULL", attr,
180 else if (is_attr("extras", attr))
181 ok = cond_append(&b, extras ? "-" : "EXTRAS", attr,
184 ok = cond_append(&b, attr, attr, o, &pos);
186 doc_next(ci->focus, m);
195 /* end of line, only display if we haven't reached
196 * the cursor or offset
198 * if cp < 0, we aren't looking for a cursor, so don't stop.
199 * if cp > 0, we haven't reached cursor yet, so don't stop
200 * if cp == 0, this is cursor pos, so stop.
204 buf_concat(&b, "</>");
205 attr = pane_mark_attr(ci->focus, m,
206 "multipart-prev:email:content-type");
209 buf_concat(&b, attr);
211 attr = pane_mark_attr(ci->focus, m,
212 "multipart-prev:email:charset");
215 buf_concat(&b, attr);
217 attr = pane_mark_attr(ci->focus, m,
218 "multipart-prev:email:filename");
220 buf_concat(&b, " file=");
221 buf_concat(&b, attr);
225 buf_concat(&b, "\n");
228 while ((wch = doc_next(ci->focus, m)) &&
229 wch != '\n' && wch != WEOF)
233 ret = comm_call(ci->comm2, "callback:render", ci->focus, ret_pos, NULL,
239 static int get_part(struct pane *p safe, struct mark *m safe)
241 char *a = pane_mark_attr(p, m, "multipart:part-num");
256 struct mark *point = ci->mark2;
257 int max_chars = ci->num;
262 p = get_part(ci->home, ci->mark);
263 scale = pane_scale(ci->focus);
266 asprintf(&c, "<image:comm:doc:multipart-%d-doc:get-bytes,width:%d,height:%d,noupscale,map:RccRccRcc>\n",
268 ci->focus->w * 1000 / scale.x,
269 ci->focus->h * 750 / scale.x);
272 map_start = strlen(c) - 2 - 9;
273 if (max_chars >= 0 && max_chars < (int)strlen(c))
276 for (i = 0; i < 9 ; i++) {
277 if (point && mark_ordered_or_same(point, ci->mark))
278 retlen = map_start + i;
279 if (max_chars >= 0 && map_start + i >= max_chars)
281 doc_next(ci->focus, ci->mark);
284 ret = comm_call(ci->comm2, "callback:render", ci->focus,
290 DEF_CMD(email_select_hide)
294 struct mark *m = ci->mark;
299 a = pane_mark_attr(ci->focus, m, "email:visible");
300 if (a && strcmp(a, "none") == 0)
302 call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
303 vis ? "none" : "preferred");
307 DEF_CMD(email_select_full)
311 struct mark *m = ci->mark;
316 a = pane_mark_attr(ci->focus, m, "email:visible");
317 if (a && strcmp(a, "orig") == 0)
319 call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
320 want_orig ? "orig" : "transformed");
324 DEF_CMD(email_select_extras)
326 char *a = attr_find(ci->home->attrs, "email:extra-headers");
327 struct mark *m = ci->mark;
329 struct pane *hdrdoc, *headers;
335 headers = call_ret(pane, "doc:multipart:get-part", ci->focus, 0);
336 hdrdoc = call_ret(pane, "doc:multipart:get-part", ci->focus, 1);
337 if (!headers || !hdrdoc)
339 point = point_new(hdrdoc);
343 call("doc:set-ref", hdrdoc, 0, point);
344 home_call(headers, "get-header", hdrdoc, 0, point, "Message-ID", 1);
345 home_call(headers, "get-header", hdrdoc, 0, point, "In-Reply-To", 1);
346 home_call(headers, "get-header", hdrdoc, 0, point, "References", 1, NULL, "list");
347 file = pane_attr_get(headers, "filename");
349 call("doc:replace", hdrdoc, 1, point, "Filename: ",
350 0, point, ",render:rfc822header=9");
351 call("doc:replace", hdrdoc, 1, point, file, 0, point);
352 call("doc:replace", hdrdoc, 1, point, "\n", 0, point);
357 attr_set_str(&ci->home->attrs, "email:extra-headers", "done");
358 call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
363 static struct map *email_view_map safe;
365 DEF_LOOKUP_CMD(email_view_handle, email_view_map);
367 static const char tspecials[] = "()<>@,;:\\\"/[]?=";
369 static int lws(char c) {
370 return c == ' ' || c == '\t' || c == '\r' || c == '\n';
373 static char *get_822_token(char **hdrp safe, int *len safe)
375 /* A "token" is one of:
377 * - single char from tspecials (except '(' or '"')
378 * - string of non-LWS, and non-tspecials
380 * (comments) are skipped.
381 * Start is returned, hdrp is moved, len is reported.
392 while (*hdr && *hdr != ')')
398 while (*hdr && *hdr != '"')
409 if (strchr(tspecials, *hdr)) {
417 while (*hdr && !lws(*hdr) && !strchr(tspecials, *hdr))
425 static char *get_822_attr(char *shdr safe, char *attr safe)
427 /* If 'hdr' contains "$attr=...", return "..."
428 * with "quotes" stripped. Return value can be used
429 * until the next call, when it will be free.
434 static char *last = NULL;
441 while ((h = get_822_token(&hdr, &len)) != NULL &&
442 (len != alen || strncasecmp(h, attr, alen) != 0))
444 h = get_822_token(&hdr, &len);
445 if (!h || len != 1 || *h != '=')
447 h = get_822_token(&hdr, &len);
450 last = strndup(h, len);
456 static char *get_822_word(char *hdr safe)
458 /* Get the first word from header, in static
459 * space (freed on next call)
461 static char *last = NULL;
467 h = get_822_token(&hdr, &len);
470 last = strndup(h, len);
474 static bool tok_matches(char *tok, int len, char *match safe)
478 if (len != (int)strlen(match))
480 return strncasecmp(tok, match, len) == 0;
483 static bool handle_text(struct pane *p safe, char *type, char *xfer, char *disp,
484 struct mark *start safe, struct mark *end safe,
485 struct pane *mp safe, struct pane *spacer safe,
488 struct pane *h, *transformed = NULL;
489 int need_charset = 0;
490 char *charset = NULL;
491 char *major, *minor = NULL;
496 h = call_ret(pane, "attach-crop", p, 0, start, NULL, 0, end);
502 xfer = get_822_token(&xfer, &xlen);
503 if (xfer && xlen == 16 &&
504 strncasecmp(xfer, "quoted-printable", 16) == 0) {
505 struct pane *hx = call_ret(pane,
506 "attach-quoted_printable",
513 if (xfer && xlen == 6 &&
514 strncasecmp(xfer, "base64", 6) == 0) {
515 struct pane *hx = call_ret(pane, "attach-base64", h);
521 if (xfer && xlen == 4 &&
522 strncasecmp(xfer, "8bit", 6) == 0)
523 need_charset = 2; // only if not utf-8
526 charset = get_822_attr(type, "charset");
527 if (need_charset && !charset && strncasecmp(type, "text/", 5) == 0)
528 /* We really do need a charset, as the doc might
529 * only provide bytes, not chars.
532 if (type && need_charset && charset != NULL &&
533 !(need_charset == 2 && strcasecmp(charset, "utf-8") == 0)) {
535 struct pane *hx = NULL;
536 charset = strsave(h, charset);
537 asprintf(&c, "attach-charset-%s", charset);
538 for (cp = c; cp && *cp; cp++)
542 hx = call_ret(pane, c, h);
545 /* windows-1251 is safer than utf-8 as the latter
546 * rejects some byte sequences, and iso-8859-* has
547 * lots of control characters.
549 hx = call_ret(pane, "attach-charset-windows-1251", h);
554 if (type && (fname = get_822_attr(type, "name")))
555 fname = strsave(h, fname);
556 if (disp && !fname &&
557 (fname = get_822_attr(disp, "filename")))
558 fname = strsave(h, fname);
559 major = get_822_token(&type, &majlen);
561 minor = get_822_token(&type, &minlen);
562 if (minor && tok_matches(minor, minlen, "/"))
563 minor = get_822_token(&type, &minlen);
568 asprintf(&ctype, "%1.*s/%1.*s", majlen, major, minlen, minor);
570 asprintf(&ctype, "%1.*s", majlen, major);
571 if (ctype && strcasecmp(ctype, "text/html") == 0) {
572 transformed = call_ret(pane, "html-to-text-w3m", h, 1);
574 transformed = call_ret(pane, "html-to-text", h, 1);
576 if (ctype && strcasecmp(ctype, "text/calendar") == 0)
577 transformed = call_ret(pane, "ical-to-text", h, 1);
578 if (ctype && strcasecmp(ctype, "application/pdf") == 0)
579 transformed = call_ret(pane, "pdf-to-text", h, 1);
580 if (ctype && strcasecmp(ctype, "application/octet-stream") == 0 &&
581 fname && strcasestr(fname, ".pdf") != NULL)
582 transformed = call_ret(pane, "pdf-to-text", h, 1);
583 if (major && strncasecmp(major, "application", majlen) == 0 &&
584 fname && (strcasestr(fname, ".docx") != NULL ||
585 strcasestr(fname, ".doc") != NULL ||
586 strcasestr(fname, ".odt") != NULL))
587 transformed = call_ret(pane, "doc-to-text", h, 1, NULL, fname);
589 if (ctype && strncasecmp(ctype, "image/", 6) == 0) {
591 transformed = call_ret(pane, "doc:from-text", h,
592 0, NULL, NULL, 0, NULL,
595 m = mark_new(transformed);
596 call("doc:set-ref", transformed, 1, m);
597 call("doc:replace", transformed, 1, m, "01",
598 0, m, ",markup:func=doc:email:render-image");
599 call("doc:replace", transformed, 1, m, "\n01",
600 0, m, ",markup:not_eol=1");
601 call("doc:replace", transformed, 1, m, "\n01\n",
602 0, m, ",markup:not_eol=1");
607 attr_set_str(&transformed->attrs, "email:is_transformed", "yes");
608 attr_set_str(&transformed->attrs, "email:preferred", "transformed");
610 transformed = call_ret(pane, "doc:from-text", h,
611 0, NULL, NULL, 0, NULL, "\n");
613 attr_set_str(&transformed->attrs, "email:preferred",
620 call("doc:set:autoclose", transformed, 1);
623 for (i = 0; ctype[i]; i++)
624 if (isupper(ctype[i]))
625 ctype[i] = tolower(ctype[i]);
626 attr_set_str(&transformed->attrs, "email:content-type", ctype);
629 attr_set_str(&h->attrs, "email:content-type", "text/plain");
630 attr_set_str(&transformed->attrs, "email:actions", "hide:save");
631 attr_set_str(&transformed->attrs, "email:path", path);
632 attr_set_str(&transformed->attrs, "email:which", "transformed");
634 attr_set_str(&transformed->attrs, "email:charset", charset);
636 attr_set_str(&transformed->attrs, "email:filename", fname);
638 attr_set_str(&transformed->attrs,
639 "email:content-disposition", disp);
640 attr_set_str(&h->attrs, "email:which", "orig");
642 home_call(mp, "multipart-add", h);
643 home_call(mp, "multipart-add", transformed);
644 home_call(mp, "multipart-add", spacer);
648 /* Find a multipart boundary between start and end, moving
649 * 'start' to after the boundary, and 'pos' to just before it.
650 * Return 0 if a non-terminal boundary is found
651 * Return 1 if a terminal boundary is found (trailing --)
652 * Return -1 if nothing is found.
654 #define is_lws(c) ({int _c2 = c; _c2 == ' ' || _c2 == '\t' || is_eol(_c2); })
655 static int find_boundary(struct pane *p safe,
656 struct mark *start safe, struct mark *end safe,
662 int len = strlen(boundary);
664 asprintf(&patn, "^--(?%d:%s)(--)?[ \\t\\r]*$", len, boundary);
665 ret = call("text-search", p, 0, start, patn, 0, end);
671 mark_to_mark(pos, start);
672 while (cnt > 0 && doc_prev(p, pos) != WEOF)
674 /* Previous char is CRLF, and must be swallowed */
675 if (doc_prior(p, pos) == '\n')
677 if (doc_prior(p, pos) == '\r')
680 while (is_lws(doc_prior(p, start))) {
684 while (is_lws(doc_following(p, start)))
688 if (ret == 2 + len + 2)
693 static bool handle_multipart(struct pane *p safe, char *type safe,
694 struct mark *start safe, struct mark *end safe,
695 struct pane *mp safe, struct pane *spacer safe,
698 char *boundary = get_822_attr(type, "boundary");
700 struct mark *pos, *part_end;
707 /* FIXME need a way to say "just display the text" */
710 found_end = find_boundary(p, start, end, NULL, boundary);
713 tok = get_822_token(&type, &len);
715 tok = get_822_token(&type, &len);
716 if (tok && tok[0] == '/')
717 tok = get_822_token(&type, &len);
719 boundary = strdup(boundary);
720 pos = mark_dup(start);
721 part_end = mark_dup(pos);
722 while (found_end == 0 &&
723 (found_end = find_boundary(p, pos, end, part_end,
725 struct pane *hdr = call_ret(pane, "attach-rfc822header", p,
728 char *ptype, *pxfer, *pdisp;
732 call("get-header", hdr, 0, NULL, "content-type",
734 call("get-header", hdr, 0, NULL, "content-transfer-encoding",
736 call("get-header", hdr, 0, NULL, "content-disposition",
738 ptype = attr_find(hdr->attrs, "rfc822-content-type");
739 pxfer = attr_find(hdr->attrs,
740 "rfc822-content-transfer-encoding");
741 pdisp = attr_find(hdr->attrs,
742 "rfc822-content-disposition");
747 asprintf(&newpath, "%s%s%1.*s:%d", path, path[0] ? ",":"",
751 if (part_end->seq < start->seq)
752 mark_to_mark(part_end, start);
753 handle_content(p, ptype, pxfer, pdisp, start, part_end, mp,
754 spacer, newpath ?:"");
756 mark_to_mark(start, pos);
758 mark_to_mark(start, pos);
765 static bool handle_content(struct pane *p safe,
766 char *type, char *xfer, char *disp,
767 struct mark *start safe, struct mark *end safe,
768 struct pane *mp safe, struct pane *spacer safe,
772 char *major, *minor = NULL;
775 if (mark_ordered_or_same(end, start))
782 major = get_822_token(&hdr, &mjlen);
784 minor = get_822_token(&hdr, &mnlen);
785 if (minor && minor[0] == '/')
786 minor = get_822_token(&hdr, &mnlen);
789 if (tok_matches(major, mjlen, "multipart"))
790 return handle_multipart(p, type, start, end, mp, spacer, path);
792 if (tok_matches(major, mjlen, "message") && tok_matches(minor, mnlen, "rfc822"))
793 return handle_rfc822(p, start, end, mp, spacer, path);
795 if (tok_matches(major, mjlen, "text") && tok_matches(minor, mnlen, "rfc822-headers"))
796 return handle_rfc822(p, start, end, mp, spacer, path);
799 tok_matches(major, mjlen, "text"))
800 return handle_text(p, type, xfer, disp, start, end,
803 /* default to plain text until we get a better default */
804 return handle_text(p, type, xfer, disp, start, end, mp, spacer, path);
807 static bool handle_rfc822(struct pane *email safe,
808 struct mark *start safe, struct mark *end safe,
809 struct pane *mp safe, struct pane *spacer safe,
812 struct pane *h2, *h3;
813 struct pane *hdrdoc = NULL;
814 struct mark *point = NULL;
815 struct mark *hdr_start;
816 char *xfer = NULL, *type = NULL, *disp = NULL;
818 char *newpath = NULL;
820 hdr_start = mark_dup(start);
821 h2 = call_ret(pane, "attach-rfc822header", email, 0, start, NULL, 0, end);
824 attr_set_str(&h2->attrs, "email:which", "orig");
826 hdrdoc = call_ret(pane, "doc:from-text", email, 0, NULL, NULL, 0, NULL, "");
829 call("doc:set:autoclose", hdrdoc, 1);
830 point = point_new(hdrdoc);
834 /* copy some headers to the header temp document */
835 home_call(h2, "get-header", hdrdoc, 0, point, "From", 1, NULL, "list");
836 home_call(h2, "get-header", hdrdoc, 0, point, "Date", 1);
837 home_call(h2, "get-header", hdrdoc, 0, point, "Subject", 1, NULL, "text");
838 home_call(h2, "get-header", hdrdoc, 0, point, "To", 1, NULL, "list");
839 home_call(h2, "get-header", hdrdoc, 0, point, "Cc", 1, NULL, "list");
840 home_call(h2, "get-header", hdrdoc, 0, point, "Reply-To", 1, NULL, "list");
842 /* copy some headers into attributes for later analysis */
843 call("get-header", h2, 0, NULL, "MIME-Version", 0, NULL, "cmd");
844 call("get-header", h2, 0, NULL, "content-type", 0, NULL, "cmd");
845 call("get-header", h2, 0, NULL, "content-transfer-encoding",
847 call("get-header", h2, 0, NULL, "content-disposition",
849 mime = attr_find(h2->attrs, "rfc822-mime-version");
851 mime = get_822_word(mime);
852 /* Some email doesn't contain MIME-Type, but is still mime... */
853 if (!mime || strcmp(mime, "1.0") == 0) {
854 type = attr_find(h2->attrs, "rfc822-content-type");
855 xfer = attr_find(h2->attrs, "rfc822-content-transfer-encoding");
856 disp = attr_find(h2->attrs, "rfc822-content-disposition");
860 asprintf(&newpath, "%s%sheaders", path, path[0] ? ",":"");
861 attr_set_str(&hdrdoc->attrs, "email:actions", "hide:extras:full");
862 attr_set_str(&hdrdoc->attrs, "email:which", "transformed");
863 attr_set_str(&hdrdoc->attrs, "email:content-type", "text/rfc822-headers");
864 attr_set_str(&hdrdoc->attrs, "email:path", newpath);
865 attr_set_str(&hdrdoc->attrs, "email:is_transformed", "yes");
866 h3 = call_ret(pane, "attach-crop", h2, 0, hdr_start, NULL, 0, start);
869 home_call(mp, "multipart-add", h3);
870 home_call(mp, "multipart-add", hdrdoc);
871 home_call(mp, "multipart-add", spacer);
875 asprintf(&newpath, "%s%sbody", path, path[0] ? ",":"");
876 if (!handle_content(email, type, xfer, disp, start, end,
877 mp, spacer, newpath?:""))
895 struct pane *email = NULL;
896 struct pane *spacer = NULL;
897 struct mark *start, *end;
901 if (ci->str == NULL || !strstarts(ci->str, "email:"))
903 fd = open(ci->str+6, O_RDONLY);
906 p = call_ret(pane, "doc:open", ci->focus, fd, NULL, ci->str + 6, 1);
915 end = mark_dup(start);
916 call("doc:set-ref", p, 0, end);
918 call("doc:set:autoclose", p, 1);
921 /* create spacer doc to be attached between each part */
922 p = call_ret(pane, "doc:from-text", p, 0, NULL, NULL, 0, NULL,
927 attr_set_str(&p->attrs, "email:which", "spacer");
928 call("doc:set:autoclose", p, 1);
930 point = point_new(p);
931 call("doc:set-ref", p, 1, point);
932 call("doc:set-attr", p, 1, point, "markup:func", 0,
933 NULL, "doc:email:render-spacer");
936 /* Create the multipart in which all the parts are assembled. */
937 p = call_ret(pane, "attach-doc-multipart", ci->home);
940 call("doc:set:autoclose", p, 1);
941 attr_set_str(&p->attrs, "render-default", "text");
942 attr_set_str(&p->attrs, "filename", ci->str+6);
943 attr_set_str(&p->attrs, "doc-type", "email");
945 if (!handle_rfc822(email, start, end, p, spacer, ""))
950 return comm_call(ci->comm2, "callback:attach", p);
961 DEF_CMD_CLOSED(email_view_close)
963 struct email_view *evi = ci->home->data;
970 static int count_buttons(struct pane *p safe, struct mark *m safe)
973 char *attr = pane_mark_attr(p, m, "multipart-prev:email:actions");
978 attr = strchr(attr, ':');
985 static inline wint_t email_next(struct pane *p safe, struct mark *m safe,
986 struct doc_ref *r safe, bool bytes)
988 struct email_view *evi = p->data;
989 bool move = r == &m->ref;
993 ret = home_call(p->parent, "doc:char", p,
997 n = get_part(p->parent, m);
998 if (move && is_spacer(n)) {
999 /* Moving in a spacer. IF after valid buttons,
1003 unsigned int buttons = count_buttons(p, m);
1004 while ((c = doc_following(p->parent, m)) != WEOF &&
1005 iswdigit(c) && (c-'0') >= buttons)
1006 doc_next(p->parent, m);
1011 static inline wint_t email_prev(struct pane *p safe, struct mark *m safe,
1012 struct doc_ref *r safe, bool bytes)
1014 struct email_view *evi = p->data;
1015 bool move = r == &m->ref;
1019 ret = home_call(p->parent, "doc:char", p,
1023 n = get_part(p->parent, m);
1024 if (is_spacer(n) && move &&
1025 ret != CHAR_RET(WEOF) && iswdigit(ret & 0x1fffff)) {
1026 /* Just stepped back over the 9 at the end of a spacer,
1027 * Maybe step further if there aren't 10 buttons.
1029 unsigned int buttons = count_buttons(p, m);
1030 wint_t c = ret & 0x1fffff;
1032 while (c != WEOF && iswdigit(c) && c - '0' >= buttons)
1033 c = doc_prev(p->parent, m);
1041 return do_char_byte(ci);
1044 DEF_CMD(email_content)
1046 /* Call the multipart doc:content telling it
1047 * what is invisible, marking all spacers as invisible
1049 struct pane *p = ci->home;
1050 struct email_view *evi = p->data;
1051 char *invis2 = strsave(p, evi->invis);
1054 for (i = 0; invis2 && invis2[i]; i++)
1057 return home_call(p->parent, ci->key, p,
1058 ci->num, ci->mark, invis2,
1059 ci->num2, ci->mark2, ci->str2,
1060 ci->x, ci->y, ci->comm2);
1063 DEF_CMD(email_set_ref)
1065 struct pane *p = ci->home;
1066 struct email_view *evi = p->data;
1070 home_call_comm(p->parent, ci->key, ci->focus, ci->comm2,
1071 ci->num, ci->mark, evi->invis);
1075 DEF_CMD(email_step_part)
1077 struct pane *p = ci->home;
1078 struct email_view *evi = p->data;
1082 home_call(p->parent, "doc:step-part", ci->focus, ci->num, ci->mark, evi->invis);
1086 DEF_CMD(email_view_get_attr)
1090 struct email_view *evi = ci->home->data;
1092 if (!ci->str || !ci->mark)
1094 if (strcmp(ci->str, "email:visible") == 0) {
1095 p = get_part(ci->home->parent, ci->mark);
1096 /* only parts can be invisible, not separators */
1098 if (p < 0 || p >= evi->parts)
1100 else if (!evi->invis)
1102 else if (evi->invis[p] != 'i')
1104 else if (evi->invis[p+1] != 'i')
1109 return comm_call(ci->comm2, "callback", ci->focus, 0, ci->mark,
1110 v, 0, NULL, ci->str);
1112 if (strcmp(ci->str, "email:image") == 0) {
1115 p = get_part(ci->home->parent, ci->mark);
1116 sprintf(im, "comm:doc:multipart-%d-doc:get-bytes", to_orig(p));
1117 return comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
1118 im, 0, NULL, ci->str);
1120 return Efallthrough;
1123 DEF_CMD(email_view_set_attr)
1126 struct email_view *evi = ci->home->data;
1128 if (!ci->str || !ci->mark)
1130 if (strcmp(ci->str, "email:visible") == 0) {
1131 struct mark *m1, *m2;
1134 p = get_part(ci->home->parent, ci->mark);
1135 /* only parts can be invisible, not separators */
1137 if (p < 0 || p >= evi->parts || !evi->invis)
1140 m1 = mark_dup(ci->mark);
1141 while (get_part(ci->home->parent, m1) > p &&
1142 home_call(ci->home->parent, "doc:step-part",
1143 ci->focus, -1, m1) > 0)
1147 if (w && strcmp(w, "preferred") == 0) {
1148 w = pane_mark_attr(ci->focus, m1,
1149 "multipart-next:email:preferred");
1152 } else if (w && (strcmp(w, "orig") == 0 ||
1153 strcmp(w, "transformed") == 0)) {
1154 call("doc:set-attr", ci->focus, 1, m1,
1155 "multipart-next:email:preferred", 0, NULL, w);
1157 evi->invis[p] = 'i';
1158 evi->invis[p+1] = 'i';
1159 if (w && strcmp(w, "orig") == 0)
1160 evi->invis[p] = 'v';
1161 if (w && strcmp(w, "transformed") == 0)
1162 evi->invis[p+1] = 'v';
1164 /* Tell viewers that visibility has changed */
1167 home_call(ci->home->parent, "doc:step-part", ci->focus,
1169 home_call(ci->home->parent, "doc:step-part", ci->focus,
1171 call("view:changed", ci->focus, 0, m1, NULL, 0, m2);
1172 call("Notify:clip", ci->focus, 0, m1, NULL, 0, m2);
1176 if (w && strcmp(w, "transformed") == 0) {
1177 /* If the transformation was delayed, tell it
1181 call_ret(pane, "doc:multipart:get-part",
1182 ci->home->parent, p+1);
1184 call("doc:notify:convert-now", part);
1189 return Efallthrough;
1192 DEF_CMD(attach_email_view)
1194 struct pane *p, *p2;
1195 struct email_view *evi;
1199 m = mark_new(ci->focus);
1202 call("doc:set-ref", ci->focus, 0, m);
1203 n = get_part(ci->focus, m);
1205 if (n <= 0 || n > 1000 )
1208 p = pane_register(ci->focus, 0, &email_view_handle.c);
1213 evi->invis = calloc(n+1, sizeof(char));
1214 for (i = 0; i < n; i++) {
1216 /* Spacers must be visible */
1217 evi->invis[i] = 'v';
1218 else if (is_orig(i) && i < 2*3)
1219 /* Headers and first part can be visible */
1220 evi->invis[i] = 'v';
1222 /* Everything else default to invisible */
1223 evi->invis[i] = 'i';
1225 p2 = call_ret(pane, "attach-line-count", p);
1228 attr_set_str(&p->attrs, "render-hide-CR", "yes");
1229 return comm_call(ci->comm2, "callback:attach", p);
1232 static void email_init_map(void)
1234 email_view_map = key_alloc();
1235 key_add(email_view_map, "Close", &email_view_close);
1236 key_add(email_view_map, "doc:char", &email_char);
1237 key_add(email_view_map, "doc:content", &email_content);
1238 key_add(email_view_map, "doc:content-bytes", &email_content);
1239 key_add(email_view_map, "doc:set-ref", &email_set_ref);
1240 key_add(email_view_map, "doc:get-boundary", &email_set_ref);
1241 key_add(email_view_map, "doc:email-step-part", &email_step_part);
1242 key_add(email_view_map, "doc:set-attr", &email_view_set_attr);
1243 key_add(email_view_map, "doc:get-attr", &email_view_get_attr);
1244 key_add(email_view_map, "doc:email:render-spacer", &email_spacer);
1245 key_add(email_view_map, "doc:email:render-image", &email_image);
1246 key_add(email_view_map, "email:select:hide", &email_select_hide);
1247 key_add(email_view_map, "email:select:full", &email_select_full);
1248 key_add(email_view_map, "email:select:extras", &email_select_extras);
1251 void edlib_init(struct pane *ed safe)
1254 call_comm("global-set-command", ed, &open_email, 0, NULL,
1256 call_comm("global-set-command", ed, &attach_email_view, 0, NULL,
1257 "attach-email-view");