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