]> git.neil.brown.name Git - edlib.git/blob - lib-qprint.c
Introduce PANE_DATA_PTR_TYPE for mode-emacs.
[edlib.git] / lib-qprint.c
1 /*
2  * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
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
7  * is actually utf-8.
8  *
9  * Chars are passed through except for '=' and following.
10  * =HH decodes the hex
11  * =\r\n disappears
12  * \r\n -> \n
13  * space/tab at eol ignored
14  *
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.
18  */
19
20 #include <unistd.h>
21 #include <stdlib.h>
22
23 #define DOC_NEXT qp_next
24 #define DOC_PREV qp_prev
25 #include "core.h"
26
27 static struct map *qp_map safe;
28 DEF_LOOKUP_CMD(qp_handle, qp_map);
29
30 static int hex(wint_t c)
31 {
32         if (c >= '0' && c <= '9')
33                 return c - '0';
34         if (c >= 'A' && c <= 'F')
35                 return c - 'A' + 10;
36         if (c >= 'a' && c <= 'f')
37                 return c - 'a' + 10;
38         return -1;
39 }
40
41 static inline wint_t qp_next(struct pane *home safe, struct mark *mark safe,
42                              struct doc_ref *r safe, bool byte)
43 {
44         int move = r == &mark->ref;
45         struct pane *p = home->parent;
46         wint_t ch, c2, c3;
47         struct mark *m = mark;
48
49 retry:
50         if (move)
51                 ch = doc_next(p, m);
52         else
53                 ch = doc_following(p, m);
54         if (ch != '=' && ch != ' ' && ch != '\t' && ch != '\r') {
55                 if (m != mark) {
56                         if (move)
57                                 mark_to_mark(mark, m);
58                         mark_free(m);
59                 }
60                 goto normalize;
61         }
62         if (ch == '\r') {
63                 /* assume CR-LF - skip an extra char */
64                 if (move)
65                         doc_next(p, m);
66                 if (m != mark) {
67                         if (move)
68                                 mark_to_mark(mark, m);
69                         mark_free(m);
70                 }
71                 ch = '\n';
72                 goto normalize;
73         }
74         if (m == mark)
75                 m = mark_dup(m);
76         if (!move)
77                 doc_next(p, m);
78         if (ch == '=') {
79                 /* CRLF or HexHex expected. */
80                 c2 = doc_next(p, m);
81                 if (c2 == '\n')
82                         goto retry;
83                 c3 = doc_next(p, m);
84                 if (c2 == '\r' && c3 == '\n')
85                         goto retry;
86                 if (hex(c2) >= 0 && hex(c3) >= 0) {
87                         ch = hex(c2)*16 + hex(c3);
88                         if (move)
89                                 mark_to_mark(mark, m);
90                 }
91                 mark_free(m);
92                 goto normalize;
93         }
94         /* Whitespace, ignore if at eol */
95         if (move)
96                 mark_to_mark(mark, m);
97         while ((c2 = doc_next(p, m)) == ' ' || c2 == '\t')
98                 ;
99         if (c2 == '\r')
100                 /* Found the white-space, retry from here and see the '\n' */
101                 goto retry;
102         if (c2 == '\n') {
103                 /* No \r, just \n.  Step back to see it */
104                 doc_prev(p, m);
105                 goto retry;
106         }
107         /* Just normal white space */
108         mark_free(m);
109 normalize:
110         if (!move)
111                 return ch;
112 normalize_more:
113         m = mark;
114         /* If next is "=\n" we need to skip over it. */
115         if (doc_following(p, m) != '=')
116                 return ch;
117         m = mark_dup(mark);
118         doc_next(p, m);
119         while ((c2 = doc_next(p, m)) == ' ' ||
120                c2 == '\t' || c2 == '\r')
121                 ;
122         if (c2 != '\n') {
123                 /* Don't need to skip this */
124                 mark_free(m);
125                 return ch;
126         }
127         mark_to_mark(mark, m);
128         mark_free(m);
129         goto normalize_more;
130 }
131
132 static inline wint_t qp_prev(struct pane *home safe, struct mark *mark safe,
133                              struct doc_ref *r safe, bool byte)
134 {
135         int move = r == &mark->ref;
136         struct pane *p = home->parent;
137         wint_t ch, c2, c3;
138         struct mark *m = mark;
139
140 retry:
141         if (move)
142                 ch = doc_prev(p, m);
143         else
144                 ch = doc_prior(p, m);
145         if (ch == '\n') {
146                 if (m == mark)
147                         m = mark_dup(m);
148                 if (!move)
149                         doc_prev(p, m);
150                 /* '\n', skip '\r' and white space */
151                 while ((ch = doc_prior(p, m)) == '\r' ||
152                        ch == ' ' || ch == '\t')
153                         doc_prev(p, m);
154                 if (ch == '=') {
155                         doc_prev(p, m);
156                         goto retry;
157                 }
158                 if (move)
159                         mark_to_mark(mark, m);
160                 mark_free(m);
161                 return '\n';
162         }
163         if (hex(ch) < 0) {
164                 if (m != mark) {
165                         if (move)
166                                 mark_to_mark(mark, m);
167                         mark_free(m);
168                 }
169                 return ch;
170         }
171         if (m == mark)
172                 m = mark_dup(m);
173         else if (move)
174                 mark_to_mark(mark, m);
175         if (!move)
176                 doc_prev(p, m);
177
178         /* Maybe =HH */
179         c3 = ch;
180         c2 = doc_prev(p, m);
181         if (hex(c2) >= 0) {
182                 wint_t ceq = doc_prev(p, m);
183                 if (ceq == '=') {
184                         /* =HH */
185                         ch = hex(c2)*16 + hex(c3);
186                         if (move)
187                                 mark_to_mark(mark, m);
188                         mark_free(m);
189                         return ch;
190                 }
191         }
192         mark_free(m);
193         return ch;
194 }
195
196 DEF_CMD(qp_char)
197 {
198         return do_char_byte(ci);
199 }
200
201 struct qpcb {
202         struct command c;
203         struct command *cb safe;
204         struct pane *p safe;
205         char state; /* \0 or '=' or hexit */
206         int size;
207         struct buf lws;
208         struct mark *lws_start; /* after first lws char */
209 };
210
211 static int qpflush(struct qpcb *c safe, const struct cmd_info *ci, wint_t ch,
212                    const char *remainder, int rlen)
213 {
214         char *lws = buf_final(&c->lws);
215         int lws_len = c->lws.len;
216         int ret = 1;
217         int i;
218
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);
223                 c->size = 0;
224                 if (ret > 0) {
225                         lws += ret;
226                         lws_len -= ret;
227                         ret = 1;
228                 }
229         }
230         buf_reinit(&c->lws);
231         mark_free(c->lws_start);
232         c->lws_start = NULL;
233         if (!ch)
234                 return ret;
235         for (i = 0; remainder && i < rlen; i++)
236                 if (strchr("=\r\n", remainder[i])) {
237                         rlen = i;
238                         if (remainder[i] == '=')
239                                 break;
240                         /* ignore trailing white space */
241                         while (i > 0 && remainder[i] <= ' ')
242                                 i -= 1;
243                         rlen = i;
244                 }
245         if (ret > 0)
246                 ret = comm_call(c->cb, ci->key, c->p, ch, ci->mark, remainder,
247                                 rlen, NULL, NULL, c->size, 0);
248         c->size = 0;
249         return ret;
250 }
251
252 DEF_CMD(qp_content_cb)
253 {
254         struct qpcb *c = container_of(ci->comm, struct qpcb, c);
255         wint_t wc = ci->num;
256         int ret = 1;
257
258         if (!ci->mark)
259                 return Enoarg;
260         if (ci->x)
261                 c->size = ci->x;
262
263         if (c->state && c->state != '=') {
264                 /* Must see a hexit */
265                 int h = hex(wc);
266                 if (h >= 0) {
267                         ret = qpflush(c, ci,  (hex(c->state) << 4) | h, NULL, 0);
268                         c->state = 0;
269                         return ret;
270                 }
271                 /* Pass first 2 literally */
272                 ret = qpflush(c, ci, '=', NULL, 0);
273                 if (ret > 0)
274                         ret = qpflush(c, ci, c->state, NULL, 0);
275                 c->state = 0;
276         }
277
278         if (wc == '\r')
279                 /* Always skip \r */
280                 return ret;
281         if (!c->state) {
282                 if (wc == '=') {
283                         /* flush lws even if this turns out to be "=\n    \n" */
284                         if (ret)
285                                 ret = qpflush(c, ci, 0, NULL, 0);
286                         c->state = wc;
287                         return ret;
288                 }
289                 if (wc == ' ' || wc == '\t') {
290                         if (!c->lws_start)
291                                 c->lws_start = mark_dup(ci->mark);
292                         buf_append(&c->lws, wc);
293                         return ret;
294                 }
295                 if (wc == '\n') {
296                         /* drop any trailing space */
297                         buf_reinit(&c->lws);
298                         mark_free(c->lws_start);
299                         c->lws_start = NULL;
300                 }
301                 if (ret > 0)
302                         ret = qpflush(c, ci, wc, ci->str, ci->num2);
303                 return ret;
304         }
305         /* Previous was '='. */
306         if (hex(wc) >= 0) {
307                 c->state = wc;
308                 return ret;
309         }
310         if (wc == ' ' || wc == '\t')
311                 /* Ignore space after =, incase at eol */
312                 return ret;
313         c->state = 0;
314         if (wc == '\n')
315                 /* The '=' was hiding the \n */
316                 return ret;
317         if (ret > 0)
318                 ret = qpflush(c, ci, '=', NULL, 0);
319         if (ret > 0)
320                 ret = qpflush(c, ci, wc, NULL, 0);
321         return ret;
322 }
323
324 DEF_CMD(qp_content)
325 {
326         struct qpcb c;
327         int ret;
328
329         if (!ci->comm2 || !ci->mark)
330                 return Enoarg;
331         /* No need to check ->key as providing bytes as chars
332          * is close enough.
333          */
334
335         c.c = qp_content_cb;
336         c.cb = ci->comm2;
337         c.p = ci->focus;
338         c.size = 0;
339         c.state = 0;
340         buf_init(&c.lws);
341         c.lws_start = NULL;
342         ret = home_call_comm(ci->home->parent, ci->key, ci->home,
343                              &c.c, 0, ci->mark, NULL, 0, ci->mark2);
344         free(c.lws.b);
345         mark_free(c.lws_start);
346         return ret;
347 }
348
349 DEF_CMD(qp_attach)
350 {
351         struct pane *p;
352
353         p = pane_register(ci->focus, 0, &qp_handle.c);
354         if (!p)
355                 return Efail;
356
357         return comm_call(ci->comm2, "callback:attach", p);
358 }
359
360 void edlib_init(struct pane *ed safe)
361 {
362
363         qp_map = key_alloc();
364
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);
369
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");
372 }