]> git.neil.brown.name Git - edlib.git/blob - render-lines.c
renderlines: don't consider m2 before calling revalidate_start
[edlib.git] / render-lines.c
1 /*
2  * Copyright Neil Brown ©2015-2021 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Rendering for any document which presents as a sequence of lines.
6  *
7  * The underlying document, or an intervening filter, must return lines of
8  * text in response to the "doc:render-line" command.
9  * This takes a mark and moves it to the end of the rendered line
10  * so that another call will produce another line.
11  * "doc:render-line" must always return a full line including '\n'
12  * unless the result would be bigger than the 'max' passed in ->num or,
13  * when ->num==-1, unless the rendering would go beyond the location in
14  * ->mark2.  In these cases it can stop before a '\n'.  In each case,
15  * the mark is moved to the end of the region that was rendered;
16  * This allows a mark to be found for a given character position, or a display
17  * position found for a given mark.
18  * For the standard 'render the whole line' functionality, ->num should
19  * be NO_NUMERIC
20  *
21  * The document or filter must also provide "doc:render-line-prev" which
22  * moves mark to a start-of-line.  If num is 0, then don't skip over any
23  * newlines.  If it is '1', then skip one newline.
24  *
25  * The returned line can contain attribute markings as <attr,attr>.  </>
26  * is used to pop most recent attributes.  << is used to include a
27  * literal '<'.  Lines generally contain UTF-8.  Control character '\n'
28  * is end of line and '\t' tabs 1-8 spaces.  '\f' marks end of page -
29  * nothing after this will be displayed.
30  *
31  * Other control characters should be rendered as
32  * e.g. <fg:red>^X</> - in particular, nul must not appear in the line.
33  *
34  * We store all start-of-line the marks found while rendering a pane in
35  * a 'view' on the document.  The line returned for a given mark is
36  * attached to extra space allocated for that mark.  When a change
37  * notification is received for a mark we discard that string.  So the
38  * string associated with a mark is certainly the string that would be
39  * rendered after that mark (though it may be truncated).  The set of
40  * marks in a view should always identify exactly the set of lines to be
41  * displayed.  Each mark should be at a start-of-line except possibly
42  * for the first and last.  The first may be internal to a long line,
43  * but the line rendering attached will always continue to the
44  * end-of-line.  We record the number of display lines in that first
45  * line.
46  * The last mark may also be mid-line, and it must never have an
47  * attached rendering.
48  * In the worst case of there being no newlines in the document, there
49  * will be precisely two marks: one contains a partial line and one that
50  * marks the end of that line.  When point moves outside that range a
51  * new start will be chosen before point using "doc:render-line-prev"
52  * and the old start is discarded.
53  *
54  * To render the pane we:
55  * 1/ call 'render-line-prev' on a mark at the point and look for that mark
56  *    in the view.
57  * 2/ If the mark matches and has a string, we have a starting point,
58  *    else we call "doc:render-line" and store the result, thus
59  *    producing a starting point.  We determine how many display lines
60  *    are needed to display this text-line and set 'y' accordingly.
61  *    At this point we have two marks: start and end, with known text of known
62  *    height between.
63  * 3/ Then we move outwards, back from the first mark and forward from
64  *    the last mark.  If we find a mark already in the view in the
65  *    desired direction with text attached it is correct and we use
66  *    that.  Otherwise we find start (when going backwards) and render a
67  *    new line.  Any old mark that is in the range is discarded.
68  * 4/ When we have a full set of marks and the full height of the pane,
69  *    we discard marks outside the range and start rendering from the
70  *    top.  ARG how is cursor drawn.
71  *
72  * If we already have correct marks on one side and not the other, we prefer
73  * to advance on that first side to maximize the amount of text that was common
74  * with the previous rendering of the page.
75  *
76  * Sometimes we need to render without a point.  In this case we start
77  * at the first mark in the view and move forward.  If we can we do this
78  * anyway, and only try the slow way if the target point wasn't found.
79  */
80
81 #define MARK_DATA_PTR struct pane
82 #define _GNU_SOURCE /*  for asprintf */
83 #include "core.h"
84 #include "misc.h"
85 #include <stdio.h> /* snprintf */
86
87 /*
88  * All functions involved in sending Draw and size requests
89  * to the display are given two panes: p and focus.
90  * 'p' is the pane where the drawing happens. 'focus' is the
91  * leaf on the current stack.
92  * These are different when the drawing is segmented into regions
93  * of the target pane, with light-weight panes being used to avoid
94  * having to refresh the whole target pane when the only change is
95  * in one region.
96  * The calls to the display are home_calls with 'focus' as the home
97  * pane, and 'p' as the focus.  The x,y co-ords are, as always,
98  * relative to the focus pane 'p'.
99  */
100
101
102 struct rl_data {
103         int             top_sol; /* true when first mark is at a start-of-line */
104         int             ignore_point;
105         int             skip_height; /* Skip display-lines for first "line" */
106         int             skip_line_height; /* height of lines in skip_height */
107         int             tail_height; /* display lines at eop not display */
108         int             cursor_line; /* line that contains the cursor starts
109                                       * on this line */
110         short           target_x, target_y;
111         short           i_moved;        /* I moved cursor, so don't clear
112                                          * target
113                                          */
114         int             do_wrap;
115         short           shift_left;
116         struct mark     *header;
117         int             typenum;
118         int             repositioned; /* send "render:reposition" when we know
119                                        * full position again.
120                                        */
121         short           lines; /* lines drawn before we hit eof */
122         short           cols; /* columns used for longest line */
123         short           margin; /* distance from top/bottom required for cursor */
124         bool            background_drawn;
125
126         /* If cursor not visible, we add this pane in bottom-right and place
127          * cursor there.
128          */
129         struct pane     *cursor_pane;
130 };
131
132 static void vmark_clear(struct mark *m safe)
133 {
134         if (m->mdata) {
135                 pane_close(m->mdata);
136                 m->mdata = NULL;
137         }
138 }
139
140 static void vmark_free(struct mark *m safe)
141 {
142         vmark_clear(m);
143         mark_free(m);
144 }
145
146 static void vmark_set(struct pane *p safe, struct mark *m safe, char *line)
147 {
148         if (!m->mdata)
149                 m->mdata = call_ret(pane, "attach-renderline", p);
150         if (m->mdata)
151                 pane_call(m->mdata, "render-line:set", p, 0, NULL, line);
152 }
153
154 static void vmark_invalidate(struct mark *m safe)
155 {
156         if (m->mdata)
157                 pane_call(m->mdata, "render-line:invalidate", m->mdata);
158 }
159
160 static bool vmark_is_valid(struct mark *m safe)
161 {
162         if (!m->mdata)
163                 return False;
164         return pane_attr_get_int(m->mdata, "render-line:valid", 0) == 1;
165 }
166
167 /* Returns 'true' at end-of-page */
168 static bool measure_line(struct pane *p safe, struct pane *focus safe,
169                          struct mark *mk safe, short cursor_offset)
170 {
171         struct pane *hp = mk->mdata;
172         int ret = 0;
173
174         if (hp) {
175                 pane_resize(hp, hp->x, hp->y, p->w, p->h);
176                 ret = pane_call(hp, "render-line:measure",
177                                 focus, cursor_offset);
178         }
179         /* end-of-page flag */
180         return ret == 2;
181 }
182
183 /* Returns offset of posx,posy */
184 static int find_xy_line(struct pane *p safe, struct pane *focus safe,
185                         struct mark *mk safe, short posx, short posy)
186 {
187         struct pane *hp = mk->mdata;
188         int ret = 0;
189
190         if (hp) {
191                 ret = pane_call(hp,
192                                 "render-line:findxy",
193                                 focus,
194                                 -1, NULL, NULL,
195                                 0, NULL, NULL,
196                                 posx - hp->x, posy - hp->y);
197         }
198         /* xypos */
199         return ret > 0 ? (ret - 1) : -1;
200 }
201
202 static bool draw_line(struct pane *p safe, struct pane *focus safe,
203                       struct mark *mk safe, short offset)
204 {
205         struct rl_data *rl = p->data;
206         struct pane *hp = mk->mdata;
207         int ret = 0;
208
209         if (hp) {
210                 ret = pane_call(hp, "render-line:draw", focus, offset);
211                 if (offset >= 0) {
212                         struct xy curs = pane_mapxy(hp, p,
213                                                     hp->cx, hp->cy, False);
214                         if (hp->cx < 0) {
215                                 p->cx = -1;
216                                 p->cy = -1;
217                         } else {
218                                 p->cx = curs.x;
219                                 p->cy = curs.y;
220                         }
221                 }
222                 if (hp->x + hp->w > rl->cols)
223                         rl->cols = hp->x + hp->w;
224         }
225         return ret == 2;
226 }
227
228 static struct mark *call_render_line_prev(struct pane *p safe,
229                                           struct mark *m safe,
230                                           int n, int *found)
231 {
232         int ret;
233         struct mark *m2;
234
235         if (m->viewnum < 0)
236                 return NULL;
237         ret = call("doc:render-line-prev", p, n, m);
238         if (ret <= 0) {
239                 /* if n>0 we can fail because start-of-file was found before
240                  * any newline.  In that case ret == Efail, and we return NULL.
241                  */
242                 if (found)
243                         *found = (ret == Efail);
244                 mark_free(m);
245                 return NULL;
246         }
247         if (ret < 0) {
248                 /* current line is start-of-file */
249                 mark_free(m);
250                 return NULL;
251         }
252
253         m2 = vmark_matching(m);
254         if (m2)
255                 mark_free(m);
256         else
257                 m2 = m;
258         return m2;
259 }
260
261 static void call_render_line(struct pane *home safe, struct pane *p safe,
262                              struct mark *start safe, struct mark **end)
263 {
264         struct mark *m, *m2;
265         char *s;
266
267         m = mark_dup_view(start);
268         if (doc_following(p, m) == WEOF) {
269                 /* We only create a subpane for EOF when it is at start
270                  * of line, else it is included in the preceding line.
271                  */
272                 call("doc:render-line-prev", p, 0, m);
273                 if (!mark_same(m, start)) {
274                         mark_free(m);
275                         vmark_clear(start);
276                         return;
277                 }
278                 s = "";
279         } else
280                 s = call_ret(strsave, "doc:render-line", p, NO_NUMERIC, m);
281
282         vmark_set(home, start, s);
283
284         m2 = vmark_matching(m);
285         if (m2)
286                 mark_free(m);
287         else
288                 m2 = m;
289         /*FIXME shouldn't be needed */
290         m2 = safe_cast m2;
291
292         /* Any mark between start and m2 must be discarded,
293          */
294         while ((m = vmark_next(start)) != NULL &&
295                m->seq < m2->seq) {
296                 if (end && m == *end)
297                         *end = m2;
298                 vmark_free(m);
299         }
300         /* Any mark at same location as m2 must go too. */
301         while ((m = vmark_next(m2)) != NULL &&
302                mark_same(m, m2)) {
303                 if (end && m == *end)
304                         *end = m2;
305                 vmark_free(m);
306         }
307 }
308
309 DEF_CMD(no_save)
310 {
311         return 1;
312 }
313
314 static struct mark *call_render_line_offset(struct pane *p safe,
315                                             struct mark *start safe, int offset)
316 {
317         struct mark *m;
318
319         m = mark_dup_view(start);
320         if (call_comm("doc:render-line", p, &no_save, offset, m) <= 0) {
321                 mark_free(m);
322                 return NULL;
323         }
324         return m;
325 }
326
327 DEF_CMD(get_len)
328 {
329         if (ci->str) {
330                 int l = strlen(ci->str);
331                 while (l >=4 && strncmp(ci->str+l-3, "</>", 3) == 0 &&
332                 ci->str[l-4] != '<')
333                         l -= 3;
334                 return l + 1;
335         } else
336                 return 1;
337 }
338
339 static int call_render_line_to_point(struct pane *p safe, struct mark *pm safe,
340                                      struct mark *start safe)
341 {
342         int len;
343         struct mark *m = mark_dup_view(start);
344
345         len = call_comm("doc:render-line", p, &get_len, -1, m, NULL, 0, pm);
346         mark_free(m);
347         if (len <= 0)
348                 return 0;
349
350         return len - 1;
351 }
352
353 /* Choose a new set of lines to display, and mark each one with a line marker.
354  * We start at pm and move both backwards and forwards one line at a time.
355  * We stop moving in one of the directions when
356  *  - we hit start/end of file
357  *  - when the edge in the *other* direction enters the previously visible
358  *     area (if there was one).  This increases stability of display when
359  *     we move off a line or 2.
360  *  - when we reach the given line count (vline).  A positive count restricts
361  *    backward movement, a negative restricts forwards movement.
362  */
363
364 static bool step_back(struct pane *p safe, struct pane *focus safe,
365                       struct mark **startp safe, struct mark **endp,
366                       short *y_pre safe, short *line_height_pre safe)
367 {
368         /* step backwards moving start */
369         struct rl_data *rl = p->data;
370         struct mark *m;
371         bool found_start = False;
372         struct mark *start = *startp;
373
374         if (!start)
375                 return True;
376         m = call_render_line_prev(focus, mark_dup_view(start),
377                                   1, &rl->top_sol);
378         if (!m) {
379                 /* no text before 'start' */
380                 found_start = True;
381         } else {
382                 short h = 0;
383                 start = m;
384                 if (!vmark_is_valid(start))
385                         call_render_line(p, focus, start, endp);
386                 measure_line(p, focus, start, -1);
387                 h = start->mdata ? start->mdata->h : 0;
388                 if (h) {
389                         *y_pre = h;
390                         *line_height_pre =
391                                 attr_find_int(start->mdata->attrs,
392                                               "line-height");
393                 } else
394                         found_start = True;
395         }
396         *startp = start;
397         return found_start;
398 }
399
400 static bool step_fore(struct pane *p safe, struct pane *focus safe,
401                       struct mark **startp safe, struct mark **endp safe,
402                       short *y_post safe, short *line_height_post safe)
403 {
404         struct mark *end = *endp;
405         bool found_end = False;
406
407         if (!end)
408                 return True;
409         if (!vmark_is_valid(end))
410                 call_render_line(p, focus, end, startp);
411         found_end = measure_line(p, focus, end, -1);
412         if (end->mdata)
413                 *y_post = end->mdata->h;
414         if (*y_post > 0 && end->mdata)
415                 *line_height_post =
416                         attr_find_int(end->mdata->attrs,
417                                       "line-height");
418         if (!end->mdata || !end->mdata->h)
419                 end = NULL;
420         else
421                 end = vmark_next(end);
422         if (!end) {
423                 found_end = 1;
424                 if (p->h >= *line_height_post *2)
425                         *y_post = p->h / 10;
426         }
427
428         *endp = end;
429         return found_end;
430 }
431
432 static int consume_space(struct pane *p safe, int y,
433                          short *y_prep safe, short *y_postp safe,
434                          short *lines_above safe, short *lines_below safe,
435                          int found_start, int found_end,
436                          int line_height_pre, int line_height_post)
437 {
438         int y_pre = *y_prep;
439         int y_post = *y_postp;
440
441         if (y_pre > 0 && y_post > 0) {
442                 int consume = (y_post < y_pre
443                                ? y_post : y_pre) * 2;
444                 int above, below;
445                 if (consume > p->h - y)
446                         consume = p->h - y;
447                 if (y_pre > y_post) {
448                         above = consume - (consume/2);
449                         below = consume/2;
450                 } else {
451                         below = consume - (consume/2);
452                         above = consume/2;
453                 }
454                 y += above + below;
455                 y_pre -= above;
456                 *lines_above += above / (line_height_pre?:1);
457                 y_post -= below;
458                 *lines_below += below / (line_height_post?:1);
459                 /* We have just consumed all of one of
460                  * lines_{above,below} so they are no longer
461                  * both > 0
462                  */
463         }
464         if (found_end && y_pre) {
465                 int consume = p->h - y;
466                 if (consume > y_pre)
467                         consume = y_pre;
468                 y_pre -= consume;
469                 y += consume;
470                 *lines_above += consume / (line_height_pre?:1);
471         }
472         if (found_start && y_post) {
473                 int consume = p->h - y;
474                 if (consume > y_post)
475                         consume = y_post;
476                 y_post -= consume;
477                 y += consume;
478                 *lines_below += consume / (line_height_post?:1);
479         }
480         *y_prep = y_pre;
481         *y_postp = y_post;
482         return y;
483 }
484
485 /*
486  * Choose new start/end to be displayed in the given pane.
487  * 'pm' must be displayed, and if vline is not NO_NUMERIC,
488  * pm should be displayed on that line of the display, where
489  * negative numbers count from the bottom of the page.
490  * Otherwise pm should be at least pm->margin from top and bottom.
491  * In no case should start-of-file be *after* top of display.
492  * If there is an existing display, move the display as little as
493  * possible while complying with the above.
494  *
495  * We start at 'pm' and move both forward and backward one line at a
496  * time measuring each line and assessing space used.
497  * - If the space above pm reaches positive vline, that will be top.
498  * - If the space below reaches negative vline, that will likely be bottom
499  * - If pm was before old top and we reach the old top going down,
500  *    and if space measured before pm has reached ->margin, we stop
501  *    moving upward.
502  * - If pm was after old bottom and we reach the old bottom going up
503  *    and if space measured after pm has reached ->margin, we stop
504  *    moving downward
505  *
506  * If we decide to stop moving in both directions, but have not
507  * reached EOF or full height of display, keep moving downwards.
508  */
509 static void find_lines(struct mark *pm safe, struct pane *p safe,
510                        struct pane *focus safe,
511                        int vline)
512 {
513         struct rl_data *rl = p->data;
514         struct mark *orig_top, *orig_bot;
515         struct mark *top, *bot;  // boundary of previous display
516         struct mark *m;
517         struct mark *start, *end; // current estimate for new display
518         short y = 0;
519         short lines_above = 0, lines_below = 0; /* distance from start/end
520                                                  * to pm.
521                                                  */
522         short offset; // pos of pm in rendering of that line
523         bool found_start = False, found_end = False;
524         /* y_pre and y_post are measurement from start/end that
525          * haven't yet been included into lines_above/lines_below.
526          */
527         short y_pre = 0, y_post = 0;
528         short line_height_pre = 1, line_height_post = 1;
529
530         orig_top = vmark_first(focus, rl->typenum, p);
531         orig_bot = vmark_last(focus, rl->typenum, p);
532         /* Protect top/bot from being freed by call_render_line */
533         if (orig_top)
534                 orig_top = mark_dup(orig_top);
535         if (orig_bot)
536                 orig_bot = mark_dup(orig_bot);
537
538         start = vmark_new(focus, rl->typenum, p);
539         if (!start)
540                 goto abort;
541         rl->repositioned = 1;
542         mark_to_mark(start, pm);
543         start = call_render_line_prev(focus, start, 0, &rl->top_sol);
544         if (!start)
545                 goto abort;
546         offset = call_render_line_to_point(focus, pm, start);
547         if (!vmark_is_valid(start))
548                 call_render_line(p, focus, start, NULL);
549         end = vmark_next(start);
550         /* Note: 'end' might be NULL if 'start' is end-of-file, otherwise
551          * call_render_line() will have created 'end' if it didn't exist.
552          */
553
554         rl->shift_left = 0;
555
556         /* ->cy is top of cursor, we want to measure from bottom */
557         if (start->mdata) {
558                 struct pane *hp = start->mdata;
559                 int curs_width;
560                 found_end = measure_line(p, focus, start, offset);
561
562                 curs_width = pane_attr_get_int(
563                         start->mdata, "curs_width", 1);
564                 while (!rl->do_wrap && hp->cx + curs_width >= p->w) {
565                         int shift = 8 * curs_width;
566                         if (shift > hp->cx)
567                                 shift = hp->cx;
568                         rl->shift_left += shift;
569                         measure_line(p, focus, start, offset);
570                 }
571                 line_height_pre = attr_find_int(start->mdata->attrs, "line-height");
572                 if (line_height_pre < 1)
573                         line_height_pre = 1;
574                 y_pre = start->mdata->cy + line_height_pre;
575                 y_post = start->mdata->h - y_pre;
576         } else {
577                 /* Should never happen */
578                 y_pre = 0;
579                 y_post = 0;
580         }
581         if (!end) {
582                 found_end = True;
583                 if (p->h > line_height_pre * 2)
584                         y_post += p->h / 10;
585                 else
586                         /* Small display, no space at EOF */
587                         y_post = 0;
588         }
589         y = 0;
590         if (rl->header && rl->header->mdata)
591                 y = rl->header->mdata->h;
592
593         /* We have start/end of the focus line.  When rendered this,
594          * plus header and eof-footed would use y_pre + y + y_post
595          * vertical space.
596          */
597
598         top = bot = NULL;
599         if (orig_bot && mark_ordered_or_same(orig_bot, start))
600                 /* could cross bot, so don't ignore it */
601                 bot = orig_bot;
602         if (orig_top && end && mark_ordered_or_same(end, orig_top))
603                 top = orig_top;
604         if (vline != NO_NUMERIC) {
605                 /* ignore current position - top/bot irrelevant */
606                 top = NULL;
607                 bot = NULL;
608         }
609
610         while ((!found_start || !found_end) && y < p->h) {
611                 if (vline != NO_NUMERIC) {
612                         if (!found_start && vline > 0 &&
613                             lines_above >= vline-1)
614                                 found_start = True;
615                         if (!found_end && vline < 0 &&
616                             lines_below >= -vline-1)
617                                 found_end = True;
618                 }
619                 if (!found_start && y_pre <= 0)
620                         found_start = step_back(p, focus, &start, &end,
621                                                 &y_pre, &line_height_pre);
622
623                 if (found_end && y_post && bot &&
624                     mark_ordered_or_same(start, bot))
625                         /* Extra vertical space gets inserted after EOF when
626                          * there is a long jump to get there, but if we hit 'bot'
627                          * soon when searching back, we discard any unused space.
628                          */
629                         y_post = 0;
630
631                 if (!found_end && bot &&
632                     mark_ordered_not_same(start, bot))
633                         /* Overlap original from below, so prefer to
634                          * maximize that overlap.
635                          */
636                         found_end = True;
637
638                 if (!found_end && bot && mark_same(start, bot) &&
639                     y_pre - rl->skip_height >= y_post)
640                         /* No overlap in marks yet, but over-lap in space,
641                          * so same result as above.
642                          */
643                         found_end = True;
644
645                 if (!found_end && y_post <= 0)
646                         /* step forwards */
647                         found_end = step_fore(p, focus, &start, &end,
648                                               &y_post, &line_height_post);
649
650                 if (!found_start && top && end &&
651                     mark_ordered_not_same(top, end))
652                         found_start = True;
653
654                 if (!found_start && top && end && mark_same(top, end)
655                     && y_post - rl->tail_height >= y_pre)
656                         found_start = True;
657
658                 y = consume_space(p, y, &y_pre, &y_post,
659                                   &lines_above, &lines_below,
660                                   found_start, found_end,
661                                   line_height_pre, line_height_post);
662         }
663         /* We might need to continue downwards even after found_end
664          * if there is more space.
665          */
666         found_end = end == NULL;
667         while (!found_end && y < p->h) {
668                 if (y_post <= 0)
669                         found_end = step_fore(p, focus, &start, &end,
670                                               &y_post, &line_height_post);
671                 y = consume_space(p, y, &y_pre, &y_post,
672                                   &lines_above, &lines_below,
673                                   found_start, found_end,
674                                   line_height_pre, line_height_post);
675         }
676
677         if (start->mdata && start->mdata->h <= y_pre) {
678                 y_pre = 0;
679                 m = vmark_next(start);
680                 vmark_free(start);
681                 start = m;
682         }
683         if (!start)
684                 goto abort;
685
686         rl->skip_height = y_pre;
687         rl->skip_line_height = line_height_pre;
688         rl->tail_height = y_post;
689         /* Now discard any marks outside start-end */
690         if (end && end->seq < start->seq)
691                 /* something confused, make sure we don't try to use 'end' after
692                  * freeing it.
693                  */
694                 end = start;
695         while ((m = vmark_prev(start)) != NULL)
696                 vmark_free(m);
697
698         if (end) {
699                 while ((m = vmark_next(end)) != NULL)
700                         vmark_free(m);
701
702                 vmark_clear(end);
703         }
704
705         y = 0;
706         if (rl->header && rl->header->mdata)
707                 y = rl->header->mdata->h;
708         y -= rl->skip_height;
709         for (m = vmark_first(focus, rl->typenum, p);
710              m && m->mdata ; m = vmark_next(m)) {
711                 struct pane *hp = m->mdata;
712                 pane_resize(hp, hp->x, y, hp->w, hp->h);
713                 y += hp->h;
714         }
715         pane_damaged(p, DAMAGED_REFRESH);
716         m = vmark_first(focus, rl->typenum, p);
717         if (!m || !orig_top || !mark_same(m, orig_top))
718                 rl->repositioned = 1;
719         m = vmark_last(focus, rl->typenum, p);
720         if (!m || !orig_bot || !mark_same(m, orig_bot))
721                 rl->repositioned = 1;
722
723 abort:
724         mark_free(orig_top);
725         mark_free(orig_bot);
726 }
727
728 DEF_CMD(cursor_handle)
729 {
730         return 0;
731 }
732
733 static int render(struct mark *pm, struct pane *p safe,
734                   struct pane *focus safe)
735 {
736         struct rl_data *rl = p->data;
737         short y = 0;
738         struct mark *m, *m2;
739         struct xy scale = pane_scale(focus);
740         char *s;
741         int hide_cursor = 0;
742         int cursor_drawn = 0;
743
744         s = pane_attr_get(focus, "hide-cursor");
745         if (s && strcmp(s, "yes") == 0)
746                 hide_cursor = 1;
747
748         rl->cols = 0;
749         m = vmark_first(focus, rl->typenum, p);
750         s = pane_attr_get(focus, "background");
751         if (s && strncmp(s, "call:", 5) == 0) {
752                 home_call(focus, "pane-clear", p);
753                 home_call(focus, s+5, p, 0, m);
754         } else if (rl->background_drawn)
755                 ;
756         else if (!s)
757                 home_call(focus, "pane-clear", p);
758         else if (strncmp(s, "color:", 6) == 0) {
759                 char *a = strdup(s);
760                 strcpy(a, "bg:");
761                 strcpy(a+3, s+6);
762                 home_call(focus, "pane-clear", p, 0, NULL, a);
763                 free(a);
764         } else if (strncmp(s, "image:", 6) == 0) {
765                 home_call(focus, "pane-clear", p);
766                 home_call(focus, "Draw:image", p, 1, NULL, s+6);
767         } else
768                 home_call(focus, "pane-clear", p);
769         rl->background_drawn = True;
770
771         if (rl->header && vmark_is_valid(rl->header)) {
772                 draw_line(p, focus, rl->header, -1);
773                 y = rl->header->mdata->h;
774         }
775         y -= rl->skip_height;
776
777         p->cx = p->cy = -1;
778         rl->cursor_line = 0;
779
780         while (m && m->mdata) {
781                 m2 = vmark_next(m);
782                 if (!hide_cursor && p->cx <= 0 && pm &&
783                     mark_ordered_or_same(m, pm) &&
784                     (!(m2 && doc_following(focus, m2) != WEOF) ||
785                      mark_ordered_not_same(pm, m2))) {
786                         short len = call_render_line_to_point(focus, pm,
787                                                               m);
788                         draw_line(p, focus, m, len);
789                         rl->cursor_line = m->mdata->y + m->mdata->cy;
790
791                         cursor_drawn = 1;
792                 } else {
793                         draw_line(p, focus, m, -1);
794                 }
795                 if (m->mdata)
796                         y = m->mdata->y + m->mdata->h;
797                 m = m2;
798         }
799         if (!cursor_drawn && !hide_cursor) {
800                 /* Place cursor in bottom right */
801                 struct pane *cp = rl->cursor_pane;
802                 short mwidth = -1;
803                 short lineheight;
804
805                 if (!cp) {
806                         cp = pane_register(p, -1, &cursor_handle);
807                         rl->cursor_pane = cp;
808                 }
809                 if (m)
810                         m2 = vmark_prev(m);
811                 else
812                         m2 = vmark_last(focus, rl->typenum, p);
813
814                 while (m2 && mwidth <= 0) {
815                         if (m2->mdata) {
816                                 mwidth = pane_attr_get_int(
817                                         m2->mdata, "curs_width", -1);
818                                 lineheight = pane_attr_get_int(
819                                         m2->mdata, "line-height", -1);
820                         }
821                         m2 = vmark_prev(m2);
822                 }
823
824                 if (mwidth <= 0) {
825                         mwidth = 1;
826                         lineheight = 1;
827                 }
828                 if (cp) {
829                         pane_resize(cp,
830                                     p->w - mwidth,
831                                     p->h - lineheight,
832                                     mwidth, lineheight);
833
834                         home_call(focus, "pane-clear", cp);
835                         home_call(focus, "Draw:text", cp, 0, NULL, " ",
836                                   scale.x, NULL, "",
837                                   0, lineheight-1);
838                 }
839         } else if (rl->cursor_pane) {
840                 pane_close(rl->cursor_pane);
841                 rl->cursor_pane = NULL;
842         }
843         return y;
844 }
845
846 DEF_CMD(render_lines_get_attr)
847 {
848         struct rl_data *rl = ci->home->data;
849
850         if (ci->str && strcmp(ci->str, "shift_left") == 0) {
851                 char ret[10];
852                 if (rl->do_wrap)
853                         return comm_call(ci->comm2, "cb", ci->focus,
854                                          0, NULL, "-1");
855                 snprintf(ret, sizeof(ret), "%d", rl->shift_left);
856                 return comm_call(ci->comm2, "cb", ci->focus, 0, NULL, ret);
857         }
858         return Efallthrough;
859 }
860
861 DEF_CMD(render_lines_point_moving)
862 {
863         struct pane *p = ci->home;
864         struct rl_data *rl = p->data;
865         struct mark *pt = call_ret(mark, "doc:point", ci->home);
866
867         if (ci->mark != pt)
868                 return 1;
869         /* Stop igoring point, because it is probably relevant now */
870         rl->ignore_point = 0;
871         if (!rl->i_moved)
872                 /* Someone else moved the point, so reset target column */
873                 rl->target_x = -1;
874         return 1;
875 }
876
877 static int revalidate_start(struct rl_data *rl safe,
878                             struct pane *p safe, struct pane *focus safe,
879                             struct mark *start safe, struct mark *pm,
880                             bool refresh_all)
881 {
882         int y;
883         bool on_screen = False;
884         struct mark *m, *m2;
885         bool found_end = False;
886
887         if (pm && !rl->do_wrap) {
888                 int prefix_len;
889                 int curs_width;
890                 /* Need to check if side-shift is needed on cursor line */
891                 m2 = mark_dup(pm);
892                 call("doc:render-line-prev", focus, 0, m2);
893
894                 m = vmark_at_or_before(focus, m2, rl->typenum, p);
895                 mark_free(m2);
896
897                 if (m && m->mdata &&
898                     (!vmark_is_valid(m) || refresh_all)) {
899                         pane_damaged(p, DAMAGED_REFRESH);
900                         call("doc:render-line-prev", focus, 0, m);
901                         call_render_line(p, focus, m, &start);
902                 }
903                 if (m && m->mdata) {
904                         struct pane *hp = m->mdata;
905                         int offset = call_render_line_to_point(focus,
906                                                                pm, m);
907                         measure_line(p, focus, m, offset);
908                         prefix_len = pane_attr_get_int(
909                                 m->mdata, "prefix_len", -1);
910                         curs_width = pane_attr_get_int(
911                                 m->mdata, "curs_width", 1);
912
913                         while (hp->cx + curs_width >= p->w) {
914                                 int shift = 8 * curs_width;
915                                 if (shift > hp->cx)
916                                         shift = hp->cx;
917                                 rl->shift_left += shift;
918                                 measure_line(p, focus, m, offset);
919                                 refresh_all = 1;
920                         }
921                         while (hp->cx < prefix_len &&
922                                rl->shift_left > 0 &&
923                                hp->cx + curs_width * 8*curs_width < p->w) {
924                                 int shift = 8 * curs_width;
925                                 if (shift > rl->shift_left)
926                                         shift = rl->shift_left;
927                                 rl->shift_left -= shift;
928                                 measure_line(p, focus, m, offset);
929                                 refresh_all = 1;
930                         }
931                 }
932         }
933         y = 0;
934         if (rl->header) {
935                 struct pane *hp = rl->header->mdata;
936                 if (refresh_all) {
937                         measure_line(p, focus, rl->header, -1);
938                         if (hp)
939                                 pane_resize(hp, hp->x, y, hp->w, hp->h);
940                 }
941                 if (hp)
942                         y = hp->h;
943         }
944         y -= rl->skip_height;
945         for (m = start; m && !found_end && y < p->h; m = vmark_next(m)) {
946                 struct pane *hp;
947                 if (refresh_all || !vmark_is_valid(m)) {
948                         pane_damaged(p, DAMAGED_REFRESH);
949                         call_render_line(p, focus, m, NULL);
950                 }
951                 found_end = measure_line(p, focus, m, -1);
952                 hp = m->mdata;
953                 if (!hp)
954                         break;
955
956                 if (y != hp->y) {
957                         pane_damaged(p, DAMAGED_REFRESH);
958                         pane_resize(hp, hp->x, y, hp->w, hp->h);
959                 }
960                 y += hp->h;
961                 m2 = vmark_next(m);
962                 if (pm && m == start && rl->skip_height > 0 && m2 &&
963                     mark_ordered_not_same(pm, m2)) {
964                         /* Point might be in this line, but off top
965                          * of the screen
966                          */
967                         int offset = call_render_line_to_point(focus,
968                                                                pm, m);
969                         if (offset >= 0) {
970                                 measure_line(p, focus, m, offset);
971                                 if (hp->cy >= rl->skip_height + rl->margin)
972                                         /* Cursor is visible on this line */
973                                         on_screen = True;
974                         }
975                 } else if (pm && y >= p->h && m->seq < pm->seq) {
976                         /* point might be in this line, but off end
977                          * of the screen
978                          */
979                         int offset = call_render_line_to_point(focus,
980                                                                pm, m);
981                         if (offset > 0) {
982                                 int lh;
983                                 measure_line(p, focus, m, offset);
984                                 lh = attr_find_int(hp->attrs,
985                                                    "line-height");
986                                 if (lh <= 0)
987                                         lh = 1;
988                                 if (y - hp->h + hp->cy <= p->h - lh - rl->margin) {
989                                         /* Cursor is on screen */
990                                         on_screen = True;
991                                 }
992                         }
993                 } else if (pm && mark_ordered_or_same(m, pm) && m2 &&
994                            mark_ordered_or_same(pm, m2)) {
995                         if (rl->margin == 0)
996                                 on_screen = True;
997                         else {
998                                 int offset = call_render_line_to_point(
999                                         focus, pm, m);
1000                                 if (offset > 0) {
1001                                         int lh;
1002                                         int cy;
1003                                         measure_line(p, focus, m, offset);
1004                                         lh = attr_find_int(hp->attrs,
1005                                                            "line-height");
1006                                         cy = y - hp->h + hp->cy;
1007                                         if (cy >= rl->margin && cy <= p->h - rl->margin - lh)
1008                                                 /* Cursor at least margin from edge */
1009                                                 on_screen = True;
1010                                 }
1011                         }
1012                 }
1013         }
1014         if (y >= p->h)
1015                 rl->tail_height = p->h - y;
1016         else
1017                 rl->tail_height = 0;
1018         if (m) {
1019                 vmark_clear(m);
1020                 while ((m2 = vmark_next(m)) != NULL) {
1021                         /* end of view has clearly changed */
1022                         rl->repositioned = 1;
1023                         vmark_free(m2);
1024                 }
1025         }
1026         if (!pm || on_screen) {
1027                 if (rl->repositioned) {
1028                         rl->repositioned = 0;
1029                         call("render:reposition", focus,
1030                              rl->lines, vmark_first(focus,
1031                                                     rl->typenum,
1032                                                     p), NULL,
1033                              rl->cols, vmark_last(focus,
1034                                                   rl->typenum,
1035                                                   p), NULL,
1036                              p->cx, p->cy);
1037                 }
1038                 return 1;
1039         }
1040         return 0;
1041 }
1042
1043 DEF_CMD(render_lines_revise)
1044 {
1045         struct pane *p = ci->home;
1046         struct pane *focus = ci->focus;
1047         struct rl_data *rl = p->data;
1048         struct mark *pm = NULL;
1049         struct mark *m1, *m2;
1050         bool refresh_all = False;
1051         char *hdr;
1052         char *a;
1053
1054         a = pane_attr_get(focus, "render-wrap");
1055         if (rl->do_wrap != (!a || strcmp(a, "yes") ==0)) {
1056                 rl->do_wrap = (!a || strcmp(a, "yes") ==0);
1057                 refresh_all = True;
1058         }
1059
1060         rl->margin = pane_attr_get_int(focus, "render-vmargin", 0);
1061         if (rl->margin >= p->h/2)
1062                 rl->margin= p->h/2;
1063
1064         hdr = pane_attr_get(focus, "heading");
1065         if (hdr && !*hdr)
1066                 hdr = NULL;
1067
1068         if (hdr) {
1069                 if (!rl->header)
1070                         rl->header = vmark_new(focus, MARK_UNGROUPED, NULL);
1071                 if (rl->header) {
1072                         vmark_set(p, rl->header, hdr);
1073                         measure_line(p, focus, rl->header, -1);
1074                 }
1075         } else if (rl->header) {
1076                 vmark_free(rl->header);
1077                 rl->header = NULL;
1078         }
1079
1080         if (!rl->ignore_point)
1081                 pm = call_ret(mark, "doc:point", focus);
1082         m1 = vmark_first(focus, rl->typenum, p);
1083         m2 = vmark_last(focus, rl->typenum, p);
1084
1085         if (m1 && !vmark_is_valid(m1))
1086                 /* newline before might have been deleted, better check */
1087                 call("doc:render-line-prev", focus, 0, m1);
1088         // FIXME double check that we invalidate line before any change...
1089
1090         if (m1 && m2 &&
1091             (!pm || (mark_ordered_or_same(m1,pm)))) {
1092                 /* We maybe be able to keep m1 as start, if things work out.
1093                  * So check all sub-panes are still valid and properly
1094                  * positioned.
1095                  */
1096                 if (revalidate_start(rl, p, focus, m1, pm, refresh_all))
1097                         return 1;
1098         }
1099         /* Need to find a new top-of-display */
1100         if (!pm)
1101                 pm = call_ret(mark, "doc:point", focus);
1102         if (!pm)
1103                 /* Don't know what to do here... */
1104                 return 1;
1105         find_lines(pm, p, focus, NO_NUMERIC);
1106         rl->repositioned = 0;
1107         call("render:reposition", focus,
1108              rl->lines, vmark_first(focus, rl->typenum, p), NULL,
1109              rl->cols, vmark_last(focus, rl->typenum, p), NULL,
1110              p->cx, p->cy);
1111         return 1;
1112 }
1113
1114 DEF_CMD(render_lines_refresh)
1115 {
1116         struct pane *p = ci->home;
1117         struct pane *focus = ci->focus;
1118         struct rl_data *rl = p->data;
1119         struct mark *m, *pm = NULL;
1120
1121         //pane_damaged(p, DAMAGED_VIEW);
1122
1123         pm = call_ret(mark, "doc:point", focus);
1124
1125         m = vmark_first(focus, rl->typenum, p);
1126
1127         if (!m)
1128                 return 1;
1129
1130         rl->lines = render(pm, p, focus);
1131
1132         return 1;
1133 }
1134
1135 DEF_CMD(render_lines_close)
1136 {
1137         struct pane *p = ci->home;
1138         struct rl_data *rl = p->data;
1139         struct mark *m;
1140
1141         while ((m = vmark_first(p, rl->typenum, p)) != NULL)
1142                 vmark_free(m);
1143         if (rl->header)
1144                 vmark_free(rl->header);
1145         rl->header = NULL;
1146
1147         call("doc:del-view", p, rl->typenum);
1148         return 1;
1149 }
1150
1151 DEF_CMD(render_lines_abort)
1152 {
1153         struct pane *p = ci->home;
1154         struct rl_data *rl = p->data;
1155
1156         rl->ignore_point = 0;
1157         rl->target_x = -1;
1158
1159         pane_damaged(p, DAMAGED_VIEW);
1160
1161         /* Allow other handlers to complete the Abort */
1162         return Efallthrough;
1163 }
1164
1165 DEF_CMD(render_lines_move)
1166 {
1167         /*
1168          * Find a new 'top' for the displayed region so that render()
1169          * will draw from there.
1170          * When moving backwards we move back a line and render it.
1171          * When moving forwards we render and then step forward
1172          * At each point we count the number of display lines that result.
1173          * When we choose a new start, we delete all earlier marks.
1174          * We also delete marks before current top when moving forward
1175          * where there are more than a page full.
1176          */
1177         struct pane *p = ci->home;
1178         struct pane *focus = ci->focus;
1179         int rpt = RPT_NUM(ci);
1180         struct rl_data *rl = p->data;
1181         struct mark *top, *old_top;
1182         int pagesize = p->h / 10;
1183
1184         top = vmark_first(focus, rl->typenum, p);
1185         if (!top)
1186                 return Efallthrough;
1187
1188         old_top = mark_dup(top);
1189         if (strcmp(ci->key, "Move-View-Large") == 0)
1190                 pagesize = p->h * 9 / 10;
1191         rpt *= pagesize ?: 1;
1192
1193         rl->ignore_point = 1;
1194
1195         if (rl->skip_line_height <= 0)
1196                 rl->skip_line_height = 1;
1197
1198         if (rpt < 0) {
1199                 /* Need to add new lines at the top and remove
1200                  * at the bottom.
1201                  */
1202                 while (rpt < 0) {
1203                         short y = 0;
1204                         struct mark *m;
1205                         struct mark *prevtop = top;
1206
1207                         if (rl->skip_height) {
1208                                 rl->skip_height -= rl->skip_line_height;
1209                                 if (rl->skip_height < rl->skip_line_height/2)
1210                                         rl->skip_height = 0;
1211                                 rpt += rl->skip_line_height;
1212                                 if (rpt > 0)
1213                                         rpt = 0;
1214                                 continue;
1215                         }
1216
1217                         m = mark_dup_view(top);
1218                         top = call_render_line_prev(focus, m,
1219                                                     1, &rl->top_sol);
1220                         if (!top && doc_prior(focus, prevtop) != WEOF) {
1221                                 /* Double check - maybe a soft top-of-file */
1222                                 m = mark_dup(prevtop);
1223                                 doc_prev(focus, m);
1224                                 top = call_render_line_prev(focus, m,
1225                                                             1, &rl->top_sol);
1226                         }
1227                         if (!top)
1228                                 break;
1229                         m = top;
1230                         while (m && m->seq < prevtop->seq &&
1231                                !mark_same(m, prevtop)) {
1232                                 if (!vmark_is_valid(m))
1233                                         call_render_line(p, focus, m, NULL);
1234                                 if (m->mdata == NULL) {
1235                                         rpt = 0;
1236                                         break;
1237                                 }
1238                                 measure_line(p, focus, m, -1);
1239                                 y += m->mdata->h;
1240                                 m = vmark_next(m);
1241                         }
1242                         /* FIXME remove extra lines, maybe add */
1243                         rl->skip_height = y;
1244                 }
1245         } else {
1246                 /* Need to remove lines from top */
1247                 if (!vmark_is_valid(top))
1248                         call_render_line(p, focus, top, NULL);
1249                 measure_line(p, focus, top, -1);
1250                 while (top && top->mdata && rpt > 0) {
1251                         short y = 0;
1252
1253                         y = top->mdata->h;
1254                         if (rpt < y - rl->skip_height) {
1255                                 rl->skip_height += rpt;
1256                                 break;
1257                         }
1258                         rpt -= y - rl->skip_height;
1259                         rl->skip_height = 0;
1260                         top = vmark_next(top);
1261                         if (!top)
1262                                 break;
1263                         if (!vmark_is_valid(top))
1264                                 call_render_line(p, focus, top, NULL);
1265                         measure_line(p, focus, top, -1);
1266                 }
1267                 if (top && top->mdata) {
1268                         /* We didn't fall off the end, so it is OK to remove
1269                          * everything before 'top'
1270                          */
1271                         struct mark *old;
1272                         while ((old = vmark_first(focus, rl->typenum, p)) != NULL &&
1273                                old != top)
1274                                 vmark_free(old);
1275                 }
1276         }
1277         rl->repositioned = 1;
1278         pane_damaged(ci->home, DAMAGED_VIEW);
1279         top = vmark_first(focus, rl->typenum, p);
1280         if (top && mark_same(top, old_top)) {
1281                 mark_free(old_top);
1282                 return 2;
1283         }
1284         mark_free(old_top);
1285         return 1;
1286 }
1287
1288 static char *get_active_tag(const char *a)
1289 {
1290         char *t;
1291         char *c;
1292
1293         if (!a)
1294                 return NULL;
1295         t = strstr(a, ",active-tag:");
1296         if (!t)
1297                 return NULL;
1298         t += 12;
1299         c = strchr(t, ',');
1300         return strndup(t, c?c-t: (int)strlen(t));
1301 }
1302
1303 DEF_CMD(render_lines_set_cursor)
1304 {
1305         /* ->num is
1306          * 1 if this resulted from a click
1307          * 2 if from a release
1308          * 3 if from motion
1309          * 0 any other reason.
1310          */
1311         struct pane *p = ci->home;
1312         struct pane *focus = ci->focus;
1313         struct rl_data *rl = p->data;
1314         struct mark *m;
1315         struct mark *m2 = NULL;
1316         struct xy cih;
1317         int xypos;
1318
1319         cih = pane_mapxy(ci->focus, ci->home,
1320                          ci->x >= 0 ? ci->x : p->cx >= 0 ? p->cx : 0,
1321                          ci->y >= 0 ? ci->y : p->cy >= 0 ? p->cx : 0,
1322                          False);
1323
1324         m = vmark_first(p, rl->typenum, p);
1325
1326         while (m && m->mdata && m->mdata->y + m->mdata->h <= cih.y)
1327                 m = vmark_next(m);
1328
1329         if (!m)
1330                 /* There is nothing rendered? */
1331                 return 1;
1332         if (!m->mdata) {
1333                 /* chi is after the last visible content, and m is the end
1334                  * of that content (possible EOF) so move there
1335                  */
1336         } else {
1337                 if (cih.y < m->mdata->y)
1338                         cih.y = m->mdata->y;
1339                 xypos = find_xy_line(p, focus, m, cih.x, cih.y);
1340                 if (xypos >= 0)
1341                         m2 = call_render_line_offset(focus, m, xypos);
1342         }
1343         if (m2) {
1344                 char *tag, *xyattr;
1345
1346                 if (ci->num == 2) { /* Mouse release */
1347                         xyattr = pane_attr_get(m->mdata, "xyattr");
1348                         tag = get_active_tag(xyattr);
1349                         if (tag) {
1350                                 char *c = NULL;
1351                                 asprintf(&c, "Mouse-Activate:%s", tag);
1352                                 if (c)
1353                                         call(c, focus, 0, m2, tag,
1354                                              0, ci->mark, xyattr);
1355                                 free(c);
1356                         }
1357                 }
1358                 m = m2;
1359         } else {
1360                 /* m is the closest we'll get */
1361         }
1362
1363         if (ci->mark)
1364                 mark_to_mark(ci->mark, m);
1365         else
1366                 call("Move-to", focus, 0, m);
1367         mark_free(m2);
1368
1369         return 1;
1370 }
1371
1372 DEF_CMD(render_lines_move_pos)
1373 {
1374         struct pane *p = ci->home;
1375         struct pane *focus = ci->focus;
1376         struct rl_data *rl = p->data;
1377         struct mark *pm = ci->mark;
1378         struct mark *top, *bot;
1379
1380         if (!pm)
1381                 return Enoarg;
1382         rl->ignore_point = 1;
1383         top = vmark_first(focus, rl->typenum, p);
1384         bot = vmark_last(focus, rl->typenum, p);
1385         if (top && rl->skip_height)
1386                 /* top line not fully displayed, being in that line is
1387                  * not sufficient */
1388                 top = vmark_next(top);
1389         if (bot)
1390                 /* last line might not be fully displayed, so don't assume */
1391                 bot = vmark_prev(bot);
1392         if (!top || !bot ||
1393             !mark_ordered_or_same(top, pm) ||
1394             !mark_ordered_not_same(pm, bot))
1395                 /* pos not displayed */
1396                 find_lines(pm, p, focus, NO_NUMERIC);
1397         pane_damaged(p, DAMAGED_REFRESH);
1398         /* FIXME this should only be done in the above find_lines(), but
1399          * search currently depends on this incorrect behaviour
1400          */
1401         rl->repositioned = 1;
1402         return 1;
1403 }
1404
1405 DEF_CMD(render_lines_view_line)
1406 {
1407         struct pane *p = ci->home;
1408         struct pane *focus = ci->focus;
1409         struct rl_data *rl = p->data;
1410         struct mark *pm = ci->mark;
1411         struct mark *top;
1412         int line = ci->num;
1413
1414         if (!pm)
1415                 return Enoarg;
1416         if (line == NO_NUMERIC)
1417                 return Einval;
1418
1419         while ((top = vmark_first(focus, rl->typenum, p)) != NULL)
1420                 vmark_free(top);
1421
1422         rl->ignore_point = 1;
1423         find_lines(pm, p, focus, line);
1424         pane_damaged(p, DAMAGED_REFRESH);
1425         return 1;
1426 }
1427
1428 DEF_CMD(render_lines_move_line)
1429 {
1430         /* FIXME should be able to select between display lines
1431          * and content lines - different when a line wraps.
1432          * For now just content lines.
1433          * target_x and target_y are the target location in a line
1434          * relative to the start of line.
1435          * We use doc:EOL to find a suitable start of line, then
1436          * render that line and find the last location not after x,y
1437          */
1438         struct pane *p = ci->home;
1439         struct pane *focus = ci->focus;
1440         struct rl_data *rl = p->data;
1441         int num;
1442         int xypos = -1;
1443         struct mark *m = ci->mark;
1444         struct mark *start;
1445
1446         if (!m)
1447                 m = call_ret(mark, "doc:point", focus);
1448         if (!m)
1449                 return Efail;
1450
1451         if (rl->target_x < 0) {
1452                 rl->target_x = p->cx;
1453                 rl->target_y = p->cy - rl->cursor_line;
1454         }
1455         if (rl->target_x < 0)
1456                 /* maybe not displayed yet */
1457                 rl->target_x = rl->target_y = 0;
1458
1459         rl->i_moved = 1;
1460         num = RPT_NUM(ci);
1461         if (num < 0)
1462                 num -= 1;
1463         else
1464                 num += 1;
1465         if (!call("doc:EOL", ci->focus, num, m)) {
1466                 rl->i_moved = 0;
1467                 return Efail;
1468         }
1469         if (RPT_NUM(ci) > 0) {
1470                 /* at end of target line, move to start */
1471                 if (!call("doc:EOL", ci->focus, -1, m)) {
1472                         rl->i_moved = 0;
1473                         return Efail;
1474                 }
1475         }
1476
1477         start = vmark_new(focus, rl->typenum, p);
1478
1479         if (start) {
1480                 mark_to_mark(start, m);
1481                 start = call_render_line_prev(focus, start, 0, NULL);
1482         }
1483
1484         if (!start) {
1485                 pane_damaged(p, DAMAGED_VIEW);
1486                 rl->i_moved = 0;
1487                 return 1;
1488         }
1489         if (vmark_first(focus, rl->typenum, p) == start) {
1490                 /* New first mark, so view will have changed */
1491                 rl->repositioned = 1;
1492         }
1493
1494         if (rl->target_x == 0 && rl->target_y == 0) {
1495                 /* No need to move to target column - already there.
1496                  * This simplifies life for render-complete which is
1497                  * always at col 0, and messes with markup a bit.
1498                  */
1499                 rl->i_moved = 0;
1500                 return 1;
1501         }
1502         /* FIXME only do this if point is active/volatile, or
1503          * if start->mdata is NULL
1504          */
1505         call_render_line(p, focus, start, NULL);
1506         if (start->mdata)
1507                 xypos = find_xy_line(p, focus, start, rl->target_x,
1508                                      rl->target_y + start->mdata->y);
1509
1510         /* xypos is the distance from start-of-line to the target */
1511         if (xypos >= 0) {
1512                 struct mark *m2 = call_render_line_offset(
1513                         focus, start, xypos);
1514                 if (m2)
1515                         mark_to_mark(m, m2);
1516                 mark_free(m2);
1517         }
1518         rl->i_moved = 0;
1519         return 1;
1520 }
1521
1522 DEF_CMD(render_lines_notify_replace)
1523 {
1524         struct pane *p = ci->home;
1525         struct rl_data *rl = p->data;
1526         struct mark *start = ci->mark;
1527         struct mark *end = ci->mark2;
1528         struct mark *first;
1529
1530         if (strcmp(ci->key, "doc:replaced") == 0) {
1531                 struct mark *pt = call_ret(mark, "doc:point", ci->home);
1532
1533                 /* If anyone changes the doc, reset the target.  This might
1534                  * be too harsh, but I mainly want target tracking for
1535                  * close-in-time movement, so it probably doesn't matter.
1536                  */
1537                 rl->target_x = -1;
1538
1539                 /* If the replacement happened at 'point', then stop
1540                  * ignoring it.
1541                  */
1542                 if (ci->mark2 == pt)
1543                         rl->ignore_point = 0;
1544         }
1545
1546         if (!start && !end) {
1547                 /* No marks given - assume everything changed */
1548                 struct mark *m;
1549                 for (m = vmark_first(p, rl->typenum, p);
1550                      m;
1551                      m = vmark_next(m))
1552                         vmark_invalidate(m);
1553
1554                 pane_damaged(p, DAMAGED_VIEW);
1555                 return Efallthrough;
1556         }
1557
1558         if (start && end && start->seq > end->seq) {
1559                 start = ci->mark2;
1560                 end = ci->mark;
1561         }
1562
1563         if (strcmp(ci->key, "doc:replaced") == 0) {
1564                 first = vmark_first(ci->home, rl->typenum, p);
1565                 if (first && start &&  end && mark_same(first, end))
1566                         /* Insert just before visible region */
1567                         mark_to_mark(first, start);
1568         }
1569
1570         if (start) {
1571                 start = vmark_at_or_before(ci->home, start, rl->typenum, p);
1572                 if (!start)
1573                         start = vmark_first(ci->home, rl->typenum, p);
1574         } else {
1575                 start = vmark_at_or_before(ci->home, end, rl->typenum, p);
1576                 if (!start)
1577                         /* change is before visible region */
1578                         return Efallthrough;
1579                 /* FIXME check 'start' is at least 'num' before end */
1580         }
1581         if (end) {
1582                 end = vmark_at_or_before(ci->home, end, rl->typenum, p);
1583                 if (!end)
1584                         end = vmark_last(ci->home, rl->typenum, p);
1585         } else if (start) { /* smatch needs to know start in not NULL */
1586                 end = vmark_at_or_before(ci->home, start, rl->typenum, p);
1587                 if (!end)
1588                         end = vmark_first(ci->home, rl->typenum, p);
1589                 if (!end)
1590                         return Efallthrough;
1591                 if (vmark_next(end))
1592                         end = vmark_next(end);
1593                 /* FIXME check that 'end' is at least 'num' after start */
1594         }
1595
1596         if (!end || !start)
1597                 /* Change outside visible region */
1598                 return Efallthrough;
1599
1600         while (end && mark_ordered_or_same(start, end)) {
1601                 vmark_invalidate(end);
1602                 end = vmark_prev(end);
1603         }
1604         /* Must be sure to invalidate the line *before* the change */
1605         if (end)
1606                 vmark_invalidate(end);
1607
1608         pane_damaged(p, DAMAGED_VIEW);
1609
1610         return Efallthrough;
1611 }
1612
1613 DEF_CMD(render_lines_clip)
1614 {
1615         struct rl_data *rl = ci->home->data;
1616
1617         marks_clip(ci->home, ci->mark, ci->mark2, rl->typenum, ci->home,
1618                    !!ci->num);
1619         if (rl->header)
1620                 mark_clip(rl->header, ci->mark, ci->mark2, !!ci->num);
1621         return Efallthrough;
1622 }
1623
1624 DEF_CMD(render_lines_attach);
1625 DEF_CMD(render_lines_clone)
1626 {
1627         struct pane *parent = ci->focus;
1628
1629         render_lines_attach.func(ci);
1630         pane_clone_children(ci->home, parent->focus);
1631         return 1;
1632 }
1633
1634 DEF_CMD(render_lines_resize)
1635 {
1636         struct pane *p = ci->home;
1637         struct rl_data *rl = p->data;
1638         struct mark *m;
1639
1640         for (m = vmark_first(p, rl->typenum, p);
1641              m;
1642              m = vmark_next(m))
1643                 vmark_invalidate(m);
1644         rl->background_drawn = False;
1645         pane_damaged(p, DAMAGED_VIEW);
1646
1647         /* Allow propagation to children */
1648         return 0;
1649 }
1650
1651 static struct map *rl_map;
1652
1653 DEF_LOOKUP_CMD(render_lines_handle, rl_map);
1654
1655 static void render_lines_register_map(void)
1656 {
1657         rl_map = key_alloc();
1658
1659         key_add(rl_map, "Move-View-Small", &render_lines_move);
1660         key_add(rl_map, "Move-View-Large", &render_lines_move);
1661         key_add(rl_map, "Move-View-Pos", &render_lines_move_pos);
1662         key_add(rl_map, "Move-View-Line", &render_lines_view_line);
1663         key_add(rl_map, "Move-CursorXY", &render_lines_set_cursor);
1664         key_add(rl_map, "Move-Line", &render_lines_move_line);
1665
1666         /* Make it easy to stop ignoring point */
1667         key_add(rl_map, "Abort", &render_lines_abort);
1668
1669         key_add(rl_map, "Close", &render_lines_close);
1670         key_add(rl_map, "Free", &edlib_do_free);
1671         key_add(rl_map, "Clone", &render_lines_clone);
1672         key_add(rl_map, "Refresh", &render_lines_refresh);
1673         key_add(rl_map, "Refresh:view", &render_lines_revise);
1674         key_add(rl_map, "Refresh:size", &render_lines_resize);
1675         key_add(rl_map, "Notify:clip", &render_lines_clip);
1676         key_add(rl_map, "get-attr", &render_lines_get_attr);
1677         key_add(rl_map, "point:moving", &render_lines_point_moving);
1678
1679         key_add(rl_map, "doc:replaced", &render_lines_notify_replace);
1680         /* view:changed is sent to a tile when the display might need
1681          * to change, even though the doc may not have*/
1682         key_add(rl_map, "view:changed", &render_lines_notify_replace);
1683 }
1684
1685 REDEF_CMD(render_lines_attach)
1686 {
1687         struct rl_data *rl;
1688         struct pane *p;
1689
1690         if (!rl_map)
1691                 render_lines_register_map();
1692
1693         alloc(rl, pane);
1694         rl->target_x = -1;
1695         rl->target_y = -1;
1696         rl->do_wrap = 1;
1697         p = ci->focus;
1698         if (strcmp(ci->key, "attach-render-text") == 0)
1699                 p = call_ret(pane, "attach-markup", p);
1700         p = pane_register(p, 0, &render_lines_handle.c, rl);
1701         if (!p) {
1702                 free(rl);
1703                 return Efail;
1704         }
1705         rl->typenum = home_call(ci->focus, "doc:add-view", p) - 1;
1706         call("doc:request:doc:replaced", p);
1707         call("doc:request:point:moving", p);
1708
1709         return comm_call(ci->comm2, "callback:attach", p);
1710 }
1711
1712 void edlib_init(struct pane *ed safe)
1713 {
1714         call_comm("global-set-command", ed, &render_lines_attach, 0, NULL,
1715                   "attach-render-lines");
1716         call_comm("global-set-command", ed, &render_lines_attach, 0, NULL,
1717                   "attach-render-text");
1718 }