]> git.neil.brown.name Git - edlib.git/blobdiff - doc-email.c
TODO: clean out done items.
[edlib.git] / doc-email.c
index f9648f9a8a585bb7ef70e53ae83a70c517b29447..58d44cbb914fb817e1c2ecd6cc48035f94daeecb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright Neil Brown ©2016-2022 <neil@brown.name>
+ * Copyright Neil Brown ©2016-2023 <neil@brown.name>
  * May be distributed under terms of GPLv2 - see file:COPYING
  *
  * doc-email: Present an email message as its intended content, with
@@ -24,7 +24,7 @@
  *   etc).
  *
  * The middle part has attributes set on the document which can be
- * accessed form the spacer using "multipart-prev:"
+ * accessed from the spacer using "multipart-prev:"
  * - email:path identify the part in the nexted multipart struture
  *   e.g. "header", "body", "body,multipart/mixed:0,mulitpart/alternate:1"
  * - email:actions a ':' separated list of buttons. "hide:save:view"
 #include <wctype.h>
 #include <ctype.h>
 #include <stdio.h>
+
+#define PANE_DATA_TYPE struct email_view
+#define DOC_NEXT email_next
+#define DOC_PREV email_prev
 #include "core.h"
 #include "misc.h"
 
+struct email_view {
+       int     parts;
+       char    *invis;
+};
+
+#include "core-pane.h"
+
 static inline bool is_orig(int p)
 {
        return p >= 0 && p % 3 == 0;
@@ -76,22 +87,19 @@ static bool handle_rfc822(struct pane *email safe,
                          char *path safe);
 
 static bool cond_append(struct buf *b safe, char *txt safe, char *tag safe,
-                       int offset, int *cp safe)
+                       int offset, int *pos safe)
 {
-       char *tagf = "active-tag:email-";
+       char *tagf = "action-activate:email-";
        int prelen = 1 + strlen(tagf) + strlen(tag) + 1 + 1;
        int postlen = 1 + 3;
        int len = prelen + strlen(txt) + postlen;
-       if (offset != NO_NUMERIC && offset >= 0 && offset <= b->len + len)
+       if (offset >= 0 && offset <= b->len + len)
                return False;
        buf_concat(b, "<");
        buf_concat(b, tagf);
        buf_concat(b, tag);
        buf_concat(b, ">[");
-       if (*cp == 0)
-               return False;
-       if (*cp > 0)
-               *cp -= 1;
+       *pos = b->len;
        buf_concat(b, txt);
        buf_concat(b, "]</>");
        return True;
@@ -111,10 +119,13 @@ DEF_CMD(email_spacer)
 {
        struct buf b;
        int visible = 1;
+       int orig = 0;
+       int extras = 0;
        struct mark *m = ci->mark;
        struct mark *pm = ci->mark2;
        int o = ci->num;
        int cp = -1;
+       int ret_pos = 0;
        char *attr;
        int ret;
        bool ok = True;
@@ -146,21 +157,37 @@ DEF_CMD(email_spacer)
        attr = pane_mark_attr(ci->focus, m, "email:visible");
        if (attr && strcmp(attr, "none") == 0)
                visible = 0;
+       if (attr && strcmp(attr, "orig") == 0)
+               orig = 1;
+       attr = attr_find(ci->home->attrs, "email:extra-headers");
+       extras = attr && *attr;
+
        attr = pane_mark_attr(ci->home, m, "multipart-prev:email:actions");
        if (!attr)
                attr = "hide";
 
        while (ok && attr && *attr) {
+               int pos = 0;
                char *a = strchr(attr, ':');
                if (a)
                        *a = 0;
                if (is_attr("hide", attr))
                        ok = cond_append(&b, visible ? "HIDE" : "SHOW", attr,
-                                        o, &cp);
+                                        o, &pos);
+               else if (is_attr("full", attr))
+                       ok = cond_append(&b, orig ? "BRIEF" : "FULL", attr,
+                                        o, &pos);
+               else if (is_attr("extras", attr))
+                       ok = cond_append(&b, extras ? "-" : "EXTRAS", attr,
+                                        o, &pos);
                else
-                       ok = cond_append(&b, attr, attr, o, &cp);
+                       ok = cond_append(&b, attr, attr, o, &pos);
                if (ok)
                        doc_next(ci->focus, m);
+               if (cp >= 0) {
+                       cp -= 1;
+                       ret_pos = pos;
+               }
                attr = a;
                if (attr)
                        *attr++ = ':';
@@ -172,7 +199,7 @@ DEF_CMD(email_spacer)
         * if cp > 0, we haven't reached cursor yet, so don't stop
         * if cp == 0, this is cursor pos, so stop.
         */
-       if (ok && cp != 0 && ((o < 0 || o == NO_NUMERIC))) {
+       if (ok && o < 0) {
                wint_t wch;
                buf_concat(&b, "</>");
                attr = pane_mark_attr(ci->focus, m,
@@ -193,13 +220,17 @@ DEF_CMD(email_spacer)
                        buf_concat(&b, " file=");
                        buf_concat(&b, attr);
                }
+               if (cp >= 0)
+                       ret_pos = b.len;
                buf_concat(&b, "\n");
+               if (cp >= 1)
+                       ret_pos = b.len;
                while ((wch = doc_next(ci->focus, m)) &&
                       wch != '\n' && wch != WEOF)
                        ;
        }
 
-       ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL,
+       ret = comm_call(ci->comm2, "callback:render", ci->focus, ret_pos, NULL,
                        buf_final(&b));
        free(b.b);
        return ret;
@@ -219,60 +250,63 @@ DEF_CMD(email_image)
        char *c = NULL;
        int p;
        int ret;
+       int retlen = 0;
+       int i;
+       struct xy scale;
+       struct mark *point = ci->mark2;
+       int max_chars = ci->num;
+       int map_start;
 
        if (!ci->mark)
                return Enoarg;
        p = get_part(ci->home, ci->mark);
-       doc_next(ci->focus, ci->mark);
-       asprintf(&c, "<image:comm:doc:multipart-%d-doc:get-bytes,noupscale>\n",
-                to_orig(p));
+       scale = pane_scale(ci->focus);
+       if (scale.x < 1)
+               scale.x = 1;
+       asprintf(&c, "<image:comm:doc:multipart-%d-doc:get-bytes,width:%d,height:%d,noupscale,map:RccRccRcc>\n",
+                to_orig(p),
+                ci->focus->w * 1000 / scale.x,
+                ci->focus->h * 750 / scale.x);
+       if (!c)
+               return Efail;
+       map_start = strlen(c) - 2 - 9;
+       if (max_chars >= 0 && max_chars < (int)strlen(c))
+               c[max_chars] = '\0';
+
+       for (i = 0; i < 9 ; i++) {
+               if (point && mark_ordered_or_same(point, ci->mark))
+                       retlen = map_start + i;
+               if (max_chars >= 0 && map_start + i >= max_chars)
+                       break;
+               doc_next(ci->focus, ci->mark);
+       }
+
        ret = comm_call(ci->comm2, "callback:render", ci->focus,
-                       0, NULL, c);
+                       retlen, NULL, c);
        free(c);
        return ret;
 }
 
-DEF_CMD(email_select)
+DEF_CMD(email_select_hide)
 {
-       /* If mark is on a button, press it... */
+       int vis = 1;
+       char *a;
        struct mark *m = ci->mark;
-       char *a, *e, *cmd = NULL;
-       wint_t ch;
-       int p;
-       int b;
 
        if (!m)
                return Enoarg;
-       p = get_part(ci->home, m);
-       if (!is_spacer(p))
-               return Efallthrough;
-       ch = doc_following(ci->home, m);
-       if (ch == WEOF || !isdigit(ch))
-               return 1;
-       b = ch - '0';
-       a = pane_mark_attr(ci->focus, m, "multipart-prev:email:actions");
-       if (!a)
-               a = "hide";
-       while (b > 0 && a) {
-               a = strchr(a, ':');
-               if (a)
-                       a += 1;
-               b -= 1;
-       }
-       if (!a)
-               return 1;
-       e = strchr(a, ':');
-       if (!e)
-               e = a + strlen(a);
-       asprintf(&cmd, "email:select:%.*s", (int)(e-a), a);
-       if (!cmd)
-               return Efail;
-       return call(cmd, ci->focus, 0, m);
+
+       a = pane_mark_attr(ci->focus, m, "email:visible");
+       if (a && strcmp(a, "none") == 0)
+               vis = 0;
+       call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
+            vis ? "none" : "preferred");
+       return 1;
 }
 
