]> git.neil.brown.name Git - edlib.git/blobdiff - lib-renderline.c
TODO: clean out done items.
[edlib.git] / lib-renderline.c
index b14fc8c0a9470a3ecc44df79a5058264b8da6d38..e5d8169aaa951529a5b7c839ac2d17df2eeff07e 100644 (file)
  *
  * The render-lines pane will place multiple renderline panes and use
  * them to do the drawing - resizing and moving them as necessary to fit
- * the size of the text.
- *
- * A renderline normally is only active when the render-lines (or other)
- * parent pane is being refreshed - that pane hands over some of the
- * task to the renderline pane.
- * Specifically a "draw-markup" command provides a marked-up line of
- * text, a scale, and other details.  The resulting image in measured
- * and possibly drawn
+ * the size of the text.  Other panes can use renderline for similar
+ * purposes.  messageline uses just one renderline.
  *
+ * A renderline pane can sit in the normal stack and receive Refresh
+ * messages to trigger drawing, or can sit "beside" the stack with a negative
+ * 'z' value. In that can the owner need to explicitly request refresh.
  *
+ * "render-line:set" will set the content of the line
+ * "render-line:measure" will determine layout and size given the available
+ *    width and other parameters
+ * "render-line:draw" will send drawing commands.
+ * "Refresh" does both the measure and the draw.
  */
 
 #define _GNU_SOURCE /*  for asprintf */
 #include <stdio.h>
 #include <ctype.h>
 #include <wctype.h>
+#include <stdint.h>
+
+#define PANE_DATA_TYPE struct rline_data
 #include "core.h"
 #include "misc.h"
 
