]> git.neil.brown.name Git - edlib.git/blob - render-lines.c
render-lines: fix vline positioning.
[edlib.git] / render-lines.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <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  * ->num < 0.  In these cases it can stop before a '\n'.  In each case,
14  * the mark is moved to the end of the region that was rendered;
15  * This allows a mark to be found for a given character position.
16  * If mark2 is given, the offset in the rendering when mark2 is reached
17  * is reported as ->num in the callback.
18  * For the standard 'render the whole line' functionality, ->num should
19  * be negative.
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 struct rl_data {
102         int             top_sol; /* true when first mark is at a start-of-line */
103         int             ignore_point;
104         int             skip_height; /* Skip display-lines for first "line" */
105         int             skip_line_height; /* height of lines in skip_height */
106         int             tail_height; /* display lines at eop not display */
107         int             cursor_line; /* line that contains the cursor starts
108                                       * on this line */
109         short           target_x, target_y;
110         short           i_moved;        /* I moved cursor, so don't clear
111                                          * target
112                                          */
113         short           do_wrap;
114         short           shift_locked;
115         short           shift_left;
116         short           shift_left_last_refresh;
117         struct mark     *header;
118         int             typenum;
119         int             repositioned; /* send "render:reposition" when we know
120                                        * full position again.
121                                        */
122         short           lines; /* lines drawn before we hit eof */
123         short           cols; /* columns used for longest line */
124         short           margin; /* distance from top/bottom required for cursor */
125         bool            background_drawn;
126         bool            background_uniform;
127
128         /* If cursor not visible, we add this pane in bottom-right and place
129          * cursor there.
130          */
131         struct pane     *cursor_pane;
132 };
133
134 static void vmark_clear(struct mark *m safe)
135 {
136         if (m->mdata) {
137                 pane_close(m->mdata);
138                 m->mdata = NULL;
139         }
140 }
141
142 static void vmark_free(struct mark *m safe)
143 {
144         vmark_clear(m);
145         mark_free(m);
146 }
147
148 static void vmark_set(struct pane *p safe, struct mark *m safe, char *line safe)
149 {
150         if (!m->mdata)
151                 m->mdata = call_ret(pane, "attach-renderline", p, -1);
152         if (m->mdata)
153                 pane_call(m->mdata, "render-line:set", p, -1, NULL, line);
154 }
155
156 static void vmark_invalidate(struct mark *m safe)
157 {
158         if (m->mdata)
159                 pane_damaged(m->mdata, DAMAGED_VIEW);
160 }
161
162 static bool vmark_is_valid(struct mark *m safe)
163 {
164         return mark_valid(m) && m->mdata && !(m->mdata->damaged & DAMAGED_VIEW);
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 (mark_valid(mk) && 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 void draw_line(struct pane *p safe, struct pane *focus safe,
203                       struct mark *mk safe, short offset, bool refresh_all)
204 {
205         struct pane *hp = mk->mdata;
206
207         if (hp &&
208             (refresh_all || hp->damaged & DAMAGED_REFRESH)) {
209                 hp->damaged &= ~DAMAGED_REFRESH;
210                 pane_call(hp, "render-line:draw", focus, offset);
211         }
212 }
213
214 static struct mark *call_render_line_prev(struct pane *p safe,
215                                           struct mark *m safe,
216                                           int n, int *found)
217 {
218         int ret;
219         struct mark *m2;
220
221         if (m->viewnum < 0) {
222                 mark_free(m);
223                 return NULL;
224         }
225         ret = call("doc:render-line-prev", p, n, m);
226         if (ret <= 0) {
227                 /* if n>0 we can fail because start-of-file was found before
228                  * any newline.  In that case ret == Efail, and we return NULL.
229                  */
230                 if (found)
231                         *found = (ret == Efail);
232                 mark_free(m);
233                 return NULL;
234         }
235         if (ret < 0) {
236                 /* current line is start-of-file */
237                 mark_free(m);
238                 return NULL;
239         }
240
241         m2 = vmark_matching(m);
242         if (m2)
243                 mark_free(m);
244         else
245                 m2 = m;
246         return m2;
247 }
248
249 static void call_render_line(struct pane *home safe, struct pane *p safe,
250                              struct mark *start safe, struct mark **end)
251 {
252         struct mark *m, *m2;
253         char *s;
254
255         if (vmark_is_valid(start))
256                 return;
257
258         m = mark_dup_view(start);
259         if (doc_following(p, m) == WEOF) {
260                 /* We only create a subpane for EOF when it is at start
261                  * of line, else it is included in the preceding line.
262                  */
263                 call("doc:render-line-prev", p, 0, m);
264                 if (!mark_same(m, start)) {
265                         mark_free(m);
266                         vmark_clear(start);
267                         return;
268                 }
269                 s = "";
270         } else
271                 s = call_ret(strsave, "doc:render-line", p, -1, m);
272
273         if (!mark_valid(start)) {
274                 mark_free(m);
275                 return;
276         }
277         if (s)
278                 vmark_set(home, start, s);
279
280         m2 = vmark_matching(m);
281         if (m2)
282                 mark_free(m);
283         else
284                 m2 = m;
285         /*FIXME shouldn't be needed */
286         m2 = safe_cast m2;
287
288         /* Any mark between start and m2 must be discarded,
289          */
290         while ((m = vmark_next(start)) != NULL &&
291                m->seq < m2->seq) {
292                 if (end && m == *end)
293                         *end = m2;
294                 vmark_free(m);
295         }
296         /* Any mark at same location as m2 must go too. */
297         while ((m = vmark_next(m2)) != NULL &&
298                mark_same(m, m2)) {
299                 if (end && m == *end)
300                         *end = m2;
301                 vmark_free(m);
302         }
303         /* Any mark at same location as start must go too. */
304         while ((m = vmark_prev(start)) != NULL &&
305                mark_same(m, start)) {
306                 vmark_free(m);
307         }
308 }
309
310 DEF_CMD(no_save)
311 {
312         return 1;
313 }
314
315 static struct mark *call_render_line_offset(struct pane *p safe,
316                                             struct mark *start safe, int offset)
317 {
318         struct mark *m;
319
320         m = mark_dup_view(start);
321         if (call_comm("doc:render-line", p, &no_save, offset, m) <= 0) {
322                 mark_free(m);
323                 return NULL;
324         }
325         return m;
326 }
327
328 DEF_CMD(get_offset)
329 {
330         if (ci->num < 0)
331                 return 1;
332         else
333                 return ci->num + 1;
334 }
335
336 static int call_render_line_to_point(struct pane *p safe, struct mark *pm safe,
337                                      struct mark *start safe)
338 {
339         int len;
340         struct mark *m = mark_dup_view(start);
341
342         len = call_comm("doc:render-line", p, &get_offset, -1, m, NULL, 0, pm);
343         mark_free(m);
344         if (len <= 0)
345                 return 0;
346
347         return len - 1;
348 }
349
350 /* Choose a new set of lines to display, and mark each one with a line marker.
351  * We start at pm and move both backwards and forwards one line at a time.
352  * We stop moving in one of the directions when
353  *  - we hit start/end of file
354  *  - when the edge in the *other* direction enters the previously visible
355  *     area (if there was one).  This increases stability of display when
356  *     we move off a line or 2.
357  *  - when we reach the given line count (vline).  A positive count restricts
358  *    backward movement, a negative restricts forwards movement.
359  */
360
361 static bool step_back(struct pane *p safe, struct pane *focus safe,
362                       struct mark **startp safe, struct mark **endp,
363                       short *y_pre safe, short *line_height_pre safe)
364 {
365         /* step backwards moving start */
366         struct rl_data *rl = p->data;
367         struct mark *m;
368         bool found_start = False;
369         struct mark *start = *startp;
370
371         if (!start)
372                 return True;
373         m = call_render_line_prev(focus, mark_dup_view(start),
374                                   1, &rl->top_sol);
375         if (!m) {
376                 /* no text before 'start' */
377                 found_start = True;
378         } else {
379                 short h = 0;
380                 start = m;
381                 call_render_line(p, focus, start, endp);
382                 measure_line(p, focus, start, -1);
383                 h = start->mdata ? start->mdata->h : 0;
384                 if (h) {
385                         *y_pre = h;
386                         *line_height_pre =
387                                 attr_find_int(start->mdata->attrs,
388                                               "line-height");
389                 } else
390                         found_start = True;
391         }
392         *startp = start;
393         return found_start;
394 }
395
396 static bool step_fore(struct pane *p safe, struct pane *focus safe,
397                       struct mark **startp safe, struct mark **endp safe,
398                       short *y_post safe, short *line_height_post safe)
399 {
400         struct mark *end = *endp;
401
402         if (!end)
403                 return True;
404         call_render_line(p, focus, end, startp);
405         measure_line(p, focus, end, -1);
406         if (end->mdata)
407                 *y_post = end->mdata->h;
408         if (*y_post > 0 && end->mdata)
409                 *line_height_post =
410                         attr_find_int(end->mdata->attrs,
411                                       "line-height");
412         if (!end->mdata || !end->mdata->h)
413                 end = NULL;
414         else
415                 end = vmark_next(end);
416         if (!end) {
417                 if (p->h >= *line_height_post *2)
418                         *y_post = p->h / 10;
419         }
420
421         *endp = end;
422         return False;
423 }
424
425 static int consume_space(struct pane *p safe, int y,
426                          short *y_prep safe, short *y_postp safe,
427                          short *lines_above safe, short *lines_below safe,
428                          int found_start, int found_end,
429                          int line_height_pre, int line_height_post,
430                          bool line_at_a_time)
431 {
432         int y_pre = *y_prep;
433         int y_post = *y_postp;
434
435         if (y_pre > 0 && y_post > 0 && !found_start && !found_end) {
436                 int consume = (y_post < y_pre
437                                ? y_post : y_pre) * 2;
438                 int above, below;
439                 if (consume > p->h - y)
440                         consume = p->h - y;
441                 if (line_at_a_time && consume > 2*line_height_pre &&
442                     line_height_pre > 0)
443                         consume = 2*line_height_pre;
444                 if (line_at_a_time && consume > 2*line_height_post &&
445                     line_height_post > 0)
446                         consume = 2*line_height_post;
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 && !found_start) {
465                 int consume = p->h - y;
466                 if (consume > y_pre)
467                         consume = y_pre;
468                 if (line_at_a_time && consume > line_height_pre &&
469                     line_height_pre > 0)
470                         consume = line_height_pre;
471                 y_pre -= consume;
472                 y += consume;
473                 *lines_above += consume / (line_height_pre?:1);
474         }
475         if (found_start && y_post && !found_end) {
476                 int consume = p->h - y;
477                 if (consume > y_post)
478                         consume = y_post;
479                 if (line_at_a_time && consume > line_height_post &&
480                     line_height_post > 0)
481                         consume = line_height_post;
482                 y_post -= consume;
483                 y += consume;
484                 *lines_below += consume / (line_height_post?:1);
485         }
486         *y_prep = y_pre;
487         *y_postp = y_post;
488         return y;
489 }
490
491 /*
492  * Choose new start/end to be displayed in the given pane.
493  * 'pm' must be displayed, and if vline is not NO_NUMERIC,
494  * pm should be displayed on that line of the display, where
495  * negative numbers count from the bottom of the page.
496  * Otherwise pm should be at least rl->margin from top and bottom,
497  * but in no case should start-of-file be *after* top of display.
498  * If there is an existing display, move the display as little as
499  * possible while complying with the above.
500  *
501  * We start at 'pm' and move both forward and backward one line at a
502  * time measuring each line and assessing space used.
503  * - If the space above pm reaches positive vline, that will be top.
504  * - If the space below reaches negative vline, that will likely be bottom
505  * - If pm was before old top and we reach the old top going down,
506  *    and if space measured before pm has reached ->margin, we stop
507  *    moving upward.
508  * - If pm was after old bottom and we reach the old bottom going up
509  *    and if space measured after pm has reached ->margin, we stop
510  *    moving downward
511  *
512  * If we decide to stop moving in both directions, but have not
513  * reached EOF or full height of display, keep moving downwards.
514  *
515  * "start" is a mark at the start of the first line we currently
516  * intend to display, and y_pre is the number of pixel from the top
517  * of the display of that line, to the top pixel that will be displayed.
518  * We only move 'start' backward when y_pre is zero, and initially y_pre
519  * is the full height of that line.
520  *
521  * Similarly "end" is the start of the last line we currently intend
522  * to display, and y_post is the number of pixel from the bottom of that display
523  * up to the point we currently intend to display.  We only move "end" forward
524  * when y_post is zero, and when we do we set y_post to the full height of the
525  * line.
526  *
527  * Until we decide on the start or end (found_start, found_end), we
528  * repeatedly add equal parts of y_pre and y_post into the total to
529  * be display - consume_space() does this.  The space removed from y_pre
530  * and y_post is added to 'y' - the total height.
531  * It is also included into lines_above and lines_below which count text lines,
532  * rather than pixels, using line_height_pre and line_height_post as scale
533  * factors.  These are used to determine when vline or rl->margin requirements
534  * have been met.
535  */
536 static void find_lines(struct mark *pm safe, struct pane *p safe,
537                        struct pane *focus safe,
538                        int vline)
539 {
540         struct rl_data *rl = p->data;
541         /* orig_top/bot bound what is currently displayed and
542          * are used to determine if the display has been repositioned.
543          * orig_bot is *after* the last displayed line.  Its ->mdata
544          * will be NULL.
545          */
546         struct mark *orig_top, *orig_bot;
547         /* top and bot are used to enhance stability.  They are NULL
548          * if vline is given, else they match orig_top/bot.
549          */
550         struct mark *top, *bot;
551         struct mark *m;
552         /* Current estimate of new display. From y_pre pixels down
553          * from the top of line at 'start', to y_post pixels up
554          * from the end of the line before 'end' there are 'y'
555          * pixel lines that we have committed to display.
556          */
557         struct mark *start, *end; // current estimate for new display
558         short y_pre = 0, y_post = 0;
559         short y = 0;
560         /* Number of text-lines in the committed region above or below
561          * the baseline of the line containing pm.  These lines might not
562          * all be the same height. line_height_pre/post are the heights of
563          * start and end-1 so changes in y_pre/y_post can be merged into these
564          * counts.
565          */
566         short lines_above = 0, lines_below = 0; /* distance from start/end
567                                                  * to pm.
568                                                  */
569         short line_height_pre = 1, line_height_post = 1;
570
571         short offset; // pos of pm while measureing the line holding the cursor.
572         /* We set found_start we we don't want to consider anything above the
573          * top that we currently intend to display.  Once it is set,
574          * 'start', y_pre, lines_above are all frozen.
575          * Similarly once found_end is set we freeze end, y_pos, lines_below,
576          * but we mught unfreeze those if there is room for more text at end of
577          * display.
578          * found_start is set:
579          *   - when y_pre is zero and start is at top of file
580          *   - when lines_above reaches positive vline
581          *   - when intended display has grown down into the previous
582          *     display.  This means we have added enough lines above and
583          *     don't want to scroll the display more than we need.
584          *   - When we hit unexpected errors moving backwards
585          * found_end is set:
586          *   - when we hit end-of-file
587          *   - when lines_below reached -vline
588          *   - when the top of the intended display overlaps the
589          *     previous display.
590          */
591         bool found_start = False, found_end = False;
592
593         orig_top = vmark_first(focus, rl->typenum, p);
594         orig_bot = vmark_last(focus, rl->typenum, p);
595         /* Protect top/bot from being freed by call_render_line */
596         if (orig_top)
597                 orig_top = mark_dup(orig_top);
598         if (orig_bot)
599                 orig_bot = mark_dup(orig_bot);
600
601         start = vmark_new(focus, rl->typenum, p);
602         if (!start)
603                 goto abort;
604         /* FIXME why is this here.  We set ->repositioned at the end
605          * if the marks move.  Maybe we need to check if y_pre moves too.
606          */
607         rl->repositioned = 1;
608         mark_to_mark(start, pm);
609         start = call_render_line_prev(focus, start, 0, &rl->top_sol);
610         if (!start)
611                 goto abort;
612
613         /* Render the cursor line, and find where the cursor is. */
614         offset = call_render_line_to_point(focus, pm, start);
615         call_render_line(p, focus, start, NULL);
616         end = vmark_next(start);
617         /* Note: 'end' might be NULL if 'start' is end-of-file, otherwise
618          * call_render_line() will have created 'end' if it didn't exist.
619          */
620
621         if (!rl->shift_locked)
622                 rl->shift_left = 0;
623
624         if (start->mdata) {
625                 struct pane *hp = start->mdata;
626                 int curs_width;
627                 found_end = measure_line(p, focus, start, offset);
628
629                 curs_width = pane_attr_get_int(
630                         start->mdata, "curs_width", 1);
631                 if (curs_width < 0)
632                         curs_width = 1;
633                 while (!rl->do_wrap && !rl->shift_locked &&
634                        hp->cx + curs_width >= p->w) {
635                         int shift = 8 * curs_width;
636                         if (shift > hp->cx)
637                                 shift = hp->cx;
638                         rl->shift_left += shift;
639                         measure_line(p, focus, start, offset);
640                 }
641                 /* ->cy is top of cursor, we want to measure from bottom */
642                 line_height_pre = attr_find_int(start->mdata->attrs, "line-height");
643                 if (line_height_pre < 1)
644                         line_height_pre = 1;
645                 y_pre = start->mdata->cy + line_height_pre;
646                 y_post = start->mdata->h - y_pre;
647         } else {
648                 /* Should never happen */
649                 y_pre = 0;
650                 y_post = 0;
651         }
652         if (!end) {
653                 /* When cursor at EOF, leave 10% height of display
654                  * blank at bottom to make this more obvious - unless
655                  * the display is so small that might push the last line partly
656                  * off display at the top.
657                  */
658                 if (p->h > line_height_pre * 2)
659                         y_post += p->h / 10;
660                 else {
661                         /* Small display, no space at EOF */
662                         y_post = 0;
663                         found_end = True;
664                 }
665         }
666         y = 0;
667         if (rl->header && rl->header->mdata)
668                 y = rl->header->mdata->h;
669
670         /* We have start/end of the focus line.  When rendered this,
671          * plus header and eof-footer would use y_pre + y + y_post
672          * vertical space.
673          */
674
675         if (vline != NO_NUMERIC) {
676                 /* ignore current position - top/bot irrelevant */
677                 top = NULL;
678                 bot = NULL;
679         } else {
680                 top = orig_top;
681                 bot = orig_bot;
682         }
683
684         while ((!found_start || !found_end) && y < p->h) {
685                 if (vline != NO_NUMERIC) {
686                         /* As lines_above/below measure from the baseline
687                          * of the cursor line, and as we want to see the top
688                          * of he cursor line as well, these two cases are
689                          * asymmetric.
690                          */
691                         if (!found_start && vline > 0 &&
692                             lines_above >= vline)
693                                 found_start = True;
694                         if (!found_end && vline < 0 &&
695                             lines_below >= -vline-1)
696                                 found_end = True;
697                 }
698                 if (!found_start && y_pre <= 0)
699                         found_start = step_back(p, focus, &start, &end,
700                                                 &y_pre, &line_height_pre);
701
702                 if (found_end && y_post && bot &&
703                     mark_ordered_or_same(start, bot))
704                         /* Extra vertical space gets inserted after EOF when
705                          * there is a long jump to get there, but if we hit 'bot'
706                          * soon when searching back, we discard any unused space.
707                          */
708                         y_post = 0;
709
710                 if (!found_end && bot &&
711                     (!end || mark_ordered_or_same(bot, end)) &&
712                     lines_below >= rl->margin)
713                         if (mark_ordered_not_same(start, bot) ||
714                             /* Overlap original from below, so prefer to
715                              * maximize that overlap.
716                              */
717                             (mark_same(start, bot) &&
718                              y_pre - rl->skip_height >= y_post))
719                                 /* No overlap in marks yet, but over-lap in
720                                  * space, so same result as above.
721                                  */
722                                 found_end = True;
723
724                 if (!found_end && y_post <= 0)
725                         /* step forwards */
726                         found_end = step_fore(p, focus, &start, &end,
727                                               &y_post, &line_height_post);
728
729                 if (!found_start && top && end &&
730                     mark_ordered_or_same(start, top) &&
731                     lines_above >= rl->margin)
732                         if (mark_ordered_not_same(top, end) ||
733                             (mark_same(top, end) &&
734                              y_post - rl->tail_height >= y_pre))
735                                 found_start = True;
736
737                 y = consume_space(p, y, &y_pre, &y_post,
738                                   &lines_above, &lines_below,
739                                   found_start, found_end,
740                                   line_height_pre, line_height_post,
741                                   vline && vline != NO_NUMERIC);
742         }
743         /* We might need to continue downwards even after found_end
744          * if there is more space.
745          */
746         found_end = end == NULL;
747         while (!found_end && y < p->h) {
748                 if (y_post <= 0)
749                         found_end = step_fore(p, focus, &start, &end,
750                                               &y_post, &line_height_post);
751                 y = consume_space(p, y, &y_pre, &y_post,
752                                   &lines_above, &lines_below,
753                                   found_start, found_end,
754                                   line_height_pre, line_height_post,
755                                   vline && vline != NO_NUMERIC);
756         }
757
758         if (start->mdata && start->mdata->h <= y_pre) {
759                 y_pre = 0;
760                 m = vmark_next(start);
761                 vmark_free(start);
762                 start = m;
763         }
764         if (!start)
765                 goto abort;
766
767         rl->skip_height = y_pre;
768         rl->skip_line_height = line_height_pre;
769         rl->tail_height = y_post;
770         /* Now discard any marks outside start-end */
771         if (end && end->seq < start->seq)
772                 /* something confused, make sure we don't try to use 'end' after
773                  * freeing it.
774                  */
775                 end = start;
776         while ((m = vmark_prev(start)) != NULL)
777                 vmark_free(m);
778
779         if (end) {
780                 while ((m = vmark_next(end)) != NULL)
781                         vmark_free(m);
782
783                 vmark_clear(end);
784         }
785
786         y = 0;
787         if (rl->header && rl->header->mdata)
788                 y = rl->header->mdata->h;
789         y -= rl->skip_height;
790         for (m = vmark_first(focus, rl->typenum, p);
791              m && m->mdata ; m = vmark_next(m)) {
792                 struct pane *hp = m->mdata;
793                 hp->damaged &= ~DAMAGED_SIZE;
794                 pane_resize(hp, hp->x, y, hp->w, hp->h);
795                 if (hp->damaged & DAMAGED_SIZE && !rl->background_uniform)
796                         pane_damaged(hp, DAMAGED_REFRESH);
797                 y += hp->h;
798         }
799         pane_damaged(p, DAMAGED_REFRESH);
800         m = vmark_first(focus, rl->typenum, p);
801         if (!m || !orig_top || !mark_same(m, orig_top))
802                 rl->repositioned = 1;
803         m = vmark_last(focus, rl->typenum, p);
804         if (!m || !orig_bot || !mark_same(m, orig_bot))
805                 rl->repositioned = 1;
806
807 abort:
808         mark_free(orig_top);
809         mark_free(orig_bot);
810 }
811
812 DEF_CMD(cursor_handle)
813 {
814         return 0;
815 }
816
817 static int render(struct mark *pm, struct pane *p safe,
818                   struct pane *focus safe)
819 {
820         struct rl_data *rl = p->data;
821         short y = 0;
822         struct mark *m, *m2;
823         struct xy scale = pane_scale(focus);
824         char *s;
825         int hide_cursor = 0;
826         int cursor_drawn = 0;
827         bool refresh_all = rl->shift_left != rl->shift_left_last_refresh;
828
829         rl->shift_left_last_refresh = rl->shift_left;
830         s = pane_attr_get(focus, "hide-cursor");
831         if (s && strcmp(s, "yes") == 0)
832                 hide_cursor = 1;
833
834         rl->cols = 0;
835         m = vmark_first(focus, rl->typenum, p);
836         if (!rl->background_drawn) {
837                 refresh_all = True;
838                 rl->background_uniform = True;
839         }
840         s = pane_attr_get(focus, "background");
841         if (s && strstarts(s, "call:")) {
842                 home_call(focus, "Draw:clear", p, 0, NULL, "");
843                 home_call(focus, s+5, p, 0, m);
844                 refresh_all = True;
845                 rl->background_uniform = False;
846         } else if (rl->background_drawn)
847                 ;
848         else if (!s)
849                 home_call(focus, "Draw:clear", p, 0, NULL, "");
850         else if (strstarts(s, "color:")) {
851                 char *a = strdup(s);
852                 strcpy(a, "bg:");
853                 strcpy(a+3, s+6);
854                 home_call(focus, "Draw:clear", p, 0, NULL, a);
855                 free(a);
856         } else if (strstarts(s, "image:")) {
857                 home_call(focus, "Draw:clear", p);
858                 home_call(focus, "Draw:image", p, 16, NULL, s+6);
859                 rl->background_uniform = False;
860         } else
861                 home_call(focus, "Draw:clear", p, 0, NULL, "");
862         rl->background_drawn = True;
863
864         if (rl->header && vmark_is_valid(rl->header)) {
865                 struct pane *hp = rl->header->mdata;
866                 draw_line(p, focus, rl->header, -1, refresh_all);
867                 y = hp->h;
868                 rl->cols = hp->x + hp->w;
869         }
870         y -= rl->skip_height;
871
872         p->cx = p->cy = -1;
873         rl->cursor_line = 0;
874
875         while (m && m->mdata) {
876                 m2 = vmark_next(m);
877                 if (!hide_cursor && p->cx <= 0 && pm &&
878                     mark_ordered_or_same(m, pm) &&
879                     (!(m2 && doc_following(focus, m2) != WEOF) ||
880                      mark_ordered_not_same(pm, m2))) {
881                         struct xy curs;
882                         struct pane *hp = m->mdata;
883                         short len = call_render_line_to_point(focus, pm,
884                                                               m);
885                         draw_line(p, focus, m, len, True);
886                         rl->cursor_line = hp->y + hp->cy;
887                         curs = pane_mapxy(hp, p, hp->cx, hp->cy, False);
888                         if (hp->cx < 0) {
889                                 p->cx = -1;
890                                 p->cy = -1;
891                         } else {
892                                 p->cx = curs.x;
893                                 p->cy = curs.y;
894                         }
895                         cursor_drawn = 1;
896                 } else {
897                         draw_line(p, focus, m, -1, refresh_all);
898                 }
899                 if (m->mdata) {
900                         int cols = m->mdata->x + m->mdata->w;
901                         if (cols > rl->cols)
902                                 rl->cols = cols;
903                         y = m->mdata->y + m->mdata->h;
904                 }
905                 m = m2;
906         }
907         if (m && !m->mdata && vmark_next(m))
908                 LOG("render-lines: break in vmark sequence");
909         if (!cursor_drawn && !hide_cursor) {
910                 /* Place cursor in bottom right */
911                 struct pane *cp = rl->cursor_pane;
912                 short mwidth = -1;
913                 short lineheight;
914
915                 if (!cp) {
916                         cp = pane_register(p, -1, &cursor_handle);
917                         rl->cursor_pane = cp;
918                 }
919                 if (m)
920                         m2 = vmark_prev(m);
921                 else
922                         m2 = vmark_last(focus, rl->typenum, p);
923
924                 while (m2 && mwidth <= 0) {
925                         if (m2->mdata) {
926                                 mwidth = pane_attr_get_int(
927                                         m2->mdata, "curs_width", -1);
928                                 lineheight = pane_attr_get_int(
929                                         m2->mdata, "line-height", -1);
930                         }
931                         m2 = vmark_prev(m2);
932                 }
933
934                 if (mwidth <= 0) {
935                         mwidth = 1;
936                         lineheight = 1;
937                 }
938                 if (cp) {
939                         pane_resize(cp,
940                                     p->w - mwidth,
941                                     p->h - lineheight,
942                                     mwidth, lineheight);
943
944                         home_call(focus, "Draw:clear", cp);
945                         home_call(focus, "Draw:text", cp, 0, NULL, " ",
946                                   scale.x, NULL, "",
947                                   0, lineheight-1);
948                 }
949         } else if (rl->cursor_pane) {
950                 pane_close(rl->cursor_pane);
951                 rl->cursor_pane = NULL;
952         }
953         return y;
954 }
955
956 DEF_CMD(render_lines_get_attr)
957 {
958         struct rl_data *rl = ci->home->data;
959
960         if (ci->str && strcmp(ci->str, "shift_left") == 0) {
961                 char ret[10];
962                 if (rl->do_wrap && !rl->shift_locked)
963                         return comm_call(ci->comm2, "cb", ci->focus,
964                                          0, NULL, "-1");
965                 snprintf(ret, sizeof(ret), "%d", rl->shift_left);
966                 return comm_call(ci->comm2, "cb", ci->focus, 0, NULL, ret);
967         }
968         return Efallthrough;
969 }
970
971 DEF_CMD(render_lines_point_moving)
972 {
973         struct pane *p = ci->home;
974         struct rl_data *rl = p->data;
975         struct mark *pt = call_ret(mark, "doc:point", ci->home);
976         struct mark *m;
977
978         if (!pt || ci->mark != pt)
979                 return 1;
980         /* Stop igoring point, because it is probably relevant now */
981         rl->ignore_point = 0;
982         if (!rl->i_moved)
983                 /* Someone else moved the point, so reset target column */
984                 rl->target_x = -1;
985         m = vmark_at_or_before(ci->focus, pt, rl->typenum, p);
986         if (m && !m->mdata)
987                 /* End marker is no use, want to refresh last line */
988                 m = vmark_prev(m);
989         if (m && m->mdata) {
990                 pane_damaged(m->mdata, DAMAGED_REFRESH);
991                 pane_damaged(m->mdata->parent, DAMAGED_REFRESH);
992         }
993         return 1;
994 }
995
996 static int revalidate_start(struct rl_data *rl safe,
997                             struct pane *p safe, struct pane *focus safe,
998                             struct mark *start safe, struct mark *pm,
999                             bool refresh_all)
1000 {
1001         int y;
1002         bool on_screen = False;
1003         struct mark *m, *m2;
1004         bool found_end = False;
1005         bool start_of_file;
1006         int shifts = 0;
1007
1008         /* This loop is fragile and sometimes spins.  So ensure we
1009          * never loop more than 1000 times.
1010          */
1011         if (pm && !rl->do_wrap && !rl->shift_locked && shifts++ < 1000) {
1012                 int prefix_len;
1013                 int curs_width;
1014                 /* Need to check if side-shift is needed on cursor line */
1015                 m2 = mark_dup(pm);
1016                 call("doc:render-line-prev", focus, 0, m2);
1017
1018                 m = vmark_at_or_before(focus, m2, rl->typenum, p);
1019                 mark_free(m2);
1020
1021                 if (m && refresh_all)
1022                         vmark_invalidate(m);
1023                 if (m && m->mdata && !vmark_is_valid(m)) {
1024                         pane_damaged(p, DAMAGED_REFRESH);
1025                         call("doc:render-line-prev", focus, 0, m);
1026                         call_render_line(p, focus, m, &start);
1027                 }
1028                 if (m && m->mdata) {
1029                         struct pane *hp = m->mdata;
1030                         int offset = call_render_line_to_point(focus,
1031                                                                pm, m);
1032                         measure_line(p, focus, m, offset);
1033                         prefix_len = pane_attr_get_int(
1034                                 m->mdata, "prefix_len", -1);
1035                         curs_width = pane_attr_get_int(
1036                                 m->mdata, "curs_width", 1);
1037
1038                         while (hp->cx + curs_width >= p->w && shifts++ < 1000) {
1039                                 int shift = 8 * curs_width;
1040                                 if (shift > hp->cx)
1041                                         shift = hp->cx;
1042                                 rl->shift_left += shift;
1043                                 measure_line(p, focus, m, offset);
1044                                 refresh_all = 1;
1045                         }
1046                         while (hp->cx < prefix_len &&
1047                                rl->shift_left > 0 &&
1048                                shifts++ < 1000 &&
1049                                hp->cx + curs_width * 8*curs_width < p->w) {
1050                                 int shift = 8 * curs_width;
1051                                 if (shift > rl->shift_left)
1052                                         shift = rl->shift_left;
1053                                 rl->shift_left -= shift;
1054                                 measure_line(p, focus, m, offset);
1055                                 refresh_all = 1;
1056                         }
1057                 }
1058         }
1059         y = 0;
1060         if (rl->header) {
1061                 struct pane *hp = rl->header->mdata;
1062                 if (refresh_all) {
1063                         measure_line(p, focus, rl->header, -1);
1064                         if (hp)
1065                                 pane_resize(hp, hp->x, y, hp->w, hp->h);
1066                 }
1067                 if (hp)
1068                         y = hp->h;
1069         }
1070         y -= rl->skip_height;
1071         start_of_file = doc_prior(focus, start) == WEOF;
1072         for (m = start; m && !found_end && y < p->h; m = vmark_next(m)) {
1073                 struct pane *hp;
1074                 if (refresh_all)
1075                         vmark_invalidate(m);
1076                 call_render_line(p, focus, m, NULL);
1077                 found_end = measure_line(p, focus, m, -1);
1078                 hp = m->mdata;
1079                 if (!mark_valid(m) || !hp)
1080                         break;
1081
1082                 if (y != hp->y) {
1083                         pane_damaged(p, DAMAGED_REFRESH);
1084                         hp->damaged &= ~DAMAGED_SIZE;
1085                         pane_resize(hp, hp->x, y, hp->w, hp->h);
1086                         if (hp->damaged & DAMAGED_SIZE && !rl->background_uniform)
1087                                 pane_damaged(hp, DAMAGED_REFRESH);
1088                 }
1089                 y += hp->h;
1090                 m2 = vmark_next(m);
1091                 if (pm && m == start && rl->skip_height > 0 && m2 &&
1092                     mark_ordered_not_same(pm, m2)) {
1093                         /* Point might be in this line, but off top
1094                          * of the screen
1095                          */
1096                         int offset = call_render_line_to_point(focus,
1097                                                                pm, m);
1098                         if (offset >= 0) {
1099                                 measure_line(p, focus, m, offset);
1100                                 if (hp->cy >= rl->skip_height + rl->margin)
1101                                         /* Cursor is visible on this line
1102                                          * and after margin from top.
1103                                          */
1104                                         on_screen = True;
1105                                 else if (start_of_file && rl->skip_height == 0)
1106                                         /* Cannot make more margin space */
1107                                         on_screen = True;
1108                         }
1109                 } else if (pm && y >= p->h && m->seq < pm->seq) {
1110                         /* point might be in this line, but off end
1111                          * of the screen
1112                          */
1113                         int offset = call_render_line_to_point(focus,
1114                                                                pm, m);
1115                         if (offset > 0) {
1116                                 int lh;
1117                                 measure_line(p, focus, m, offset);
1118                                 lh = attr_find_int(hp->attrs,
1119                                                    "line-height");
1120                                 if (lh <= 0)
1121                                         lh = 1;
1122                                 if (y - hp->h + hp->cy <= p->h - lh - rl->margin) {
1123                                         /* Cursor is on screen */
1124                                         on_screen = True;
1125                                 }
1126                         }
1127                 } else if (pm && mark_ordered_or_same(m, pm) && m2 &&
1128                            mark_ordered_or_same(pm, m2)) {
1129                         if (rl->margin == 0)
1130                                 on_screen = True;
1131                         else {
1132                                 int offset = call_render_line_to_point(
1133                                         focus, pm, m);
1134                                 if (offset > 0) {
1135                                         int lh;
1136                                         int cy;
1137                                         measure_line(p, focus, m, offset);
1138                                         lh = attr_find_int(hp->attrs,
1139                                                            "line-height");
1140                                         cy = y - hp->h + hp->cy;
1141                                         if (cy >= rl->margin &&
1142                                             cy <= p->h - rl->margin - lh)
1143                                                 /* Cursor at least margin from edge */
1144                                                 on_screen = True;
1145                                 }
1146                         }
1147                 }
1148         }
1149         if (y >= p->h)
1150                 rl->tail_height = p->h - y;
1151         else
1152                 rl->tail_height = 0;
1153         if (mark_valid(m)) {
1154                 vmark_clear(m);
1155                 while (mark_valid(m2 = vmark_next(m)) && m2) {
1156                         /* end of view has clearly changed */
1157                         rl->repositioned = 1;
1158                         vmark_free(m2);
1159                 }
1160         }
1161         if (!pm || on_screen) {
1162                 if (rl->repositioned) {
1163                         rl->repositioned = 0;
1164                         call("render:reposition", focus,
1165                              rl->lines, vmark_first(focus,
1166                                                     rl->typenum,
1167                                                     p), NULL,
1168                              rl->cols, vmark_last(focus,
1169                                                   rl->typenum,
1170                                                   p), NULL,
1171                              p->cx, p->cy);
1172                 }
1173                 return 1;
1174         }
1175         return 0;
1176 }
1177
1178 DEF_CMD(render_lines_revise)
1179 {
1180         struct pane *p = ci->home;
1181         struct pane *focus = ci->focus;
1182         struct rl_data *rl = p->data;
1183         struct mark *pm = NULL;
1184         struct mark *m1, *m2;
1185         bool refresh_all = False;
1186         char *hdr;
1187         char *a;
1188         int shift;
1189
1190         a = pane_attr_get(focus, "render-wrap");
1191         if (rl->do_wrap != (!a || strcmp(a, "yes") ==0)) {
1192                 rl->do_wrap = (!a || strcmp(a, "yes") ==0);
1193                 refresh_all = True;
1194         }
1195
1196         shift = pane_attr_get_int(focus, "shift-left", -1);
1197         if (shift >= 0) {
1198                 if (rl->shift_left != shift)
1199                         refresh_all = True;
1200
1201                 rl->shift_left = shift;
1202                 rl->shift_locked = 1;
1203         } else {
1204                 if (rl->shift_locked)
1205                         refresh_all = True;
1206                 rl->shift_locked = 0;
1207         }
1208         if (refresh_all) {
1209                 struct mark *v;
1210                 for (v = vmark_first(focus, rl->typenum, p);
1211                      (v && v->mdata) ; v = vmark_next(v))
1212                         pane_damaged(v->mdata, DAMAGED_REFRESH);
1213         }
1214
1215         rl->margin = pane_attr_get_int(focus, "render-vmargin", 0);
1216         if (rl->margin >= p->h/2)
1217                 rl->margin = p->h/2;
1218
1219         hdr = pane_attr_get(focus, "heading");
1220         if (hdr && !*hdr)
1221                 hdr = NULL;
1222
1223         if (hdr) {
1224                 if (!rl->header)
1225                         rl->header = vmark_new(focus, MARK_UNGROUPED, NULL);
1226                 if (rl->header) {
1227                         vmark_set(p, rl->header, hdr);
1228                         measure_line(p, focus, rl->header, -1);
1229                 }
1230         } else if (rl->header) {
1231                 vmark_free(rl->header);
1232                 rl->header = NULL;
1233         }
1234
1235         if (!rl->ignore_point)
1236                 pm = call_ret(mark, "doc:point", focus);
1237         m1 = vmark_first(focus, rl->typenum, p);
1238         m2 = vmark_last(focus, rl->typenum, p);
1239
1240         if (m1 && !vmark_is_valid(m1))
1241                 /* newline before might have been deleted, better check */
1242                 call("doc:render-line-prev", focus, 0, m1);
1243         // FIXME double check that we invalidate line before any change...
1244
1245         if (m1 && m2 &&
1246             (!pm || (mark_ordered_or_same(m1,pm)))) {
1247                 /* We maybe be able to keep m1 as start, if things work out.
1248                  * So check all sub-panes are still valid and properly
1249                  * positioned.
1250                  */
1251                 if (revalidate_start(rl, p, focus, m1, pm, refresh_all))
1252                         return 1;
1253         }
1254         /* Need to find a new top-of-display */
1255         if (!pm)
1256                 pm = call_ret(mark, "doc:point", focus);
1257         if (!pm)
1258                 /* Don't know what to do here... */
1259                 return 1;
1260         find_lines(pm, p, focus, NO_NUMERIC);
1261         rl->repositioned = 0;
1262         call("render:reposition", focus,
1263              rl->lines, vmark_first(focus, rl->typenum, p), NULL,
1264              rl->cols, vmark_last(focus, rl->typenum, p), NULL,
1265              p->cx, p->cy);
1266         return 1;
1267 }
1268
1269 DEF_CMD(render_lines_refresh)
1270 {
1271         struct pane *p = ci->home;
1272         struct pane *focus = ci->focus;
1273         struct rl_data *rl = p->data;
1274         struct mark *m, *pm = NULL;
1275
1276         //pane_damaged(p, DAMAGED_VIEW);
1277
1278         pm = call_ret(mark, "doc:point", focus);
1279
1280         m = vmark_first(focus, rl->typenum, p);
1281
1282         if (!m)
1283                 return 1;
1284
1285         rl->lines = render(pm, p, focus);
1286
1287         return 1;
1288 }
1289
1290 DEF_CMD(render_lines_close)
1291 {
1292         struct rl_data *rl = ci->home->data;
1293
1294         if (rl->header)
1295                 vmark_free(rl->header);
1296         rl->header = NULL;
1297
1298         return 1;
1299 }
1300
1301 DEF_CMD(render_lines_close_mark)
1302 {
1303         struct mark *m = ci->mark;
1304
1305         if (m)
1306                 vmark_clear(m);
1307         return 1;
1308 }
1309
1310 DEF_CMD(render_lines_abort)
1311 {
1312         struct pane *p = ci->home;
1313         struct rl_data *rl = p->data;
1314
1315         rl->ignore_point = 0;
1316         rl->target_x = -1;
1317
1318         pane_damaged(p, DAMAGED_VIEW);
1319
1320         /* Allow other handlers to complete the Abort */
1321         return Efallthrough;
1322 }
1323
1324 DEF_CMD(render_lines_move_view)
1325 {
1326         /*
1327          * Find a new 'top' for the displayed region so that render()
1328          * will draw from there.
1329          * When moving backwards we move back a line and render it.
1330          * When moving forwards we render and then step forward
1331          * At each point we count the number of display lines that result.
1332          * When we choose a new start, we delete all earlier marks.
1333          * We also delete marks before current top when moving forward
1334          * where there are more than a page full.
1335          */
1336         struct pane *p = ci->home;
1337         struct pane *focus = ci->focus;
1338         int rpt = RPT_NUM(ci);
1339         struct rl_data *rl = p->data;
1340         struct mark *top, *old_top;
1341
1342         top = vmark_first(focus, rl->typenum, p);
1343         if (!top)
1344                 return Efallthrough;
1345
1346         old_top = mark_dup(top);
1347         rpt *= p->h ?: 1;
1348         rpt /= 1000;
1349
1350         rl->ignore_point = 1;
1351
1352         if (rl->skip_line_height <= 0)
1353                 rl->skip_line_height = 1;
1354
1355         if (rpt < 0) {
1356                 /* Need to add new lines at the top and remove
1357                  * at the bottom.
1358                  */
1359                 while (rpt < 0) {
1360                         short y = 0;
1361                         struct mark *m;
1362                         struct mark *prevtop = top;
1363
1364                         if (rl->skip_height) {
1365                                 rl->skip_height -= rl->skip_line_height;
1366                                 if (rl->skip_height < rl->skip_line_height/2)
1367                                         rl->skip_height = 0;
1368                                 rpt += rl->skip_line_height;
1369                                 if (rpt > 0)
1370                                         rpt = 0;
1371                                 continue;
1372                         }
1373
1374                         m = mark_dup_view(top);
1375                         top = call_render_line_prev(focus, m,
1376                                                     1, &rl->top_sol);
1377                         if (!top && doc_prior(focus, prevtop) != WEOF) {
1378                                 /* Double check - maybe a soft top-of-file - Ctrl-L*/
1379                                 m = mark_dup(prevtop);
1380                                 doc_prev(focus, m);
1381                                 top = call_render_line_prev(focus, m,
1382                                                             1, &rl->top_sol);
1383                         }
1384                         if (!top)
1385                                 break;
1386                         m = top;
1387                         while (m && m->seq < prevtop->seq &&
1388                                !mark_same(m, prevtop)) {
1389                                 call_render_line(p, focus, m, NULL);
1390                                 if (m->mdata == NULL) {
1391                                         rpt = 0;
1392                                         break;
1393                                 }
1394                                 measure_line(p, focus, m, -1);
1395                                 y += m->mdata->h;
1396                                 m = vmark_next(m);
1397                         }
1398                         /* FIXME remove extra lines, maybe add */
1399                         rl->skip_height = y;
1400                 }
1401         } else {
1402                 /* Need to remove lines from top */
1403                 call_render_line(p, focus, top, NULL);
1404                 measure_line(p, focus, top, -1);
1405                 while (top && top->mdata && rpt > 0) {
1406                         short y = 0;
1407
1408                         y = top->mdata->h;
1409                         if (rpt < y - rl->skip_height) {
1410                                 rl->skip_height += rpt;
1411                                 break;
1412                         }
1413                         rpt -= y - rl->skip_height;
1414                         rl->skip_height = 0;
1415                         top = vmark_next(top);
1416                         if (!top)
1417                                 break;
1418                         call_render_line(p, focus, top, NULL);
1419                         measure_line(p, focus, top, -1);
1420                 }
1421                 if (top && top->mdata) {
1422                         /* We didn't fall off the end, so it is OK to remove
1423                          * everything before 'top'
1424                          */
1425                         struct mark *old;
1426                         while ((old = vmark_first(focus, rl->typenum, p)) != NULL &&
1427                                old != top)
1428                                 vmark_free(old);
1429                 }
1430         }
1431         rl->repositioned = 1;
1432         pane_damaged(ci->home, DAMAGED_VIEW);
1433         top = vmark_first(focus, rl->typenum, p);
1434         if (top && mark_same(top, old_top)) {
1435                 mark_free(old_top);
1436                 return 2;
1437         }
1438         mark_free(old_top);
1439         return 1;
1440 }
1441
1442 static char *get_active_tag(const char *a)
1443 {
1444         char *t;
1445         char *c;
1446
1447         if (!a)
1448                 return NULL;
1449         t = strstr(a, ",active-tag:");
1450         if (!t)
1451                 return NULL;
1452         t += 12;
1453         c = strchr(t, ',');
1454         return strndup(t, c?c-t: (int)strlen(t));
1455 }
1456
1457 DEF_CMD(render_lines_set_cursor)
1458 {
1459         /* ->num is
1460          * 1 if this resulted from a click
1461          * 2 if from a release
1462          * 3 if from motion
1463          * 0 any other reason.
1464          */
1465         struct pane *p = ci->home;
1466         struct pane *focus = ci->focus;
1467         struct rl_data *rl = p->data;
1468         struct mark *m;
1469         struct mark *m2 = NULL;
1470         struct xy cih;
1471         int xypos;
1472
1473         cih = pane_mapxy(ci->focus, ci->home,
1474                          ci->x >= 0 ? ci->x : p->cx >= 0 ? p->cx : 0,
1475                          ci->y >= 0 ? ci->y : p->cy >= 0 ? p->cy : 0,
1476                          False);
1477
1478         m = vmark_first(p, rl->typenum, p);
1479
1480         while (m && m->mdata && m->mdata->y + m->mdata->h <= cih.y &&
1481                vmark_next(m))
1482                 m = vmark_next(m);
1483
1484         if (!m)
1485                 /* There is nothing rendered? */
1486                 return 1;
1487         if (!m->mdata) {
1488                 /* chi is after the last visible content, and m is the end
1489                  * of that content (possible EOF) so move there
1490                  */
1491         } else {
1492                 if (cih.y < m->mdata->y)
1493                         cih.y = m->mdata->y;
1494                 xypos = find_xy_line(p, focus, m, cih.x, cih.y);
1495                 if (xypos >= 0)
1496                         m2 = call_render_line_offset(focus, m, xypos);
1497         }
1498         if (m2) {
1499                 char *tag, *xyattr;
1500
1501                 if (ci->num == 2) { /* Mouse release */
1502                         xyattr = pane_attr_get(m->mdata, "xyattr");
1503                         tag = get_active_tag(xyattr);
1504                         if (tag) {
1505                                 char *c = NULL;
1506                                 asprintf(&c, "Activate:%s", tag);
1507                                 if (c)
1508                                         call(c, focus, 0, m2, tag,
1509                                              0, ci->mark, xyattr);
1510                                 free(c);
1511                         }
1512                 }
1513                 m = m2;
1514         } else {
1515                 /* m is the closest we'll get */
1516         }
1517
1518         if (ci->mark)
1519                 mark_to_mark(ci->mark, m);
1520         else
1521                 call("Move-to", focus, 0, m);
1522         mark_free(m2);
1523
1524         return 1;
1525 }
1526
1527 DEF_CMD(render_lines_activate)
1528 {
1529         /* If there is an active-tag: at '->mark', send Activate:tag
1530          * to the focus
1531          */
1532         struct mark *m = ci->mark;
1533         struct pane *p = ci->home;
1534         struct rl_data *rl = p->data;
1535         struct pane *focus = ci->focus;
1536         struct mark *v, *n;
1537         int offset;
1538         char *attr, *tag;
1539
1540         if (!m)
1541                 return Enoarg;
1542         v = vmark_first(p, rl->typenum, p);
1543
1544         while (v && v->mdata && (n = vmark_next(v)) &&
1545                mark_ordered_or_same(n, m))
1546                 v = n;
1547
1548         if (!v || !v->mdata || !mark_ordered_or_same(v, m))
1549                 return Efallthrough;
1550         offset = call_render_line_to_point(focus, m, v);
1551         if (offset < 0)
1552                 return Efallthrough;
1553         measure_line(p, focus, v, offset);
1554         attr = pane_attr_get(v->mdata, "cursattr");
1555         tag = get_active_tag(attr);
1556         if (tag) {
1557                 char *c = NULL;
1558                 asprintf(&c, "Activate:%s", tag);
1559                 if (c)
1560                         call(c, focus, 0, m, tag,
1561                              0, NULL, attr);
1562                 free(c);
1563         }
1564         return 1;
1565 }
1566
1567 DEF_CMD(render_lines_move_pos)
1568 {
1569         struct pane *p = ci->home;
1570         struct pane *focus = ci->focus;
1571         struct rl_data *rl = p->data;
1572         struct mark *pm = ci->mark;
1573         struct mark *top, *bot;
1574
1575         if (!pm)
1576                 return Enoarg;
1577         rl->ignore_point = 1;
1578         top = vmark_first(focus, rl->typenum, p);
1579         bot = vmark_last(focus, rl->typenum, p);
1580         if (top && rl->skip_height)
1581                 /* top line not fully displayed, being in that line is
1582                  * not sufficient */
1583                 top = vmark_next(top);
1584         if (bot)
1585                 /* last line might not be fully displayed, so don't assume */
1586                 bot = vmark_prev(bot);
1587         if (!top || !bot ||
1588             !mark_ordered_or_same(top, pm) ||
1589             !mark_ordered_not_same(pm, bot))
1590                 /* pos not displayed */
1591                 find_lines(pm, p, focus, NO_NUMERIC);
1592         pane_damaged(p, DAMAGED_REFRESH);
1593         return 1;
1594 }
1595
1596 DEF_CMD(render_lines_view_line)
1597 {
1598         struct pane *p = ci->home;
1599         struct pane *focus = ci->focus;
1600         struct rl_data *rl = p->data;
1601         struct mark *pm = ci->mark;
1602         int line = ci->num;
1603
1604         if (!pm)
1605                 return Enoarg;
1606         if (line == NO_NUMERIC)
1607                 return Einval;
1608
1609         rl->ignore_point = 1;
1610         find_lines(pm, p, focus, line);
1611         pane_damaged(p, DAMAGED_REFRESH);
1612         return 1;
1613 }
1614
1615 DEF_CMD(render_lines_move_line)
1616 {
1617         /* FIXME should be able to select between display lines
1618          * and content lines - different when a line wraps.
1619          * For now just content lines.
1620          * target_x and target_y are the target location in a line
1621          * relative to the start of line.
1622          * We use doc:EOL to find a suitable start of line, then
1623          * render that line and find the last location not after x,y
1624          */
1625         struct pane *p = ci->home;
1626         struct pane *focus = ci->focus;
1627         struct rl_data *rl = p->data;
1628         int num;
1629         int xypos = -1;
1630         struct mark *m = ci->mark;
1631         struct mark *start, *m2;
1632
1633         if (!m)
1634                 m = call_ret(mark, "doc:point", focus);
1635         if (!m)
1636                 return Efail;
1637
1638         if (rl->target_x < 0) {
1639                 rl->target_x = p->cx;
1640                 rl->target_y = p->cy - rl->cursor_line;
1641         }
1642         if (rl->target_x < 0)
1643                 /* maybe not displayed yet */
1644                 rl->target_x = rl->target_y = 0;
1645
1646         rl->i_moved = 1;
1647         num = RPT_NUM(ci);
1648         if (call("doc:EOL", ci->focus, num, m, NULL, 1) <= 0) {
1649                 rl->i_moved = 0;
1650                 return Efail;
1651         }
1652         if (RPT_NUM(ci) < 0) {
1653                 /* at end of target line, move to start */
1654                 if (call("doc:EOL", ci->focus, -1, m) <= 0) {
1655                         rl->i_moved = 0;
1656                         return Efail;
1657                 }
1658         }
1659
1660         /* We are at the start of the target line.  We might
1661          * like to find the target_x column, but if anything
1662          * goes wrong it isn't a failure.
1663          * Need to ensure there is a vmark here. call_render_line_prev()
1664          * wil only move the mark if it is in a multi-line rendering,
1665          * such as an image which acts as though it is multiple lines.
1666          * It will check if there is already a mark at the target location.
1667          * It will free the mark passed in unless it returns it.
1668          */
1669         start = vmark_new(focus, rl->typenum, p);
1670
1671         if (start) {
1672                 mark_to_mark(start, m);
1673                 start = call_render_line_prev(focus, start, 0, NULL);
1674         }
1675
1676         if (!start) {
1677                 pane_damaged(p, DAMAGED_VIEW);
1678                 goto done;
1679         }
1680         if (vmark_first(focus, rl->typenum, p) == start &&
1681             !vmark_is_valid(start))
1682                 /* New first mark, so view will have changed */
1683                 rl->repositioned = 1;
1684
1685         if (rl->target_x == 0 && rl->target_y == 0)
1686                 /* No need to move to target column - already there.
1687                  * This simplifies life for render-complete which is
1688                  * always at col 0, and messes with markup a bit.
1689                  */
1690                 goto done;
1691
1692         /* FIXME only do this if point is active/volatile, or
1693          * if start->mdata is NULL
1694          */
1695         vmark_invalidate(start);
1696         call_render_line(p, focus, start, NULL);
1697         if (!start->mdata)
1698                 goto done;
1699
1700         xypos = find_xy_line(p, focus, start, rl->target_x,
1701                              rl->target_y + start->mdata->y);
1702
1703         if (xypos < 0)
1704                 goto done;
1705         /* xypos is the distance from start-of-line to the target */
1706
1707         m2 = call_render_line_offset(focus, start, xypos);
1708         if (!m2)
1709                 goto done;
1710
1711         if (!mark_same(start, m)) {
1712                 /* This is a multi-line render and we aren't on
1713                  * the first line.  We might need a larger 'y'.
1714                  * For now, ensure that we move in the right
1715                  * direction.
1716                  * FIXME this loses target_x and can move up
1717                  * too far.  How to fix??
1718                  */
1719                 if (num > 0 && mark_ordered_not_same(m2, m))
1720                         mark_to_mark(m2, m);
1721                 if (num < 0 && mark_ordered_not_same(m, m2))
1722                         mark_to_mark(m2, m);
1723         }
1724         mark_to_mark(m, m2);
1725
1726         mark_free(m2);
1727
1728 done:
1729         rl->i_moved = 0;
1730         return 1;
1731 }
1732
1733 DEF_CMD(render_lines_notify_replace)
1734 {
1735         struct pane *p = ci->home;
1736         struct rl_data *rl = p->data;
1737         struct mark *start = ci->mark;
1738         struct mark *end = ci->mark2;
1739         struct mark *first;
1740
1741         if (strcmp(ci->key, "doc:replaced") == 0) {
1742                 struct mark *pt = call_ret(mark, "doc:point", ci->home);
1743
1744                 /* If anyone changes the doc, reset the target.  This might
1745                  * be too harsh, but I mainly want target tracking for
1746                  * close-in-time movement, so it probably doesn't matter.
1747                  */
1748                 rl->target_x = -1;
1749
1750                 /* If the replacement happened at 'point', then stop
1751                  * ignoring it, and handle the fact that point moved.
1752                  */
1753                 if (ci->mark2 == pt)
1754                         pane_call(p, "mark:moving", ci->focus, 0, pt);
1755         }
1756
1757         if (strcmp(ci->key, "view:changed") == 0)
1758                 /* Cursor possibly moved, so need to refresh */
1759                 pane_damaged(ci->home, DAMAGED_REFRESH);
1760
1761         if (!start && !end) {
1762                 /* No marks given - assume everything changed */
1763                 struct mark *m;
1764                 for (m = vmark_first(p, rl->typenum, p);
1765                      m;
1766                      m = vmark_next(m))
1767                         vmark_invalidate(m);
1768
1769                 pane_damaged(p, DAMAGED_VIEW);
1770                 return Efallthrough;
1771         }
1772
1773         if (start && end && start->seq > end->seq) {
1774                 start = ci->mark2;
1775                 end = ci->mark;
1776         }
1777
1778         if (strcmp(ci->key, "doc:replaced") == 0) {
1779                 first = vmark_first(ci->home, rl->typenum, p);
1780                 if (first && start &&  end && mark_same(first, end))
1781                         /* Insert just before visible region */
1782                         mark_to_mark(first, start);
1783         }
1784
1785         if (start) {
1786                 start = vmark_at_or_before(ci->home, start, rl->typenum, p);
1787                 if (!start)
1788                         start = vmark_first(ci->home, rl->typenum, p);
1789         } else {
1790                 start = vmark_at_or_before(ci->home, end, rl->typenum, p);
1791                 if (!start)
1792                         /* change is before visible region */
1793                         return Efallthrough;
1794                 /* FIXME check 'start' is at least 'num' before end */
1795         }
1796         if (end) {
1797                 end = vmark_at_or_before(ci->home, end, rl->typenum, p);
1798                 if (!end)
1799                         end = vmark_last(ci->home, rl->typenum, p);
1800         } else if (start) { /* smatch needs to know start in not NULL */
1801                 end = vmark_at_or_before(ci->home, start, rl->typenum, p);
1802                 if (!end)
1803                         end = vmark_first(ci->home, rl->typenum, p);
1804                 if (!end)
1805                         return Efallthrough;
1806                 if (vmark_next(end))
1807                         end = vmark_next(end);
1808                 /* FIXME check that 'end' is at least 'num' after start */
1809         }
1810
1811         if (!end || !start)
1812                 /* Change outside visible region */
1813                 return Efallthrough;
1814
1815         while (end && mark_ordered_or_same(start, end)) {
1816                 vmark_invalidate(end);
1817                 end = vmark_prev(end);
1818         }
1819         /* Must be sure to invalidate the line *before* the change */
1820         if (end)
1821                 vmark_invalidate(end);
1822
1823         pane_damaged(p, DAMAGED_VIEW);
1824
1825         return Efallthrough;
1826 }
1827
1828 DEF_CMD(render_lines_clip)
1829 {
1830         struct rl_data *rl = ci->home->data;
1831
1832         marks_clip(ci->home, ci->mark, ci->mark2, rl->typenum, ci->home,
1833                    !!ci->num);
1834         if (rl->header)
1835                 mark_clip(rl->header, ci->mark, ci->mark2, !!ci->num);
1836         return Efallthrough;
1837 }
1838
1839 DEF_CMD(render_lines_attach);
1840 DEF_CMD(render_lines_clone)
1841 {
1842         struct pane *parent = ci->focus;
1843
1844         render_lines_attach.func(ci);
1845         pane_clone_children(ci->home, parent->focus);
1846         return 1;
1847 }
1848
1849 DEF_CMD(render_lines_resize)
1850 {
1851         struct pane *p = ci->home;
1852         struct rl_data *rl = p->data;
1853         struct mark *m;
1854
1855         for (m = vmark_first(p, rl->typenum, p);
1856              m;
1857              m = vmark_next(m)) {
1858                 vmark_invalidate(m);
1859                 pane_damaged(m->mdata, DAMAGED_REFRESH);
1860         }
1861         rl->background_drawn = False;
1862         pane_damaged(p, DAMAGED_VIEW | DAMAGED_REFRESH);
1863
1864         /* Allow propagation to children */
1865         return 0;
1866 }
1867
1868 DEF_CMD(render_send_reposition)
1869 {
1870         /* Some (probably new) pane wants to know the extend of the
1871          * view, so resent render:resposition.
1872          */
1873         struct pane *p = ci->home;
1874         struct rl_data *rl = p->data;
1875
1876         rl->repositioned = 1;
1877         return Efallthrough;
1878 }
1879
1880 static struct map *rl_map;
1881
1882 DEF_LOOKUP_CMD(render_lines_handle, rl_map);
1883
1884 static void render_lines_register_map(void)
1885 {
1886         rl_map = key_alloc();
1887
1888         key_add(rl_map, "Move-View", &render_lines_move_view);
1889         key_add(rl_map, "Move-View-Pos", &render_lines_move_pos);
1890         key_add(rl_map, "Move-View-Line", &render_lines_view_line);
1891         key_add(rl_map, "Move-CursorXY", &render_lines_set_cursor);
1892         key_add(rl_map, "Move-Line", &render_lines_move_line);
1893
1894         /* Make it easy to stop ignoring point */
1895         key_add(rl_map, "Abort", &render_lines_abort);
1896
1897         key_add(rl_map, "Activate", &render_lines_activate);
1898
1899         key_add(rl_map, "Close", &render_lines_close);
1900         key_add(rl_map, "Close:mark", &render_lines_close_mark);
1901         key_add(rl_map, "Free", &edlib_do_free);
1902         key_add(rl_map, "Clone", &render_lines_clone);
1903         key_add(rl_map, "Refresh", &render_lines_refresh);
1904         key_add(rl_map, "Refresh:view", &render_lines_revise);
1905         key_add(rl_map, "Refresh:size", &render_lines_resize);
1906         key_add(rl_map, "Notify:clip", &render_lines_clip);
1907         key_add(rl_map, "get-attr", &render_lines_get_attr);
1908         key_add(rl_map, "mark:moving", &render_lines_point_moving);
1909
1910         key_add(rl_map, "doc:replaced", &render_lines_notify_replace);
1911         key_add(rl_map, "doc:replaced-attr", &render_lines_notify_replace);
1912         /* view:changed is sent to a tile when the display might need
1913          * to change, even though the doc may not have*/
1914         key_add(rl_map, "view:changed", &render_lines_notify_replace);
1915         key_add(rl_map, "render:request:reposition", &render_send_reposition);
1916 }
1917
1918 REDEF_CMD(render_lines_attach)
1919 {
1920         struct rl_data *rl;
1921         struct pane *p;
1922
1923         if (!rl_map)
1924                 render_lines_register_map();
1925
1926         alloc(rl, pane);
1927         rl->target_x = -1;
1928         rl->target_y = -1;
1929         rl->do_wrap = 1;
1930         p = ci->focus;
1931         if (strcmp(ci->key, "attach-render-text") == 0) {
1932                 p = call_ret(pane, "attach-markup", p);
1933                 if (!p)
1934                         p = ci->focus;
1935         }
1936         p = pane_register(p, 0, &render_lines_handle.c, rl);
1937         if (!p) {
1938                 free(rl);
1939                 return Efail;
1940         }
1941         rl->typenum = home_call(ci->focus, "doc:add-view", p) - 1;
1942         call("doc:request:doc:replaced", p);
1943         call("doc:request:doc:replaced-attr", p);
1944         call("doc:request:mark:moving", p);
1945
1946         return comm_call(ci->comm2, "callback:attach", p);
1947 }
1948
1949 void edlib_init(struct pane *ed safe)
1950 {
1951         call_comm("global-set-command", ed, &render_lines_attach, 0, NULL,
1952                   "attach-render-lines");
1953         call_comm("global-set-command", ed, &render_lines_attach, 0, NULL,
1954                   "attach-render-text");
1955 }