2 * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Filter a view on a document to make quoted-printable look like the
6 * decoded bytes. A UTF-8 filter would be needed if the text
9 * Chars are passed through except for '=' and following.
13 * space/tab at eol ignored
15 * So when stepping backward if we see a \n or hex char we need
16 * to look further to see what is really there.
17 * When stepping forwards, we need only check for '=' or white space.
23 #define DOC_NEXT qp_next
24 #define DOC_PREV qp_prev
25 #define PANE_DATA_VOID
28 static struct map *qp_map safe;
29 DEF_LOOKUP_CMD(qp_handle, qp_map);
31 static int hex(wint_t c)
33 if (c >= '0' && c <= '9')
35 if (c >= 'A' && c <= 'F')
37 if (c >= 'a' && c <= 'f')
42 static inline wint_t qp_next(struct pane *home safe, struct mark *mark safe,
43 struct doc_ref *r safe, bool byte)
45 int move = r == &mark->ref;
46 struct pane *p = home->parent;
48 struct mark *m = mark;
54 ch = doc_following(p, m);
55 if (ch != '=' && ch != ' ' && ch != '\t' && ch != '\r') {
58 mark_to_mark(mark, m);
64 /* assume CR-LF - skip an extra char */
69 mark_to_mark(mark, m);
80 /* CRLF or HexHex expected. */
85 if (c2 == '\r' && c3 == '\n')
87 if (hex(c2) >= 0 && hex(c3) >= 0) {
88 ch = hex(c2)*16 + hex(c3);
90 mark_to_mark(mark, m);
95 /* Whitespace, ignore if at eol */
97 mark_to_mark(mark, m);
98 while ((c2 = doc_next(p, m)) == ' ' || c2 == '\t')
101 /* Found the white-space, retry from here and see the '\n' */
104 /* No \r, just \n. Step back to see it */
108 /* Just normal white space */
115 /* If next is "=\n" we need to skip over it. */
116 if (doc_following(p, m) != '=')
120 while ((c2 = doc_next(p, m)) == ' ' ||
121 c2 == '\t' || c2 == '\r')
124 /* Don't need to skip this */
128 mark_to_mark(mark, m);
133 static inline wint_t qp_prev(struct pane *home safe, struct mark *mark safe,
134 struct doc_ref *r safe, bool byte)
136 int move = r == &mark->ref;
137 struct pane *p = home->parent;
139 struct mark *m = mark;
145 ch = doc_prior(p, m);
151 /* '\n', skip '\r' and white space */
152 while ((ch = doc_prior(p, m)) == '\r' ||
153 ch == ' ' || ch == '\t')
160 mark_to_mark(mark, m);
167 mark_to_mark(mark, m);
175 mark_to_mark(mark, m);
183 wint_t ceq = doc_prev(p, m);
186 ch = hex(c2)*16 + hex(c3);
188 mark_to_mark(mark, m);
199 return do_char_byte(ci);
204 struct command *cb safe;
206 char state; /* \0 or '=' or hexit */
209 struct mark *lws_start; /* after first lws char */
212 static int qpflush(struct qpcb *c safe, const struct cmd_info *ci, wint_t ch,
213 const char *remainder, int rlen)
215 char *lws = buf_final(&c->lws);
216 int lws_len = c->lws.len;
220 while (ret > 0 && lws_len > 0 && c->lws_start) {
221 ret = comm_call(c->cb, ci->key, c->p, *lws, c->lws_start, lws+1,
222 lws_len - 1, NULL, NULL, c->size, 0);
223 doc_next(ci->home, c->lws_start);
232 mark_free(c->lws_start);
236 for (i = 0; remainder && i < rlen; i++)
237 if (strchr("=\r\n", remainder[i])) {
239 if (remainder[i] == '=')
241 /* ignore trailing white space */
242 while (i > 0 && remainder[i] <= ' ')
247 ret = comm_call(c->cb, ci->key, c->p, ch, ci->mark, remainder,
248 rlen, NULL, NULL, c->size, 0);
253 DEF_CMD(qp_content_cb)
255 struct qpcb *c = container_of(ci->comm, struct qpcb, c);
264 if (c->state && c->state != '=') {
265 /* Must see a hexit */
268 ret = qpflush(c, ci, (hex(c->state) << 4) | h, NULL, 0);
272 /* Pass first 2 literally */
273 ret = qpflush(c, ci, '=', NULL, 0);
275 ret = qpflush(c, ci, c->state, NULL, 0);
284 /* flush lws even if this turns out to be "=\n \n" */
286 ret = qpflush(c, ci, 0, NULL, 0);
290 if (wc == ' ' || wc == '\t') {
292 c->lws_start = mark_dup(ci->mark);
293 buf_append(&c->lws, wc);
297 /* drop any trailing space */
299 mark_free(c->lws_start);
303 ret = qpflush(c, ci, wc, ci->str, ci->num2);
306 /* Previous was '='. */
311 if (wc == ' ' || wc == '\t')
312 /* Ignore space after =, incase at eol */
316 /* The '=' was hiding the \n */
319 ret = qpflush(c, ci, '=', NULL, 0);
321 ret = qpflush(c, ci, wc, NULL, 0);
330 if (!ci->comm2 || !ci->mark)
332 /* No need to check ->key as providing bytes as chars
343 ret = home_call_comm(ci->home->parent, ci->key, ci->home,
344 &c.c, 0, ci->mark, NULL, 0, ci->mark2);
346 mark_free(c.lws_start);
354 p = pane_register(ci->focus, 0, &qp_handle.c);
358 return comm_call(ci->comm2, "callback:attach", p);
361 void edlib_init(struct pane *ed safe)
364 qp_map = key_alloc();
366 key_add(qp_map, "doc:char", &qp_char);
367 key_add(qp_map, "doc:byte", &qp_char);
368 key_add(qp_map, "doc:content", &qp_content);
369 key_add(qp_map, "doc:content-bytes", &qp_content);
371 call_comm("global-set-command", ed, &qp_attach, 0, NULL, "attach-quoted_printable");
372 call_comm("global-set-command", ed, &qp_attach, 0, NULL, "attach-qprint");