]> git.neil.brown.name Git - edlib.git/blob - lib-markup.c
TODO: clean out done items.
[edlib.git] / lib-markup.c
1 /*
2  * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * This module provides render-line and render-line-prev, making use of
6  * the chars returned by doc:char.
7  * A line is normally text ending with a newline.  However if no newline
8  * is found in a long distance, we drop a mark and use that as the start
9  * of a line.
10  */
11
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <wchar.h>
15 #include <wctype.h>
16 #include <string.h>
17
18 #include <stdio.h>
19 #define PANE_DATA_TYPE struct mu_info
20 #include "core.h"
21 #include "misc.h"
22
23 struct mu_info {
24         int     view;
25 };
26 #include "core-pane.h"
27
28 static struct map *mu_map safe;
29
30 #define LARGE_LINE 5000
31
32 static int is_render_eol(wint_t ch, struct pane *p safe, struct mark *m safe)
33 {
34         char *attr;
35         if (!is_eol(ch))
36                 return 0;
37         attr = pane_mark_attr(p, m, "markup:not_eol");
38         if (attr && *attr)
39                 return 0;
40         return 1;
41 }
42
43 DEF_CMD(render_prev)
44 {
45         /* In the process of rendering a line we need to find the
46          * start of line.  We use a mark to create an artificial
47          * start-of-line where none can be found.
48          * Search backwards until a newline or start-of-file or the
49          * mark is found.  Move backwards at most LARGE_LINE characters
50          * and if nothing else is found, put a mark there and treat as s-o-l.
51          *
52          * If RPT_NUM == 1, step back at least one character so we get
53          * the previous line and not the line we are on.
54          * If we hit start-of-file without finding newline, return Efail;
55          */
56         struct mark *m = ci->mark;
57         struct pane *f = ci->focus;
58         struct mu_info *mu = ci->home->data;
59         struct mark *boundary = NULL;
60         struct mark *doc_boundary = NULL;
61         int count = 0;
62         int rpt = RPT_NUM(ci);
63         wint_t ch;
64
65         if (!m)
66                 return Enoarg;
67
68         if (!rpt) {
69                 boundary = vmark_at_or_before(f, m, mu->view, ci->home);
70                 doc_boundary = call_ret(mark, "doc:get-boundary", f, -1, m);
71         }
72         while ((ch = doc_prev(f, m)) != WEOF &&
73                (!is_render_eol(ch, f, m) || rpt > 0) &&
74                count < LARGE_LINE &&
75                (!boundary || mark_ordered_not_same(boundary, m)) &&
76                (!doc_boundary || mark_ordered_not_same(doc_boundary, m))) {
77                 if (rpt) {
78                         boundary = vmark_at_or_before(f, m, mu->view, ci->home);
79                         doc_boundary = call_ret(mark, "doc:get-boundary", f, -1, m);
80                 }
81                 rpt = 0;
82                 count += 1;
83         }
84         if (ch != WEOF && !is_render_eol(ch, f, m) &&
85             (!doc_boundary || !mark_same(doc_boundary, m))) {
86                 /* Just crossed the boundary, or the max count.
87                  * Need to step back, and ensure there is a stable boundary
88                  * here.
89                  */
90                 mark_free(doc_boundary);
91                 doc_next(f, m);
92                 if (!boundary || !mark_same(boundary, m)) {
93                         boundary = vmark_new(f, mu->view, ci->home);
94                         if (boundary)
95                                 mark_to_mark(boundary, m);
96                 }
97                 return 1;
98         }
99         mark_free(doc_boundary);
100         if (ch == WEOF && rpt)
101                 return Efail;
102         /* Found a '\n', so step forward over it for start-of-line. */
103         if (is_render_eol(ch, f, m))
104                 doc_next(f, m);
105         return 1;
106 }
107
108 /* 'ast' is a stack is all the attributes that should be applied "here".
109  * They are sorted by priority with the highest first.
110  * 'end' is an offset in chars-since-start-of-line where the attribute
111  * should stop applying.  The current chars-since-start-of-line is 'chars'.
112  * The stack structure reflects the nesting of <attr> and </>.
113  * To change an attribute (normally add or delete) we pop it and any attributes
114  * above it in the stack and push them onto tmpst, which is then in
115  * reverse priority order.  As we do that, we count them in 'popped'.
116  * Changes can be made in the secondary stack.
117  * When all change have been made, we add 'popped' ETX marked to the output,
118  * then process everything in 'tmpst', either discarding it if end<=chars, or
119  * outputting the attributes and pushing back on 'ast'.
120  */
121 struct attr_return {
122         struct command rtn;
123         struct command fwd;
124         struct attr_stack {
125                 struct attr_stack       *next;
126                 char                    *attr safe;
127                 int                     end;
128                 unsigned short          priority;
129         } *ast, *tmpst;
130         int min_end;
131         int chars;
132         struct buf insert;
133         short popped;
134 };
135
136 /* Find which attibutes should be finished by 'pos'.  The depth of
137  * to deepest such is returned, and the next time to endpoint is
138  * record in *nextp.
139  * Everything higher than that returned depth will need to be closed,
140  * so that the deepest one can be closed.
141  * Then some of the higher ones might get re-opened.
142  */
143 static int find_finished(struct attr_stack *st, int pos, int *nextp safe)
144 {
145         int depth = 1;
146         int fdepth = -1;
147         int next = -1;
148
149         for (; st ; st = st->next, depth++) {
150                 if (st->end <= pos)
151                         fdepth = depth;
152                 else if (next < 0 || next > st->end)
153                         next = st->end;
154         }
155         *nextp = next;
156         return fdepth;
157 }
158
159 /* Move the top 'depth' attributes from 'ast' to 'tmpst', updating 'popped' */
160 static void as_pop(struct attr_return *ar safe, int depth)
161 {
162         struct attr_stack *from = ar->ast;
163         struct attr_stack *to = ar->tmpst;
164
165         while (from && depth > 0) {
166                 struct attr_stack *t;
167                 ar->popped += 1;
168                 t = from;
169                 from = t->next;
170                 t->next = to;
171                 to = t;
172                 depth -= 1;
173         }
174         ar->ast = from;
175         ar->tmpst = to;
176 }
177
178 /* re-push any attriubtes that are still valid, freeing those that aren't */
179 static void as_repush(struct attr_return *ar safe, struct buf *b safe)
180 {
181         struct attr_stack *from = ar->tmpst;
182         struct attr_stack *to = ar->ast;
183
184         while (ar->popped > 0) {
185                 buf_append(b, etx);
186                 ar->popped -= 1;
187         }
188
189         while (from) {
190                 struct attr_stack *t = from->next;
191                 if (from->end <= ar->chars) {
192                         free(from->attr);
193                         free(from);
194                 } else {
195                         buf_append(b, soh);
196                         buf_concat(b, from->attr);
197                         buf_append(b, stx);
198                         from->next = to;
199                         to = from;
200                         if (from->end < ar->min_end)
201                                 ar->min_end = from->end;
202                 }
203                 from = t;
204         }
205         ar->tmpst = from;
206         ar->ast = to;
207 }
208
209 static void as_add(struct attr_return *ar safe,
210                    int end, int prio, const char *attr safe)
211 {
212         struct attr_stack *new, **here;
213
214         while (ar->ast && ar->ast->priority > prio)
215                 as_pop(ar, 1);
216
217         here = &ar->tmpst;
218         while (*here && (*here)->priority <= prio)
219                 here = &(*here)->next;
220         new = calloc(1, sizeof(*new));
221         new->next = *here;
222         new->attr = strdup(attr);
223         if (end == 0 || INT_MAX - end <= ar->chars)
224                 end = INT_MAX - 1 - ar->chars;
225         new->end = ar->chars + end;
226         new->priority = prio;
227         *here = new;
228 }
229
230 static void as_clear(struct attr_return *ar safe,
231                      int prio, const char *attr)
232 {
233         struct attr_stack *st;
234
235         while (ar->ast && ar->ast->priority >= prio)
236                 as_pop(ar, 1);
237
238         for (st = ar->tmpst; st && st->priority <= prio; st = st->next)
239                 if (st->priority == prio &&
240                     (attr == NULL || strcmp(st->attr, attr) == 0))
241                         st->end = ar->chars;
242 }
243
244 DEF_CB(text_attr_forward)
245 {
246         struct attr_return *ar = container_of(ci->comm, struct attr_return, fwd);
247         if (!ci->str || !ci->str2)
248                 return Enoarg;
249         return call_comm("map-attr", ci->focus, &ar->rtn, 0, ci->mark, ci->str2,
250                          0, NULL, ci->str);
251 }
252
253 DEF_CB(text_attr_callback)
254 {
255         struct attr_return *ar = container_of(ci->comm, struct attr_return,
256                                               rtn);
257         int prio = ci->num2;
258         if (prio < 0)
259                 prio = 0;
260         if (prio > 65535)
261                 prio = 65535;
262         if (ci->num >= 0) {
263                 if (ci->str)
264                         as_add(ar, ci->num, prio, ci->str);
265         } else
266                 as_clear(ar, prio, ci->str);
267         if (ci->str2) {
268                 const char *c = ci->str2;
269                 wint_t wch;
270                 while ((wch = get_utf8(&c, NULL)) != WEOF)
271                         buf_append(&ar->insert, wch);
272         }
273         return 1;
274 }
275
276 static void call_map_mark(struct pane *f safe, struct mark *m safe,
277                           struct attr_return *ar safe)
278 {
279         const char *key = "render:";
280         const char *val;
281
282         while ((key = attr_get_next_key(m->attrs, key, -1, &val)) != NULL &&
283                strstarts(key, "render:"))
284                 call_comm("map-attr", f, &ar->rtn, 0, m, key, 0, NULL, val);
285 }
286
287 static int want_vis_newline(struct attr_stack *as)
288 {
289         while (as && strstr(as->attr, "vis-nl") == NULL)
290                 as = as->next;
291         return as != NULL;
292 }
293
294 DEF_CMD(render_line)
295 {
296         /* Render the line from 'mark' to the first '\n' or until
297          * 'num' chars.
298          */
299         struct buf b;
300         struct pane *focus = ci->focus;
301         struct mu_info *mu = ci->home->data;
302         struct mark *m = ci->mark;
303         struct mark *pm = ci->mark2; /* The location to render as cursor */
304         struct mark *boundary, *start_boundary = NULL;
305         struct mark *doc_boundary;
306         int o = ci->num;
307         int pm_offset = -1;
308         wint_t ch;
309         int chars = 0;
310         int ret;
311         struct attr_return ar;
312         int add_newline = 0;
313         char *oneline;
314         char *noret;
315         char *attr;
316
317         ar.rtn = text_attr_callback;
318         ar.fwd = text_attr_forward;
319         ar.ast = ar.tmpst = NULL;
320         ar.min_end = -1;
321         ar.chars = 0;
322         buf_init(&ar.insert);
323         ar.popped = 0;
324
325         if (!m)
326                 return Enoarg;
327
328         oneline = pane_attr_get(focus, "render-one-line");
329         if (oneline && strcmp(oneline, "yes") != 0)
330                 oneline = NULL;
331         noret = pane_attr_get(focus, "render-hide-CR");
332         if (noret && strcmp(noret, "yes") != 0)
333                 noret = NULL;
334
335         ch = doc_following(focus, m);
336         if (ch == WEOF)
337                 return Efail;
338         if ((attr = pane_mark_attr(focus, m, "markup:func")) != NULL) {
339                 /* An alternate function handles this line */
340                 ret = call_comm(attr, focus, ci->comm2, o, m, NULL, 0, pm);
341                 if (ret)
342                         return ret;
343         }
344         boundary = vmark_at_or_before(focus, m, mu->view, ci->home);
345         if (boundary) {
346                 if (mark_same(m, boundary))
347                         start_boundary = boundary;
348                 boundary = vmark_next(boundary);
349         }
350         doc_boundary = call_ret(mark, "doc:get-boundary", focus, 1, m);
351
352         buf_init(&b);
353         /* Assert that '<' are not quoted */
354         buf_append(&b, ack);
355         call_comm("map-attr", focus, &ar.rtn, 0, m, "start-of-line");
356         if (ar.insert.len) {
357                 buf_concat(&b, buf_final(&ar.insert));
358                 buf_reinit(&ar.insert);
359         }
360         while (1) {
361                 struct mark *m2;
362                 int is_true_eol = 0;
363
364                 if (o >= 0 && b.len >= o)
365                         break;
366
367                 if (boundary && mark_ordered_or_same(boundary, m))
368                         break;
369                 if (doc_boundary && mark_ordered_or_same(doc_boundary, m))
370                         break;
371
372                 if (pm && mark_same(m, pm) && pm_offset < 0)
373                         pm_offset = b.len;
374
375                 if (ar.ast && ar.min_end <= chars) {
376                         int depth = find_finished(ar.ast, chars, &ar.min_end);
377                         as_pop(&ar, depth);
378                 }
379
380                 ar.chars = chars;
381                 call_comm("doc:get-attr", focus, &ar.fwd, 0, m, "render:", 1);
382
383                 /* find all marks "here". They might get moved when we call map_mark,
384                  * so move 'm' among them
385                  */
386                 mark_step(m, 0);
387                 while ((m2 = mark_next(m)) != NULL &&
388                        mark_same(m, m2)) {
389                         mark_to_mark_noref(m, m2);
390                         call_map_mark(focus, m2, &ar);
391                 }
392
393                 as_repush(&ar, &b);
394
395                 if (o >= 0 && b.len >= o)
396                         break;
397
398                 if (ar.insert.len) {
399                         buf_concat(&b, buf_final(&ar.insert));
400                         buf_reinit(&ar.insert);
401                 }
402
403                 ch = doc_next(focus, m);
404                 if (ch == WEOF)
405                         break;
406
407                 if (!oneline && is_eol(ch)) {
408                         doc_prev(focus, m);
409                         is_true_eol = is_render_eol(ch, focus, m);
410                         doc_next(focus, m);
411                 }
412                 if (is_true_eol) {
413                         add_newline = 1;
414                         break;
415                 }
416                 chars++;
417                 if (ch == '\r' && noret) {
418                         /* do nothing */
419                 } else if (ch < ' ' && ch != '\t') {
420                         buf_concat(&b, SOH "fg:red" STX "^");
421                         buf_append(&b, '@' + ch);
422                         buf_concat(&b, ETX);
423                 } else if (ch == 0x7f) {
424                         buf_concat(&b, SOH "fg:red" STX "^?" ETX);
425                 } else if (ch >= 0x80 && iswcntrl(ch)) {
426                         /* Extra unicode control */
427                         buf_concat(&b, SOH "fg:magenta" STX "^");
428                         buf_append(&b, 96 + (ch & 0x1f));
429                         buf_concat(&b, ETX);
430                 } else
431                         buf_append(&b, ch);
432         }
433         if (add_newline && want_vis_newline(ar.ast))
434                 buf_concat(&b, "↩");
435         while (ar.ast)
436                 as_pop(&ar, 100);
437         ar.chars = INT_MAX;
438         as_repush(&ar, &b);
439         if (add_newline) {
440                 if (o >= 0 && b.len >= o)
441                         /* skip the newline */
442                         doc_prev(focus, m);
443                 else
444                         buf_append(&b, '\n');
445         }
446
447         if (start_boundary && chars < LARGE_LINE - 5)
448                 /* This boundary is no longer well-placed. */
449                 mark_free(start_boundary);
450
451         mark_free(doc_boundary);
452
453         if (pm && pm_offset < 0)
454                 pm_offset = b.len;
455
456         ret = comm_call(ci->comm2, "callback:render", focus, pm_offset, NULL,
457                         buf_final(&b));
458         free(b.b);
459         free(ar.insert.b);
460         return ret ?: 1;
461 }
462
463 DEF_LOOKUP_CMD(markup_handle, mu_map);
464
465 static struct pane *do_markup_attach(struct pane *p safe)
466 {
467         struct pane *ret;
468         struct mu_info *mu;
469
470         ret = pane_register(p, 0, &markup_handle.c);
471         if (!ret)
472                 return NULL;
473         mu = ret->data;
474         mu->view = home_call(p, "doc:add-view", ret) - 1;
475
476         return ret;
477 }
478
479 DEF_CMD(markup_attach)
480 {
481         struct pane *ret;
482
483         ret = do_markup_attach(ci->focus);
484         if (!ret)
485                 return Efail;
486         return comm_call(ci->comm2, "callback:attach", ret);
487 }
488
489 DEF_CMD(mu_clone)
490 {
491         struct pane *parent = ci->focus;
492         struct pane *child = do_markup_attach(parent);
493         pane_clone_children(ci->home, child);
494         return 1;
495 }
496
497 DEF_CMD(mu_clip)
498 {
499         struct mu_info *mu = ci->home->data;
500
501         marks_clip(ci->home, ci->mark, ci->mark2,
502                    mu->view, ci->home, !!ci->num);
503         return Efallthrough;
504 }
505
506 void edlib_init(struct pane *ed safe)
507 {
508         mu_map = key_alloc();
509
510         key_add(mu_map, "doc:render-line", &render_line);
511         key_add(mu_map, "doc:render-line-prev", &render_prev);
512         key_add(mu_map, "Clone", &mu_clone);
513         key_add(mu_map, "Notify:clip", &mu_clip);
514
515         call_comm("global-set-command", ed, &markup_attach,
516                   0, NULL, "attach-markup");
517 }