-DEF_CMD(email_select_hide)
+DEF_CMD(email_select_full)
 {
-       int vis = 1;
+       int want_orig = 1;
        char *a;
        struct mark *m = ci->mark;
 
@@ -280,10 +314,49 @@ DEF_CMD(email_select_hide)
                return Enoarg;
 
        a = pane_mark_attr(ci->focus, m, "email:visible");
-       if (a && strcmp(a, "none") == 0)
-               vis = 0;
+       if (a && strcmp(a, "orig") == 0)
+               want_orig = 0;
        call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
-            vis ? "none" : "preferred");
+            want_orig ? "orig" : "transformed");
+       return 1;
+}
+
+DEF_CMD(email_select_extras)
+{
+       char *a = attr_find(ci->home->attrs, "email:extra-headers");
+       struct mark *m = ci->mark;
+       struct mark *point;
+       struct pane *hdrdoc, *headers;
+
+       if (!m)
+               return Enoarg;
+       if (a && *a)
+               return 1;
+       headers = call_ret(pane, "doc:multipart:get-part", ci->focus, 0);
+       hdrdoc = call_ret(pane, "doc:multipart:get-part", ci->focus, 1);
+       if (!headers || !hdrdoc)
+               return Einval;
+       point = point_new(hdrdoc);
+       if (point) {
+               char *file;
+
+               call("doc:set-ref", hdrdoc, 0, point);
+               home_call(headers, "get-header", hdrdoc, 0, point, "Message-ID", 1);
+               home_call(headers, "get-header", hdrdoc, 0, point, "In-Reply-To", 1);
+               home_call(headers, "get-header", hdrdoc, 0, point, "References", 1, NULL, "list");
+               file = pane_attr_get(headers, "filename");
+               if (file) {
+                       call("doc:replace", hdrdoc, 1, point, "Filename: ",
+                            0, point, ",render:rfc822header=9");
+                       call("doc:replace", hdrdoc, 1, point, file, 0, point);
+                       call("doc:replace", hdrdoc, 1, point, "\n", 0, point);
+               }
+       }
+       mark_free(point);
+
+       attr_set_str(&ci->home->attrs, "email:extra-headers", "done");
+       call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
+            "transformed");
        return 1;
 }
 