-struct render_list {
-       struct render_list *next;
-       const char      *text_orig;
-       const char      *text safe, *attr safe; // both are allocated
-       bool            wrap; /* set for LWS when word-wrap enabled, and when
-                              * "wrap" attr seen.
-                              */
-       short           x, width;
-       short           cursorpos;
-       const char      *xypos; /* location in text_orig where given x,y was found */
+/* There is one render_item entry for
+ * - each string of text with all the same attributes
+ * - each individual TAB
+ * - each unknown control character
+ * - the \n \f or \0 which ends the line
+ * When word-wrap is enabled, strings of spaces get
+ * different attributes, so a different render_item entry.
+ *
+ * attributes understood at this level are:
+ *  left:nn            - left margin - in "points" (10 points per char normally)
+ *  right:nn           - right margin
+ *  tab:nn             - move to nn from left margin or -nn from right margin
+ *  rtab               - from here to next tab or eol right-aligned
+ *  center or centre   - equal space inserted here and before next
+ *  or ctab              tab-stop or margin
+ *  space-above:nn     - extra space before (wrapped) line
+ *  space-below:nn     - extra space after (wrapped) line
+ *  height:nn          - override height.  This effectively adds space above
+ *                       every individual line if the whole line is wrapped
+ *  wrap               - text with this attr can be hidden when used as a wrap
+ *                       point.  Not hidden if cursor in the region.
+ *  wrap-margin                - remember this x offset as left margin of wrapped lines
+ *  wrap-head=xx       - text is inserted at start of line when wrapped
+ *  wrap-tail=xx       - text to include at end of line when wrapped.  This
+ *                       determines how far before right margin the wrap is
+ *                       triggered.
+ *  wrap-XXXX          - attrs to apply to wrap head/tail. Anything not
+ *                       recognised has "wrap-" stripped and is used for the
+ *                       head and tail. Default is fg:blue,underline
+ *  hide               - Text is hidden if cursor is not within range.
+ *
+ * "nn" is measured in "points" which is 1/10 the nominal width of chars
+ * in the default font size, which is called "10".  A positive value is
+ * measured from the left margin or, which setting margins, from the
+ * relevant page edge.  A negative value is measures from the right margin.
+ *
+ */
+
+/* When an entry is split for line-wrap:
+ *   'split_cnt' is count of splits (total lines - 1)
+ *   ' split_list' is offsets from start where split happens
+ *   'x' position of wrapped portions is wrap_marign or head_length
+ *   'y' position of wrapped portions increases line_height for each
+ */
+struct render_item {
+       struct render_item *next;
+       const char      *attr safe;
+       unsigned short  *split_list;
+       unsigned short  start, len; // in rd->line
+       unsigned short  height, width;
+       signed short    x,y; /* signed so x can go negative when shifted */
+       signed short    tab;
+       unsigned short  wrap_x; /* If this item wraps, wrap_x is the margin */
+       uint8_t         split_cnt; /* Wrap happens this many times at byte
+                                   * positions in split_list */
+       uint8_t         wrap;   /* This and consecutive render_items
+                                * with the same wrap number form an
+                                * optional wrap point.  It is only visible
+                                * when not wrapped, or when cursor is in
+                                * it.
+                                */
+       uint8_t         hide;   /* This and consecutive render_items
+                                * with the same hide number form a
+                                * hidden extent which is visible when
+                                * the cursor is in it.
+                                */
+       bool            wrap_margin:1;
+       bool            hidden:1;
+       bool            eol:1;
+       unsigned int    tab_cols:4; /* For \t char */
+       enum tab_align {
+               TAB_LEFT = 0,   // No extra space (after tab stop)
+               TAB_RIGHT,      // Add extra space here so there no space at
+                               // next tab stop or margin
+               TAB_CENTRE,     // Add extra space here, half of what TAB_RIGHT
+                               // would insert
+       }               tab_align:2;
 };
+/* A "tab" value of 0 means left margin, and negative is measured from right
+ * margin, so we need some other value to say "no value here"
+ */
+static const short TAB_UNSET = (1<<(14-2));
 
 struct rline_data {
-       short           prefix_len;
-       const char      *xyattr;
-       const char      *cursattr;
+       unsigned short  prefix_bytes, prefix_pixels;
        short           curs_width;
-       int             scale;
-       const char      *line;
+       short           left_margin, right_margin;
+       short           space_above, space_below;
+       unsigned short  line_height, min_height;
+       unsigned short  scale;
+       unsigned short  width;
+       unsigned short  ascent;
+       char            *wrap_head, *wrap_tail, *wrap_attr;
+       int             head_length, tail_length;
+       char            *line safe;
+       char            *background;
+       bool            word_wrap;
+       bool            image;
+       int             curspos;
+
+       struct xy       image_size;
+       /* These used to check is measuring is needed, or to record
+        * results of last measurement */
+       unsigned short measure_width, measure_height;
+       short measure_offset, measure_shift_left;
+       struct render_item *content;
 };
+#include "core-pane.h"
 
-enum {
-       OK = 0,
-       WRAP,
-       XYPOS,
-};
+static void aappend(struct buf *b safe, char const *a safe)
+{
+       const char *end = a;
+       while (*end >= ' ' && *end != ',')
+               end++;
+       buf_concat_len(b, a, end-a);
+       buf_append(b, ',');
+}
 
-static int draw_some(struct pane *p safe, struct pane *focus safe,
-                    struct render_list **rlp safe,
-                    int *x safe,
-                    const char *start safe, const char **endp safe,
-                    const char *attr safe, bool wrap,
-                    int margin, int cursorpos, int xpos,
-                    int scale)
+static void add_render(struct rline_data *rd safe,
+                      struct render_item **safe*rip safe,
+                      const char *start safe, const char *end safe,
+                      char *attr safe,
+                      short *tab safe, enum tab_align *align safe,
+                      bool *wrap_margin safe,
+                      short wrap, short hide)
 {
-       /* Measure the text from 'start' to '*endp', expecting to
-        * draw to p[x,?].
-        * Update 'x' and 'endp' past what was drawn.
-        * Everything will be drawn with the same attributes: attr.
-        * If the text would get closer to right end than 'margin',
-        * we stop drawing before then.  If this happens, WRAP is returned.
-        * If drawing would pass xpos: stop there, record pointer
-        * into 'endp', and return XYPOS.
-        * If cursorpos is between 0 and len inclusive, a cursor is drawn there.
-        * Don't actually draw anything, just append a new entry to the
-        * render_list rlp.
+       struct render_item *ri;
+       struct render_item **riend = *rip;
+
+       alloc(ri, pane);
+       ri->attr = strdup(attr);
+       ri->start = start - rd->line;
+       ri->len = end - start;
+       ri->tab_align = *align;
+       ri->tab = *tab;
+       ri->wrap = wrap;
+       ri->hide = hide;
+       ri->wrap_margin = *wrap_margin;
+       ri->eol = !!strchr("\n\f\0", *start);
+       *tab = TAB_UNSET;
+       *align = TAB_LEFT;
+       *wrap_margin = False;
+       *riend = ri;
+       riend = &ri->next;
+       *rip = riend;
+}
+
+static void parse_line(struct rline_data *rd safe)
+{
+       /* Parse out markup in line into a renderlist with
+        * global content directly in rd.
         */
-       int len = *endp - start;
-       char *str;
-       struct call_return cr;
-       int max;
-       int ret = WRAP;
-       int rmargin = p->w - margin;
-       struct render_list *rl;
-
-       if (cursorpos > len)
-               cursorpos = -1;
-       if (len == 0 && cursorpos < 0)
-               /* Nothing to do */
-               return OK;
-
-       if (!wrap && strstr(attr, ",wrap,"))
-               wrap = True;
-       if ((*rlp == NULL ||
-            ((*rlp)->next == NULL && (*rlp)->text_orig == NULL)) &&
-           wrap && cursorpos < 0)
-               /* The text in a <wrap> marker that causes a wrap is
-                * suppressed unless the cursor is in it.
-                * This will only ever be at start of line.  <wrap> text
-                * elsewhere is not active.
-                */
-               return OK;
-       str = strndup(start, len);
-       if (*str == '\t')
-               /* TABs are only sent one at a time, and are rendered as space */
-               *str = ' ';
-       if (xpos >= 0 && xpos >= *x && xpos < rmargin) {
-               /* reduce marking to given position, and record that
-                * as xypos when we hit it.
-                */
-               rmargin = xpos;
-               ret = XYPOS;
+       struct buf attr, wrapattr;
+       struct render_item *ri = NULL, **riend = &ri;
+       const char *line = rd->line;
+       bool wrap_margin = False;
+       short tab = TAB_UNSET;
+       enum tab_align align = TAB_LEFT;
+       int hide = 0, hide_num = 0, hide_depth = 0;
+       int wrap = 0, wrap_num = 0, wrap_depth = 0;
+       unsigned char c;
+
+       rd->left_margin = rd->right_margin = 0;
+       rd->space_above = rd->space_below = 0;
+       rd->min_height = 0;
+       aupdate(&rd->wrap_head, NULL);
+       aupdate(&rd->wrap_tail, NULL);
+       aupdate(&rd->wrap_attr, NULL);
+
+       if (!line) {
+               rd->image = False;
+               return;
        }
+       rd->image = strstarts(line, SOH "image:");
+       if (rd->image) {
+               rd->image_size.x = 0;
+               return;
+       }
+       buf_init(&attr);
+       buf_init(&wrapattr);
+
+       ri = rd->content;
+       rd->content = NULL;
+       rd->measure_width = 0; // force re-measure
+       while (ri) {
+               struct render_item *r = ri;
+               ri = r->next;
+               free(r->split_list);
+               unalloc_str_safe(r->attr, pane);
+               unalloc(r, pane);
+       }
+
+       do {
+               const char *st = line;
+               c = *line++;
+
+               while (c >= ' ' &&
+                      (!rd->word_wrap || c != ' '))
+                       c = *line++;
 
-       rl = calloc(1, sizeof(*rl));
-       cr = home_call_ret(all, focus, "Draw:text-size", p,
-                          rmargin - *x, NULL, str,
-                          scale, NULL, attr);
-       max = cr.i;
-       if (max == 0 && ret == XYPOS) {
-               /* Must have already reported XY position, don't do it again */
-               rl->xypos = start;
-               ret = WRAP;
-               rmargin = p->w - margin;
-               cr = home_call_ret(all, focus, "Draw:text-size", p,
-                                  rmargin - *x, NULL, str,
-                                  scale, NULL, attr);
-               max = cr.i;
+               if (line - 1 > st || tab != TAB_UNSET) {
+                       /* All text from st to line-1 has "attr' */
+                       add_render(rd, &riend, st, line-1, buf_final(&attr),
+                                  &tab, &align, &wrap_margin, wrap, hide);
+                       st = line - 1;
+               }
+               switch (c) {
+               case soh: {
+                       int old_len;
+                       const char *a, *v;
+                       st = line;
+                       /* Move 'line' over the attrs */
+                       while (*line && line[-1] != stx)
+                               line += 1;
+
+                       /* A set of attrs begins and ends with ',' so that
+                        * ",," separates sets of attrs
+                        * An empty set will be precisely 1 ','.  We strip
+                        * "attr," as long as we can, then strip one more ',',
+                        * which should leave either a trailing comma, or an
+                        * empty string.
+                        */
+                       old_len = attr.len;
+                       buf_append(&attr, ',');
+                       foreach_attr(a, v, st, line) {
+                               if (amatch(a, "centre") || amatch(a, "center") ||
+                                   amatch(a, "ctab")) {
+                                       if (v)
+                                               tab = anum(v);
+                                       align = TAB_CENTRE;
+                               } else if (amatch(a, "tab") && v) {
+                                       tab = anum(v);
+                                       align = TAB_LEFT;
+                               } else if (amatch(a, "rtab")) {
+                                       align = TAB_RIGHT;
+                               } else if (amatch(a, "left") && v) {
+                                       rd->left_margin = anum(v);
+                               } else if (amatch(a, "right") && v) {
+                                       rd->right_margin = anum(v);
+                               } else if (amatch(a, "space-above") && v) {
+                                       rd->space_above = anum(v);
+                               } else if (amatch(a, "space-below") && v) {
+                                       rd->space_below = anum(v);
+                               } else if (amatch(a, "height") && v) {
+                                       rd->min_height = anum(v);
+                               } else if (amatch(a, "wrap")) {
+                                       wrap = ++wrap_num;
+                                       wrap_depth = old_len;
+                               } else if (amatch(a, "wrap-margin")) {
+                                       wrap_margin = True;
+                               } else if (amatch(a, "wrap-head")) {
+                                       aupdate(&rd->wrap_head, v);
+                               } else if (amatch(a, "wrap-tail")) {
+                                       aupdate(&rd->wrap_tail, v);
+                               } else if (aprefix(a, "wrap-")) {
+                                       aappend(&wrapattr, a+5);
+                               } else if (amatch(a, "word-wrap")) {
+                                       if (!v || *v == '1')
+                                               rd->word_wrap = 1;
+                                       else if (*v == '0')
+                                               rd->word_wrap = 0;
+                               } else if (amatch(a, "hide")) {
+                                       hide = ++hide_num;
+                                       hide_depth = old_len;
+                               } else
+                                       aappend(&attr, a);
+                       }
+                       break;
+                       }
+               case etx:
+                       /* strip last set of attrs */
+                       while (attr.len >= 2 &&
+                       attr.b[attr.len-1] == ',' &&
+                       attr.b[attr.len-2] != ',') {
+                               /* strip this attr */
+                               attr.len -= 2;
+                               while (attr.len && attr.b[attr.len-1] != ',')
+                                       attr.len -= 1;
+                       }
+                       /* strip one more ',' */
+                       if (attr.len > 0)
+                               attr.len -= 1;
+                       if (wrap && attr.len <= wrap_depth)
+                               wrap = 0;
+                       if (hide && attr.len <= hide_depth)
+                               hide = 0;
+                       break;
+               case ack:
+                       /* Just ignore this */
+                       break;
+               case ' ':
+                       /* This and following spaces are wrappable */
+                       st = line;
+                       while (*line == ' ')
+                               line += 1;
+                       wrap = ++wrap_num;
+                       add_render(rd, &riend, st - 1, line, buf_final(&attr),
+                                  &tab, &align, &wrap_margin, wrap, hide);
+                       wrap = 0;
+                       break;
+               case '\0':
+               case '\n':
+               case '\f':
+               case '\t':
+               default:
+                       /* Each tab gets an entry of its own, as does any
+                        * stray control character.
+                        * \f \n and even \0 do.  These are needed for
+                        * easy cursor placement.
+                        */
+                       add_render(rd, &riend, st, line, buf_final(&attr),
+                                  &tab, &align, &wrap_margin, wrap, hide);
+                       break;
+               }
+       } while (c);
+
+       rd->content = ri;
+       free(attr.b);
+       if (buf_final(&wrapattr)[0])
+               rd->wrap_attr = buf_final(&wrapattr);
+       else {
+               free(wrapattr.b);
+               rd->wrap_attr = strdup(",fg:blue,underline,");
        }
-       if (max < len) {
-               /* It didn't all fit, so trim the string and
-                * try again.  It must fit this time.
-                * I don't know what we expect to be different the second time..
+}
+
+static inline struct call_return do_measure(struct pane *p safe,
+                                           struct render_item *ri safe,
+                                           int splitpos, int len,
+                                           int maxwidth)
+{
+       struct rline_data *rd = p->data;
+       struct call_return cr;
+       char tb[] = "        ";
+       char *str = rd->line + ri->start + splitpos;
+       char tmp;
+
+       if (ri->len && rd->line[ri->start] == '\t') {
+               str = tb;
+               if (len < 0)
+                       len = ri->tab_cols - splitpos;
+       } else
+               if (len < 0)
+                       len = ri->len - splitpos;
+       tmp = str[len];
+       str[len] = 0;
+
+       cr = call_ret(all, "Draw:text-size", p,
+                     maxwidth, NULL, str,
+                     rd->scale, NULL, ri->attr);
+
+       str[len] = tmp;
+       if (cr.ret == 1 && maxwidth >= 0 &&
+           cr.i >= len)
+               /* All fits in maxwidth */
+               cr.ret = 2;
+       /* Report position in rd->line */
+       if (str == tb) {
+               cr.s = rd->line + ri->start;
+               if (splitpos + cr.i >= ri->tab_cols)
+                       cr.s += 1;
+       } else
+               cr.s = str + cr.i;
+       return cr;
+}
+
+static inline struct call_return measure_str(struct pane *p safe,
+                                            char *str safe,
+                                            const char *attr)
+{
+       struct rline_data *rd = p->data;
+
+       return call_ret(all, "Draw:text-size", p,
+                       -1, NULL, str,
+                       rd->scale, NULL, attr);
+}
+
+static inline void do_draw(struct pane *p safe,
+                          struct pane *focus safe,
+                          struct render_item *ri safe, int split,
+                          int offset,
+                          int x, int y)
+{
+       struct rline_data *rd = p->data;
+       char tmp;
+       char *str;
+       int len;
+       char tb[] = "         ";
+
+       str = rd->line + ri->start;
+       len = ri->len;
+
+       y += rd->ascent;
+       if (ri->len && strchr("\f\n\0", str[0])) {
+               /* end marker - len extends past end of string,
+                * but mustn't write there.  Only need to draw if
+                * cursor is here.
                 */
-               str[max] = 0;
-               cr = home_call_ret(all, focus, "Draw:text-size", p,
-                                  rmargin - *x, NULL, str,
-                                  scale, NULL, attr);
+               if (offset == 0)
+                       home_call(focus, "Draw:text", p, offset, NULL, "",
+                                 rd->scale, NULL, ri->attr, x, y);
+               return;
+       }
+       if (ri->len && str[0] == '\t') {
+               len = ri->tab_cols;
+               if (split)
+                       offset = -1;
+       }
+       if (ri->split_list) {
+               if (split < ri->split_cnt)
+                       len = ri->split_list[split];
+               if (split)
+                       len -= ri->split_list[split-1];
        }
 
-       rl->text_orig = start;
-       rl->text = str;
-       rl->attr = strdup(attr);
-       rl->wrap = wrap;
-       rl->width = cr.x;
-       rl->x = *x;
-       *x += rl->width;
-       if (ret == XYPOS)
-               rl->xypos = start + strlen(str);
-
-       if (cursorpos >= 0 && cursorpos <= len && cursorpos <= max)
-               rl->cursorpos = cursorpos;
+       if (ri->len && str[0] == '\t')
+               /* Tab need a list of spaces */
+               str = tb;
        else
-               rl->cursorpos = -1;
-       while (*rlp)
-               rlp = &(*rlp)->next;
-       *rlp = rl;
-
-       if (max >= len)
-               return OK;
-       /* Didn't draw everything. */
-       *endp = start + max;
-       return ret;
+               if (split > 0 && split <= ri->split_cnt && ri->split_list) {
+                       str += ri->split_list[split-1];
+                       offset -= ri->split_list[split-1];
+               }
+
+       tmp = str[len];
+       str[len] = 0;
+
+       if (offset >= len)
+               offset = -1;
+       home_call(focus, "Draw:text", p, offset, NULL, str,
+                          rd->scale, NULL, ri->attr, x, y);
+       str[len] = tmp;
 }
 
-static char *get_last_attr(const char *attrs safe, const char *attr safe)
+static inline void draw_wrap(struct pane *p safe,
+                            struct pane *focus safe,
+                            char *str safe,
+                            int x, int y)
 {
-       const char *com = attrs + strlen(attrs);
-       int len = strlen(attr);
+       struct rline_data *rd = p->data;
 
-       for (; com >= attrs ; com--) {
-               int i = 1;
-               if (*com != ',' && com > attrs)
-                       continue;
-               if (com == attrs)
-                       i = 0;
-               if (strncmp(com+i, attr, len) != 0)
-                       continue;
-               if (com[i+len] != ':')
-                       continue;
-               com += i+len+1;
-               i = strcspn(com, ",");
-               return strndup(com, i);
-       }
-       return NULL;
+       home_call(focus, "Draw:text", p,
+                 -1, NULL, str,
+                 rd->scale, NULL, rd->wrap_attr,
+                 x, y + rd->ascent);
 }
 
-static int flush_line(struct pane *p safe, struct pane *focus safe, int dodraw,
-                     struct render_list **rlp safe,
-                     int y, int scale, int wrap_pos, int *wrap_margin safe,
-                     int *wrap_prefix_sizep,
-                     const char **xypos, const char **xyattr, const char **cursattr)
+static bool add_split(struct render_item *ri safe, int split)
 {
-       /* Flush a render_list returning x-space used.
-        * If wrap_pos is > 0, stop rendering before last entry with the
-        * "wrap" attribute; draw the wrap-tail at wrap_pos, and insert
-        * wrap_head into the render_list before the next line.
-        * If any render_list entry reports xypos, store that in xypos with
-        * matching attributes in xyattr.
-        */
-       struct render_list *last_wrap = NULL, *end_wrap = NULL, *last_rl = NULL;
-       int in_wrap = 0;
-       int wrap_len = 0; /* length of text in final <wrap> section */
-       int wrap_prefix_size = 0; /* wrap margin plus prefix */
-       struct render_list *rl, *tofree;
-       int x = 0;
-       char *head;
-
-       if (!*rlp)
+       int i = ri->split_cnt;
+       if (i > 250)
+               return False;
+       ri->split_cnt += 1;
+       ri->split_list = realloc(ri->split_list,
+                                sizeof(ri->split_list[0]) * ri->split_cnt);
+       ri->split_list[i] = split;
+       return True;
+}
+
+static int calc_pos(int num, int margin, int width)
+{
+       if (num >= 0)
+               return num * width / 10;
+       if (-num * width / 10 > margin)
                return 0;
-       for (rl = *rlp; wrap_pos && rl; rl = rl->next) {
-               if (rl->wrap && rl != *rlp) {
-                       if (!in_wrap) {
-                               last_wrap = rl;
-                               in_wrap = 1;
-                               wrap_len = 0;
-                       }
-                       wrap_len += strlen(rl->text);
-                       end_wrap = rl->next;
-               } else {
-                       if (in_wrap)
-                               end_wrap = rl;
-                       in_wrap = 0;
+       return margin + num * width / 10;
+}
+
+static int measure_line(struct pane *p safe, struct pane *focus safe, int offset)
+{
+       /* First measure each render_item entry setting
+        * height, ascent, width.
+        * Then use that with tab information to set 'x' position for
+        * each unit.
+        * Finally identify line-break locations if needed and set 'y'
+        * positions
+        *
+        * Return 1 if there is an EOL ('\n')
+        * 2 if there is an end-of-page ('\f')
+        * 3 if both.
+        * 0 if neither
+        */
+       struct rline_data *rd = p->data;
+       struct render_item *ri, *wraprl;
+       int shift_left = pane_attr_get_int(focus, "render-wrap", -1);
+       bool wrap = shift_left < 0;
+       int wrap_margin;
+       int right_margin;
+       int left_margin;
+       struct xy xyscale = pane_scale(focus);
+       int curs_height;
+       int xdiff, ydiff;
+       struct call_return cr;
+       int x, y;
+       int ret = 0;
+       bool seen_rtab = False;
+       unsigned int offset_hide = 0;
+
+       if (!rd->content)
+               return ret;
+       if (xyscale.x == rd->scale && p->w == rd->measure_width &&
+           shift_left == rd->measure_shift_left &&
+           offset == rd->measure_offset) {
+               /* No change */
+               for (ri = rd->content ; ri ; ri = ri->next) {
+                       if (ri->hidden)
+                               continue;
+                       if (ri->eol && rd->line[ri->start] == '\n')
+                               ret |= 1;
+                       if (ri->eol && rd->line[ri->start] == '\f')
+                               ret |= 2;
                }
-               last_rl = rl;
+               pane_resize(p, p->x, p->y, p->w, rd->measure_height);
+               return ret;
        }
-       if (last_wrap)
-               /* A wrap was found, so finish there */
-               last_rl = last_wrap;
-
-       for (rl = *rlp; rl && rl != last_wrap; rl = rl->next) {
-               int cp = rl->cursorpos;
+       rd->scale = xyscale.x;
+       rd->measure_width = p->w;
+       rd->measure_offset = offset;
+       rd->measure_shift_left = shift_left;
+
+       cr = measure_str(p, "M", "");
+       rd->curs_width = cr.x;
+       curs_height = cr.y;
+       rd->line_height = cr.y;
+       rd->ascent = cr.i2;
+       if (rd->min_height > 10)
+               rd->line_height = rd->line_height * rd->min_height / 10;
+
+       if (rd->wrap_head) {
+               cr = measure_str(p, rd->wrap_head, rd->wrap_attr);
+               rd->head_length = cr.x;
+       }
+       cr = measure_str(p, rd->wrap_tail ?: "\\", rd->wrap_attr);
+       rd->tail_length = cr.x;
+
+       left_margin = calc_pos(rd->left_margin, p->w, rd->curs_width);
+       /* 0 means right edge for right_margin, and left edge for all others */
+       right_margin = p->w - calc_pos(-rd->right_margin, p->w, rd->curs_width);
+
+       if (offset >= 0)
+               for (ri = rd->content ; ri ; ri = ri->next)
+                       if (offset < ri->start + ri->len) {
+                               offset_hide = ri->hide;
+                               break;
+                       }
 
-               if (!*wrap_margin && strstr(rl->attr, ",wrap-margin,"))
-                       *wrap_margin = rl->x;
+       rd->width = 0;
+       for (ri = rd->content; ri; ri = ri->next) {
+               ri->hidden = (ri->hide && ri->hide != offset_hide);
+               if (ri->hidden) {
+                       ri->width = 0;
+                       continue;
+               }
+               if (ri->len == 0 ||
+                   (unsigned char)rd->line[ri->start] >= ' ') {
+                       cr = do_measure(p, ri, 0, -1, -1);
+               } else {
+                       char tmp[4];
+                       if (ri->eol) {
+                               /* Ensure attributes of newline add to line
+                                * height. The width will be ignored. */
+                               strcpy(tmp, "M");
+                               if (rd->line[ri->start] == '\n')
+                                       ret |= 1;
+                               if (rd->line[ri->start] == '\f')
+                                       ret |= 2;
+                       } else if (rd->line[ri->start] == '\t') {
+                               strcpy(tmp, " ");
+                       } else {
+                               strcpy(tmp, "^x");
+                               tmp[1] = '@' + (rd->line[ri->start] & 31);
+                       }
+                       cr = measure_str(p, tmp, ri->attr);
+               }
 
-               if (wrap_pos &&
-                   cp >= (int)strlen(rl->text) + wrap_len)
-                       /* Don't want to place cursor at end of line before
-                        * the wrap, only on the next line after the
-                        * wrap.
-                        */
-                       cp = -1;
-
-               x = rl->x;
-               if (dodraw)
-                       home_call(focus, "Draw:text", p, cp, NULL, rl->text,
-                                 scale, NULL, rl->attr,
-                                 x, y);
-               x += rl->width;
-               if (xypos && rl->xypos) {
-                       *xypos = rl->xypos;
-                       if (xyattr)
-                               *xyattr = strsave(p, rl->attr);
+               if (cr.y > rd->line_height)
+                       rd->line_height = cr.y;
+               ri->height = cr.y;
+               if (cr.i2 > rd->ascent)
+                       rd->ascent = cr.i2;
+               ri->width = ri->eol ? 0 : cr.x;
+               rd->width += ri->width;
+
+               if (ri->start <= offset && offset <= ri->start + ri->len) {
+                       cr = measure_str(p, "M", ri->attr);
+                       rd->curs_width = cr.x;
                }
-               if (cp >= 0 && cursattr)
-                       *cursattr = strsave(p, rl->attr);
-       }
-       /* Draw the wrap text if it contains the cursor */
-       for (; rl && rl != end_wrap; rl = rl->next) {
-               int cp = rl->cursorpos;
-               if (cp >= (int)strlen(rl->text))
-                       cp = -1;
-
-               if (cp >= 0 && dodraw)
-                       home_call(focus, "Draw:text", p, cp, NULL, rl->text,
-                                 scale, NULL, rl->attr,
-                                 rl->x, y);
-               x = rl->x + rl->width;
+
+               ri->split_cnt = 0;
+               free(ri->split_list);
+               ri->split_list = NULL;
        }
-       /* Draw the wrap-tail */
-       if (wrap_pos && last_rl && dodraw) {
-               char *e = get_last_attr(last_rl->attr, "wrap-tail");
-               home_call(focus, "Draw:text", p, -1, NULL, e ?: "\\",
-                         scale, NULL, "underline,fg:blue",
-                         wrap_pos, y);
-               free(e);
+       /* Set 'x' position honouring tab stops, and set length
+        * of "\t" characters.  Also handle \n and \f.
+        */
+       x = left_margin - (shift_left > 0 ? shift_left : 0);
+       y = rd->space_above * curs_height / 10;
+       for (ri = rd->content; ri; ri = ri->next) {
+               int w, margin;
+               struct render_item *ri2;
+               ri->y = y;
+               if (ri->hidden) {
+                       ri->x = x;
+                       continue;
+               }
+               if (ri->tab != TAB_UNSET)
+                       x =  left_margin + calc_pos(ri->tab,
+                                                   right_margin - left_margin,
+                                                   rd->curs_width);
+               if (ri->eol) {
+                       /* EOL */
+                       ri->x = x;
+                       x = 0; /* Don't include shift. probably not margin */
+                       if (rd->line[ri->start])
+                               y += rd->line_height;
+                       continue;
+               }
+               if (ri->tab_align == TAB_LEFT) {
+                       ri->x = x;
+                       if (ri->len && rd->line[ri->start] == '\t') {
+                               int col = x / ri->width;
+                               int cols= 8 - (col % 8);
+                               ri->tab_cols = cols;
+                               ri->width *= cols;
+                       }
+                       x += ri->width;
+                       continue;
+               }
+               if (ri->tab_align == TAB_RIGHT)
+                       seen_rtab = True;
+               w = ri->width;
+               for (ri2 = ri->next;
+                    ri2 && ri2->tab_align == TAB_LEFT && ri2->tab == TAB_UNSET;
+                    ri2 = ri2->next)
+                       w += ri2->width;
+               while (ri2 && ri2->tab == TAB_UNSET)
+                       ri2 = ri2->next;
+               margin = right_margin - left_margin;
+               if (ri2)
+                       margin =  left_margin + calc_pos(ri2->tab,
+                                                        right_margin - left_margin,
+                                                        rd->curs_width);
+               if (ri->tab_align == TAB_RIGHT)
+                       x = x + margin - x - w;
+               else
+                       x = x + (margin - x - w) / 2;
+               ri->x = x;
+               while (ri->next && ri->next->next && ri->next->tab_align == TAB_LEFT) {
+                       x += ri->width;
+                       ri = ri->next;
+                       ri->x = x;
+                       ri->y = y;
+               }
        }
 
-       tofree = *rlp;
-       *rlp = end_wrap;
-
-       /* Queue the wrap-head for the next flush */
-       if (wrap_pos && last_rl) {
-               head = get_last_attr(last_rl->attr, "wrap-head");
-               if (head) {
-                       struct call_return cr =
-                               home_call_ret(all, focus, "Draw:text-size", p,
-                                             p->w, NULL, head,
-                                             scale, NULL, last_rl->attr);
-                       rl = calloc(1, sizeof(*rl));
-                       rl->text = head;
-                       rl->attr = strdup(last_rl->attr); // FIXME underline,fg:blue ???
-                       rl->width = cr.x;
-                       rl->x = *wrap_margin;
-                       rl->cursorpos = -1;
-                       rl->next = *rlp;
-                       *rlp = rl;
-                       /* 'x' is how much to shift-left remaining rl entries,
-                        * Don't want to shift them over the wrap-head
+       /* Now we check to see if the line needs to be wrapped and
+        * if so, adjust some y values and reduce x.  If we need to
+        * split an individual entry we create an array of split points.
+        */
+       xdiff = 0; ydiff = 0; y = 0;
+       wraprl = NULL;
+       wrap_margin = left_margin + rd->head_length;
+       for (ri = rd->content ; wrap && ri ; ri = ri->next) {
+               int splitpos;
+               if (ri->hidden)
+                       continue;
+               if (ri->wrap && (wraprl == NULL || ri->wrap != wraprl->wrap))
+                       wraprl = ri;
+               if (ri->wrap_margin)
+                       wrap_margin = ri->x + xdiff;
+               ri->wrap_x = wrap_margin;
+               ri->x += xdiff;
+               ri->y += ydiff;
+               y = ri->y;
+               if (ri->eol) {
+                       xdiff = 0;
+                       continue;
+               }
+               if (ri->x + ri->width <= right_margin - rd->tail_length)
+                       continue;
+               if ((ri->next == NULL || ri->next->eol) &&
+                   ri->x + ri->width <= right_margin &&
+                   seen_rtab)
+                       /* Don't need any tail space for last item.
+                        * This allows rtab to fully right-justify,
+                        * but leaves no-where for the cursor.  So
+                        * only do it if rtab is present.
                         */
-                       x -= cr.x;
-                       wrap_prefix_size += cr.x;
+                       continue;
+               /* This doesn't fit here */
+               if (wraprl) {
+                       /* Move wraprl to next line and hide it unless it contains cursor */
+                       int xd;
+                       struct render_item *wraprl2, *ri2;
+
+                       /* Find last ritem in wrap region.*/
+                       for (wraprl2 = wraprl ;
+                            wraprl2->next && wraprl2->next->wrap == wraprl->wrap ;
+                            wraprl2 = wraprl2->next)
+                               ;
+                       wrap_margin = wraprl2->wrap_x;
+                       if (wraprl2->next) {
+                               xd = wraprl2->next->x - wrap_margin;
+                               if (wraprl2->next->start > ri->start)
+                                       xd += xdiff;
+                       } else {
+                               xd = wraprl2->x - wrap_margin;
+                               if (wraprl2->start > ri->start)
+                                       xd += xdiff;
+                       }
+                       if (offset >= 0 &&
+                           offset >= wraprl->start &&
+                           offset <= wraprl2->start + wraprl2->len) {
+                               /* Cursor is here, so keep it visible.
+                                * If we are still in the wrap region, pretend
+                                * it didn't exist, else move first item
+                                * after it to next line
+                                */
+                               if (ri->wrap == wraprl->wrap)
+                                       goto normal_wrap;
+                       } else {
+                               /* Hide the wrap region */
+                               while (wraprl != wraprl2) {
+                                       wraprl->hidden = True;
+                                       wraprl = wraprl->next;
+                               }
+                               if (wraprl)
+                                       wraprl->hidden = True;
+                               while (ri->next && ri->next->hidden)
+                                       ri = ri->next;
+                       }
+                       for (ri2 = wraprl2->next ; ri2 && ri2 != ri->next; ri2 = ri2->next) {
+                               ri2->y += rd->line_height;
+                               ri2->x -= xd;
+                               if (ri2->wrap_margin)
+                                       wrap_margin = ri2->x;
+                               ri2->wrap_x = wrap_margin;
+                       }
+                       xdiff -= xd;
+                       ydiff += rd->line_height;
+                       wraprl = NULL;
+                       if (ri->hidden ||
+                           ri->x + ri->width <= right_margin - rd->tail_length)
+                               continue;
+               }
+       normal_wrap:
+               /* Might need to split this ri into two or more pieces */
+               x = ri->x;
+               splitpos = 0;
+               while (1) {
+                       cr = do_measure(p, ri, splitpos, -1,
+                                       right_margin - rd->tail_length - x);
+                       if (cr.ret == 2)
+                               /* Remainder fits now */
+                               break;
+                       if (cr.i == 0 && splitpos == 0) {
+                               /* None of this fits here, move to next line */
+                               xdiff -= ri->x - wrap_margin;
+                               ri->x = wrap_margin;
+                               x = ri->x;
+                               ydiff += rd->line_height;
+                               ri->y += rd->line_height;
+                               wraprl = NULL;
+                       }
+                       if (cr.i == 0)
+                               /* Nothing fits and we already split - give up */
+                               break;
+                       /* re-measure the first part */
+                       cr = do_measure(p, ri, splitpos,
+                                       cr.i,
+                                       right_margin - rd->tail_length - x);
+                       ydiff += rd->line_height;
+                       xdiff -= cr.x;
+                       if (splitpos == 0) {
+                               xdiff -= ri->x - wrap_margin;
+                               x = wrap_margin;
+                       }
+                       splitpos += cr.i;
+                       if (!add_split(ri, splitpos))
+                               break;
                }
-               x -= *wrap_margin;
-               wrap_prefix_size += *wrap_margin;
-       }
-       if (wrap_prefix_sizep)
-               *wrap_prefix_sizep = wrap_prefix_size;
-
-       for (rl = tofree; rl && rl != end_wrap; rl = tofree) {
-               tofree = rl->next;
-               free((void*)rl->text);
-               free((void*)rl->attr);
-               free(rl);
        }
-
-       /* Shift remaining rl to the left */
-       for (rl = end_wrap; rl; rl = rl->next)
-               rl->x -= x;
-       return x;
+       rd->measure_height =
+               (rd->space_above + rd->space_below) * curs_height / 10 +
+               ydiff + rd->line_height;
+       pane_resize(p, p->x, p->y, p->w, rd->measure_height);
+       attr_set_int(&p->attrs, "line-height", rd->line_height);
+       return ret;
 }
 
-static void update_line_height_attr(struct pane *p safe,
-                                   struct pane *focus safe,
-                                   int *h safe,
-                                   int *a safe,int *w, char *attr safe,
-                                   char *str safe, int scale)
+static void draw_line(struct pane *p safe, struct pane *focus safe, int offset)
 {
-       struct call_return cr = home_call_ret(all, focus, "Draw:text-size", p,
-                                             -1, NULL, str,
-                                             scale, NULL, attr);
-       if (cr.y > *h)
-               *h = cr.y;
-       if (cr.i2 > *a)
-               *a = cr.i2;
-       if (w)
-               *w += cr.x;
-}
+       struct rline_data *rd = p->data;
+       struct render_item *ri;
+       char *wrap_tail = rd->wrap_tail ?: "\\";
+       char *wrap_head = rd->wrap_head ?: "";
 
-static void strip_ctrl(char *s safe)
-{
-       while (*s) {
-               if (*s < ' ' || ((unsigned)*s >= 128 && (unsigned)*s < 128+' '))
-                       *s = 'M';
-               s += 1;
+       home_call(focus, "Draw:clear", p, 0, NULL, rd->background);
+
+       if (!rd->content)
+               return;
+       for (ri = rd->content ; ri; ri = ri->next) {
+               int split = 0;
+               short y = ri->y;
+               int cpos;
+
+               if (ri->hidden)
+                       continue;
+               if (offset < 0 || offset >= ri->start + ri->len)
+                       cpos = -1;
+               else if (offset < ri->start)
+                       cpos = 0;
+               else
+                       cpos = offset - ri->start;
+
+               do_draw(p, focus, ri, 0, cpos, ri->x, y);
+
+               while (split < ri->split_cnt ||
+                      (ri->next && !ri->next->eol && ri->next->y > y)) {
+                       /* line wrap here */
+                       /* don't show head/tail for wrap-regions */
+                       if (*wrap_tail /*&& !ri->wrap*/)
+                               draw_wrap(p, focus, wrap_tail,
+                                         p->w - rd->tail_length, y);
+                       y += rd->line_height;
+                       if (*wrap_head /*&& !ri->wrap*/)
+                               draw_wrap(p, focus, wrap_head,
+                                         0, y);
+                       if (ri->split_list && split < ri->split_cnt) {
+                               split += 1;
+                               do_draw(p, focus, ri, split, cpos,
+                                       ri->wrap_x,
+                                       y);
+                       } else
+                               break;
+               }
+               if (offset < ri->start + ri->len)
+                       offset = -1;
        }
 }
 
-static void update_line_height(struct pane *p safe, struct pane *focus safe,
-                              int *h safe, int *a safe,
-                              int *w safe, int *center, const char *line safe,
-                              int scale)
+static int find_xy(struct pane *p safe, struct pane *focus safe,
+                  short x, short y, const char **xyattr)
 {
-       /* Extract general geometry information from the line.
-        * Maximum height and ascent are extracted together with total
-        * with and h-alignment info:
-        * 0 - left, 1 = centered, >=2 = space-on-left, <=-2 = space from right
+       /* Find the location in ->line that is best match for x,y.
+        * If x,y is on the char at that location, when store attributes
+        * for the char in xyattr
+        * We always return a location, even if no xyattr.
+        * We use the last render_item that is not definitely after x,y
+        * We do not consider the eol render_item
         */
-       struct buf attr;
-       int attr_found = 0;
-       const char *segstart = line;
-       int above = 0, below = 0;
+       struct call_return cr;
+       struct rline_data *rd = p->data;
+       struct render_item *r, *ri = NULL;
+       int splitpos = 0;
+       int start = 0;
 
-       buf_init(&attr);
-       buf_append(&attr, ',');
-       while (*line) {
-               char c = *line++;
-               const char *st = line;
-               if (c == '<' && *line == '<') {
-                       line += 1;
+       for (r = rd->content; r ; r = r->next) {
+               int split;
+               if (r->hidden)
                        continue;
+               if (r->y <= y && r->x <= x) {
+                       ri = r;
+                       start = r->start;
+                       splitpos = 0;
                }
-               if (c != '<')
-                       continue;
-
-               if (line - 1 > segstart) {
-                       char *l = strndup(segstart, line - 1 - segstart);
-                       strip_ctrl(l);
-                       update_line_height_attr(p, focus, h, a, w,
-                                               buf_final(&attr), l, scale);
-                       free(l);
-               }
-               while (*line && line[-1] != '>')
-                       line += 1;
-               segstart = line;
-               if (st[0] != '/') {
-                       char *c2;
-                       char *b;
-                       const char *aend;
-
-                       /* attrs must not contain ",," */
-                       aend = strstr(st, ",,");
-                       if (aend)
-                               aend += 1;
-                       if (!aend || line < aend)
-                               aend = line;
-
-                       buf_concat_len(&attr, st, aend-st);
-                       /* Replace trailing '>' with ',', and append ','
-                        * so ",," marks where to strip back to when we
-                        * find </>.
-                        */
-                       attr.b[attr.len-1] = ',';
-                       buf_append(&attr, ',');
-                       b = buf_final(&attr);
-                       if (center && strstr(b, ",center,"))
-                               *center = 1;
-                       if (center && (c2=strstr(b, ",left:")) != NULL)
-                               *center = 2 + atoi(c2+6) * scale / 1000;
-                       if (center && (c2=strstr(b, ",right:")) != NULL)
-                               *center = -2 - atoi(c2+7) * scale / 1000;
-                       if ((c2=strstr(b, ",space-above:")) != NULL)
-                               above = atoi(c2+13) * scale / 1000;
-                       if ((c2=strstr(b, ",space-below:")) != NULL)
-                               below = atoi(c2+13) * scale / 1000;
-                       if ((c2=strstr(b, ",tab:")) != NULL)
-                               *w = atoi(c2+5) * scale / 1000;
-                       attr_found = 1;
-                       update_line_height_attr(p, focus, h, a, w, b, "",
-                                               scale);
-               } else {
-                       /* strip back to ",," */
-                       if (attr.len >= 2)
-                               attr.len -= 2;
-                       while (attr.len > 0 &&
-                              (attr.b[attr.len] != ',' ||
-                               attr.b[attr.len+1] != ','))
-                               attr.len -= 1;
+               for (split = 0; split < r->split_cnt; split++) {
+                       if (r->y + (split + 1) * rd->line_height <= y &&
+                           r->wrap_x <= x && r->split_list) {
+                               ri = r;
+                               splitpos = r->split_list[split];
+                               start = r->start + splitpos;
+                       }
                }
        }
-       if (line > segstart && line[-1] == '\n')
-               line -= 1;
-       if (line > segstart || !attr_found) {
-               char *l = strndup(segstart, line - segstart);
-               strip_ctrl(l);
-               update_line_height_attr(p, focus, h, a, w,
-                                       buf_final(&attr), l, scale);
-               free(l);
+       if (!ri)
+               return 0;
+       if (ri->eol)
+               /* newline or similar.  Can only be at start */
+               return start;
+       cr = do_measure(p, ri, splitpos, -1, x - ri->x);
+       if ((splitpos ? ri->wrap_x : ri->x ) + cr.x > x &&
+           ri->y + rd->line_height * (1 + splitpos) > y &&
+           xyattr) {
+               /* This is a bit of a hack.
+                * We stick the x,y co-ords of the start
+                * of the current attr in front of the
+                * attrs so render-lines can provide a
+                * good location for a menu
+                */
+               char buf[100];
+               struct render_item *ri2;
+               int ax = ri->x;
+               for (ri2 = rd->content; ri2 != ri; ri2 = ri2->next)
+                       if (strcmp(ri2->attr, ri->attr) == 0)
+                               ax = ri2->x;
+               snprintf(buf, sizeof(buf), "%dx%d,", ax, y);
+               *xyattr = strconcat(p, buf, ri->attr);
        }
-       *h += above + below;
-       *a += above;
-       free(buf_final(&attr));
+       if (cr.s)
+               return cr.s - rd->line;
+       return start + cr.i;
+}
+
+static struct xy find_curs(struct pane *p safe, int offset, const char **cursattr)
+{
+       struct call_return cr;
+       struct xy xy = {0,0};
+       int split;
+       int st;
+       struct rline_data *rd = p->data;
+       struct render_item *r, *ri = NULL;
+
+       for (r = rd->content; r; r = r->next) {
+               if (r->hidden)
+                       continue;
+               if (offset < r->start)
+                       break;
+               ri = r;
+       }
+       if (!ri) {
+               /* This should be impossible as the eol goes past
+                * the largest offset.
+                */
+               return xy;
+       }
+       if (offset < ri->start)
+               /* in the attrs?? */
+               offset = 0;
+       else
+               offset -= ri->start;
+       /* offset now from ri->start */
+       if (ri->len && rd->line[ri->start] == '\t' && offset)
+               offset = ri->tab_cols;
+       if (cursattr)
+               *cursattr = ri->attr;
+       st = 0;
+       for (split = 0; split < ri->split_cnt && ri->split_list; split ++) {
+               if (offset < ri->split_list[split])
+                       break;
+               st = ri->split_list[split];
+       }
+       if (ri->eol)
+               cr.x = offset ? ri->width : 0;
+       else
+               cr = do_measure(p, ri, st, offset - st, -1);
+
+       if (split)
+               xy.x = ri->wrap_x + cr.x;
+       else
+               xy.x = ri->x + cr.x;
+       xy.y = ri->y + split * rd->line_height;
+       if (ri->next == NULL && offset > ri->len) {
+               /* After the newline ? Go to next line */
+               xy.x = 0;
+               xy.y += rd->line_height;
+       }
+       return xy;
 }
 
 static void parse_map(const char *map safe, short *rowsp safe, short *colsp safe)
@@ -480,31 +1030,37 @@ static void parse_map(const char *map safe, short *rowsp safe, short *colsp safe
 
 static int render_image(struct pane *p safe, struct pane *focus safe,
                        const char *line safe,
-                       int dodraw, int scale,
+                       int dodraw,
                        int offset, int want_xypos, short x, short y)
 {
+       struct rline_data *rd = p->data;
        char *fname = NULL;
        const char *orig_line = line;
        short width, height;
        short rows = -1, cols = -1;
        int map_offset = 0;
        int ioffset;
-       char *ssize = attr_find(p->attrs, "cached-size");
+       struct xy xyscale = pane_scale(focus);
+       int scale = xyscale.x;
        struct xy size= {-1, -1};
+       char mode[30] = "";
+       const char *a, *v;
+       const char *end;
+
+       if (dodraw)
+               home_call(focus, "Draw:clear", p);
 
        width = p->parent->w/2;
        height = p->parent->h/2;
 
-       while (*line == '<')
+       while (*line == soh)
                line += 1;
 
-       while (*line && *line != '>') {
-               int len = strcspn(line, ",>");
-
-               if (strstarts(line, "image:")) {
-                       fname = strndup(line+6, len-6);
-                       if (!ssize ||
-                           sscanf(ssize, "%hdx%hd", &size.x, &size.y) != 2) {
+       end = line + strcspn(line, STX);
+       foreach_attr(a, v, line, end) {
+               if (amatch(a, "image") && v) {
+                       aupdate(&fname, v);
+                       if (rd->image_size.x <= 0) {
                                struct call_return cr =
                                        home_call_ret(all, focus,
                                                      "Draw:image-size",
@@ -512,26 +1068,22 @@ static int render_image(struct pane *p safe, struct pane *focus safe,
                                if (cr.x > 0 && cr.y > 0) {
                                        size.x = cr.x;
                                        size.y = cr.y;
-                                       asprintf(&ssize, "%hdx%hd",
-                                                cr.x, cr.y);
-                                       attr_set_str(&p->attrs,
-                                                    "cached-size", ssize);
+                                       rd->image_size = size;
                                }
-                       }
-               } else if (strstarts(line, "width:")) {
-                       width = atoi(line + 6);
-                       width = width * scale / 1000;
-               } else if (strstarts(line, "height:")) {
-                       height = atoi(line + 7);
-                       height = height * scale / 1000;
-               } else if (strstarts(line, "noupscale") &&
+                       } else
+                               size = rd->image_size;
+               } else if (amatch(a, "width") && v) {
+                       width = anum(v) * scale / 1000;
+               } else if (amatch(a, "height") && v) {
+                       height = anum(v) * scale / 1000;
+               } else if (amatch(a, "noupscale") &&
                           fname && size.x > 0) {
                        if (size.x < p->parent->w)
                                width = size.x;
                        if (size.y < p->parent->h)
                                height = size.y;
                } else if ((offset >= 0 || want_xypos) &&
-                          strstarts(line, "map:")) {
+                          amatch(a, "map") && v) {
                        /*
                         * A map is map:LxxxLxxxLxxxLxxx or similar
                         * Where each "Lxxx" recognised by a CAP followed
@@ -543,11 +1095,12 @@ static int render_image(struct pane *p safe, struct pane *focus safe,
                         * If offset is in the map, then set ->cx,->cy to
                         * the appropriate location.
                         */
-                       map_offset = line+4 - orig_line;
-                       parse_map(line+4, &rows, &cols);
+                       map_offset = v - orig_line;
+                       parse_map(v, &rows, &cols);
+                       if (rows > 0 && cols > 0)
+                               snprintf(mode, sizeof(mode),
+                                        ":%dx%d", cols, rows);
                }
-               line += len;
-               line += strspn(line, ",");
        }
        pane_resize(p, (p->parent->w - width)/2, p->y, width, height);
 
@@ -580,8 +1133,8 @@ static int render_image(struct pane *p safe, struct pane *focus safe,
        }
 
        if (fname && dodraw)
-               home_call(focus, "Draw:image", p, 5, NULL, fname,
-                         0, NULL, NULL, cols, rows);
+               home_call(focus, "Draw:image", p, 0, NULL, fname,
+                         0, NULL, mode);
 
        free(fname);
 
@@ -596,474 +1149,97 @@ static int render_image(struct pane *p safe, struct pane *focus safe,
        return 1;
 }
 
-static void set_xypos(struct render_list *rlst,
-                     struct pane *p safe, struct pane *focus safe, int posx,
-                     int scale)
+DEF_CMD(renderline_draw)
 {
-       /* Find the text postition in the rlst which corresponds to
-        * the screen position posx, and report attribtes there too.
-        */
-       for (; rlst && rlst->x <= posx; rlst = rlst->next) {
-               if (rlst->x + rlst->width < posx)
-                       continue;
-
-               if (rlst->x == posx)
-                       rlst->xypos = rlst->text_orig;
-               else {
-                       struct call_return cr =
-                               home_call_ret(all, focus, "Draw:text-size", p,
-                                             posx - rlst->x, NULL, rlst->text,
-                                             scale, NULL, rlst->attr);
-                       rlst->xypos = rlst->text_orig + cr.i;
-               }
-       }
-}
-
-/* Render a line, with attributes and wrapping.
- * The marked-up text to be processed has already been provided with
- *   render-line:set.  It is in rd->line;
- * ->num is <0, or an index into ->str where the cursor is,
- *   and the x,y co-ords will be stored in p->cx,p->cy
- * If key is "render-line:draw", then send drawing commands, otherwise
- * just perform measurements.
- * If key is "render-line:findxy", then only measure until the position
- *   in x,y is reached, then return an index into ->str of where we reached.
- *   Store the attributes active at the time so they can be fetched later.
- */
-DEF_CMD(renderline)
-{
-       struct pane *p = ci->home;
-       struct rline_data *rd = p->data;
-       struct pane *focus = ci->focus;
-       const char *line = rd->line;
-       int dodraw = strcmp(ci->key, "render-line:draw") == 0;
-       short posx;
-       short offset = ci->num;
-       int x = 0;
-       int y = 0;
-       const char *line_start;
-       const char *start;
-       struct buf attr;
-       unsigned char ch;
-       int wrap_offset = 0; /*number of columns displayed in earlier lines,
-                             * use for calculating TAB size. */
-       int wrap_margin = 0; /* left margin for wrap - carried forward from
-                             * line to line. */
-       int in_tab = 0;
-       int shift_left = pane_attr_get_int(focus, "shift_left", 0);
-       bool word_wrap = pane_attr_get_int(focus, "word-wrap", 0) != 0;
-       bool in_lws = False;
-       bool seen_non_space = False;
-       int wrap = shift_left < 0;
-       char *prefix = pane_attr_get(focus, "prefix");
-       int line_height = 0;
-       int ascent = -1;
-       int mwidth = -1;
-       int ret = OK;
-       int twidth = 0;
-       int center = 0;
-       int margin;
-       int end_of_page = 0;
-       struct render_list *rlst = NULL;
-       const char *xypos = NULL;
-       const char *ret_xypos = NULL;
-       const char *xyattr = NULL;
-       const char *cursattr = NULL;
-       /* want_xypos becomes 2 when the pos is found */
-       int want_xypos = strcmp(ci->key, "render-line:findxy") == 0;
-       struct xy xyscale = pane_scale(focus);
-       int scale = xyscale.x;
-       short cx = -1, cy = -1;
-
-       if (!line)
-               return Enoarg;
-       /* line_start doesn't change
-        * start is the start of the current segment(since attr)
-        *    is update after we call draw_some()
-        * line is where we are now up to.
-        */
-       start = line_start = line;
-
-       rd->scale = scale;
-
-       if (dodraw)
-               home_call(focus, "Draw:clear", p);
-
-       if (strstarts(line, "<image:"))
-               /* For now an <image> must be on a line by itself.
-                * Maybe this can be changed later if I decide on
-                * something that makes sense.
-                */
-               return render_image(p, focus, line, dodraw, scale,
-                                   offset, want_xypos, ci->x, ci->y);
-
-       update_line_height(p, focus, &line_height, &ascent, &twidth, &center,
-                          line, scale);
+       struct rline_data *rd = ci->home->data;
+       struct xy xy;
+       int offset = -1;
 
-       if (line_height <= 0)
-               return Einval;
+       if (ci->num >= 0)
+               offset = rd->prefix_bytes + ci->num;
 
-       if (!wrap)
-               x -= shift_left;
+       if (rd->image)
+               render_image(ci->home, ci->focus, rd->line, True,
+                            offset, False, 0, 0);
        else
-               shift_left = 0;
-
-       if (prefix) {
-               const char *s = prefix + strlen(prefix);
-               update_line_height_attr(p, focus, &line_height, &ascent, NULL,
-                                       "bold", prefix, scale);
-               draw_some(p, focus, &rlst, &x, prefix, &s, ",bold,", False,
-                         0, -1, -1, scale);
-               rd->prefix_len = x + shift_left;
-       } else
-               rd->prefix_len = 0;
-
-       if (center == 1)
-               x += (p->w - x - twidth) / 2;
-       if (center >= 2)
-               x += center - 2;
-       if (center <= -2)
-               x = p->w - x - twidth + (center + 2);
-       /* tabs are measured against this margin */
-       margin = x;
-
-       /* The attr string starts and ends with ',' and
-        * attrs are separated by commas.
-        * Groups of attrs to be popped by the next </>
-        * are separated by ",,"
-        */
-       buf_init(&attr);
-       buf_append(&attr, ',');
-
-       rd->curs_width = 0;
+               draw_line(ci->home, ci->focus, offset);
 
-       /* If findxy was requested, ci->x and ci->y tells us
-        * what to look for, and we return index into line where this
-        * co-ordinate was reached.
-        * want_xypos will be set to 2 when we pass the co-ordinate
-        * At that time ret_xypos is set, to be used to provide return value.
-        * This might happen when y exceeds ypos, or we hit end-of-page.
-        */
-       if (want_xypos) {
-               free((void*)rd->xyattr);
-               rd->xyattr = NULL;
+       if (ci->num >= 0) {
+               xy = find_curs(ci->home, rd->prefix_bytes + ci->num, NULL);
+               ci->home->cx = xy.x;
+               ci->home->cy = xy.y;
        }
+       return 1;
+}
 
-       while (*line && y < p->h && !end_of_page) {
-               if (mwidth <= 0) {
-                       /* mwidth is recalculated whenever attrs change */
-                       struct call_return cr = home_call_ret(all, focus,
-                                                             "Draw:text-size", p,
-                                                             -1, NULL, "M",
-                                                             scale, NULL,
-                                                             buf_final(&attr));
-                       mwidth = cr.x;
-                       if (mwidth <= 0)
-                               mwidth = 1;
-                       if (!rd->curs_width)
-                               rd->curs_width = mwidth;
-               }
-
-               if (want_xypos == 1 &&
-                   y > ci->y - line_height &&
-                   y <= ci->y)
-                       posx = ci->x;
-               else
-                       posx = -1;
-
-               if (want_xypos == 1 && xypos) {
-                       rd->xyattr = xyattr ? strdup(xyattr) : NULL;
-                       ret_xypos = xypos;
-                       want_xypos = 2;
-               }
-
-               if (offset >= 0 && start - line_start <= offset) {
-                       if (y >= 0 && (y == 0 || y + line_height <= p->h)) {
-                               /* Don't update cursor pos while in a TAB
-                                * as we want to leave cursor at the start.
-                                */
-                               if (!in_tab) {
-                                       cy = y;
-                                       cx = x;
-                               }
-                       } else {
-                               cy = cx = -1;
-                       }
-               }
-
-               if ((ret == WRAP || x >= p->w - mwidth) &&
-                   (line[0] != '<' || line[1] == '<')) {
-                       /* No room for more text */
-                       if (wrap && *line && *line != '\n') {
-                               int wrap_prefix_size;
-                               int len = flush_line(p, focus, dodraw, &rlst,
-                                                    y+ascent, scale,
-                                                    p->w - mwidth, &wrap_margin,
-                                                    &wrap_prefix_size,
-                                                    &xypos, &xyattr, &cursattr);
-                               if (len + wrap_prefix_size <= cx && cy == y) {
-                                       cx -= len;
-                                       cy += line_height;
-                               }
-                               wrap_offset += len;
-                               x -= len;
-                               if (x < 0)
-                                       x = 0;
-                               y += line_height;
-                               if (want_xypos == 1 &&
-                                   y >= ci->y - line_height &&
-                                   y <= ci->y)
-                                       /* cursor is in the tail of rlst that
-                                        * was relocated - reassess xypos
-                                        */
-                                       set_xypos(rlst, p, focus, ci->x, scale);
-                       } else {
-                               /* truncate: skip over normal text, but
-                                * stop at newline.
-                                */
-                               line += strcspn(line, "\n");
-                               start = line;
-                       }
-               }
-
-               ret = OK;
-               ch = *line;
-               if (line == line_start + offset)
-                       rd->curs_width = mwidth;
-               if (ch >= ' ' && ch != '<') {
-                       bool was_in_lws = in_lws;
-                       line += 1;
-                       /* Only flush out if string is getting a bit long.
-                        * i.e.  if we have reached the offset we are
-                        * measuring to, or if we could have reached the
-                        * right margin.
-                        * Alternately, if we are doing word-wrap and we
-                        * have found start or end of a word.
-                        */
-                       if ((*line & 0xc0) == 0x80)
-                               /* In the middle of a UTF-8 */
-                               continue;
-                       if (word_wrap) {
-                               wint_t wch = ch;
-                               const char *trimmed_line = line - 1;
-                               if (ch & 0x80) {
-                                       const char *l = start + utf8_round_len(start, trimmed_line-start);
-                                       trimmed_line = l;
-                                       wch = get_utf8(&l, NULL);
-                               }
-                               if (!iswspace(wch)) {
-                                       seen_non_space = True;
-                                       if (in_lws)
-                                               line = trimmed_line;
-                                       in_lws = False;
-                               } else if (!in_lws&& seen_non_space && iswspace(wch)) {
-                                       in_lws = True;
-                                       line = trimmed_line;
-                               }
-                       }
-                       if (offset == (line - line_start) ||
-                           (line-start) * mwidth >= p->w - x ||
-                           (posx > x && (line - start)*mwidth > posx - x) ||
-                           was_in_lws != in_lws
-                       ) {
-                               ret = draw_some(p, focus, &rlst, &x, start,
-                                               &line,
-                                               buf_final(&attr),
-                                               was_in_lws,
-                                               wrap ? mwidth : 0,
-                                               offset - (start - line_start),
-                                               posx, scale);
-                               start = line;
-                       }
-                       continue;
-               }
-               ret = draw_some(p, focus, &rlst, &x, start, &line,
-                               buf_final(&attr), in_lws,
-                               wrap ? mwidth : 0,
-                               in_tab ?:offset - (start - line_start),
-                               posx, scale);
-               start = line;
-               if (ret != OK || !ch)
-                       continue;
-               if (ch == '<') {
-                       line += 1;
-                       if (*line == '<') {
-                               in_lws = False;
-                               ret = draw_some(p, focus, &rlst, &x, start, &line,
-                                               buf_final(&attr), in_lws,
-                                               wrap ? mwidth : 0,
-                                               in_tab ?:offset - (start - line_start),
-                                               posx, scale);
-                               if (ret != OK)
-                                       continue;
-                               start += 2;
-                               line = start;
-                       } else {
-                               const char *a = line;
-
-                               while (*line && line[-1] != '>')
-                                       line += 1;
-
-                               if (a[0] != '/') {
-                                       int ln = attr.len;
-                                       char *tb;
-                                       const char *aend;
-
-                                       /* attrs must not contain ",," */
-                                       aend = strstr(a, ",,");
-                                       if (aend)
-                                               aend += 1;
-                                       if (!aend || line < aend)
-                                               aend = line;
-
-                                       buf_concat_len(&attr, a, aend-a);
-                                       /* Replace trailing '>' with ',', and
-                                        * append ',' so ",," marks where to
-                                        * strip back to when we find </>.
-                                        */
-                                       attr.b[attr.len-1] = ',';
-                                       buf_append(&attr, ',');
-                                       tb = strstr(buf_final(&attr)+ln,
-                                                   "tab:");
-                                       if (tb)
-                                               x = margin +
-                                                       atoi(tb+4) * scale / 1000;
-                               } else {
-                                       /* strip back to ",," */
-                                       if (attr.len > 0)
-                                               attr.len -= 2;
-                                       while (attr.len >=2 &&
-                                              (attr.b[attr.len-1] != ',' ||
-                                               attr.b[attr.len-2] != ','))
-                                               attr.len -= 1;
-                                       if (attr.len == 1)
-                                               attr.len = 0;
-                               }
-                               if (offset == start - line_start)
-                                       offset += line-start;
-                               start = line;
-                               mwidth = -1;
-                       }
-                       continue;
-               }
-
-               line += 1;
-               if (ch == '\n') {
-                       xypos = line-1;
-                       flush_line(p, focus, dodraw, &rlst, y+ascent, scale, 0,
-                                  &wrap_margin, NULL, &xypos, &xyattr, &cursattr);
-                       y += line_height;
-                       x = 0;
-                       wrap_offset = 0;
-                       start = line;
-               } else if (ch == '\f') {
-                       x = 0;
-                       start = line;
-                       wrap_offset = 0;
-                       end_of_page = 1;
-               } else if (ch == '\t') {
-                       int xc = (wrap_offset + x) / mwidth;
-                       /* Note xc might be negative, so "xc % 8" won't work here */
-                       int w = 8 - (xc & 7);
-                       if (seen_non_space)
-                               in_lws = True;
-                       ret = draw_some(p, focus, &rlst, &x, start, &line,
-                                       buf_final(&attr), in_lws,
-                                       wrap ? mwidth*2: 0,
-                                       offset == (start - line_start)
-                                       ? in_tab : -1,
-                                       posx, scale);
-                       if (ret == WRAP)
-                               ;
-                       else if (w > 1) {
-                               line -= 1;
-                               in_tab = -1; // suppress extra cursors
-                       } else
-                               in_tab = 0;
-                       start = line;
-               } else {
-                       char buf[4];
-                       const char *b;
-                       int l = attr.len;
-                       buf[0] = '^';
-                       buf[1] = ch + '@';
-                       buf[2] = 0;
-                       b = buf+2;
-                       buf_concat(&attr, ",underline,fg:red,");
-                       in_lws = False;
-                       ret = draw_some(p, focus, &rlst, &x, buf, &b,
-                                       buf_final(&attr), in_lws,
-                                       wrap ? mwidth*2: 0,
-                                       offset - (start - line_start),
-                                       posx, scale);
-                       attr.len = l;
-                       start = line;
-               }
-       }
-       if (!*line && (line > start || offset == start - line_start)) {
-               /* Some more to draw */
-               if (want_xypos == 1 &&
-                   y > ci->y - line_height &&
-                   y <= ci->y)
-                       posx = ci->x;
-               else
-                       posx = -1;
-
-               draw_some(p, focus, &rlst, &x, start, &line,
-                         buf_final(&attr), False,
-                         wrap ? mwidth : 0, offset - (start - line_start),
-                         posx, scale);
+DEF_CMD(renderline_refresh)
+{
+       struct rline_data *rd = ci->home->data;
+       int offset = -1;
+
+       if (rd->curspos >= 0)
+               offset = rd->prefix_bytes + rd->curspos;
+       if (rd->image)
+               render_image(ci->home, ci->focus, rd->line, True,
+                            offset, False, 0, 0);
+       else {
+               measure_line(ci->home, ci->focus, offset);
+               draw_line(ci->home, ci->focus, offset);
        }
+       return 1;
+}
 
-       flush_line(p, focus, dodraw, &rlst, y+ascent, scale, 0,
-                  &wrap_margin, NULL,  &xypos, &xyattr, &cursattr);
-
-       if (want_xypos == 1) {
-               rd->xyattr = xyattr ? strdup(xyattr) : NULL;
-               ret_xypos = xypos ?: line;
-               want_xypos = 2;
+DEF_CMD(renderline_measure)
+{
+       struct rline_data *rd = ci->home->data;
+       int ret;
+
+       if (rd->image)
+               return render_image(ci->home, ci->focus, rd->line,
+                                   False, ci->num, False, 0, 0);
+
+       ret = measure_line(ci->home, ci->focus,
+                          ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
+       rd->prefix_pixels = 0;
+       if (rd->prefix_bytes) {
+               struct xy xy = find_curs(ci->home, rd->prefix_bytes, NULL);
+               rd->prefix_pixels = xy.x;
        }
-       if (cursattr) {
-               free((void*)rd->cursattr);
-               rd->cursattr = strdup(cursattr);
+       if (ci->num >= 0) {
+               /* Find cursor and report x,y pos and attributes */
+               const char *cursattr = NULL;
+               struct xy xy;
+               xy = find_curs(ci->home, rd->prefix_bytes + ci->num, &cursattr);
+               comm_call(ci->comm2, "cb", ci->focus, ret, NULL,
+                         cursattr);
+               ci->home->cx = xy.x;
+               ci->home->cy = xy.y;
        }
+       return ret | 4;
+}
 
-       if (offset >= 0 && line - line_start <= offset) {
-               if (y >= 0 && (y == 0 || y + line_height <= p->h)) {
-                       cy = y;
-                       cx = x;
-               } else {
-                       cy = cx = -1;
-               }
-       }
-       if (x > 0 || y == 0)
-               /* No newline at the end .. but we must render as whole lines */
-               y += line_height;
-       free(buf_final(&attr));
-       if (offset >= 0) {
-               p->cx = cx;
-               p->cy = cy;
-       }
-       if (!dodraw)
-               /* Mustn't resize after clearing the pane, or we'll
-                * be out-of-sync with display manager.
-                */
-               pane_resize(p, p->x, p->y, p->w, y);
-       attr_set_int(&p->attrs, "line-height", line_height);
-       while (rlst) {
-               struct render_list *r = rlst;
-               rlst = r->next;
-               free((void*)r->text);
-               free((void*)r->attr);
-               free(r);
+DEF_CMD(renderline_findxy)
+{
+       struct rline_data *rd = ci->home->data;
+       const char *xyattr = NULL;
+       int pos;
+
+       if (rd->image)
+               return render_image(ci->home, ci->focus, rd->line,
+                                   False, -1, True,
+                                   ci->x, ci->y);
+
+       measure_line(ci->home, ci->focus,
+                    ci->num < 0 ? -1 : rd->prefix_bytes + ci->num);
+       pos = find_xy(ci->home, ci->focus, ci->x, ci->y, &xyattr);
+       if (pos >= rd->prefix_bytes)
+               pos -= rd->prefix_bytes;
+       else {
+               pos = 0;
+               xyattr = NULL;
        }
-       if (want_xypos) {
-               if (ret_xypos)
-                       return ret_xypos - line_start + 1;
-               else
-                       return 1;
-       } else
-               return end_of_page ? 2 : 1;
+       comm_call(ci->comm2, "cb", ci->focus, pos, NULL, xyattr);
+       return pos+1;
 }
 
 DEF_CMD(renderline_get)
@@ -1075,13 +1251,11 @@ DEF_CMD(renderline_get)
        if (!ci->str)
                return Enoarg;
        if (strcmp(ci->str, "prefix_len") == 0)
-               snprintf(buf, sizeof(buf), "%d", rd->prefix_len);
+               snprintf(buf, sizeof(buf), "%d", rd->prefix_pixels);
        else if (strcmp(ci->str, "curs_width") == 0)
                snprintf(buf, sizeof(buf), "%d", rd->curs_width);
-       else if (strcmp(ci->str, "xyattr") == 0)
-               val = rd->xyattr;
-       else if (strcmp(ci->str, "cursattr") == 0)
-               val = rd->cursattr;
+       else if (strcmp(ci->str, "width") == 0)
+               snprintf(buf, sizeof(buf), "%d", rd->width);
        else
                return Einval;
 
@@ -1089,33 +1263,115 @@ DEF_CMD(renderline_get)
        return 1;
 }
 
+static char *cvt(char *str safe)
+{
+       /* Convert:
+        *    << to < ack  (ack is a no-op)
+        *    < stuff > to soh stuff stx
+        *    </> to ack ack etx
+        */
+       char *c, *c1;
+       for (c = str; *c; c += 1) {
+               if (c[0] == soh || c[0] == ack)
+                       break;
+               if (c[0] == '<' && c[1] == '<') {
+                       c[1] = ack;
+                       c++;
+                       continue;
+               }
+               if (c[0] != '<')
+                       continue;
+               if (c[1] == '/') {
+                       while (*c && *c != '>')
+                               *c++ = ack;
+                       if (!*c)
+                               break;
+                       *c = etx;
+                       continue;
+               }
+               c[0] = soh;
+               c += 1;
+               c1 = c;
+               while (*c && *c != '>') {
+                       if (*c == '\\' &&
+                           (c[1] == '\\' || c[1] == '>'))
+                               c++;
+                       *c1++ = *c++;
+               }
+               while (c1 < c)
+                       *c1++ = ack;
+               if (!*c)
+                       break;
+               *c = stx;
+       }
+       return str;
+}
+
 DEF_CMD(renderline_set)
 {
        struct rline_data *rd = ci->home->data;
        const char *old = rd->line;
-       struct xy xyscale = pane_scale(ci->focus);
+       char *prefix = pane_attr_get(ci->focus, "prefix");
+       bool word_wrap = pane_attr_get_int(ci->focus, "word-wrap", 0) != 0;
+       bool bg_changed = False;
 
-       if (ci->str)
-               rd->line = strdup(ci->str);
+       if (!ci->str)
+               return -Enoarg;
+       if (prefix)
+               prefix = strconcat(ci->home, ACK SOH "bold" STX,
+                                  prefix, // No mark in prefix!
+                                  ETX);
+       if (prefix)
+               rd->line = strconcat(NULL, prefix, ci->str);
        else
-               rd->line = NULL;
-       if (strcmp(rd->line ?:"", old ?:"") != 0 ||
-           (old && xyscale.x != rd->scale)) {
+               rd->line = strdup(ci->str);
+       rd->prefix_bytes = strlen(prefix?:"");
+       cvt(rd->line + rd->prefix_bytes);
+
+       if (ci->str2 && !rd->background) {
+               rd->background = strdup(ci->str2);
+               bg_changed = True;
+       } else if (!ci->str2 && rd->background) {
+               free(rd->background);
+               rd->background = NULL;
+               bg_changed = True;
+       } else if (ci->str2 && rd->background &&
+                  strcmp(ci->str2, rd->background) != 0) {
+               free(rd->background);
+               rd->background = strdup(ci->str2);
+               bg_changed = True;
+       }
+
+       rd->curspos = ci->num;
+       if (strcmp(rd->line, old) != 0 || bg_changed ||
+           (old && word_wrap != rd->word_wrap)) {
                pane_damaged(ci->home, DAMAGED_REFRESH);
                pane_damaged(ci->home->parent, DAMAGED_REFRESH);
+               rd->word_wrap = word_wrap;
+               parse_line(rd);
        }
        free((void*)old);
        ci->home->damaged &= ~DAMAGED_VIEW;
        return 1;
 }
 
-DEF_CMD(renderline_close)
+DEF_CMD_CLOSED(renderline_close)
 {
        struct rline_data *rd = ci->home->data;
+       struct render_item *ri = rd->content;
 
-       free((void*)rd->xyattr);
        free((void*)rd->line);
-       rd->xyattr = NULL;
+       while (ri) {
+               struct render_item *r = ri;
+               ri = r->next;
+               free(r->split_list);
+               unalloc_str_safe(r->attr, pane);
+               unalloc(r, pane);
+       }
+       aupdate(&rd->wrap_head, NULL);
+       aupdate(&rd->wrap_tail, NULL);
+       aupdate(&rd->wrap_attr, NULL);
+       aupdate(&rd->background, NULL);
        return 1;
 }
 
@@ -1124,26 +1380,26 @@ DEF_LOOKUP_CMD(renderline_handle, rl_map);
 
 DEF_CMD(renderline_attach)
 {
-       struct rline_data *rd;
        struct pane *p;
+       struct rline_data *rd;
 
        if (!rl_map) {
                rl_map = key_alloc();
-               key_add(rl_map, "render-line:draw", &renderline);
-               key_add(rl_map, "render-line:measure", &renderline);
-               key_add(rl_map, "render-line:findxy", &renderline);
+               key_add(rl_map, "render-line:draw", &renderline_draw);
+               key_add(rl_map, "Refresh", &renderline_refresh);
+               key_add(rl_map, "render-line:measure", &renderline_measure);
+               key_add(rl_map, "render-line:findxy", &renderline_findxy);
                key_add(rl_map, "get-attr", &renderline_get);
                key_add(rl_map, "render-line:set", &renderline_set);
                key_add(rl_map, "Close", &renderline_close);
-               key_add(rl_map, "Free", &edlib_do_free);
        }
 
-       alloc(rd, pane);
-       p = pane_register(ci->focus, -10, &renderline_handle.c, rd);
-       if (!p) {
-               unalloc(rd, pane);
+       p = pane_register(ci->focus, ci->num, &renderline_handle.c);
+       if (!p)
                return Efail;
-       }
+       rd = p->data;
+       rd->line = strdup(ETX); // Imposible string
+
        return comm_call(ci->comm2, "cb", p);
 }