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