@@ -291,7 +364,7 @@ static struct map *email_view_map safe;
 
 DEF_LOOKUP_CMD(email_view_handle, email_view_map);
 
-static char tspecials[] = "()<>@,;:\\\"/[]?=";
+static const char tspecials[] = "()<>@,;:\\\"/[]?=";
 
 static int lws(char c) {
        return c == ' '  || c == '\t' || c == '\r' || c == '\n';
@@ -447,10 +520,16 @@ static bool handle_text(struct pane *p safe, char *type, char *xfer, char *disp,
                }
                if (xfer && xlen == 4 &&
                    strncasecmp(xfer, "8bit", 6) == 0)
-                       need_charset = 2; // ony if not utf-8
+                       need_charset = 2; // only if not utf-8
        }
-       if (type && need_charset &&
-           (charset = get_822_attr(type, "charset")) != NULL &&
+       if (type)
+               charset = get_822_attr(type, "charset");
+       if (need_charset && !charset && strncasecmp(type, "text/", 5) == 0)
+               /* We really do need a charset, as the doc might
+                * only provide bytes, not chars.
+                */
+               charset = "utf-8";
+       if (type && need_charset && charset != NULL &&
            !(need_charset == 2 && strcasecmp(charset, "utf-8") == 0)) {
                char *c = NULL, *cp;
                struct pane *hx = NULL;
@@ -470,7 +549,8 @@ static bool handle_text(struct pane *p safe, char *type, char *xfer, char *disp,
                        hx = call_ret(pane, "attach-charset-windows-1251", h);
                if (hx)
                        h = hx;
-       }
+       } else
+               charset = NULL;
        if (type && (fname = get_822_attr(type, "name")))
                fname = strsave(h, fname);
        if (disp && !fname &&
@@ -488,22 +568,38 @@ static bool handle_text(struct pane *p safe, char *type, char *xfer, char *disp,
                asprintf(&ctype, "%1.*s/%1.*s", majlen, major, minlen, minor);
        else
                asprintf(&ctype, "%1.*s", majlen, major);
-       if (ctype && strcasecmp(ctype, "text/html") == 0)
-               transformed = call_ret(pane, "html-to-text", h);
+       if (ctype && strcasecmp(ctype, "text/html") == 0) {
+               transformed = call_ret(pane, "html-to-text-w3m", h, 1);
+               if (!transformed)
+                       transformed = call_ret(pane, "html-to-text", h, 1);
+       }
+       if (ctype && strcasecmp(ctype, "text/calendar") == 0)
+               transformed = call_ret(pane, "ical-to-text", h, 1);
        if (ctype && strcasecmp(ctype, "application/pdf") == 0)
-               transformed = call_ret(pane, "pdf-to-text", h);
+               transformed = call_ret(pane, "pdf-to-text", h, 1);
        if (ctype && strcasecmp(ctype, "application/octet-stream") == 0 &&
            fname && strcasestr(fname, ".pdf") != NULL)
-               transformed = call_ret(pane, "pdf-to-text", h);
+               transformed = call_ret(pane, "pdf-to-text", h, 1);
+       if (major && strncasecmp(major, "application", majlen) == 0 &&
+           fname && (strcasestr(fname, ".docx") != NULL ||
+                     strcasestr(fname, ".doc") != NULL ||
+                     strcasestr(fname, ".odt") != NULL))
+               transformed = call_ret(pane, "doc-to-text", h, 1, NULL, fname);
+
        if (ctype && strncasecmp(ctype, "image/", 6) == 0) {
                struct mark *m;
                transformed = call_ret(pane, "doc:from-text", h,
-                                      0, NULL, NULL, 0, NULL, "\n");
+                                      0, NULL, NULL, 0, NULL,
+                                      "");
                if (transformed) {
-                       m = vmark_new(transformed, MARK_UNGROUPED, NULL);
+                       m = mark_new(transformed);
                        call("doc:set-ref", transformed, 1, m);
-                       call("doc:set-attr", transformed, 1, m, "markup:func", 0,
-                            NULL, "doc:email:render-image");
+                       call("doc:replace", transformed, 1, m, "01",
+                            0, m, ",markup:func=doc:email:render-image");
+                       call("doc:replace", transformed, 1, m, "\n01",
+                            0, m, ",markup:not_eol=1");
+                       call("doc:replace", transformed, 1, m, "\n01\n",
+                            0, m, ",markup:not_eol=1");
                        mark_free(m);
                }
        }
@@ -538,6 +634,9 @@ static bool handle_text(struct pane *p safe, char *type, char *xfer, char *disp,
                attr_set_str(&transformed->attrs, "email:charset", charset);
        if (fname)
                attr_set_str(&transformed->attrs, "email:filename", fname);
+       if (disp)
+               attr_set_str(&transformed->attrs,
+                            "email:content-disposition", disp);
        attr_set_str(&h->attrs, "email:which", "orig");
 
        home_call(mp, "multipart-add", h);
@@ -552,7 +651,7 @@ static bool handle_text(struct pane *p safe, char *type, char *xfer, char *disp,
  * Return 1 if a terminal boundary is found (trailing --)
  * Return -1 if nothing is found.
  */
-#define is_lws(c) ({int __c2 = c; __c2 == ' ' || __c2 == '\t' || is_eol(__c2); })
+#define is_lws(c) ({int _c2 = c; _c2 == ' ' || _c2 == '\t' || is_eol(_c2); })
 static int find_boundary(struct pane *p safe,
                         struct mark *start safe, struct mark *end safe,
                         struct mark *pos,
@@ -710,13 +809,15 @@ static bool handle_rfc822(struct pane *email safe,
                          struct pane *mp safe, struct pane *spacer safe,
                          char *path safe)
 {
-       struct pane *h2;
+       struct pane *h2, *h3;
        struct pane *hdrdoc = NULL;
        struct mark *point = NULL;
+       struct mark *hdr_start;
        char *xfer = NULL, *type = NULL, *disp = NULL;
        char *mime;
        char *newpath = NULL;
 
+       hdr_start = mark_dup(start);
        h2 = call_ret(pane, "attach-rfc822header", email, 0, start, NULL, 0, end);
        if (!h2)
                goto out;
@@ -726,16 +827,17 @@ static bool handle_rfc822(struct pane *email safe,
        if (!hdrdoc)
                goto out;
        call("doc:set:autoclose", hdrdoc, 1);
-       point = vmark_new(hdrdoc, MARK_POINT, NULL);
+       point = point_new(hdrdoc);
        if (!point)
                goto out;
 
        /* copy some headers to the header temp document */
-       home_call(h2, "get-header", hdrdoc, 0, point, "From", 1);
+       home_call(h2, "get-header", hdrdoc, 0, point, "From", 1, NULL, "list");
        home_call(h2, "get-header", hdrdoc, 0, point, "Date", 1);
        home_call(h2, "get-header", hdrdoc, 0, point, "Subject", 1, NULL, "text");
        home_call(h2, "get-header", hdrdoc, 0, point, "To", 1, NULL, "list");
        home_call(h2, "get-header", hdrdoc, 0, point, "Cc", 1, NULL, "list");
+       home_call(h2, "get-header", hdrdoc, 0, point, "Reply-To", 1, NULL, "list");
 
        /* copy some headers into attributes for later analysis */
        call("get-header", h2, 0, NULL, "MIME-Version", 0, NULL, "cmd");
@@ -756,12 +858,15 @@ static bool handle_rfc822(struct pane *email safe,
 
        newpath = NULL;
        asprintf(&newpath, "%s%sheaders", path, path[0] ? ",":"");
-       attr_set_str(&hdrdoc->attrs, "email:actions", "hide");
+       attr_set_str(&hdrdoc->attrs, "email:actions", "hide:extras:full");
        attr_set_str(&hdrdoc->attrs, "email:which", "transformed");
        attr_set_str(&hdrdoc->attrs, "email:content-type", "text/rfc822-headers");
        attr_set_str(&hdrdoc->attrs, "email:path", newpath);
        attr_set_str(&hdrdoc->attrs, "email:is_transformed", "yes");
-       home_call(mp, "multipart-add", h2);
+       h3 = call_ret(pane, "attach-crop", h2, 0, hdr_start, NULL, 0, start);
+       if (!h3)
+               h3 = h2;
+       home_call(mp, "multipart-add", h3);
        home_call(mp, "multipart-add", hdrdoc);
        home_call(mp, "multipart-add", spacer);
        free(newpath);
@@ -793,8 +898,7 @@ DEF_CMD(open_email)
        struct pane *p;
        struct mark *point;
 
-       if (ci->str == NULL ||
-           strncmp(ci->str, "email:", 6) != 0)
+       if (ci->str == NULL || !strstarts(ci->str, "email:"))
                return Efallthrough;
        fd = open(ci->str+6, O_RDONLY);
        if (fd < 0)
@@ -803,7 +907,7 @@ DEF_CMD(open_email)
        close(fd);
        if (!p)
                return Efallthrough;
-       start = vmark_new(p, MARK_UNGROUPED, NULL);
+       start = mark_new(p);
        if (!start) {
                pane_close(p);
                return Efallthrough;
@@ -816,16 +920,15 @@ DEF_CMD(open_email)
 
        /* create spacer doc to be attached between each part */
        p = call_ret(pane, "doc:from-text", p, 0, NULL, NULL, 0, NULL,
-                    "\n0123456789\n");
+                    "0123456789\n");
        if (!p)
                goto out;
 
        attr_set_str(&p->attrs, "email:which", "spacer");
        call("doc:set:autoclose", p, 1);
        spacer = p;
-       point = vmark_new(p, MARK_POINT, NULL);
+       point = point_new(p);
        call("doc:set-ref", p, 1, point);
-       doc_next(p, point);
        call("doc:set-attr", p, 1, point, "markup:func", 0,
             NULL, "doc:email:render-spacer");
        mark_free(point);
@@ -855,17 +958,12 @@ out:
        return Efail;
 }
 
-struct email_view {
-       int     parts;
-       char    *invis safe;
-};
-
-DEF_CMD(email_view_free)
+DEF_CMD_CLOSED(email_view_close)
 {
        struct email_view *evi = ci->home->data;
 
        free(evi->invis);
-       unalloc(evi, pane);
+       evi->invis = NULL;
        return 1;
 }
 
@@ -884,85 +982,68 @@ static int count_buttons(struct pane *p safe, struct mark *m safe)
        return cnt;
 }
 
-static int email_step(struct pane *home safe, struct mark *mark safe,
-                     int forward, int move)
+static inline wint_t email_next(struct pane *p safe, struct mark *m safe,
+                               struct doc_ref *r safe, bool bytes)
 {
-       struct pane *p = home;
        struct email_view *evi = p->data;
+       bool move = r == &m->ref;
        wint_t ret;
        int n = -1;
 
-       if (forward) {
-               ret = home_call(p->parent, "doc:char", home,
-                               move ? 1 : 0,
-                               mark, evi->invis,
-                               move ? 0 : 1);
-               n = get_part(p->parent, mark);
-               if (move && is_spacer(n)) {
-                       /* Moving in a spacer, If after valid buttons,
-                        * move to end
-                        */
-                       wint_t c;
-                       unsigned int buttons;
-                       buttons = count_buttons(p, mark);
-                       while ((c = doc_following(p->parent, mark)) != WEOF
-                              && iswdigit(c) && (c - '0') >= buttons)
-                                       doc_next(p->parent, mark);
-               }
-       } else {
-               ret = home_call(p->parent, "doc:char", home,
-                               move ? -1 : 0,
-                               mark, evi->invis,
-                               move ? 0 : -1);
-               n = get_part(p->parent, mark);
-               if (is_spacer(n) && move &&
-                   ret != CHAR_RET(WEOF) && iswdigit(ret & 0x1fffff)) {
-                       /* Just stepped back over the 9 at the end of a spacer,
-                        * Maybe step further if there aren't 10 buttons.
-                        */
-                       unsigned int buttons = count_buttons(p, mark);
-                       wint_t c = ret & 0x1fffff;
-
-                       while (c != WEOF && iswdigit(c) && c - '0' >= buttons)
-                               c = doc_prev(p->parent, mark);
-                       ret = CHAR_RET(c);
-               }
+       ret = home_call(p->parent, "doc:char", p,
+                       move ? 1 : 0,
+                       m, evi->invis,
+                       move ? 0 : 1);
+       n = get_part(p->parent, m);
+       if (move && is_spacer(n)) {
+               /* Moving in a spacer.  IF after valid buttons,
+                * move to end.
+                */
+               wint_t c;
+               unsigned int buttons = count_buttons(p, m);
+               while ((c = doc_following(p->parent, m)) != WEOF &&
+                      iswdigit(c) && (c-'0') >= buttons)
+                       doc_next(p->parent, m);
        }
        return ret;
 }
 
-DEF_CMD(email_char)
+static inline wint_t email_prev(struct pane *p safe, struct mark *m safe,
+                               struct doc_ref *r safe, bool bytes)
 {
-       struct mark *m = ci->mark;
-       struct mark *end = ci->mark2;
-       int steps = ci->num;
-       int forward = steps > 0;
-       int ret = Einval;
+       struct email_view *evi = p->data;
+       bool move = r == &m->ref;
+       wint_t ret;
+       int n = -1;
 
-       if (!m)
-               return Enoarg;
-       if (end && mark_same(m, end))
-               return 1;
-       if (end && (end->seq < m->seq) != (steps < 0))
-               /* Can never cross 'end' */
-               return Einval;
-       while (steps && ret != CHAR_RET(WEOF) && (!end || mark_same(m, end))) {
-               ret = email_step(ci->home, m, forward, 1);
-               steps -= forward*2 - 1;
+       ret = home_call(p->parent, "doc:char", p,
+                       move ? -1 : 0,
+                       m, evi->invis,
+                       move ? 0 : -1);
+       n = get_part(p->parent, m);
+       if (is_spacer(n) && move &&
+           ret != CHAR_RET(WEOF) && iswdigit(ret & 0x1fffff)) {
+               /* Just stepped back over the 9 at the end of a spacer,
+                * Maybe step further if there aren't 10 buttons.
+                */
+               unsigned int buttons = count_buttons(p, m);
+               wint_t c = ret & 0x1fffff;
+
+               while (c != WEOF && iswdigit(c) && c - '0' >= buttons)
+                       c = doc_prev(p->parent, m);
+               ret = c;
        }
-       if (end)
-               return 1 + (forward ? ci->num - steps : steps - ci->num);
-       if (ret == CHAR_RET(WEOF) || ci->num2 == 0)
-               return ret;
-       if (ci->num &&(ci->num2 < 0) == forward)
-               return ret;
-       /* Want the 'next' char */
-       return email_step(ci->home, m, ci->num2 > 0, 0);
+       return ret;
+}
+
+DEF_CMD(email_char)
+{
+       return do_char_byte(ci);
 }
 
 DEF_CMD(email_content)
 {
-       /* Call the multipart doc:content telling in
+       /* Call the multipart doc:content telling it
         * what is invisible, marking all spacers as invisible
         */
        struct pane *p = ci->home;
@@ -986,7 +1067,19 @@ DEF_CMD(email_set_ref)
 
        if (!ci->mark)
                return Enoarg;
-       home_call(p->parent, ci->key, ci->focus, ci->num, ci->mark, evi->invis);
+       home_call_comm(p->parent, ci->key, ci->focus, ci->comm2,
+                      ci->num, ci->mark, evi->invis);
+       return 1;
+}
+
+DEF_CMD(email_step_part)
+{
+       struct pane *p = ci->home;
+       struct email_view *evi = p->data;
+
+       if (!ci->mark)
+               return Enoarg;
+       home_call(p->parent, "doc:step-part", ci->focus, ci->num, ci->mark, evi->invis);
        return 1;
 }
 
@@ -1004,6 +1097,8 @@ DEF_CMD(email_view_get_attr)
                p = to_orig(p);
                if (p < 0 || p >= evi->parts)
                        v = "none";
+               else if (!evi->invis)
+                       v = "none";
                else if (evi->invis[p] != 'i')
                        v = "orig";
                else if (evi->invis[p+1] != 'i')
@@ -1014,6 +1109,14 @@ DEF_CMD(email_view_get_attr)
                return comm_call(ci->comm2, "callback", ci->focus, 0, ci->mark,
                                 v, 0, NULL, ci->str);
        }
+       if (strcmp(ci->str, "email:image") == 0) {
+               char im[100];
+
+               p = get_part(ci->home->parent, ci->mark);
+               sprintf(im, "comm:doc:multipart-%d-doc:get-bytes", to_orig(p));
+               return comm_call(ci->comm2, "cb", ci->focus, 0, ci->mark,
+                                im, 0, NULL, ci->str);
+       }
        return Efallthrough;
 }
 
@@ -1031,7 +1134,7 @@ DEF_CMD(email_view_set_attr)
                p = get_part(ci->home->parent, ci->mark);
                /* only parts can be invisible, not separators */
                p = to_orig(p);
-               if (p < 0 || p >= evi->parts)
+               if (p < 0 || p >= evi->parts || !evi->invis)
                        return Efail;
 
                m1 = mark_dup(ci->mark);
@@ -1070,6 +1173,17 @@ DEF_CMD(email_view_set_attr)
                mark_free(m1);
                mark_free(m2);
 
+               if (w && strcmp(w, "transformed") == 0) {
+                       /* If the transformation was delayed, tell it
+                        * to start now.
+                        */
+                       struct pane *part =
+                               call_ret(pane, "doc:multipart:get-part",
+                                        ci->home->parent, p+1);
+                       if (part)
+                               call("doc:notify:convert-now", part);
+               }
+
                return 1;
        }
        return Efallthrough;
@@ -1077,12 +1191,12 @@ DEF_CMD(email_view_set_attr)
 
 DEF_CMD(attach_email_view)
 {
-       struct pane *p;
+       struct pane *p, *p2;
        struct email_view *evi;
        struct mark *m;
-       int n;
+       int n, i;
 
-       m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
+       m = mark_new(ci->focus);
        if (!m)
                return Efail;
        call("doc:set-ref", ci->focus, 0, m);
@@ -1091,15 +1205,26 @@ DEF_CMD(attach_email_view)
        if (n <= 0 || n > 1000 )
                return Einval;
 
-       alloc(evi, pane);
+       p = pane_register(ci->focus, 0, &email_view_handle.c);
+       if (!p)
+               return Efail;
+       evi = p->data;
        evi->parts = n;
        evi->invis = calloc(n+1, sizeof(char));
-       memset(evi->invis, 'v', n);
-       p = pane_register(ci->focus, 0, &email_view_handle.c, evi);
-       if (!p) {
-               free(evi);
-               return Efail;
+       for (i = 0; i < n; i++) {
+               if (is_spacer(i))
+                       /* Spacers must be visible */
+                       evi->invis[i] = 'v';
+               else if (is_orig(i) && i < 2*3)
+                       /* Headers and first part can be visible */
+                       evi->invis[i] = 'v';
+               else
+                       /* Everything else default to invisible */
+                       evi->invis[i] = 'i';
        }
+       p2 = call_ret(pane, "attach-line-count", p);
+       if (p2)
+               p = p2;
        attr_set_str(&p->attrs, "render-hide-CR", "yes");
        return comm_call(ci->comm2, "callback:attach", p);
 }
@@ -1107,17 +1232,20 @@ DEF_CMD(attach_email_view)
 static void email_init_map(void)
 {
        email_view_map = key_alloc();
-       key_add(email_view_map, "Free", &email_view_free);
+       key_add(email_view_map, "Close", &email_view_close);
        key_add(email_view_map, "doc:char", &email_char);
        key_add(email_view_map, "doc:content", &email_content);
        key_add(email_view_map, "doc:content-bytes", &email_content);
        key_add(email_view_map, "doc:set-ref", &email_set_ref);
+       key_add(email_view_map, "doc:get-boundary", &email_set_ref);
+       key_add(email_view_map, "doc:email-step-part", &email_step_part);
        key_add(email_view_map, "doc:set-attr", &email_view_set_attr);
        key_add(email_view_map, "doc:get-attr", &email_view_get_attr);
        key_add(email_view_map, "doc:email:render-spacer", &email_spacer);
        key_add(email_view_map, "doc:email:render-image", &email_image);
-       key_add(email_view_map, "doc:email:select", &email_select);
        key_add(email_view_map, "email:select:hide", &email_select_hide);
+       key_add(email_view_map, "email:select:full", &email_select_full);
+       key_add(email_view_map, "email:select:extras", &email_select_extras);
 }
 
 void edlib_init(struct pane *ed safe)
@@ -1127,7 +1255,4 @@ void edlib_init(struct pane *ed safe)
                  "open-doc-email");
        call_comm("global-set-command", ed, &attach_email_view, 0, NULL,
                  "attach-email-view");
-
-       call("global-load-module", ed, 0, NULL, "lib-html-to-text");
-       call("global-load-module", ed, 0, NULL, "lib-pdf-to-text");
 }