]> git.neil.brown.name Git - edlib.git/blob - lib-renderline.c
TODO: clean out done items.
[edlib.git] / lib-renderline.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * A renderline pane will take a single line of marked-up text
6  * and draw it.  The "line" may well be longer that the width
7  * of the pane, and it might then be wrapped generatinging
8  * multiple display lines.
9  *
10  * The render-lines pane will place multiple renderline panes and use
11  * them to do the drawing - resizing and moving them as necessary to fit
12  * the size of the text.  Other panes can use renderline for similar
13  * purposes.  messageline uses just one renderline.
14  *
15  * A renderline pane can sit in the normal stack and receive Refresh
16  * messages to trigger drawing, or can sit "beside" the stack with a negative
17  * 'z' value. In that can the owner need to explicitly request refresh.
18  *
19  * "render-line:set" will set the content of the line
20  * "render-line:measure" will determine layout and size given the available
21  *    width and other parameters
22  * "render-line:draw" will send drawing commands.
23  * "Refresh" does both the measure and the draw.
24  */
25
26 #define _GNU_SOURCE /*  for asprintf */
27 #include <stdio.h>
28 #include <ctype.h>
29 #include <wctype.h>
30 #include <stdint.h>
31
32 #define PANE_DATA_TYPE struct rline_data
33 #include "core.h"
34 #include "misc.h"
35
36 /* There is one render_item entry for
37  * - each string of text with all the same attributes
38  * - each individual TAB
39  * - each unknown control character
40  * - the \n \f or \0 which ends the line
41  * When word-wrap is enabled, strings of spaces get
42  * different attributes, so a different render_item entry.
43  *
44  * attributes understood at this level are:
45  *  left:nn             - left margin - in "points" (10 points per char normally)
46  *  right:nn            - right margin
47  *  tab:nn              - move to nn from left margin or -nn from right margin
48  *  rtab                - from here to next tab or eol right-aligned
49  *  center or centre    - equal space inserted here and before next
50  *  or ctab               tab-stop or margin
51  *  space-above:nn      - extra space before (wrapped) line
52  *  space-below:nn      - extra space after (wrapped) line
53  *  height:nn           - override height.  This effectively adds space above
54  *                        every individual line if the whole line is wrapped
55  *  wrap                - text with this attr can be hidden when used as a wrap
56  *                        point.  Not hidden if cursor in the region.
57  *  wrap-margin         - remember this x offset as left margin of wrapped lines
58  *  wrap-head=xx        - text is inserted at start of line when wrapped
59  *  wrap-tail=xx        - text to include at end of line when wrapped.  This
60  *                        determines how far before right margin the wrap is
61  *                        triggered.
62  *  wrap-XXXX           - attrs to apply to wrap head/tail. Anything not
63  *                        recognised has "wrap-" stripped and is used for the
64  *                        head and tail. Default is fg:blue,underline
65  *  hide                - Text is hidden if cursor is not within range.
66  *
67  * "nn" is measured in "points" which is 1/10 the nominal width of chars
68  * in the default font size, which is called "10".  A positive value is
69  * measured from the left margin or, which setting margins, from the
70  * relevant page edge.  A negative value is measures from the right margin.
71  *
72  */
73
74 /* When an entry is split for line-wrap:
75  *   'split_cnt' is count of splits (total lines - 1)
76  *   ' split_list' is offsets from start where split happens
77  *   'x' position of wrapped portions is wrap_marign or head_length
78  *   'y' position of wrapped portions increases line_height for each
79  */
80 struct render_item {
81         struct render_item *next;
82         const char      *attr safe;
83         unsigned short  *split_list;
84         unsigned short  start, len; // in rd->line
85         unsigned short  height, width;
86         signed short    x,y; /* signed so x can go negative when shifted */
87         signed short    tab;
88         unsigned short  wrap_x; /* If this item wraps, wrap_x is the margin */
89         uint8_t         split_cnt; /* Wrap happens this many times at byte
90                                     * positions in split_list */
91         uint8_t         wrap;   /* This and consecutive render_items
92                                  * with the same wrap number form an
93                                  * optional wrap point.  It is only visible
94                                  * when not wrapped, or when cursor is in
95                                  * it.
96                                  */
97         uint8_t         hide;   /* This and consecutive render_items
98                                  * with the same hide number form a
99                                  * hidden extent which is visible when
100                                  * the cursor is in it.
101                                  */
102         bool            wrap_margin:1;
103         bool            hidden:1;
104         bool            eol:1;
105         unsigned int    tab_cols:4; /* For \t char */
106         enum tab_align {
107                 TAB_LEFT = 0,   // No extra space (after tab stop)
108                 TAB_RIGHT,      // Add extra space here so there no space at
109                                 // next tab stop or margin
110                 TAB_CENTRE,     // Add extra space here, half of what TAB_RIGHT
111                                 // would insert
112         }               tab_align:2;
113 };
114 /* A "tab" value of 0 means left margin, and negative is measured from right
115  * margin, so we need some other value to say "no value here"
116  */
117 static const short TAB_UNSET = (1<<(14-2));
118
119 struct rline_data {
120         unsigned short  prefix_bytes, prefix_pixels;
121         short           curs_width;
122         short           left_margin, right_margin;
123         short           space_above, space_below;
124         unsigned short  line_height, min_height;
125         unsigned short  scale;
126         unsigned short  width;
127         unsigned short  ascent;
128         char            *wrap_head, *wrap_tail, *wrap_attr;
129         int             head_length, tail_length;
130         char            *line safe;
131         char            *background;
132         bool            word_wrap;
133         bool            image;
134         int             curspos;
135
136         struct xy       image_size;
137         /* These used to check is measuring is needed, or to record
138          * results of last measurement */
139         unsigned short measure_width, measure_height;
140         short measure_offset, measure_shift_left;
141         struct render_item *content;
142 };
143 #include "core-pane.h"
144
145 static void aappend(struct buf *b safe, char const *a safe)
146 {
147         const char *end = a;
148         while (*end >= ' ' && *end != ',')
149                 end++;
150         buf_concat_len(b, a, end-a);
151         buf_append(b, ',');
152 }
153
154 static void add_render(struct rline_data *rd safe,
155                        struct render_item **safe*rip safe,
156                        const char *start safe, const char *end safe,
157                        char *attr safe,
158                        short *tab safe, enum tab_align *align safe,
159                        bool *wrap_margin safe,
160                        short wrap, short hide)
161 {
162         struct render_item *ri;
163         struct render_item **riend = *rip;
164
165         alloc(ri, pane);
166         ri->attr = strdup(attr);
167         ri->start = start - rd->line;
168         ri->len = end - start;
169         ri->tab_align = *align;
170         ri->tab = *tab;
171         ri->wrap = wrap;
172         ri->hide = hide;
173         ri->wrap_margin = *wrap_margin;
174         ri->eol = !!strchr("\n\f\0", *start);
175         *tab = TAB_UNSET;
176         *align = TAB_LEFT;
177         *wrap_margin = False;
178         *riend = ri;
179         riend = &ri->next;
180         *rip = riend;
181 }
182
183 static void parse_line(struct rline_data *rd safe)
184 {
185         /* Parse out markup in line into a renderlist with
186          * global content directly in rd.
187          */
188         struct buf attr, wrapattr;
189         struct render_item *ri = NULL, **riend = &ri;
190         const char *line = rd->line;
191         bool wrap_margin = False;
192         short tab = TAB_UNSET;
193         enum tab_align align = TAB_LEFT;
194         int hide = 0, hide_num = 0, hide_depth = 0;
195         int wrap = 0, wrap_num = 0, wrap_depth = 0;
196         unsigned char c;
197
198         rd->left_margin = rd->right_margin = 0;
199         rd->space_above = rd->space_below = 0;
200         rd->min_height = 0;
201         aupdate(&rd->wrap_head, NULL);
202         aupdate(&rd->wrap_tail, NULL);
203         aupdate(&rd->wrap_attr, NULL);
204
205         if (!line) {
206                 rd->image = False;
207                 return;
208         }
209         rd->image = strstarts(line, SOH "image:");
210         if (rd->image) {
211                 rd->image_size.x = 0;
212                 return;
213         }
214         buf_init(&attr);
215         buf_init(&wrapattr);
216
217         ri = rd->content;
218         rd->content = NULL;
219         rd->measure_width = 0; // force re-measure
220         while (ri) {
221                 struct render_item *r = ri;
222                 ri = r->next;
223                 free(r->split_list);
224                 unalloc_str_safe(r->attr, pane);
225                 unalloc(r, pane);
226         }
227
228         do {
229                 const char *st = line;
230                 c = *line++;
231
232                 while (c >= ' ' &&
233                        (!rd->word_wrap || c != ' '))
234                         c = *line++;
235
236                 if (line - 1 > st || tab != TAB_UNSET) {
237                         /* All text from st to line-1 has "attr' */
238                         add_render(rd, &riend, st, line-1, buf_final(&attr),
239                                    &tab, &align, &wrap_margin, wrap, hide);
240                         st = line - 1;
241                 }
242                 switch (c) {
243                 case soh: {
244                         int old_len;
245                         const char *a, *v;
246                         st = line;
247                         /* Move 'line' over the attrs */
248                         while (*line && line[-1] != stx)
249                                 line += 1;
250
251                         /* A set of attrs begins and ends with ',' so that
252                          * ",," separates sets of attrs
253                          * An empty set will be precisely 1 ','.  We strip
254                          * "attr," as long as we can, then strip one more ',',
255                          * which should leave either a trailing comma, or an
256                          * empty string.
257                          */
258                         old_len = attr.len;
259                         buf_append(&attr, ',');
260                         foreach_attr(a, v, st, line) {
261                                 if (amatch(a, "centre") || amatch(a, "center") ||
262                                     amatch(a, "ctab")) {
263                                         if (v)
264                                                 tab = anum(v);
265                                         align = TAB_CENTRE;
266                                 } else if (amatch(a, "tab") && v) {
267                                         tab = anum(v);
268                                         align = TAB_LEFT;
269                                 } else if (amatch(a, "rtab")) {
270                                         align = TAB_RIGHT;
271                                 } else if (amatch(a, "left") && v) {
272                                         rd->left_margin = anum(v);
273                                 } else if (amatch(a, "right") && v) {
274                                         rd->right_margin = anum(v);
275                                 } else if (amatch(a, "space-above") && v) {
276                                         rd->space_above = anum(v);
277                                 } else if (amatch(a, "space-below") && v) {
278                                         rd->space_below = anum(v);
279                                 } else if (amatch(a, "height") && v) {
280                                         rd->min_height = anum(v);
281                                 } else if (amatch(a, "wrap")) {
282                                         wrap = ++wrap_num;
283                                         wrap_depth = old_len;
284                                 } else if (amatch(a, "wrap-margin")) {
285                                         wrap_margin = True;
286                                 } else if (amatch(a, "wrap-head")) {
287                                         aupdate(&rd->wrap_head, v);
288                                 } else if (amatch(a, "wrap-tail")) {
289                                         aupdate(&rd->wrap_tail, v);
290                                 } else if (aprefix(a, "wrap-")) {
291                                         aappend(&wrapattr, a+5);
292                                 } else if (amatch(a, "word-wrap")) {
293                                         if (!v || *v == '1')
294                                                 rd->word_wrap = 1;
295                                         else if (*v == '0')
296                                                 rd->word_wrap = 0;
297                                 } else if (amatch(a, "hide")) {
298                                         hide = ++hide_num;
299                                         hide_depth = old_len;
300                                 } else
301                                         aappend(&attr, a);
302                         }
303                         break;
304                         }
305                 case etx:
306                         /* strip last set of attrs */
307                         while (attr.len >= 2 &&
308                         attr.b[attr.len-1] == ',' &&
309                         attr.b[attr.len-2] != ',') {
310                                 /* strip this attr */
311                                 attr.len -= 2;
312                                 while (attr.len && attr.b[attr.len-1] != ',')
313                                         attr.len -= 1;
314                         }
315                         /* strip one more ',' */
316                         if (attr.len > 0)
317                                 attr.len -= 1;
318                         if (wrap && attr.len <= wrap_depth)
319                                 wrap = 0;
320                         if (hide && attr.len <= hide_depth)
321                                 hide = 0;
322                         break;
323                 case ack:
324                         /* Just ignore this */
325                         break;
326                 case ' ':
327                         /* This and following spaces are wrappable */
328                         st = line;
329                         while (*line == ' ')
330                                 line += 1;
331                         wrap = ++wrap_num;
332                         add_render(rd, &riend, st - 1, line, buf_final(&attr),
333                                    &tab, &align, &wrap_margin, wrap, hide);
334                         wrap = 0;
335                         break;
336                 case '\0':
337                 case '\n':
338                 case '\f':
339                 case '\t':
340                 default:
341                         /* Each tab gets an entry of its own, as does any
342                          * stray control character.
343                          * \f \n and even \0 do.  These are needed for
344                          * easy cursor placement.
345                          */
346                         add_render(rd, &riend, st, line, buf_final(&attr),
347                                    &tab, &align, &wrap_margin, wrap, hide);
348                         break;
349                 }
350         } while (c);
351
352         rd->content = ri;
353         free(attr.b);
354         if (buf_final(&wrapattr)[0])
355                 rd->wrap_attr = buf_final(&wrapattr);
356         else {
357                 free(wrapattr.b);
358                 rd->wrap_attr = strdup(",fg:blue,underline,");
359         }
360 }
361
362 static inline struct call_return do_measure(struct pane *p safe,
363                                             struct render_item *ri safe,
364                                             int splitpos, int len,
365                                             int maxwidth)
366 {
367         struct rline_data *rd = p->data;
368         struct call_return cr;
369         char tb[] = "        ";
370         char *str = rd->line + ri->start + splitpos;
371         char tmp;
372
373         if (ri->len && rd->line[ri->start] == '\t') {
374                 str = tb;
375                 if (len < 0)
376                         len = ri->tab_cols - splitpos;
377         } else
378                 if (len < 0)
379                         len = ri->len - splitpos;
380         tmp = str[len];
381         str[len] = 0;
382
383         cr = call_ret(all, "Draw:text-size", p,
384                       maxwidth, NULL, str,
385                       rd->scale, NULL, ri->attr);
386
387         str[len] = tmp;
388         if (cr.ret == 1 && maxwidth >= 0 &&
389             cr.i >= len)
390                 /* All fits in maxwidth */
391                 cr.ret = 2;
392         /* Report position in rd->line */
393         if (str == tb) {
394                 cr.s = rd->line + ri->start;
395                 if (splitpos + cr.i >= ri->tab_cols)
396                         cr.s += 1;
397         } else
398                 cr.s = str + cr.i;
399         return cr;
400 }
401
402 static inline struct call_return measure_str(struct pane *p safe,
403                                              char *str safe,
404                                              const char *attr)
405 {
406         struct rline_data *rd = p->data;
407
408         return call_ret(all, "Draw:text-size", p,
409                         -1, NULL, str,
410                         rd->scale, NULL, attr);
411 }
412
413 static inline void do_draw(struct pane *p safe,
414                            struct pane *focus safe,
415                            struct render_item *ri safe, int split,
416                            int offset,
417                            int x, int y)
418 {
419         struct rline_data *rd = p->data;
420         char tmp;
421         char *str;
422         int len;
423         char tb[] = "         ";
424
425         str = rd->line + ri->start;
426         len = ri->len;
427
428         y += rd->ascent;
429         if (ri->len && strchr("\f\n\0", str[0])) {
430                 /* end marker - len extends past end of string,
431                  * but mustn't write there.  Only need to draw if
432                  * cursor is here.
433                  */
434                 if (offset == 0)
435                         home_call(focus, "Draw:text", p, offset, NULL, "",
436                                   rd->scale, NULL, ri->attr, x, y);
437                 return;
438         }
439         if (ri->len && str[0] == '\t') {
440                 len = ri->tab_cols;
441                 if (split)
442                         offset = -1;
443         }
444         if (ri->split_list) {
445                 if (split < ri->split_cnt)
446                         len = ri->split_list[split];
447                 if (split)
448                         len -= ri->split_list[split-1];
449         }
450
451         if (ri->len && str[0] == '\t')
452                 /* Tab need a list of spaces */
453                 str = tb;
454         else
455                 if (split > 0 && split <= ri->split_cnt && ri->split_list) {
456                         str += ri->split_list[split-1];
457                         offset -= ri->split_list[split-1];
458                 }
459
460         tmp = str[len];
461         str[len] = 0;
462
463         if (offset >= len)
464                 offset = -1;
465         home_call(focus, "Draw:text", p, offset, NULL, str,
466                            rd->scale, NULL, ri->attr, x, y);
467         str[len] = tmp;
468 }
469
470 static inline void draw_wrap(struct pane *p safe,
471                              struct pane *focus safe,
472                              char *str safe,
473                              int x, int y)
474 {
475         struct rline_data *rd = p->data;
476
477         home_call(focus, "Draw:text", p,
478                   -1, NULL, str,
479                   rd->scale, NULL, rd->wrap_attr,
480                   x, y + rd->ascent);
481 }
482
483 static bool add_split(struct render_item *ri safe, int split)
484 {
485         int i = ri->split_cnt;
486         if (i > 250)
487                 return False;
488         ri->split_cnt += 1;
489         ri->split_list = realloc(ri->split_list,
490                                  sizeof(ri->split_list[0]) * ri->split_cnt);
491         ri->split_list[i] = split;
492         return True;
493 }
494
495 static int calc_pos(int num, int margin, int width)
496 {
497         if (num >= 0)
498                 return num * width / 10;
499         if (-num * width / 10 > margin)
500                 return 0;
501         return margin + num * width / 10;
502 }
503
504 static int measure_line(struct pane *p safe, struct pane *focus safe, int offset)
505 {
506         /* First measure each render_item entry setting
507          * height, ascent, width.
508          * Then use that with tab information to set 'x' position for
509          * each unit.
510          * Finally identify line-break locations if needed and set 'y'
511          * positions
512          *
513          * Return 1 if there is an EOL ('\n')
514          * 2 if there is an end-of-page ('\f')
515          * 3 if both.
516          * 0 if neither
517          */
518         struct rline_data *rd = p->data;
519         struct render_item *ri, *wraprl;
520         int shift_left = pane_attr_get_int(focus, "render-wrap", -1);
521         bool wrap = shift_left < 0;
522         int wrap_margin;
523         int right_margin;
524         int left_margin;
525         struct xy xyscale = pane_scale(focus);
526         int curs_height;
527         int xdiff, ydiff;
528         struct call_return cr;
529         int x, y;
530         int ret = 0;
531         bool seen_rtab = False;
532         unsigned int offset_hide = 0;
533
534         if (!rd->content)
535                 return ret;
536         if (xyscale.x == rd->scale && p->w == rd->measure_width &&
537             shift_left == rd->measure_shift_left &&
538             offset == rd->measure_offset) {
539                 /* No change */
540                 for (ri = rd->content ; ri ; ri = ri->next) {
541                         if (ri->hidden)
542                                 continue;
543                         if (ri->eol && rd->line[ri->start] == '\n')
544                                 ret |= 1;
545                         if (ri->eol && rd->line[ri->start] == '\f')
546                                 ret |= 2;
547                 }
548                 pane_resize(p, p->x, p->y, p->w, rd->measure_height);
549                 return ret;
550         }
551         rd->scale = xyscale.x;
552         rd->measure_width = p->w;
553         rd->measure_offset = offset;
554         rd->measure_shift_left = shift_left;
555
556         cr = measure_str(p, "M", "");
557         rd->curs_width = cr.x;
558         curs_height = cr.y;
559         rd->line_height = cr.y;
560         rd->ascent = cr.i2;
561         if (rd->min_height > 10)
562                 rd->line_height = rd->line_height * rd->min_height / 10;
563
564         if (rd->wrap_head) {
565                 cr = measure_str(p, rd->wrap_head, rd->wrap_attr);
566                 rd->head_length = cr.x;
567         }
568         cr = measure_str(p, rd->wrap_tail ?: "\\", rd->wrap_attr);
569         rd->tail_length = cr.x;
570
571         left_margin = calc_pos(rd->left_margin, p->w, rd->curs_width);
572         /* 0 means right edge for right_margin, and left edge for all others */
573         right_margin = p->w - calc_pos(-rd->right_margin, p->w, rd->curs_width);
574
575         if (offset >= 0)
576                 for (ri = rd->content ; ri ; ri = ri->next)
577                         if (offset < ri->start + ri->len) {
578                                 offset_hide = ri->hide;
579                                 break;
580                         }
581
582         rd->width = 0;
583         for (ri = rd->content; ri; ri = ri->next) {
584                 ri->hidden = (ri->hide && ri->hide != offset_hide);
585                 if (ri->hidden) {
586                         ri->width = 0;
587                         continue;
588                 }
589                 if (ri->len == 0 ||
590                     (unsigned char)rd->line[ri->start] >= ' ') {
591                         cr = do_measure(p, ri, 0, -1, -1);
592                 } else {
593                         char tmp[4];
594                         if (ri->eol) {
595                                 /* Ensure attributes of newline add to line
596                                  * height. The width will be ignored. */
597                                 strcpy(tmp, "M");
598                                 if (rd->line[ri->start] == '\n')
599                                         ret |= 1;
600                                 if (rd->line[ri->start] == '\f')
601                                         ret |= 2;
602                         } else if (rd->line[ri->start] == '\t') {
603                                 strcpy(tmp, " ");
604                         } else {
605                                 strcpy(tmp, "^x");
606                                 tmp[1] = '@' + (rd->line[ri->start] & 31);
607                         }
608                         cr = measure_str(p, tmp, ri->attr);
609                 }
610
611                 if (cr.y > rd->line_height)
612                         rd->line_height = cr.y;
613                 ri->height = cr.y;
614                 if (cr.i2 > rd->ascent)
615                         rd->ascent = cr.i2;
616                 ri->width = ri->eol ? 0 : cr.x;
617                 rd->width += ri->width;
618
619                 if (ri->start <= offset && offset <= ri->start + ri->len) {
620                         cr = measure_str(p, "M", ri->attr);
621                         rd->curs_width = cr.x;
622                 }
623
624                 ri->split_cnt = 0;
625                 free(ri->split_list);
626                 ri->split_list = NULL;
627         }
628         /* Set 'x' position honouring tab stops, and set length
629          * of "\t" characters.  Also handle \n and \f.
630          */
631         x = left_margin - (shift_left > 0 ? shift_left : 0);
632         y = rd->space_above * curs_height / 10;
633         for (ri = rd->content; ri; ri = ri->next) {
634                 int w, margin;
635                 struct render_item *ri2;
636                 ri->y = y;
637                 if (ri->hidden) {
638                         ri->x = x;
639                         continue;
640                 }
641                 if (ri->tab != TAB_UNSET)
642                         x =  left_margin + calc_pos(ri->tab,
643                                                     right_margin - left_margin,
644                                                     rd->curs_width);
645                 if (ri->eol) {
646                         /* EOL */
647                         ri->x = x;
648                         x = 0; /* Don't include shift. probably not margin */
649                         if (rd->line[ri->start])
650                                 y += rd->line_height;
651                         continue;
652                 }
653                 if (ri->tab_align == TAB_LEFT) {
654                         ri->x = x;
655                         if (ri->len && rd->line[ri->start] == '\t') {
656                                 int col = x / ri->width;
657                                 int cols= 8 - (col % 8);
658                                 ri->tab_cols = cols;
659                                 ri->width *= cols;
660                         }
661                         x += ri->width;
662                         continue;
663                 }
664                 if (ri->tab_align == TAB_RIGHT)
665                         seen_rtab = True;
666                 w = ri->width;
667                 for (ri2 = ri->next;
668                      ri2 && ri2->tab_align == TAB_LEFT && ri2->tab == TAB_UNSET;
669                      ri2 = ri2->next)
670                         w += ri2->width;
671                 while (ri2 && ri2->tab == TAB_UNSET)
672                         ri2 = ri2->next;
673                 margin = right_margin - left_margin;
674                 if (ri2)
675                         margin =  left_margin + calc_pos(ri2->tab,
676                                                          right_margin - left_margin,
677                                                          rd->curs_width);
678                 if (ri->tab_align == TAB_RIGHT)
679                         x = x + margin - x - w;
680                 else
681                         x = x + (margin - x - w) / 2;
682                 ri->x = x;
683                 while (ri->next && ri->next->next && ri->next->tab_align == TAB_LEFT) {
684                         x += ri->width;
685                         ri = ri->next;
686                         ri->x = x;
687                         ri->y = y;
688                 }
689         }
690
691         /* Now we check to see if the line needs to be wrapped and
692          * if so, adjust some y values and reduce x.  If we need to
693          * split an individual entry we create an array of split points.
694          */
695         xdiff = 0; ydiff = 0; y = 0;
696         wraprl = NULL;
697         wrap_margin = left_margin + rd->head_length;
698         for (ri = rd->content ; wrap && ri ; ri = ri->next) {
699                 int splitpos;
700                 if (ri->hidden)
701                         continue;
702                 if (ri->wrap && (wraprl == NULL || ri->wrap != wraprl->wrap))
703                         wraprl = ri;
704                 if (ri->wrap_margin)
705                         wrap_margin = ri->x + xdiff;
706                 ri->wrap_x = wrap_margin;
707                 ri->x += xdiff;
708                 ri->y += ydiff;
709                 y = ri->y;
710                 if (ri->eol) {
711                         xdiff = 0;
712                         continue;
713                 }
714                 if (ri->x + ri->width <= right_margin - rd->tail_length)
715                         continue;
716                 if ((ri->next == NULL || ri->next->eol) &&
717                     ri->x + ri->width <= right_margin &&
718                     seen_rtab)
719                         /* Don't need any tail space for last item.
720                          * This allows rtab to fully right-justify,
721                          * but leaves no-where for the cursor.  So
722                          * only do it if rtab is present.
723                          */
724                         continue;
725                 /* This doesn't fit here */
726                 if (wraprl) {
727                         /* Move wraprl to next line and hide it unless it contains cursor */
728                         int xd;
729                         struct render_item *wraprl2, *ri2;
730
731                         /* Find last ritem in wrap region.*/
732                         for (wraprl2 = wraprl ;
733                              wraprl2->next && wraprl2->next->wrap == wraprl->wrap ;
734                              wraprl2 = wraprl2->next)
735                                 ;
736                         wrap_margin = wraprl2->wrap_x;
737                         if (wraprl2->next) {
738                                 xd = wraprl2->next->x - wrap_margin;
739                                 if (wraprl2->next->start > ri->start)
740                                         xd += xdiff;
741                         } else {
742                                 xd = wraprl2->x - wrap_margin;
743                                 if (wraprl2->start > ri->start)
744                                         xd += xdiff;
745                         }
746                         if (offset >= 0 &&
747                             offset >= wraprl->start &&
748                             offset <= wraprl2->start + wraprl2->len) {
749                                 /* Cursor is here, so keep it visible.
750                                  * If we are still in the wrap region, pretend
751                                  * it didn't exist, else move first item
752                                  * after it to next line
753                                  */
754                                 if (ri->wrap == wraprl->wrap)
755                                         goto normal_wrap;
756                         } else {
757                                 /* Hide the wrap region */
758                                 while (wraprl != wraprl2) {
759                                         wraprl->hidden = True;
760                                         wraprl = wraprl->next;
761                                 }
762                                 if (wraprl)
763                                         wraprl->hidden = True;
764                                 while (ri->next && ri->next->hidden)
765                                         ri = ri->next;
766                         }
767                         for (ri2 = wraprl2->next ; ri2 && ri2 != ri->next; ri2 = ri2->next) {
768                                 ri2->y += rd->line_height;
769                                 ri2->x -= xd;
770                                 if (ri2->wrap_margin)
771                                         wrap_margin = ri2->x;
772                                 ri2->wrap_x = wrap_margin;
773                         }
774                         xdiff -= xd;
775                         ydiff += rd->line_height;
776                         wraprl = NULL;
777                         if (ri->hidden ||
778                             ri->x + ri->width <= right_margin - rd->tail_length)
779                                 continue;
780                 }
781         normal_wrap:
782                 /* Might need to split this ri into two or more pieces */
783                 x = ri->x;
784                 splitpos = 0;
785                 while (1) {
786                         cr = do_measure(p, ri, splitpos, -1,
787                                         right_margin - rd->tail_length - x);
788                         if (cr.ret == 2)
789                                 /* Remainder fits now */
790                                 break;
791                         if (cr.i == 0 && splitpos == 0) {
792                                 /* None of this fits here, move to next line */
793                                 xdiff -= ri->x - wrap_margin;
794                                 ri->x = wrap_margin;
795                                 x = ri->x;
796                                 ydiff += rd->line_height;
797                                 ri->y += rd->line_height;
798                                 wraprl = NULL;
799                         }
800                         if (cr.i == 0)
801                                 /* Nothing fits and we already split - give up */
802                                 break;
803                         /* re-measure the first part */
804                         cr = do_measure(p, ri, splitpos,
805                                         cr.i,
806                                         right_margin - rd->tail_length - x);
807                         ydiff += rd->line_height;
808                         xdiff -= cr.x;
809                         if (splitpos == 0) {
810                                 xdiff -= ri->x - wrap_margin;
811                                 x = wrap_margin;
812                         }
813                         splitpos += cr.i;
814                         if (!add_split(ri, splitpos))
815                                 break;
816                 }
817         }
818         rd->measure_height =
819                 (rd->space_above + rd->space_below) * curs_height / 10 +
820                 ydiff + rd->line_height;
821         pane_resize(p, p->x, p->y, p->w, rd->measure_height);
822         attr_set_int(&p->attrs, "line-height", rd->line_height);
823         return ret;
824 }
825
826 static void draw_line(struct pane *p safe, struct pane *focus safe, int offset)
827 {
828         struct rline_data *rd = p->data;
829         struct render_item *ri;
830         char *wrap_tail = rd->wrap_tail ?: "\\";
831         char *wrap_head = rd->wrap_head ?: "";
832
833         home_call(focus, "Draw:clear", p, 0, NULL, rd->background);
834
835         if (!rd->content)
836                 return;
837         for (ri = rd->content ; ri; ri = ri->next) {
838                 int split = 0;
839                 short y = ri->y;
840                 int cpos;
841
842                 if (ri->hidden)
843                         continue;
844                 if (offset < 0 || offset >= ri->start + ri->len)
845                         cpos = -1;
846                 else if (offset < ri->start)
847                         cpos = 0;
848                 else
849                         cpos = offset - ri->start;
850
851                 do_draw(p, focus, ri, 0, cpos, ri->x, y);
852
853                 while (split < ri->split_cnt ||
854                        (ri->next && !ri->next->eol && ri->next->y > y)) {
855                         /* line wrap here */
856                         /* don't show head/tail for wrap-regions */
857                         if (*wrap_tail /*&& !ri->wrap*/)
858                                 draw_wrap(p, focus, wrap_tail,
859                                           p->w - rd->tail_length, y);
860                         y += rd->line_height;
861                         if (*wrap_head /*&& !ri->wrap*/)
862                                 draw_wrap(p, focus, wrap_head,
863                                           0, y);
864                         if (ri->split_list && split < ri->split_cnt) {
865                                 split += 1;
866                                 do_draw(p, focus, ri, split, cpos,
867                                         ri->wrap_x,
868                                         y);
869                         } else
870                                 break;
871                 }
872                 if (offset < ri->start + ri->len)
873                         offset = -1;
874         }
875 }
876
877 static int find_xy(struct pane *p safe, struct pane *focus safe,
878                    short x, short y, const char **xyattr)
879 {
880         /* Find the location in ->line that is best match for x,y.
881          * If x,y is on the char at that location, when store attributes
882          * for the char in xyattr
883          * We always return a location, even if no xyattr.
884          * We use the last render_item that is not definitely after x,y
885          * We do not consider the eol render_item
886          */
887         struct call_return cr;
888         struct rline_data *rd = p->data;
889         struct render_item *r, *ri = NULL;
890         int splitpos = 0;
891         int start = 0;
892
893         for (r = rd->content; r ; r = r->next) {
894                 int split;
895                 if (r->hidden)
896                         continue;
897                 if (r->y <= y && r->x <= x) {
898                         ri = r;
899                         start = r->start;
900                         splitpos = 0;
901                 }
902                 for (split = 0; split < r->split_cnt; split++) {
903                         if (r->y + (split + 1) * rd->line_height <= y &&
904                             r->wrap_x <= x && r->split_list) {
905                                 ri = r;
906                                 splitpos = r->split_list[split];
907                                 start = r->start + splitpos;
908                         }
909                 }
910         }
911         if (!ri)
912                 return 0;
913         if (ri->eol)
914                 /* newline or similar.  Can only be at start */
915                 return start;
916         cr = do_measure(p, ri, splitpos, -1, x - ri->x);
917         if ((splitpos ? ri->wrap_x : ri->x ) + cr.x > x &&
918             ri->y + rd->line_height * (1 + splitpos) > y &&
919             xyattr) {
920                 /* This is a bit of a hack.
921                  * We stick the x,y co-ords of the start
922                  * of the current attr in front of the
923                  * attrs so render-lines can provide a
924                  * good location for a menu
925                  */
926                 char buf[100];
927                 struct render_item *ri2;
928                 int ax = ri->x;
929                 for (ri2 = rd->content; ri2 != ri; ri2 = ri2->next)
930                         if (strcmp(ri2->attr, ri->attr) == 0)
931                                 ax = ri2->x;
932                 snprintf(buf, sizeof(buf), "%dx%d,", ax, y);
933                 *xyattr = strconcat(p, buf, ri->attr);
934         }
935         if (cr.s)
936                 return cr.s - rd->line;
937         return start + cr.i;
938 }
939
940 static struct xy find_curs(struct pane *p safe, int offset, const char **cursattr)
941 {
942         struct call_return cr;
943         struct xy xy = {0,0};
944         int split;
945         int st;
946         struct rline_data *rd = p->data;
947         struct render_item *r, *ri = NULL;
948
949         for (r = rd->content; r; r = r->next) {
950                 if (r->hidden)
951                         continue;
952                 if (offset < r->start)
953                         break;
954                 ri = r;
955         }
956         if (!ri) {
957                 /* This should be impossible as the eol goes past
958                  * the largest offset.
959                  */
960                 return xy;
961         }
962         if (offset < ri->start)
963                 /* in the attrs?? */
964                 offset = 0;
965         else
966                 offset -= ri->start;
967         /* offset now from ri->start */
968         if (ri->len && rd->line[ri->start] == '\t' && offset)
969                 offset = ri->tab_cols;
970         if (cursattr)
971                 *cursattr = ri->attr;
972         st = 0;
973         for (split = 0; split < ri->split_cnt && ri->split_list; split ++) {
974                 if (offset < ri->split_list[split])
975                         break;
976                 st = ri->split_list[split];
977         }
978         if (ri->eol)
979                 cr.x = offset ? ri->width : 0;
980         else
981                 cr = do_measure(p, ri, st, offset - st, -1);
982
983         if (split)
984                 xy.x = ri->wrap_x + cr.x;
985         else
986                 xy.x = ri->x + cr.x;
987         xy.y = ri->y + split * rd->line_height;
988         if (ri->next == NULL && offset > ri->len) {
989                 /* After the newline ? Go to next line */
990                 xy.x = 0;
991                 xy.y += rd->line_height;
992         }
993         return xy;
994 }
995
996 static void parse_map(const char *map safe, short *rowsp safe, short *colsp safe)
997 {
998         /* The map must be a sequence of rows, each of which is
999          * a sequence of chars starting CAPS and continuing lower.
1000          * Each row must be the same length.
1001          */
1002         short cols = -1;
1003         short rows = 0;
1004         short this_cols = 0;
1005
1006         for (; *map && isalpha(*map); map += 1) {
1007                 if (isupper(*map)) {
1008                         if (rows > 1)
1009                                 if (this_cols != cols)
1010                                         /* Rows aren't all the same */
1011                                         return;
1012                         if (rows)
1013                                 cols = this_cols;
1014
1015                         this_cols = 1;
1016                         rows += 1;
1017                 } else if (rows == 0) {
1018                         /* First row malformed */
1019                         return;
1020                 } else {
1021                         this_cols += 1;
1022                 }
1023         }
1024         if (this_cols != cols)
1025                 /* Last row is wrong length */
1026                 return;
1027         *rowsp = rows;
1028         *colsp = cols;
1029 }
1030
1031 static int render_image(struct pane *p safe, struct pane *focus safe,
1032                         const char *line safe,
1033                         int dodraw,
1034                         int offset, int want_xypos, short x, short y)
1035 {
1036         struct rline_data *rd = p->data;
1037         char *fname = NULL;
1038         const char *orig_line = line;
1039         short width, height;
1040         short rows = -1, cols = -1;
1041         int map_offset = 0;
1042         int ioffset;
1043         struct xy xyscale = pane_scale(focus);
1044         int scale = xyscale.x;
1045         struct xy size= {-1, -1};
1046         char mode[30] = "";
1047         const char *a, *v;
1048         const char *end;
1049
1050         if (dodraw)
1051                 home_call(focus, "Draw:clear", p);
1052
1053         width = p->parent->w/2;
1054         height = p->parent->h/2;
1055
1056         while (*line == soh)
1057                 line += 1;
1058
1059         end = line + strcspn(line, STX);
1060         foreach_attr(a, v, line, end) {
1061                 if (amatch(a, "image") && v) {
1062                         aupdate(&fname, v);
1063                         if (rd->image_size.x <= 0) {
1064                                 struct call_return cr =
1065                                         home_call_ret(all, focus,
1066                                                       "Draw:image-size",
1067                                                       p, 0, NULL, fname);
1068                                 if (cr.x > 0 && cr.y > 0) {
1069                                         size.x = cr.x;
1070                                         size.y = cr.y;
1071                                         rd->image_size = size;
1072                                 }
1073                         } else
1074                                 size = rd->image_size;
1075                 } else if (amatch(a, "width") && v) {
1076                         width = anum(v) * scale / 1000;
1077                 } else if (amatch(a, "height") && v) {
1078                         height = anum(v) * scale / 1000;
1079                 } else if (amatch(a, "noupscale") &&
1080                            fname && size.x > 0) {
1081                         if (size.x < p->parent->w)
1082                                 width = size.x;
1083                         if (size.y < p->parent->h)
1084                                 height = size.y;
1085                 } else if ((offset >= 0 || want_xypos) &&
1086                            amatch(a, "map") && v) {
1087                         /*
1088                          * A map is map:LxxxLxxxLxxxLxxx or similar
1089                          * Where each "Lxxx" recognised by a CAP followed
1090                          * by lower is a row, and each char is a column.
1091                          * So we count the CAPs to get row count, and
1092                          * count the chars to get col count.
1093                          * If want_xypos then map x,y ino that matrix
1094                          * and return pos in original line of cell.
1095                          * If offset is in the map, then set ->cx,->cy to
1096                          * the appropriate location.
1097                          */
1098                         map_offset = v - orig_line;
1099                         parse_map(v, &rows, &cols);
1100                         if (rows > 0 && cols > 0)
1101                                 snprintf(mode, sizeof(mode),
1102                                          ":%dx%d", cols, rows);
1103                 }
1104         }
1105         pane_resize(p, (p->parent->w - width)/2, p->y, width, height);
1106
1107         attr_set_int(&p->attrs, "line-height", p->h);
1108
1109         /* Adjust size to be the scaled size - it must fit in
1110          * p->w, p->h
1111          */
1112         if (size.x * p->h > size.y * p->w) {
1113                 /* Image is wider than space */
1114                 size.y = size.y * p->w / size.x;
1115                 size.x = p->w;
1116                 ioffset = 0;
1117         } else {
1118                 /* Image is taller than space */
1119                 size.x = size.x * p->h / size.y;
1120                 size.y = p->h;
1121                 ioffset = (p->w - size.x) / 2;
1122         }
1123
1124         p->cx = p->cy = -1;
1125
1126         if (offset >= 0 && map_offset > 0 && rows > 0 &&
1127             offset >= map_offset && offset < map_offset + (rows*cols)) {
1128                 /* Place cursor based on where 'offset' is in the map */
1129                 short r = (offset - map_offset) / cols;
1130                 short c = offset - map_offset - r * cols;
1131                 p->cx = size.x / cols * c + ioffset;
1132                 p->cy = size.y / rows * r;
1133         }
1134
1135         if (fname && dodraw)
1136                 home_call(focus, "Draw:image", p, 0, NULL, fname,
1137                           0, NULL, mode);
1138
1139         free(fname);
1140
1141         if (want_xypos && map_offset > 0 && rows > 0) {
1142                 /* report where x,y is as a position in the map */
1143                 short r = y * rows / size.y;
1144                 short c = (x > ioffset ? x - ioffset : 0) * cols / size.x;
1145                 if (c >= cols) c = cols - 1;
1146                 /* +1 below because result must never be zero */
1147                 return map_offset + r * cols + c + 1;
1148         }
1149         return 1;
1150 }
1151
1152 DEF_CMD(renderline_draw)
1153 {
1154         struct rline_data *rd = ci->home->data;
1155         struct xy xy;
1156         int offset = -1;
1157
1158         if (ci->num >= 0)
1159                 offset = rd->prefix_bytes + ci->num;
1160
1161         if (rd->image)
1162                 render_image(ci->home, ci->focus, rd->line, True,
1163                              offset, False, 0, 0);
1164         else
1165                 draw_line(ci->home, ci->focus, offset);
1166
1167         if (ci->num >= 0) {
1168                 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, NULL);
1169                 ci->home->cx = xy.x;
1170                 ci->home->cy = xy.y;
1171         }
1172         return 1;
1173 }
1174
1175 DEF_CMD(renderline_refresh)
1176 {
1177         struct rline_data *rd = ci->home->data;
1178         int offset = -1;
1179
1180         if (rd->curspos >= 0)
1181                 offset = rd->prefix_bytes + rd->curspos;
1182         if (rd->image)
1183                 render_image(ci->home, ci->focus, rd->line, True,
1184                              offset, False, 0, 0);
1185         else {
1186                 measure_line(ci->home, ci->focus, offset);
1187                 draw_line(ci->home, ci->focus, offset);
1188         }
1189         return 1;
1190 }
1191
1192 DEF_CMD(renderline_measure)
1193 {
1194         struct rline_data *rd = ci->home->data;
1195         int ret;
1196
1197         if (rd->image)
1198                 return render_image(ci->home, ci->focus, rd->line,
1199                                     False, ci->num, False, 0, 0);
1200
1201         ret = measure_line(ci->home, ci->focus,
1202                            ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1203         rd->prefix_pixels = 0;
1204         if (rd->prefix_bytes) {
1205                 struct xy xy = find_curs(ci->home, rd->prefix_bytes, NULL);
1206                 rd->prefix_pixels = xy.x;
1207         }
1208         if (ci->num >= 0) {
1209                 /* Find cursor and report x,y pos and attributes */
1210                 const char *cursattr = NULL;
1211                 struct xy xy;
1212                 xy = find_curs(ci->home, rd->prefix_bytes + ci->num, &cursattr);
1213                 comm_call(ci->comm2, "cb", ci->focus, ret, NULL,
1214                           cursattr);
1215                 ci->home->cx = xy.x;
1216                 ci->home->cy = xy.y;
1217         }
1218         return ret | 4;
1219 }
1220
1221 DEF_CMD(renderline_findxy)
1222 {
1223         struct rline_data *rd = ci->home->data;
1224         const char *xyattr = NULL;
1225         int pos;
1226
1227         if (rd->image)
1228                 return render_image(ci->home, ci->focus, rd->line,
1229                                     False, -1, True,
1230                                     ci->x, ci->y);
1231
1232         measure_line(ci->home, ci->focus,
1233                      ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
1234         pos = find_xy(ci->home, ci->focus, ci->x, ci->y, &xyattr);
1235         if (pos >= rd->prefix_bytes)
1236                 pos -= rd->prefix_bytes;
1237         else {
1238                 pos = 0;
1239                 xyattr = NULL;
1240         }
1241         comm_call(ci->comm2, "cb", ci->focus, pos, NULL, xyattr);
1242         return pos+1;
1243 }
1244
1245 DEF_CMD(renderline_get)
1246 {
1247         struct rline_data *rd = ci->home->data;
1248         char buf[20];
1249         const char *val = buf;
1250
1251         if (!ci->str)
1252                 return Enoarg;
1253         if (strcmp(ci->str, "prefix_len") == 0)
1254                 snprintf(buf, sizeof(buf), "%d", rd->prefix_pixels);
1255         else if (strcmp(ci->str, "curs_width") == 0)
1256                 snprintf(buf, sizeof(buf), "%d", rd->curs_width);
1257         else if (strcmp(ci->str, "width") == 0)
1258                 snprintf(buf, sizeof(buf), "%d", rd->width);
1259         else
1260                 return Einval;
1261
1262         comm_call(ci->comm2, "attr", ci->focus, 0, NULL, val);
1263         return 1;
1264 }
1265
1266 static char *cvt(char *str safe)
1267 {
1268         /* Convert:
1269          *    << to < ack  (ack is a no-op)
1270          *    < stuff > to soh stuff stx
1271          *    </> to ack ack etx
1272          */
1273         char *c, *c1;
1274         for (c = str; *c; c += 1) {
1275                 if (c[0] == soh || c[0] == ack)
1276                         break;
1277                 if (c[0] == '<' && c[1] == '<') {
1278                         c[1] = ack;
1279                         c++;
1280                         continue;
1281                 }
1282                 if (c[0] != '<')
1283                         continue;
1284                 if (c[1] == '/') {
1285                         while (*c && *c != '>')
1286                                 *c++ = ack;
1287                         if (!*c)
1288                                 break;
1289                         *c = etx;
1290                         continue;
1291                 }
1292                 c[0] = soh;
1293                 c += 1;
1294                 c1 = c;
1295                 while (*c && *c != '>') {
1296                         if (*c == '\\' &&
1297                             (c[1] == '\\' || c[1] == '>'))
1298                                 c++;
1299                         *c1++ = *c++;
1300                 }
1301                 while (c1 < c)
1302                         *c1++ = ack;
1303                 if (!*c)
1304                         break;
1305                 *c = stx;
1306         }
1307         return str;
1308 }
1309
1310 DEF_CMD(renderline_set)
1311 {
1312         struct rline_data *rd = ci->home->data;
1313         const char *old = rd->line;
1314         char *prefix = pane_attr_get(ci->focus, "prefix");
1315         bool word_wrap = pane_attr_get_int(ci->focus, "word-wrap", 0) != 0;
1316         bool bg_changed = False;
1317
1318         if (!ci->str)
1319                 return -Enoarg;
1320         if (prefix)
1321                 prefix = strconcat(ci->home, ACK SOH "bold" STX,
1322                                    prefix, // No mark in prefix!
1323                                    ETX);
1324         if (prefix)
1325                 rd->line = strconcat(NULL, prefix, ci->str);
1326         else
1327                 rd->line = strdup(ci->str);
1328         rd->prefix_bytes = strlen(prefix?:"");
1329         cvt(rd->line + rd->prefix_bytes);
1330
1331         if (ci->str2 && !rd->background) {
1332                 rd->background = strdup(ci->str2);
1333                 bg_changed = True;
1334         } else if (!ci->str2 && rd->background) {
1335                 free(rd->background);
1336                 rd->background = NULL;
1337                 bg_changed = True;
1338         } else if (ci->str2 && rd->background &&
1339                    strcmp(ci->str2, rd->background) != 0) {
1340                 free(rd->background);
1341                 rd->background = strdup(ci->str2);
1342                 bg_changed = True;
1343         }
1344
1345         rd->curspos = ci->num;
1346         if (strcmp(rd->line, old) != 0 || bg_changed ||
1347             (old && word_wrap != rd->word_wrap)) {
1348                 pane_damaged(ci->home, DAMAGED_REFRESH);
1349                 pane_damaged(ci->home->parent, DAMAGED_REFRESH);
1350                 rd->word_wrap = word_wrap;
1351                 parse_line(rd);
1352         }
1353         free((void*)old);
1354         ci->home->damaged &= ~DAMAGED_VIEW;
1355         return 1;
1356 }
1357
1358 DEF_CMD_CLOSED(renderline_close)
1359 {
1360         struct rline_data *rd = ci->home->data;
1361         struct render_item *ri = rd->content;
1362
1363         free((void*)rd->line);
1364         while (ri) {
1365                 struct render_item *r = ri;
1366                 ri = r->next;
1367                 free(r->split_list);
1368                 unalloc_str_safe(r->attr, pane);
1369                 unalloc(r, pane);
1370         }
1371         aupdate(&rd->wrap_head, NULL);
1372         aupdate(&rd->wrap_tail, NULL);
1373         aupdate(&rd->wrap_attr, NULL);
1374         aupdate(&rd->background, NULL);
1375         return 1;
1376 }
1377
1378 static struct map *rl_map;
1379 DEF_LOOKUP_CMD(renderline_handle, rl_map);
1380
1381 DEF_CMD(renderline_attach)
1382 {
1383         struct pane *p;
1384         struct rline_data *rd;
1385
1386         if (!rl_map) {
1387                 rl_map = key_alloc();
1388                 key_add(rl_map, "render-line:draw", &renderline_draw);
1389                 key_add(rl_map, "Refresh", &renderline_refresh);
1390                 key_add(rl_map, "render-line:measure", &renderline_measure);
1391                 key_add(rl_map, "render-line:findxy", &renderline_findxy);
1392                 key_add(rl_map, "get-attr", &renderline_get);
1393                 key_add(rl_map, "render-line:set", &renderline_set);
1394                 key_add(rl_map, "Close", &renderline_close);
1395         }
1396
1397         p = pane_register(ci->focus, ci->num, &renderline_handle.c);
1398         if (!p)
1399                 return Efail;
1400         rd = p->data;
1401         rd->line = strdup(ETX); // Imposible string
1402
1403         return comm_call(ci->comm2, "cb", p);
1404 }
1405
1406 void edlib_init(struct pane *ed safe)
1407 {
1408         call_comm("global-set-command", ed, &renderline_attach, 0, NULL,
1409                   "attach-renderline");
1410 }