]> git.neil.brown.name Git - edlib.git/blobdiff - render-lines.c
TODO: clean out done items.
[edlib.git] / render-lines.c
index d556b625e5a3d798908b7cf3568443f604abd8b3..52eeb416c4e061e782730cdf9e8d82db3952399d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright Neil Brown ©2015-2021 <neil@brown.name>
+ * Copyright Neil Brown ©2015-2023 <neil@brown.name>
  * May be distributed under terms of GPLv2 - see file:COPYING
  *
  * Rendering for any document which presents as a sequence of lines.
@@ -9,14 +9,14 @@
  * This takes a mark and moves it to the end of the rendered line
  * so that another call will produce another line.
  * "doc:render-line" must always return a full line including '\n'
- * unless the result would be bigger than the 'max' passed in ->num or,
- * when ->num==-1, unless the rendering would go beyond the location in
- * ->mark2.  In these cases it can stop before a '\n'.  In each case,
+ * unless the result would be bigger than the 'max' passed in ->num or
+ * ->num < 0.  In these cases it can stop before a '\n'.  In each case,
  * the mark is moved to the end of the region that was rendered;
- * This allows a mark to be found for a given character position, or a display
- * position found for a given mark.
+ * This allows a mark to be found for a given character position.
+ * If mark2 is given, the offset in the rendering when mark2 is reached
+ * is reported as ->num in the callback.
  * For the standard 'render the whole line' functionality, ->num should
- * be NO_NUMERIC
+ * be negative.
  *
  * The document or filter must also provide "doc:render-line-prev" which
  * moves mark to a start-of-line.  If num is 0, then don't skip over any
@@ -80,6 +80,7 @@
 
 #define        MARK_DATA_PTR struct pane
 #define _GNU_SOURCE /*  for asprintf */
+#define PANE_DATA_TYPE struct rl_data
 #include "core.h"
 #include "misc.h"
 #include <stdio.h> /* snprintf */
