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