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
27 static struct map *qp_map safe;
28 DEF_LOOKUP_CMD(qp_handle, qp_map);
30 static int hex(wint_t c)
32 if (c >= '0' && c <= '9')
34 if (c >= 'A' && c <= 'F')
36 if (c >= 'a' && c <= 'f')
41 static inline wint_t qp_next(struct pane *home safe, struct mark *mark safe,
42 struct doc_ref *r safe, bool byte)
44 int move = r == &mark->ref;
45 struct pane *p = home->parent;
47 struct mark *m = mark;
53 ch = doc_following(p, m);
54 if (ch != '=' && ch != ' ' && ch != '\t' && ch != '\r') {
57 mark_to_mark(mark, m);
63 /* assume CR-LF - skip an extra char */
68 mark_to_mark(mark, m);
79 /* CRLF or HexHex expected. */
84 if (c2 == '\r' && c3 == '\n')
86 if (hex(c2) >= 0 && hex(c3) >= 0) {
87 ch = hex(c2)*16 + hex(c3);
89 mark_to_mark(mark, m);
94 /* Whitespace, ignore if at eol */
96 mark_to_mark(mark, m);
97 while ((c2 = doc_next(p, m)) == ' ' || c2 == '\t')
100 /* Found the white-space, retry from here and see the '\n' */
103 /* No \r, just \n. Step back to see it */
107 /* Just normal white space */
114 /* If next is "=\n" we need to skip over it. */
115 if (doc_following(p, m) != '=')
119 while ((c2 = doc_next(p, m)) == ' ' ||
120 c2 == '\t' || c2 == '\r')
123 /* Don't need to skip this */
127 mark_to_mark(mark, m);
132 static inline wint_t qp_prev(struct pane *home safe, struct mark *mark safe,
133 struct doc_ref *r safe, bool byte)
135 int move = r == &mark->ref;
136 struct pane *p = home->parent;
138 struct mark *m = mark;
144 ch = doc_prior(p, m);
150 /* '\n', skip '\r' and white space */
151 while ((ch = doc_prior(p, m)) == '\r' ||
152 ch == ' ' || ch == '\t')
159 mark_to_mark(mark, m);
166 mark_to_mark(mark, m);
174 mark_to_mark(mark, m);
182 wint_t ceq = doc_prev(p, m);
185 ch = hex(c2)*16 + hex(c3);
187 mark_to_mark(mark, m);
198 return do_char_byte(ci);
203 struct command *cb safe;
205 char state; /* \0 or '=' or hexit */
208 struct mark *lws_start; /* after first lws char */
211 static int qpflush(struct qpcb *c safe, const struct cmd_info *ci, wint_t ch,
212 const char *remainder, int rlen)
214 char *lws = buf_final(&c->lws);
215 int lws_len = c->lws.len;
219 while (ret > 0 && lws_len > 0 && c->lws_start) {
220 ret = comm_call(c->cb, ci->key, c->p, *lws, c->lws_start, lws+1,
221 lws_len - 1, NULL, NULL, c->size, 0);
222 doc_next(ci->home, c->lws_start);
231 mark_free(c->lws_start);
235 for (i = 0; remainder && i < rlen; i++)
236 if (strchr("=\r\n", remainder[i])) {
238 if (remainder[i] == '=')
240 /* ignore trailing white space */
241 while (i > 0 && remainder[i] <= ' ')
246 ret = comm_call(c->cb, ci->key, c->p, ch, ci->mark, remainder,
247 rlen, NULL, NULL, c->size, 0);
252 DEF_CMD(qp_content_cb)
254 struct qpcb *c = container_of(ci->comm, struct qpcb, c);
263 if (c->state && c->state != '=') {
264 /* Must see a hexit */
267 ret = qpflush(c, ci, (hex(c->state) << 4) | h, NULL, 0);
271 /* Pass first 2 literally */
272 ret = qpflush(c, ci, '=', NULL, 0);
274 ret = qpflush(c, ci, c->state, NULL, 0);
283 /* flush lws even if this turns out to be "=\n \n" */
285 ret = qpflush(c, ci, 0, NULL, 0);
289 if (wc == ' ' || wc == '\t') {
291 c->lws_start = mark_dup(ci->mark);
292 buf_append(&c->lws, wc);
296 /* drop any trailing space */
298 mark_free(c->lws_start);
302 ret = qpflush(c, ci, wc, ci->str, ci->num2);
305 /* Previous was '='. */
310 if (wc == ' ' || wc == '\t')
311 /* Ignore space after =, incase at eol */
315 /* The '=' was hiding the \n */
318 ret = qpflush(c, ci, '=', NULL, 0);
320 ret = qpflush(c, ci, wc, NULL, 0);
329 if (!ci->comm2 || !ci->mark)
331 /* No need to check ->key as providing bytes as chars
342 ret = home_call_comm(ci->home->parent, ci->key, ci->home,
343 &c.c, 0, ci->mark, NULL, 0, ci->mark2);
345 mark_free(c.lws_start);
353 p = pane_register(ci->focus, 0, &qp_handle.c);
357 return comm_call(ci->comm2, "callback:attach", p);
360 void edlib_init(struct pane *ed safe)
363 qp_map = key_alloc();
365 key_add(qp_map, "doc:char", &qp_char);
366 key_add(qp_map, "doc:byte", &qp_char);
367 key_add(qp_map, "doc:content", &qp_content);
368 key_add(qp_map, "doc:content-bytes", &qp_content);
370 call_comm("global-set-command", ed, &qp_attach, 0, NULL, "attach-quoted_printable");
371 call_comm("global-set-command", ed, &qp_attach, 0, NULL, "attach-qprint");