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