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