2 * Copyright Neil Brown ©2016-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
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
27 static struct map *mu_map safe;
29 #define LARGE_LINE 5000
31 static int is_render_eol(wint_t ch, struct pane *p safe, struct mark *m safe)
36 attr = pane_mark_attr(p, m, "markup:not_eol");
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.
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;
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;
61 int rpt = RPT_NUM(ci);
68 boundary = vmark_at_or_before(f, m, mu->view, ci->home);
69 doc_boundary = call_ret(mark, "doc:get-boundary", f, -1, m);
71 while ((ch = doc_prev(f, m)) != WEOF &&
72 (!is_render_eol(ch, f, m) || rpt > 0) &&
74 (!boundary || mark_ordered_not_same(boundary, m)) &&
75 (!doc_boundary || mark_ordered_not_same(doc_boundary, m))) {
77 boundary = vmark_at_or_before(f, m, mu->view, ci->home);
78 doc_boundary = call_ret(mark, "doc:get-boundary", f, -1, m);
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
89 mark_free(doc_boundary);
91 if (!boundary || !mark_same(boundary, m)) {
92 boundary = vmark_new(f, mu->view, ci->home);
94 mark_to_mark(boundary, m);
98 mark_free(doc_boundary);
99 if (ch == WEOF && rpt)
101 /* Found a '\n', so step forward over it for start-of-line. */
102 if (is_render_eol(ch, f, m))
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'.
124 struct attr_stack *next;
127 unsigned short priority;
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
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.
142 static int find_finished(struct attr_stack *st, int pos, int *nextp safe)
148 for (; st ; st = st->next, depth++) {
151 else if (next < 0 || next > st->end)
158 /* Move the top 'depth' attributes from 'ast' to 'tmpst', updating 'popped' */
159 static void as_pop(struct attr_return *ar safe, int depth)
161 struct attr_stack *from = ar->ast;
162 struct attr_stack *to = ar->tmpst;
164 while (from && depth > 0) {
165 struct attr_stack *t;
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)
180 struct attr_stack *from = ar->tmpst;
181 struct attr_stack *to = ar->ast;
183 while (ar->popped > 0) {
184 buf_concat(b, "</>");
189 struct attr_stack *t = from->next;
190 if (from->end <= ar->chars) {
195 buf_concat(b, from->attr);
199 if (from->end < ar->min_end)
200 ar->min_end = from->end;
208 static void as_add(struct attr_return *ar safe,
209 int end, int prio, const char *attr safe)
211 struct attr_stack *new, **here;
213 while (ar->ast && ar->ast->priority > prio)
217 while (*here && (*here)->priority <= prio)
218 here = &(*here)->next;
219 new = calloc(1, sizeof(*new));
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;
229 new->priority = prio;
233 static void as_clear(struct attr_return *ar safe,
234 int prio, const char *attr)
236 struct attr_stack *st;
238 while (ar->ast && ar->ast->priority >= prio)
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))
247 DEF_CB(text_attr_forward)
249 struct attr_return *ar = container_of(ci->comm, struct attr_return, fwd);
250 if (!ci->str || !ci->str2)
252 return call_comm("map-attr", ci->focus, &ar->rtn, 0, ci->mark, ci->str2,
256 DEF_CB(text_attr_callback)
258 struct attr_return *ar = container_of(ci->comm, struct attr_return,
263 as_add(ar, ci->num, ci->num2, ci->str);
265 as_clear(ar, ci->num2, ci->str);
267 buf_concat(&ar->insert, ci->str2);
271 static void call_map_mark(struct pane *f safe, struct mark *m safe,
272 struct attr_return *ar safe)
274 const char *key = "render:";
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);
282 static int want_vis_newline(struct attr_stack *as)
284 while (as && strstr(as->attr, "vis-nl") == NULL)
291 /* Render the line from 'mark' to the first '\n' or until
293 * Convert '<' to '<<' and if a char has the 'highlight' attribute,
294 * include that between '<>'.
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;
307 struct attr_return ar;
316 ar.rtn = text_attr_callback;
317 ar.fwd = text_attr_forward;
318 ar.ast = ar.tmpst = NULL;
321 buf_init(&ar.insert);
327 oneline = pane_attr_get(focus, "render-one-line");
328 if (oneline && strcmp(oneline, "yes") != 0)
330 noret = pane_attr_get(focus, "render-hide-CR");
331 if (noret && strcmp(noret, "yes") != 0)
334 ch = doc_following(focus, m);
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);
343 boundary = vmark_at_or_before(focus, m, mu->view, ci->home);
345 if (mark_same(m, boundary))
346 start_boundary = boundary;
347 boundary = vmark_next(boundary);
349 doc_boundary = call_ret(mark, "doc:get-boundary", focus, 1, m);
352 call_comm("map-attr", focus, &ar.rtn, 0, m, "start-of-line");
354 buf_concat(&b, buf_final(&ar.insert));
355 buf_reinit(&ar.insert);
360 if (o >= 0 && b.len >= o)
362 if (pm && mark_same(m, pm))
365 if (ar.ast && ar.min_end <= chars) {
366 int depth = find_finished(ar.ast, chars, &ar.min_end);
371 call_comm("doc:get-attr", focus, &ar.fwd, 0, m, "render:", 1);
373 /* find all marks "here". They might get moved when we call map_mark,
374 * so move 'm' among them
377 while ((m2 = mark_next(m)) != NULL &&
379 mark_to_mark_noref(m, m2);
380 call_map_mark(focus, m2, &ar);
385 if (o >= 0 && b.len >= o)
389 buf_concat(&b, buf_final(&ar.insert));
390 buf_reinit(&ar.insert);
393 ch = doc_next(focus, m);
396 if (!oneline && is_eol(ch)) {
400 if (boundary && mark_ordered_or_same(boundary, m))
402 if (doc_boundary && mark_ordered_or_same(doc_boundary, m))
405 if (ar.ast && strcmp(ar.ast->attr, "hide") == 0)
408 if (o >= 0 && b.len+1 >= o) {
414 if (ch == '\r' && noret) {
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, "</>");
430 if (add_newline && want_vis_newline(ar.ast))
437 if (o >= 0 && b.len >= o)
438 /* skip the newline */
441 buf_append(&b, '\n');
444 if (start_boundary && chars < LARGE_LINE - 5)
445 /* This boundary is no longer well-placed. */
446 mark_free(start_boundary);
448 mark_free(doc_boundary);
450 ret = comm_call(ci->comm2, "callback:render", focus, 0, NULL,
457 DEF_LOOKUP_CMD(markup_handle, mu_map);
459 static struct pane *do_markup_attach(struct pane *p safe)
465 ret = pane_register(p, 0, &markup_handle.c, mu);
468 mu->view = home_call(p, "doc:add-view", ret) - 1;
473 DEF_CMD(markup_attach)
477 ret = do_markup_attach(ci->focus);
480 return comm_call(ci->comm2, "callback:attach", ret);
485 struct pane *parent = ci->focus;
486 struct pane *child = do_markup_attach(parent);
487 pane_clone_children(ci->home, child);
493 struct mu_info *mu = ci->home->data;
495 marks_clip(ci->home, ci->mark, ci->mark2,
496 mu->view, ci->home, !!ci->num);
500 void edlib_init(struct pane *ed safe)
502 mu_map = key_alloc();
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);
510 call_comm("global-set-command", ed, &markup_attach,
511 0, NULL, "attach-markup");