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