@@ -110,7 +111,8 @@ struct rl_data {
        short           i_moved;        /* I moved cursor, so don't clear
                                         * target
                                         */
-       int             do_wrap;
+       short           do_wrap;
+       short           shift_locked;
        short           shift_left;
        short           shift_left_last_refresh;
        struct mark     *header;
@@ -129,6 +131,7 @@ struct rl_data {
         */
        struct pane     *cursor_pane;
 };
+#include "core-pane.h"
 
 static void vmark_clear(struct mark *m safe)
 {
@@ -144,12 +147,13 @@ static void vmark_free(struct mark *m safe)
        mark_free(m);
 }
 
-static void vmark_set(struct pane *p safe, struct mark *m safe, char *line safe)
+static void vmark_set(struct pane *p safe, struct pane *focus safe,
+                     struct mark *m safe, char *line safe)
 {
        if (!m->mdata)
-               m->mdata = call_ret(pane, "attach-renderline", p);
+               m->mdata = call_ret(pane, "attach-renderline", p, -1);
        if (m->mdata)
-               pane_call(m->mdata, "render-line:set", p, 0, NULL, line);
+               pane_call(m->mdata, "render-line:set", focus, -1, NULL, line);
 }
 
 static void vmark_invalidate(struct mark *m safe)
@@ -160,42 +164,75 @@ static void vmark_invalidate(struct mark *m safe)
 
 static bool vmark_is_valid(struct mark *m safe)
 {
-       return m->mdata && !(m->mdata->damaged & DAMAGED_VIEW);
+       return mark_valid(m) && m->mdata && !(m->mdata->damaged & DAMAGED_VIEW);
 }
 
 /* Returns 'true' at end-of-page */
-static bool measure_line(struct pane *p safe, struct pane *focus safe,
-                        struct mark *mk safe, short cursor_offset)
+static int _measure_line(struct pane *p safe, struct pane *focus safe,
+                         struct mark *mk safe, short cursor_offset,
+                         char **cursor_attr)
 {
+       struct rl_data *rl = p->data;
        struct pane *hp = mk->mdata;
-       int ret = 0;
-
-       if (hp) {
-               pane_resize(hp, hp->x, hp->y, p->w, p->h);
-               ret = pane_call(hp, "render-line:measure",
-                               focus, cursor_offset);
+       struct call_return cr;
+
+       if (!mark_valid(mk) || !hp)
+               return False;
+       pane_resize(hp, hp->x, hp->y, p->w, p->h);
+       if (!rl->shift_locked) {
+               int sl = pane_attr_get_int(focus, "render-wrap", -2);
+               if (sl != rl->shift_left) {
+                       char *sla = NULL;
+                       asprintf(&sla, "%d auto", rl->shift_left);
+                       attr_set_str(&focus->attrs, "render-wrap", sla);
+                       free(sla);
+               }
        }
+       cr = pane_call_ret(all, hp, "render-line:measure",
+                          focus, cursor_offset);
+       if (cursor_attr)
+               *cursor_attr = cr.s;
        /* end-of-page flag */
-       return ret == 2;
+       return cr.ret & 3;
+}
+#define measure_line(...) VFUNC(measure_line, __VA_ARGS__)
+static inline int measure_line3(struct pane *p safe, struct pane *focus safe,
+                                struct mark *mk safe)
+{
+       return _measure_line(p, focus, mk, -1, NULL);
+}
+static inline int measure_line4(struct pane *p safe, struct pane *focus safe,
+                                struct mark *mk safe, short cursor_offset)
+{
+       return _measure_line(p, focus, mk, cursor_offset, NULL);
+}
+static inline int measure_line5(struct pane *p safe, struct pane *focus safe,
+                                struct mark *mk safe, short cursor_offset,
+                                char **cursor_attr)
+{
+       return _measure_line(p, focus, mk, cursor_offset, cursor_attr);
 }
 
 /* Returns offset of posx,posy */
 static int find_xy_line(struct pane *p safe, struct pane *focus safe,
-                       struct mark *mk safe, short posx, short posy)
+                       struct mark *mk safe, short posx, short posy,
+                       const char **xyattr)
 {
        struct pane *hp = mk->mdata;
-       int ret = 0;
-
-       if (hp) {
-               ret = pane_call(hp,
-                               "render-line:findxy",
-                               focus,
-                               -1, NULL, NULL,
-                               0, NULL, NULL,
-                               posx - hp->x, posy - hp->y);
-       }
+       struct call_return cr;
+
+       if (!hp)
+               return -1;
+       cr = pane_call_ret(all, hp,
+                          "render-line:findxy",
+                          focus,
+                          -1, NULL, NULL,
+                          0, NULL, NULL,
+                          posx - hp->x, posy - hp->y);
+       if (xyattr)
+               *xyattr = cr.s;
        /* xypos */
-       return ret > 0 ? (ret - 1) : -1;
+       return cr.ret > 0 ? (cr.ret - 1) : -1;
 }
 
 static void draw_line(struct pane *p safe, struct pane *focus safe,
@@ -217,8 +254,10 @@ static struct mark *call_render_line_prev(struct pane *p safe,
        int ret;
        struct mark *m2;
 
-       if (m->viewnum < 0)
+       if (m->viewnum < 0) {
+               mark_free(m);
                return NULL;
+       }
        ret = call("doc:render-line-prev", p, n, m);
        if (ret <= 0) {
                /* if n>0 we can fail because start-of-file was found before
@@ -265,10 +304,14 @@ static void call_render_line(struct pane *home safe, struct pane *p safe,
                }
                s = "";
        } else
-               s = call_ret(strsave, "doc:render-line", p, NO_NUMERIC, m);
+               s = call_ret(strsave, "doc:render-line", p, -1, m);
 
+       if (!mark_valid(start)) {
+               mark_free(m);
+               return;
+       }
        if (s)
-               vmark_set(home, start, s);
+               vmark_set(home, p, start, s);
 
        m2 = vmark_matching(m);
        if (m2)
@@ -318,16 +361,12 @@ static struct mark *call_render_line_offset(struct pane *p safe,
        return m;
 }
 
-DEF_CMD(get_len)
+DEF_CMD(get_offset)
 {
-       if (ci->str) {
-               int l = strlen(ci->str);
-               while (l >=4 && strncmp(ci->str+l-3, "</>", 3) == 0 &&
-               ci->str[l-4] != '<')
-                       l -= 3;
-               return l + 1;
-       } else
+       if (ci->num < 0)
                return 1;
+       else
+               return ci->num + 1;
 }
 
 static int call_render_line_to_point(struct pane *p safe, struct mark *pm safe,
@@ -336,7 +375,7 @@ static int call_render_line_to_point(struct pane *p safe, struct mark *pm safe,
        int len;
        struct mark *m = mark_dup_view(start);
 
-       len = call_comm("doc:render-line", p, &get_len, -1, m, NULL, 0, pm);
+       len = call_comm("doc:render-line", p, &get_offset, -1, m, NULL, 0, pm);
        mark_free(m);
        if (len <= 0)
                return 0;
@@ -376,7 +415,7 @@ static bool step_back(struct pane *p safe, struct pane *focus safe,
                short h = 0;
                start = m;
                call_render_line(p, focus, start, endp);
-               measure_line(p, focus, start, -1);
+               measure_line(p, focus, start);
                h = start->mdata ? start->mdata->h : 0;
                if (h) {
                        *y_pre = h;
@@ -395,12 +434,11 @@ static bool step_fore(struct pane *p safe, struct pane *focus safe,
                      short *y_post safe, short *line_height_post safe)
 {
        struct mark *end = *endp;
-       bool found_end = False;
 
        if (!end)
                return True;
        call_render_line(p, focus, end, startp);
-       found_end = measure_line(p, focus, end, -1);
+       measure_line(p, focus, end);
        if (end->mdata)
                *y_post = end->mdata->h;
        if (*y_post > 0 && end->mdata)
@@ -412,30 +450,36 @@ static bool step_fore(struct pane *p safe, struct pane *focus safe,
        else
                end = vmark_next(end);
        if (!end) {
-               found_end = 1;
                if (p->h >= *line_height_post *2)
                        *y_post = p->h / 10;
        }
 
        *endp = end;
-       return found_end;
+       return False;
 }
 
 static int consume_space(struct pane *p safe, int y,
                         short *y_prep safe, short *y_postp safe,
                         short *lines_above safe, short *lines_below safe,
                         int found_start, int found_end,
-                        int line_height_pre, int line_height_post)
+                        int line_height_pre, int line_height_post,
+                        bool line_at_a_time)
 {
        int y_pre = *y_prep;
        int y_post = *y_postp;
 
-       if (y_pre > 0 && y_post > 0) {
+       if (y_pre > 0 && y_post > 0 && !found_start && !found_end) {
                int consume = (y_post < y_pre
                               ? y_post : y_pre) * 2;
                int above, below;
                if (consume > p->h - y)
                        consume = p->h - y;
+               if (line_at_a_time && consume > 2*line_height_pre &&
+                   line_height_pre > 0)
+                       consume = 2*line_height_pre;
+               if (line_at_a_time && consume > 2*line_height_post &&
+                   line_height_post > 0)
+                       consume = 2*line_height_post;
                if (y_pre > y_post) {
                        above = consume - (consume/2);
                        below = consume/2;
@@ -453,18 +497,24 @@ static int consume_space(struct pane *p safe, int y,
                 * both > 0
                 */
        }
-       if (found_end && y_pre) {
+       if (found_end && y_pre && !found_start) {
                int consume = p->h - y;
                if (consume > y_pre)
                        consume = y_pre;
+               if (line_at_a_time && consume > line_height_pre &&
+                   line_height_pre > 0)
+                       consume = line_height_pre;
                y_pre -= consume;
                y += consume;
                *lines_above += consume / (line_height_pre?:1);
        }
-       if (found_start && y_post) {
+       if (found_start && y_post && !found_end) {
                int consume = p->h - y;
                if (consume > y_post)
                        consume = y_post;
+               if (line_at_a_time && consume > line_height_post &&
+                   line_height_post > 0)
+                       consume = line_height_post;
                y_post -= consume;
                y += consume;
                *lines_below += consume / (line_height_post?:1);
@@ -497,28 +547,85 @@ static int consume_space(struct pane *p safe, int y,
  *
  * If we decide to stop moving in both directions, but have not
  * reached EOF or full height of display, keep moving downwards.
+ *
+ * "start" is a mark at the start of the first line we currently
+ * intend to display, and y_pre is the number of pixel from the top
+ * of the display of that line, to the top pixel that will be displayed.
+ * We only move 'start' backward when y_pre is zero, and initially y_pre
+ * is the full height of that line.
+ *
+ * Similarly "end" is the start of the last line we currently intend
+ * to display, and y_post is the number of pixel from the bottom of that display
+ * up to the point we currently intend to display.  We only move "end" forward
+ * when y_post is zero, and when we do we set y_post to the full height of the
+ * line.
+ *
+ * Until we decide on the start or end (found_start, found_end), we
+ * repeatedly add equal parts of y_pre and y_post into the total to
+ * be display - consume_space() does this.  The space removed from y_pre
+ * and y_post is added to 'y' - the total height.
+ * It is also included into lines_above and lines_below which count text lines,
+ * rather than pixels, using line_height_pre and line_height_post as scale
+ * factors.  These are used to determine when vline or rl->margin requirements
+ * have been met.
  */
 static void find_lines(struct mark *pm safe, struct pane *p safe,
                       struct pane *focus safe,
                       int vline)
 {
        struct rl_data *rl = p->data;
+       /* orig_top/bot bound what is currently displayed and
+        * are used to determine if the display has been repositioned.
+        * orig_bot is *after* the last displayed line.  Its ->mdata
+        * will be NULL.
+        */
        struct mark *orig_top, *orig_bot;
-       struct mark *top, *bot;  // boundary of previous display
+       /* top and bot are used to enhance stability.  They are NULL
+        * if vline is given, else they match orig_top/bot.
+        */
+       struct mark *top, *bot;
        struct mark *m;
+       /* Current estimate of new display. From y_pre pixels down
+        * from the top of line at 'start', to y_post pixels up
+        * from the end of the line before 'end' there are 'y'
+        * pixel lines that we have committed to display.
+        */
        struct mark *start, *end; // current estimate for new display
+       short y_pre = 0, y_post = 0;
        short y = 0;
+       /* Number of text-lines in the committed region above or below
+        * the baseline of the line containing pm.  These lines might not
+        * all be the same height. line_height_pre/post are the heights of
+        * start and end-1 so changes in y_pre/y_post can be merged into these
+        * counts.
+        */
        short lines_above = 0, lines_below = 0; /* distance from start/end
                                                 * to pm.
                                                 */
-       short offset; // pos of pm in rendering of that line
-       bool found_start = False, found_end = False;
-       /* y_pre and y_post are measurement from start/end that
-        * haven't yet been included into lines_above/lines_below.
-        */
-       short y_pre = 0, y_post = 0;
        short line_height_pre = 1, line_height_post = 1;
 
+       short offset; // pos of pm while measureing the line holding the cursor.
+       /* We set found_start we we don't want to consider anything above the
+        * top that we currently intend to display.  Once it is set,
+        * 'start', y_pre, lines_above are all frozen.
+        * Similarly once found_end is set we freeze end, y_pos, lines_below,
+        * but we mught unfreeze those if there is room for more text at end of
+        * display.
+        * found_start is set:
+        *   - when y_pre is zero and start is at top of file
+        *   - when lines_above reaches positive vline
+        *   - when intended display has grown down into the previous
+        *     display.  This means we have added enough lines above and
+        *     don't want to scroll the display more than we need.
+        *   - When we hit unexpected errors moving backwards
+        * found_end is set:
+        *   - when we hit end-of-file
+        *   - when lines_below reached -vline
+        *   - when the top of the intended display overlaps the
+        *     previous display.
+        */
+       bool found_start = False, found_end = False;
+
        orig_top = vmark_first(focus, rl->typenum, p);
        orig_bot = vmark_last(focus, rl->typenum, p);
        /* Protect top/bot from being freed by call_render_line */
@@ -530,11 +637,16 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
        start = vmark_new(focus, rl->typenum, p);
        if (!start)
                goto abort;
+       /* FIXME why is this here.  We set ->repositioned at the end
+        * if the marks move.  Maybe we need to check if y_pre moves too.
+        */
        rl->repositioned = 1;
        mark_to_mark(start, pm);
        start = call_render_line_prev(focus, start, 0, &rl->top_sol);
        if (!start)
                goto abort;
+
+       /* Render the cursor line, and find where the cursor is. */
        offset = call_render_line_to_point(focus, pm, start);
        call_render_line(p, focus, start, NULL);
        end = vmark_next(start);
@@ -542,27 +654,37 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
         * call_render_line() will have created 'end' if it didn't exist.
         */
 
-       rl->shift_left = 0;
+       if (!rl->shift_locked)
+               rl->shift_left = 0;
 
-       /* ->cy is top of cursor, we want to measure from bottom */
        if (start->mdata) {
                struct pane *hp = start->mdata;
                int curs_width;
-               found_end = measure_line(p, focus, start, offset);
+               int shifts = 0;
+               found_end = measure_line(p, focus, start, offset) & 2;
 
                curs_width = pane_attr_get_int(
                        start->mdata, "curs_width", 1);
-               while (!rl->do_wrap && curs_width > 0 &&
+               if (curs_width <= 0)
+                       curs_width = 1;
+               // FIXME this loops indefinitely if cursor after
+               // right-justified text.
+               while (!rl->do_wrap && !rl->shift_locked &&
                       hp->cx + curs_width >= p->w) {
                        int shift = 8 * curs_width;
                        if (shift > hp->cx)
                                shift = hp->cx;
                        rl->shift_left += shift;
                        measure_line(p, focus, start, offset);
+                       if (shifts++ > 100)
+                               break;
                }
+               /* ->cy is top of cursor, we want to measure from bottom */
                line_height_pre = attr_find_int(start->mdata->attrs, "line-height");
                if (line_height_pre < 1)
                        line_height_pre = 1;
+               /* We now have a better estimate than '1' */
+               line_height_post = line_height_pre;
                y_pre = start->mdata->cy + line_height_pre;
                y_post = start->mdata->h - y_pre;
        } else {
@@ -571,19 +693,25 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
                y_post = 0;
        }
        if (!end) {
-               found_end = True;
+               /* When cursor at EOF, leave 10% height of display
+                * blank at bottom to make this more obvious - unless
+                * the display is so small that might push the last line partly
+                * off display at the top.
+                */
                if (p->h > line_height_pre * 2)
                        y_post += p->h / 10;
-               else
+               else {
                        /* Small display, no space at EOF */
                        y_post = 0;
+                       found_end = True;
+               }
        }
        y = 0;
        if (rl->header && rl->header->mdata)
                y = rl->header->mdata->h;
 
        /* We have start/end of the focus line.  When rendered this,
-        * plus header and eof-footed would use y_pre + y + y_post
+        * plus header and eof-footer would use y_pre + y + y_post
         * vertical space.
         */
 
@@ -598,8 +726,13 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
 
        while ((!found_start || !found_end) && y < p->h) {
                if (vline != NO_NUMERIC) {
+                       /* As lines_above/below measure from the baseline
+                        * of the cursor line, and as we want to see the top
+                        * of he cursor line as well, these two cases are
+                        * asymmetric.
+                        */
                        if (!found_start && vline > 0 &&
-                           lines_above >= vline-1)
+                           lines_above >= vline)
                                found_start = True;
                        if (!found_end && vline < 0 &&
                            lines_below >= -vline-1)
@@ -636,9 +769,13 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
                        found_end = step_fore(p, focus, &start, &end,
                                              &y_post, &line_height_post);
 
+               /* This test has "> rl->margin" while found_end test has
+                * ">= rl->margin" due to the asymmetry of measuring from the
+                * baseline, not the centreling, of the target text.
+                */
                if (!found_start && top && end &&
                    mark_ordered_or_same(start, top) &&
-                   lines_above >= rl->margin)
+                   lines_above > rl->margin)
                        if (mark_ordered_not_same(top, end) ||
                            (mark_same(top, end) &&
                             y_post - rl->tail_height >= y_pre))
@@ -647,7 +784,8 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
                y = consume_space(p, y, &y_pre, &y_post,
                                  &lines_above, &lines_below,
                                  found_start, found_end,
-                                 line_height_pre, line_height_post);
+                                 line_height_pre, line_height_post,
+                                 vline && vline != NO_NUMERIC);
        }
        /* We might need to continue downwards even after found_end
         * if there is more space.
@@ -660,7 +798,8 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
                y = consume_space(p, y, &y_pre, &y_post,
                                  &lines_above, &lines_below,
                                  found_start, found_end,
-                                 line_height_pre, line_height_post);
+                                 line_height_pre, line_height_post,
+                                 vline && vline != NO_NUMERIC);
        }
 
        if (start->mdata && start->mdata->h <= y_pre) {
@@ -692,18 +831,25 @@ static void find_lines(struct mark *pm safe, struct pane *p safe,
        }
 
        y = 0;
-       if (rl->header && rl->header->mdata)
+       rl->cols = 0;
+       if (rl->header && rl->header->mdata) {
                y = rl->header->mdata->h;
+               rl->cols = pane_attr_get_int(rl->header->mdata, "width", 0);
+       }
        y -= rl->skip_height;
        for (m = vmark_first(focus, rl->typenum, p);
             m && m->mdata ; m = vmark_next(m)) {
                struct pane *hp = m->mdata;
-               hp->damaged &= ~DAMAGED_SIZE;
-               pane_resize(hp, hp->x, y, hp->w, hp->h);
-               if (hp->damaged & DAMAGED_SIZE && !rl->background_uniform)
+               int cols;
+               if (pane_resize(hp, hp->x, y, hp->w, hp->h) &&
+                   !rl->background_uniform)
                        pane_damaged(hp, DAMAGED_REFRESH);
                y += hp->h;
+               cols = pane_attr_get_int(hp, "width", 0);
+               if (cols > rl->cols)
+                       rl->cols = cols;
        }
+       rl->lines = y;
        pane_damaged(p, DAMAGED_REFRESH);
        m = vmark_first(focus, rl->typenum, p);
        if (!m || !orig_top || !mark_same(m, orig_top))
@@ -730,14 +876,14 @@ static int render(struct mark *pm, struct pane *p safe,
        struct mark *m, *m2;
        struct xy scale = pane_scale(focus);
        char *s;
-       int hide_cursor = 0;
-       int cursor_drawn = 0;
+       bool hide_cursor = False;
+       bool cursor_drawn = False;
        bool refresh_all = rl->shift_left != rl->shift_left_last_refresh;
 
        rl->shift_left_last_refresh = rl->shift_left;
        s = pane_attr_get(focus, "hide-cursor");
        if (s && strcmp(s, "yes") == 0)
-               hide_cursor = 1;
+               hide_cursor = True;
 
        rl->cols = 0;
        m = vmark_first(focus, rl->typenum, p);
@@ -746,7 +892,7 @@ static int render(struct mark *pm, struct pane *p safe,
                rl->background_uniform = True;
        }
        s = pane_attr_get(focus, "background");
-       if (s && strncmp(s, "call:", 5) == 0) {
+       if (s && strstarts(s, "call:")) {
                home_call(focus, "Draw:clear", p, 0, NULL, "");
                home_call(focus, s+5, p, 0, m);
                refresh_all = True;
@@ -755,15 +901,15 @@ static int render(struct mark *pm, struct pane *p safe,
                ;
        else if (!s)
                home_call(focus, "Draw:clear", p, 0, NULL, "");
-       else if (strncmp(s, "color:", 6) == 0) {
+       else if (strstarts(s, "color:")) {
                char *a = strdup(s);
                strcpy(a, "bg:");
                strcpy(a+3, s+6);
                home_call(focus, "Draw:clear", p, 0, NULL, a);
                free(a);
-       } else if (strncmp(s, "image:", 6) == 0) {
+       } else if (strstarts(s, "image:")) {
                home_call(focus, "Draw:clear", p);
-               home_call(focus, "Draw:image", p, 1, NULL, s+6);
+               home_call(focus, "Draw:image", p, 0, NULL, s+6, 0, NULL, "S");
                rl->background_uniform = False;
        } else
                home_call(focus, "Draw:clear", p, 0, NULL, "");
@@ -773,7 +919,7 @@ static int render(struct mark *pm, struct pane *p safe,
                struct pane *hp = rl->header->mdata;
                draw_line(p, focus, rl->header, -1, refresh_all);
                y = hp->h;
-               rl->cols = hp->x + hp->w;
+               rl->cols = pane_attr_get_int(hp, "width", 0);
        }
        y -= rl->skip_height;
 
@@ -793,25 +939,27 @@ static int render(struct mark *pm, struct pane *p safe,
                        draw_line(p, focus, m, len, True);
                        rl->cursor_line = hp->y + hp->cy;
                        curs = pane_mapxy(hp, p, hp->cx, hp->cy, False);
-                       if (hp->cx < 0) {
+                       if (hp->cx < 0 || hp->cx >= hp->w) {
                                p->cx = -1;
                                p->cy = -1;
                        } else {
                                p->cx = curs.x;
                                p->cy = curs.y;
+                               cursor_drawn = True;
                        }
-                       cursor_drawn = 1;
                } else {
                        draw_line(p, focus, m, -1, refresh_all);
                }
                if (m->mdata) {
-                       int cols = m->mdata->x + m->mdata->w;
+                       int cols = pane_attr_get_int(m->mdata, "width", 0);
                        if (cols > rl->cols)
                                rl->cols = cols;
                        y = m->mdata->y + m->mdata->h;
                }
                m = m2;
        }
+       if (m && !m->mdata && vmark_next(m))
+               LOG("render-lines: break in vmark sequence");
        if (!cursor_drawn && !hide_cursor) {
                /* Place cursor in bottom right */
                struct pane *cp = rl->cursor_pane;
@@ -819,6 +967,9 @@ static int render(struct mark *pm, struct pane *p safe,
                short lineheight;
 
                if (!cp) {
+                       /* Note: this pane will container rl_data
+                        * which isn't used.
+                        */
                        cp = pane_register(p, -1, &cursor_handle);
                        rl->cursor_pane = cp;
                }
@@ -859,21 +1010,6 @@ static int render(struct mark *pm, struct pane *p safe,
        return y;
 }
 
-DEF_CMD(render_lines_get_attr)
-{
-       struct rl_data *rl = ci->home->data;
-
-       if (ci->str && strcmp(ci->str, "shift_left") == 0) {
-               char ret[10];
-               if (rl->do_wrap)
-                       return comm_call(ci->comm2, "cb", ci->focus,
-                                        0, NULL, "-1");
-               snprintf(ret, sizeof(ret), "%d", rl->shift_left);
-               return comm_call(ci->comm2, "cb", ci->focus, 0, NULL, ret);
-       }
-       return Efallthrough;
-}
-
 DEF_CMD(render_lines_point_moving)
 {
        struct pane *p = ci->home;
@@ -889,6 +1025,9 @@ DEF_CMD(render_lines_point_moving)
                /* Someone else moved the point, so reset target column */
                rl->target_x = -1;
        m = vmark_at_or_before(ci->focus, pt, rl->typenum, p);
+       if (m && !m->mdata)
+               /* End marker is no use, want to refresh last line */
+               m = vmark_prev(m);
        if (m && m->mdata) {
                pane_damaged(m->mdata, DAMAGED_REFRESH);
                pane_damaged(m->mdata->parent, DAMAGED_REFRESH);
@@ -909,9 +1048,9 @@ static int revalidate_start(struct rl_data *rl safe,
        int shifts = 0;
 
        /* This loop is fragile and sometimes spins.  So ensure we
-        * never loop more than 1000 times.
+        * never loop more than 100 times.
         */
-       if (pm && !rl->do_wrap && shifts++ < 1000) {
+       if (pm && !rl->do_wrap && !rl->shift_locked && shifts++ < 100) {
                int prefix_len;
                int curs_width;
                /* Need to check if side-shift is needed on cursor line */
@@ -930,6 +1069,7 @@ static int revalidate_start(struct rl_data *rl safe,
                }
                if (m && m->mdata) {
                        struct pane *hp = m->mdata;
+                       int cols;
                        int offset = call_render_line_to_point(focus,
                                                               pm, m);
                        measure_line(p, focus, m, offset);
@@ -938,7 +1078,7 @@ static int revalidate_start(struct rl_data *rl safe,
                        curs_width = pane_attr_get_int(
                                m->mdata, "curs_width", 1);
 
-                       while (hp->cx + curs_width >= p->w && shifts++ < 1000) {
+                       while (hp->cx + curs_width > p->w && shifts++ < 100) {
                                int shift = 8 * curs_width;
                                if (shift > hp->cx)
                                        shift = hp->cx;
@@ -946,15 +1086,21 @@ static int revalidate_start(struct rl_data *rl safe,
                                measure_line(p, focus, m, offset);
                                refresh_all = 1;
                        }
-                       while (hp->cx < prefix_len &&
+                       /* We shift right is cursor is off the left end, or if
+                        * doing so wouldn't hide anything on the right end
+                        */
+                       cols = pane_attr_get_int(hp, "width", 0);
+                       while ((hp->cx < prefix_len
+                               || (cols-rl->shift_left) + curs_width * 8 + curs_width < p->w) &&
                               rl->shift_left > 0 &&
-                              shifts++ < 1000 &&
-                              hp->cx + curs_width * 8*curs_width < p->w) {
+                              shifts++ < 100 &&
+                              hp->cx + curs_width * 8 < p->w) {
                                int shift = 8 * curs_width;
                                if (shift > rl->shift_left)
                                        shift = rl->shift_left;
                                rl->shift_left -= shift;
                                measure_line(p, focus, m, offset);
+                               cols = pane_attr_get_int(hp, "width", 0);
                                refresh_all = 1;
                        }
                }
@@ -963,7 +1109,7 @@ static int revalidate_start(struct rl_data *rl safe,
        if (rl->header) {
                struct pane *hp = rl->header->mdata;
                if (refresh_all) {
-                       measure_line(p, focus, rl->header, -1);
+                       measure_line(p, focus, rl->header);
                        if (hp)
                                pane_resize(hp, hp->x, y, hp->w, hp->h);
                }
@@ -974,14 +1120,35 @@ static int revalidate_start(struct rl_data *rl safe,
        start_of_file = doc_prior(focus, start) == WEOF;
        for (m = start; m && !found_end && y < p->h; m = vmark_next(m)) {
                struct pane *hp;
+               int found = 0;
+               int offset = -1;
                if (refresh_all)
                        vmark_invalidate(m);
                call_render_line(p, focus, m, NULL);
-               found_end = measure_line(p, focus, m, -1);
+               m2 = vmark_next(m);
+               /* The "found & 1" handles case when EOF is at the end
+                * of a non-empty line.
+                */
+               if (pm && m2 && mark_same(pm, m2))
+                       /* Cursor at end shouldn't affect appearance */
+                       found = measure_line(p, focus, m);
+               if (pm && m2 && mark_ordered_or_same(m, pm) &&
+                   (mark_ordered_not_same(pm, m2) ||
+                    (mark_same(pm, m2) && !(found & 1))))
+                       /* Cursor is on this line */
+                       offset = call_render_line_to_point(focus,
+                                                          pm, m);
+               if (pm && mark_same(m, pm))
+                       /* Probably EOF - cursor is here */
+                       offset = 0;
+               found = measure_line(p, focus, m, offset);
+
                hp = m->mdata;
-               if (!hp)
+               if (!mark_valid(m) || !hp)
                        break;
 
+               found_end = found & 2;
+
                if (y != hp->y) {
                        pane_damaged(p, DAMAGED_REFRESH);
                        hp->damaged &= ~DAMAGED_SIZE;
@@ -990,16 +1157,17 @@ static int revalidate_start(struct rl_data *rl safe,
                                pane_damaged(hp, DAMAGED_REFRESH);
                }
                y += hp->h;
-               m2 = vmark_next(m);
-               if (pm && m == start && rl->skip_height > 0 && m2 &&
-                   mark_ordered_not_same(pm, m2)) {
-                       /* Point might be in this line, but off top
-                        * of the screen
-                        */
-                       int offset = call_render_line_to_point(focus,
-                                                              pm, m);
-                       if (offset >= 0) {
-                               measure_line(p, focus, m, offset);
+               if (offset >= 0) {
+                       /* Cursor is on this line */
+                       int lh = attr_find_int(hp->attrs,
+                                              "line-height");
+                       int cy = y - hp->h + hp->cy;
+                       if (lh < 1)
+                               lh = 1;
+                       if (m == start && rl->skip_height > 0) {
+                               /* Point might be in this line, but off top
+                                * of the screen
+                                */
                                if (hp->cy >= rl->skip_height + rl->margin)
                                        /* Cursor is visible on this line
                                         * and after margin from top.
@@ -1008,54 +1176,30 @@ static int revalidate_start(struct rl_data *rl safe,
                                else if (start_of_file && rl->skip_height == 0)
                                        /* Cannot make more margin space */
                                        on_screen = True;
-                       }
-               } else if (pm && y >= p->h && m->seq < pm->seq) {
-                       /* point might be in this line, but off end
-                        * of the screen
-                        */
-                       int offset = call_render_line_to_point(focus,
-                                                              pm, m);
-                       if (offset > 0) {
-                               int lh;
-                               measure_line(p, focus, m, offset);
-                               lh = attr_find_int(hp->attrs,
-                                                  "line-height");
-                               if (lh <= 0)
-                                       lh = 1;
-                               if (y - hp->h + hp->cy <= p->h - lh - rl->margin) {
+                       } else if (y >= p->h) {
+                               /* point might be in this line, but off end
+                                * of the screen
+                                */
+                               if (hp->cy >= 0 &&
+                                   y - hp->h + hp->cy <= p->h - lh - rl->margin) {
                                        /* Cursor is on screen */
                                        on_screen = True;
                                }
-                       }
-               } else if (pm && mark_ordered_or_same(m, pm) && m2 &&
-                          mark_ordered_or_same(pm, m2)) {
-                       if (rl->margin == 0)
+                       } else if (rl->margin == 0)
+                               on_screen = True;
+                       else if (cy >= rl->margin &&
+                                cy <= p->h - rl->margin - lh)
+                               /* Cursor at least margin from edge */
                                on_screen = True;
-                       else {
-                               int offset = call_render_line_to_point(
-                                       focus, pm, m);
-                               if (offset > 0) {
-                                       int lh;
-                                       int cy;
-                                       measure_line(p, focus, m, offset);
-                                       lh = attr_find_int(hp->attrs,
-                                                          "line-height");
-                                       cy = y - hp->h + hp->cy;
-                                       if (cy >= rl->margin &&
-                                           cy <= p->h - rl->margin - lh)
-                                               /* Cursor at least margin from edge */
-                                               on_screen = True;
-                               }
-                       }
                }
        }
        if (y >= p->h)
                rl->tail_height = p->h - y;
        else
                rl->tail_height = 0;
-       if (m) {
+       if (mark_valid(m)) {
                vmark_clear(m);
-               while ((m2 = vmark_next(m)) != NULL) {
+               while (mark_valid(m2 = vmark_next(m)) && m2) {
                        /* end of view has clearly changed */
                        rl->repositioned = 1;
                        vmark_free(m2);
@@ -1086,13 +1230,52 @@ DEF_CMD(render_lines_revise)
        struct mark *pm = NULL;
        struct mark *m1, *m2;
        bool refresh_all = False;
+       bool wrap;
        char *hdr;
        char *a;
+       int shift;
 
        a = pane_attr_get(focus, "render-wrap");
-       if (rl->do_wrap != (!a || strcmp(a, "yes") ==0)) {
-               rl->do_wrap = (!a || strcmp(a, "yes") ==0);
+       wrap = (!a || strcmp(a, "yes") == 0);
+       if (rl->do_wrap != wrap) {
+               rl->do_wrap = wrap;
                refresh_all = True;
+               rl->shift_left = 0;
+       }
+       if (wrap)
+               rl->shift_locked = True;
+       if (!a)
+               /* avoid any ambiguity */
+               attr_set_str(&focus->attrs, "render-wrap", "yes");
+
+       if (a) {
+               char *end;
+               shift = strtol(a, &end, 10);
+               if (end == a || (end && *end && *end != ' '))
+                       shift = -1;
+       }
+       else
+               shift = -1;
+
+       if (a && shift >= 0) {
+               if (rl->shift_left != shift)
+                       refresh_all = True;
+
+               rl->shift_left = shift;
+               rl->shift_locked = strstr(a, "auto") == NULL;
+       } else if (!wrap) {
+               /* unrecognised - no wrap, not locked */
+               if (rl->shift_locked)
+                       refresh_all = True;
+               rl->shift_left = 0;
+               rl->shift_locked = 0;
+               attr_set_str(&focus->attrs, "render-wrap", "0 auto");
+       }
+       if (refresh_all) {
+               struct mark *v;
+               for (v = vmark_first(focus, rl->typenum, p);
+                    (v && v->mdata) ; v = vmark_next(v))
+                       pane_damaged(v->mdata, DAMAGED_REFRESH);
        }
 
        rl->margin = pane_attr_get_int(focus, "render-vmargin", 0);
@@ -1105,10 +1288,10 @@ DEF_CMD(render_lines_revise)
 
        if (hdr) {
                if (!rl->header)
-                       rl->header = vmark_new(focus, MARK_UNGROUPED, NULL);
+                       rl->header = mark_new(focus);
                if (rl->header) {
-                       vmark_set(p, rl->header, hdr);
-                       measure_line(p, focus, rl->header, -1);
+                       vmark_set(p, focus, rl->header, hdr);
+                       measure_line(p, focus, rl->header);
                }
        } else if (rl->header) {
                vmark_free(rl->header);
@@ -1155,6 +1338,8 @@ DEF_CMD(render_lines_refresh)
        struct pane *focus = ci->focus;
        struct rl_data *rl = p->data;
        struct mark *m, *pm = NULL;
+       int cols = rl->cols;
+       int lines = rl->lines;
 
        //pane_damaged(p, DAMAGED_VIEW);
 
@@ -1166,11 +1351,13 @@ DEF_CMD(render_lines_refresh)
                return 1;
 
        rl->lines = render(pm, p, focus);
+       if (rl->lines != lines || rl->cols != cols)
+               call("render:reposition", focus, rl->lines, NULL, NULL, rl->cols);
 
        return 1;
 }
 
-DEF_CMD(render_lines_close)
+DEF_CMD_CLOSED(render_lines_close)
 {
        struct rl_data *rl = ci->home->data;
 
@@ -1181,7 +1368,7 @@ DEF_CMD(render_lines_close)
        return 1;
 }
 
-DEF_CMD(render_lines_close_mark)
+DEF_CMD_CLOSED(render_lines_close_mark)
 {
        struct mark *m = ci->mark;
 
@@ -1274,7 +1461,7 @@ DEF_CMD(render_lines_move_view)
                                        rpt = 0;
                                        break;
                                }
-                               measure_line(p, focus, m, -1);
+                               measure_line(p, focus, m);
                                y += m->mdata->h;
                                m = vmark_next(m);
                        }
@@ -1284,7 +1471,7 @@ DEF_CMD(render_lines_move_view)
        } else {
                /* Need to remove lines from top */
                call_render_line(p, focus, top, NULL);
-               measure_line(p, focus, top, -1);
+               measure_line(p, focus, top);
                while (top && top->mdata && rpt > 0) {
                        short y = 0;
 
@@ -1299,7 +1486,7 @@ DEF_CMD(render_lines_move_view)
                        if (!top)
                                break;
                        call_render_line(p, focus, top, NULL);
-                       measure_line(p, focus, top, -1);
+                       measure_line(p, focus, top);
                }
                if (top && top->mdata) {
                        /* We didn't fall off the end, so it is OK to remove
@@ -1322,40 +1509,47 @@ DEF_CMD(render_lines_move_view)
        return 1;
 }
 
-static char *get_active_tag(const char *a)
+static char *get_action_tag(const char *tag safe, const char *a)
 {
+       int taglen = strlen(tag);
        char *t;
        char *c;
 
        if (!a)
                return NULL;
-       t = strstr(a, ",active-tag:");
-       if (!t)
-               return NULL;
-       t += 12;
+       do {
+               t = strstr(a, ",action-");
+               if (!t)
+                       return NULL;
+               a = t+1;
+       } while (!(strncmp(t+8, tag, taglen) == 0 &&
+                  t[8+taglen] == ':'));
+
+       t += 8 + taglen + 1;
        c = strchr(t, ',');
        return strndup(t, c?c-t: (int)strlen(t));
 }
 
 DEF_CMD(render_lines_set_cursor)
 {
-       /* ->num is
-        * 1 if this resulted from a click
-        * 2 if from a release
-        * 3 if from motion
-        * 0 any other reason.
+       /* ->str gives a context specific action to perform
+        * If the attributes at the location include
+        * action-$str then the value of that attribute
+        * is send as a command
         */
        struct pane *p = ci->home;
        struct pane *focus = ci->focus;
        struct rl_data *rl = p->data;
+       const char *action = ci->str;
+       const char *xyattr = NULL;
        struct mark *m;
        struct mark *m2 = NULL;
        struct xy cih;
        int xypos;
 
        cih = pane_mapxy(ci->focus, ci->home,
-                        ci->x >= 0 ? ci->x : p->cx >= 0 ? p->cx : 0,
-                        ci->y >= 0 ? ci->y : p->cy >= 0 ? p->cy : 0,
+                        ci->x == INT_MAX ? p->cx : ci->x,
+                        ci->y == INT_MAX ? p->cy : ci->y,
                         False);
 
        m = vmark_first(p, rl->typenum, p);
@@ -1367,37 +1561,45 @@ DEF_CMD(render_lines_set_cursor)
        if (!m)
                /* There is nothing rendered? */
                return 1;
-       if (!m->mdata) {
-               /* chi is after the last visible content, and m is the end
-                * of that content (possible EOF) so move there
-                */
-       } else {
-               if (cih.y < m->mdata->y)
+       if (m->mdata) {
+               /* might be able to find a position in the line */
+               if (cih.y < m->mdata->y) {
+                       /* action only permitted in precise match */
+                       action = NULL;
                        cih.y = m->mdata->y;
-               xypos = find_xy_line(p, focus, m, cih.x, cih.y);
-               if (xypos >= 0)
-                       m2 = call_render_line_offset(focus, m, xypos);
-       }
-       if (m2) {
-               char *tag, *xyattr;
-
-               if (ci->num == 2) { /* Mouse release */
-                       xyattr = pane_attr_get(m->mdata, "xyattr");
-                       tag = get_active_tag(xyattr);
-                       if (tag) {
-                               char *c = NULL;
-                               asprintf(&c, "Mouse-Activate:%s", tag);
-                               if (c)
-                                       call(c, focus, 0, m2, tag,
-                                            0, ci->mark, xyattr);
-                               free(c);
+               }
+               xypos = find_xy_line(p, focus, m, cih.x, cih.y, &xyattr);
+               if (xypos >= 0 &&
+                   (m2 = call_render_line_offset(focus, m, xypos)) != NULL) {
+                       char *tag;
+                       wint_t c = doc_following(focus, m2);
+                       if (c == WEOF || is_eol(c))
+                               /* after last char on line - no action. */
+                               action = NULL;
+                       if (action && xyattr &&
+                           (tag = get_action_tag(action, xyattr)) != NULL) {
+                               int x, y;
+                               /* This is a hack to get the
+                                * start of these attrs so menu
+                                * can be placed correctly.
+                                * Only works for menus below
+                                * the line.
+                                */
+                               if (!strstr(xyattr, ",menu-at-mouse,") &&
+                                   sscanf(xyattr, "%dx%d,", &x, &y) == 2) {
+                                       cih.x = x;
+                                       cih.y = m->mdata->y + y +
+                                               attr_find_int(m->mdata->attrs,
+                                                             "line-height");
+                                       ;
+                               }
+                               call(tag, focus, 0, m2, xyattr,
+                                    0, ci->mark, action,
+                                    cih.x, cih.y);
                        }
+                       m = m2;
                }
-               m = m2;
-       } else {
-               /* m is the closest we'll get */
        }
-
        if (ci->mark)
                mark_to_mark(ci->mark, m);
        else
@@ -1407,6 +1609,40 @@ DEF_CMD(render_lines_set_cursor)
        return 1;
 }
 
+DEF_CMD(render_lines_action)
+{
+       /* If there is an action-$str: at '->mark', send the command
+        * to the focus
+        */
+       struct mark *m = ci->mark;
+       struct pane *p = ci->home;
+       struct rl_data *rl = p->data;
+       struct pane *focus = ci->focus;
+       struct mark *v, *n;
+       int offset;
+       char *attr = NULL, *tag;
+
+       if (!m || !ci->str)
+               return Enoarg;
+       v = vmark_first(p, rl->typenum, p);
+
+       while (v && v->mdata && (n = vmark_next(v)) &&
+              mark_ordered_or_same(n, m))
+               v = n;
+
+       if (!v || !v->mdata || !mark_ordered_or_same(v, m))
+               return Efallthrough;
+       offset = call_render_line_to_point(focus, m, v);
+       measure_line(p, focus, v, offset, &attr);
+       if (!attr)
+               return Efallthrough;
+       tag = get_action_tag(ci->str, attr);
+       if (!tag)
+               return Efallthrough;
+       call(tag, focus, 0, m, attr, 0, NULL, ci->str);
+       return 1;
+}
+
 DEF_CMD(render_lines_move_pos)
 {
        struct pane *p = ci->home;
@@ -1424,7 +1660,7 @@ DEF_CMD(render_lines_move_pos)
                /* top line not fully displayed, being in that line is
                 * not sufficient */
                top = vmark_next(top);
-       if (bot)
+       if (bot && rl->tail_height)
                /* last line might not be fully displayed, so don't assume */
                bot = vmark_prev(bot);
        if (!top || !bot ||
@@ -1471,7 +1707,7 @@ DEF_CMD(render_lines_move_line)
        int num;
        int xypos = -1;
        struct mark *m = ci->mark;
-       struct mark *start;
+       struct mark *start, *m2;
 
        if (!m)
                m = call_ret(mark, "doc:point", focus);
@@ -1490,16 +1726,25 @@ DEF_CMD(render_lines_move_line)
        num = RPT_NUM(ci);
        if (call("doc:EOL", ci->focus, num, m, NULL, 1) <= 0) {
                rl->i_moved = 0;
-               return Efail;
+               return Efalse;
        }
        if (RPT_NUM(ci) < 0) {
                /* at end of target line, move to start */
                if (call("doc:EOL", ci->focus, -1, m) <= 0) {
                        rl->i_moved = 0;
-                       return Efail;
+                       return Efalse;
                }
        }
 
+       /* We are at the start of the target line.  We might
+        * like to find the target_x column, but if anything
+        * goes wrong it isn't a failure.
+        * Need to ensure there is a vmark here. call_render_line_prev()
+        * wil only move the mark if it is in a multi-line rendering,
+        * such as an image which acts as though it is multiple lines.
+        * It will check if there is already a mark at the target location.
+        * It will free the mark passed in unless it returns it.
+        */
        start = vmark_new(focus, rl->typenum, p);
 
        if (start) {
@@ -1509,39 +1754,57 @@ DEF_CMD(render_lines_move_line)
 
        if (!start) {
                pane_damaged(p, DAMAGED_VIEW);
-               rl->i_moved = 0;
-               return 1;
+               goto done;
        }
-       if (vmark_first(focus, rl->typenum, p) == start) {
+       if (vmark_first(focus, rl->typenum, p) == start &&
+           !vmark_is_valid(start))
                /* New first mark, so view will have changed */
                rl->repositioned = 1;
-       }
 
-       if (rl->target_x == 0 && rl->target_y == 0) {
+       if (rl->target_x == 0 && rl->target_y == 0)
                /* No need to move to target column - already there.
                 * This simplifies life for render-complete which is
                 * always at col 0, and messes with markup a bit.
                 */
-               rl->i_moved = 0;
-               return 1;
-       }
+               goto done;
+
        /* FIXME only do this if point is active/volatile, or
         * if start->mdata is NULL
         */
        vmark_invalidate(start);
        call_render_line(p, focus, start, NULL);
-       if (start->mdata)
-               xypos = find_xy_line(p, focus, start, rl->target_x,
-                                    rl->target_y + start->mdata->y);
+       if (!start->mdata)
+               goto done;
 
+       xypos = find_xy_line(p, focus, start, rl->target_x,
+                            rl->target_y + start->mdata->y, NULL);
+
+       if (xypos < 0)
+               goto done;
        /* xypos is the distance from start-of-line to the target */
-       if (xypos >= 0) {
-               struct mark *m2 = call_render_line_offset(
-                       focus, start, xypos);
-               if (m2)
-                       mark_to_mark(m, m2);
-               mark_free(m2);
+
+       m2 = call_render_line_offset(focus, start, xypos);
+       if (!m2)
+               goto done;
+
+       if (!mark_same(start, m)) {
+               /* This is a multi-line render and we aren't on
+                * the first line.  We might need a larger 'y'.
+                * For now, ensure that we move in the right
+                * direction.
+                * FIXME this loses target_x and can move up
+                * too far.  How to fix??
+                */
+               if (num > 0 && mark_ordered_not_same(m2, m))
+                       mark_to_mark(m2, m);
+               if (num < 0 && mark_ordered_not_same(m, m2))
+                       mark_to_mark(m2, m);
        }
+       mark_to_mark(m, m2);
+
+       mark_free(m2);
+
+done:
        rl->i_moved = 0;
        return 1;
 }
@@ -1567,7 +1830,7 @@ DEF_CMD(render_lines_notify_replace)
                 * ignoring it, and handle the fact that point moved.
                 */
                if (ci->mark2 == pt)
-                       pane_call(p, "point:moving", ci->focus, 0, pt);
+                       pane_call(p, "mark:moving", ci->focus, 0, pt);
        }
 
        if (strcmp(ci->key, "view:changed") == 0)
@@ -1710,16 +1973,16 @@ static void render_lines_register_map(void)
        /* Make it easy to stop ignoring point */
        key_add(rl_map, "Abort", &render_lines_abort);
 
+       key_add(rl_map, "Action", &render_lines_action);
+
        key_add(rl_map, "Close", &render_lines_close);
        key_add(rl_map, "Close:mark", &render_lines_close_mark);
-       key_add(rl_map, "Free", &edlib_do_free);
        key_add(rl_map, "Clone", &render_lines_clone);
        key_add(rl_map, "Refresh", &render_lines_refresh);
        key_add(rl_map, "Refresh:view", &render_lines_revise);
        key_add(rl_map, "Refresh:size", &render_lines_resize);
        key_add(rl_map, "Notify:clip", &render_lines_clip);
-       key_add(rl_map, "get-attr", &render_lines_get_attr);
-       key_add(rl_map, "point:moving", &render_lines_point_moving);
+       key_add(rl_map, "mark:moving", &render_lines_point_moving);
 
        key_add(rl_map, "doc:replaced", &render_lines_notify_replace);
        key_add(rl_map, "doc:replaced-attr", &render_lines_notify_replace);
@@ -1737,22 +2000,23 @@ REDEF_CMD(render_lines_attach)
        if (!rl_map)
                render_lines_register_map();
 
-       alloc(rl, pane);
-       rl->target_x = -1;
-       rl->target_y = -1;
-       rl->do_wrap = 1;
        p = ci->focus;
-       if (strcmp(ci->key, "attach-render-text") == 0)
+       if (strcmp(ci->key, "attach-render-text") == 0) {
                p = call_ret(pane, "attach-markup", p);
-       p = pane_register(p, 0, &render_lines_handle.c, rl);
-       if (!p) {
-               free(rl);
-               return Efail;
+               if (!p)
+                       p = ci->focus;
        }
+       p = pane_register(p, 0, &render_lines_handle.c);
+       if (!p)
+               return Efail;
+       rl = p->data;
+       rl->target_x = -1;
+       rl->target_y = -1;
+       rl->do_wrap = 1;
        rl->typenum = home_call(ci->focus, "doc:add-view", p) - 1;
        call("doc:request:doc:replaced", p);
        call("doc:request:doc:replaced-attr", p);
-       call("doc:request:point:moving", p);
+       call("doc:request:mark:moving", p);
 
        return comm_call(ci->comm2, "callback:attach", p);
 }