2 * Copyright Neil Brown ©2015-2021 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Rendering for any document which presents as a sequence of lines.
7 * The underlying document, or an intervening filter, must return lines of
8 * text in response to the "doc:render-line" command.
9 * This takes a mark and moves it to the end of the rendered line
10 * so that another call will produce another line.
11 * "doc:render-line" must always return a full line including '\n'
12 * unless the result would be bigger than the 'max' passed in ->num or,
13 * when ->num==-1, unless the rendering would go beyond the location in
14 * ->mark2. In these cases it can stop before a '\n'. In each case,
15 * the mark is moved to the end of the region that was rendered;
16 * This allows a mark to be found for a given character position, or a display
17 * position found for a given mark.
18 * For the standard 'render the whole line' functionality, ->num should
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.
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.
31 * Other control characters should be rendered as
32 * e.g. <fg:red>^X</> - in particular, nul must not appear in the line.
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
46 * The last mark may also be mid-line, and it must never have an
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.
54 * To render the pane we:
55 * 1/ call 'render-line-prev' on a mark at the point and look for that mark
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
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.
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.
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.
81 #define MARK_DATA_PTR struct pane
82 #define _GNU_SOURCE /* for asprintf */
85 #include <stdio.h> /* snprintf */
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
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'.
103 int top_sol; /* true when first mark is at a start-of-line */
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
110 short target_x, target_y;
111 short i_moved; /* I moved cursor, so don't clear
118 int repositioned; /* send "render:reposition" when we know
119 * full position again.
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;
126 /* If cursor not visible, we add this pane in bottom-right and place
129 struct pane *cursor_pane;
132 static void vmark_clear(struct mark *m safe)
135 pane_close(m->mdata);
140 static void vmark_free(struct mark *m safe)
146 static void vmark_set(struct pane *p safe, struct mark *m safe, char *line)
149 m->mdata = call_ret(pane, "attach-renderline", p);
151 pane_call(m->mdata, "render-line:set", p, 0, NULL, line);
154 static void vmark_invalidate(struct mark *m safe)
157 pane_call(m->mdata, "render-line:invalidate", m->mdata);
160 static bool vmark_is_valid(struct mark *m safe)
164 return pane_attr_get_int(m->mdata, "render-line:valid", 0) == 1;
167 /* Returns 'true' at end-of-page */
168 static bool measure_line(struct pane *p safe, struct pane *focus safe,
169 struct mark *mk safe, short cursor_offset)
171 struct pane *hp = mk->mdata;
175 pane_resize(hp, hp->x, hp->y, p->w, p->h);
176 ret = pane_call(hp, "render-line:measure",
177 focus, cursor_offset);
179 /* end-of-page flag */
183 /* Returns offset of posx,posy */
184 static int find_xy_line(struct pane *p safe, struct pane *focus safe,
185 struct mark *mk safe, short posx, short posy)
187 struct pane *hp = mk->mdata;
192 "render-line:findxy",
196 posx - hp->x, posy - hp->y);
199 return ret > 0 ? (ret - 1) : -1;
202 static bool draw_line(struct pane *p safe, struct pane *focus safe,
203 struct mark *mk safe, short offset)
205 struct rl_data *rl = p->data;
206 struct pane *hp = mk->mdata;
210 ret = pane_call(hp, "render-line:draw", focus, offset);
212 struct xy curs = pane_mapxy(hp, p,
213 hp->cx, hp->cy, False);
222 if (hp->x + hp->w > rl->cols)
223 rl->cols = hp->x + hp->w;
228 static struct mark *call_render_line_prev(struct pane *p safe,
237 ret = call("doc:render-line-prev", p, n, m);
239 /* if n>0 we can fail because start-of-file was found before
240 * any newline. In that case ret == Efail, and we return NULL.
243 *found = (ret == Efail);
248 /* current line is start-of-file */
253 m2 = vmark_matching(m);
261 static void call_render_line(struct pane *home safe, struct pane *p safe,
262 struct mark *start safe, struct mark **end)
267 m = mark_dup_view(start);
268 if (doc_following(p, m) == WEOF) {
269 /* We only create a subpane for EOF when it is at start
270 * of line, else it is included in the preceding line.
272 call("doc:render-line-prev", p, 0, m);
273 if (!mark_same(m, start)) {
280 s = call_ret(strsave, "doc:render-line", p, NO_NUMERIC, m);
282 vmark_set(home, start, s);
284 m2 = vmark_matching(m);
289 /*FIXME shouldn't be needed */
292 /* Any mark between start and m2 must be discarded,
294 while ((m = vmark_next(start)) != NULL &&
296 if (end && m == *end)
300 /* Any mark at same location as m2 must go too. */
301 while ((m = vmark_next(m2)) != NULL &&
303 if (end && m == *end)
314 static struct mark *call_render_line_offset(struct pane *p safe,
315 struct mark *start safe, int offset)
319 m = mark_dup_view(start);
320 if (call_comm("doc:render-line", p, &no_save, offset, m) <= 0) {
330 int l = strlen(ci->str);
331 while (l >=4 && strncmp(ci->str+l-3, "</>", 3) == 0 &&
339 static int call_render_line_to_point(struct pane *p safe, struct mark *pm safe,
340 struct mark *start safe)
343 struct mark *m = mark_dup_view(start);
345 len = call_comm("doc:render-line", p, &get_len, -1, m, NULL, 0, pm);
353 /* Choose a new set of lines to display, and mark each one with a line marker.
354 * We start at pm and move both backwards and forwards one line at a time.
355 * We stop moving in one of the directions when
356 * - we hit start/end of file
357 * - when the edge in the *other* direction enters the previously visible
358 * area (if there was one). This increases stability of display when
359 * we move off a line or 2.
360 * - when we reach the given line count (vline). A positive count restricts
361 * backward movement, a negative restricts forwards movement.
364 static bool step_back(struct pane *p safe, struct pane *focus safe,
365 struct mark **startp safe, struct mark **endp,
366 short *y_pre safe, short *line_height_pre safe)
368 /* step backwards moving start */
369 struct rl_data *rl = p->data;
371 bool found_start = False;
372 struct mark *start = *startp;
376 m = call_render_line_prev(focus, mark_dup_view(start),
379 /* no text before 'start' */
384 if (!vmark_is_valid(start))
385 call_render_line(p, focus, start, endp);
386 measure_line(p, focus, start, -1);
387 h = start->mdata ? start->mdata->h : 0;
391 attr_find_int(start->mdata->attrs,
400 static bool step_fore(struct pane *p safe, struct pane *focus safe,
401 struct mark **startp safe, struct mark **endp safe,
402 short *y_post safe, short *line_height_post safe)
404 struct mark *end = *endp;
405 bool found_end = False;
409 if (!vmark_is_valid(end))
410 call_render_line(p, focus, end, startp);
411 found_end = measure_line(p, focus, end, -1);
413 *y_post = end->mdata->h;
414 if (*y_post > 0 && end->mdata)
416 attr_find_int(end->mdata->attrs,
418 if (!end->mdata || !end->mdata->h)
421 end = vmark_next(end);
424 if (p->h >= *line_height_post *2)
432 static int consume_space(struct pane *p safe, int y,
433 short *y_prep safe, short *y_postp safe,
434 short *lines_above safe, short *lines_below safe,
435 int found_start, int found_end,
436 int line_height_pre, int line_height_post)
439 int y_post = *y_postp;
441 if (y_pre > 0 && y_post > 0) {
442 int consume = (y_post < y_pre
443 ? y_post : y_pre) * 2;
445 if (consume > p->h - y)
447 if (y_pre > y_post) {
448 above = consume - (consume/2);
451 below = consume - (consume/2);
456 *lines_above += above / (line_height_pre?:1);
458 *lines_below += below / (line_height_post?:1);
459 /* We have just consumed all of one of
460 * lines_{above,below} so they are no longer
464 if (found_end && y_pre) {
465 int consume = p->h - y;
470 *lines_above += consume / (line_height_pre?:1);
472 if (found_start && y_post) {
473 int consume = p->h - y;
474 if (consume > y_post)
478 *lines_below += consume / (line_height_post?:1);
486 * Choose new start/end to be displayed in the given pane.
487 * 'pm' must be displayed, and if vline is not NO_NUMERIC,
488 * pm should be displayed on that line of the display, where
489 * negative numbers count from the bottom of the page.
490 * Otherwise pm should be at least pm->margin from top and bottom.
491 * In no case should start-of-file be *after* top of display.
492 * If there is an existing display, move the display as little as
493 * possible while complying with the above.
495 * We start at 'pm' and move both forward and backward one line at a
496 * time measuring each line and assessing space used.
497 * - If the space above pm reaches positive vline, that will be top.
498 * - If the space below reaches negative vline, that will likely be bottom
499 * - If pm was before old top and we reach the old top going down,
500 * and if space measured before pm has reached ->margin, we stop
502 * - If pm was after old bottom and we reach the old bottom going up
503 * and if space measured after pm has reached ->margin, we stop
506 * If we decide to stop moving in both directions, but have not
507 * reached EOF or full height of display, keep moving downwards.
509 static void find_lines(struct mark *pm safe, struct pane *p safe,
510 struct pane *focus safe,
513 struct rl_data *rl = p->data;
514 struct mark *orig_top, *orig_bot;
515 struct mark *top, *bot; // boundary of previous display
517 struct mark *start, *end; // current estimate for new display
519 short lines_above = 0, lines_below = 0; /* distance from start/end
522 short offset; // pos of pm in rendering of that line
523 bool found_start = False, found_end = False;
524 /* y_pre and y_post are measurement from start/end that
525 * haven't yet been included into lines_above/lines_below.
527 short y_pre = 0, y_post = 0;
528 short line_height_pre = 1, line_height_post = 1;
530 orig_top = vmark_first(focus, rl->typenum, p);
531 orig_bot = vmark_last(focus, rl->typenum, p);
532 /* Protect top/bot from being freed by call_render_line */
534 orig_top = mark_dup(orig_top);
536 orig_bot = mark_dup(orig_bot);
538 start = vmark_new(focus, rl->typenum, p);
541 rl->repositioned = 1;
542 mark_to_mark(start, pm);
543 start = call_render_line_prev(focus, start, 0, &rl->top_sol);
546 offset = call_render_line_to_point(focus, pm, start);
547 if (!vmark_is_valid(start))
548 call_render_line(p, focus, start, NULL);
549 end = vmark_next(start);
550 /* Note: 'end' might be NULL if 'start' is end-of-file, otherwise
551 * call_render_line() will have created 'end' if it didn't exist.
556 /* ->cy is top of cursor, we want to measure from bottom */
558 struct pane *hp = start->mdata;
560 found_end = measure_line(p, focus, start, offset);
562 curs_width = pane_attr_get_int(
563 start->mdata, "curs_width", 1);
564 while (!rl->do_wrap && hp->cx + curs_width >= p->w) {
565 int shift = 8 * curs_width;
568 rl->shift_left += shift;
569 measure_line(p, focus, start, offset);
571 line_height_pre = attr_find_int(start->mdata->attrs, "line-height");
572 if (line_height_pre < 1)
574 y_pre = start->mdata->cy + line_height_pre;
575 y_post = start->mdata->h - y_pre;
577 /* Should never happen */
583 if (p->h > line_height_pre * 2)
586 /* Small display, no space at EOF */
590 if (rl->header && rl->header->mdata)
591 y = rl->header->mdata->h;
593 /* We have start/end of the focus line. When rendered this,
594 * plus header and eof-footed would use y_pre + y + y_post
599 if (orig_bot && mark_ordered_or_same(orig_bot, start))
600 /* could cross bot, so don't ignore it */
602 if (orig_top && end && mark_ordered_or_same(end, orig_top))
604 if (vline != NO_NUMERIC) {
605 /* ignore current position - top/bot irrelevant */
610 while ((!found_start || !found_end) && y < p->h) {
611 if (vline != NO_NUMERIC) {
612 if (!found_start && vline > 0 &&
613 lines_above >= vline-1)
615 if (!found_end && vline < 0 &&
616 lines_below >= -vline-1)
619 if (!found_start && y_pre <= 0)
620 found_start = step_back(p, focus, &start, &end,
621 &y_pre, &line_height_pre);
623 if (found_end && y_post && bot &&
624 mark_ordered_or_same(start, bot))
625 /* Extra vertical space gets inserted after EOF when
626 * there is a long jump to get there, but if we hit 'bot'
627 * soon when searching back, we discard any unused space.
631 if (!found_end && bot &&
632 mark_ordered_not_same(start, bot))
633 /* Overlap original from below, so prefer to
634 * maximize that overlap.
638 if (!found_end && bot && mark_same(start, bot) &&
639 y_pre - rl->skip_height >= y_post)
640 /* No overlap in marks yet, but over-lap in space,
641 * so same result as above.
645 if (!found_end && y_post <= 0)
647 found_end = step_fore(p, focus, &start, &end,
648 &y_post, &line_height_post);
650 if (!found_start && top && end &&
651 mark_ordered_not_same(top, end))
654 if (!found_start && top && end && mark_same(top, end)
655 && y_post - rl->tail_height >= y_pre)
658 y = consume_space(p, y, &y_pre, &y_post,
659 &lines_above, &lines_below,
660 found_start, found_end,
661 line_height_pre, line_height_post);
663 /* We might need to continue downwards even after found_end
664 * if there is more space.
666 found_end = end == NULL;
667 while (!found_end && y < p->h) {
669 found_end = step_fore(p, focus, &start, &end,
670 &y_post, &line_height_post);
671 y = consume_space(p, y, &y_pre, &y_post,
672 &lines_above, &lines_below,
673 found_start, found_end,
674 line_height_pre, line_height_post);
677 if (start->mdata && start->mdata->h <= y_pre) {
679 m = vmark_next(start);
686 rl->skip_height = y_pre;
687 rl->skip_line_height = line_height_pre;
688 rl->tail_height = y_post;
689 /* Now discard any marks outside start-end */
690 if (end && end->seq < start->seq)
691 /* something confused, make sure we don't try to use 'end' after
695 while ((m = vmark_prev(start)) != NULL)
699 while ((m = vmark_next(end)) != NULL)
706 if (rl->header && rl->header->mdata)
707 y = rl->header->mdata->h;
708 y -= rl->skip_height;
709 for (m = vmark_first(focus, rl->typenum, p);
710 m && m->mdata ; m = vmark_next(m)) {
711 struct pane *hp = m->mdata;
712 pane_resize(hp, hp->x, y, hp->w, hp->h);
715 pane_damaged(p, DAMAGED_REFRESH);
716 m = vmark_first(focus, rl->typenum, p);
717 if (!m || !orig_top || !mark_same(m, orig_top))
718 rl->repositioned = 1;
719 m = vmark_last(focus, rl->typenum, p);
720 if (!m || !orig_bot || !mark_same(m, orig_bot))
721 rl->repositioned = 1;
728 DEF_CMD(cursor_handle)
733 static int render(struct mark *pm, struct pane *p safe,
734 struct pane *focus safe)
736 struct rl_data *rl = p->data;
739 struct xy scale = pane_scale(focus);
742 int cursor_drawn = 0;
744 s = pane_attr_get(focus, "hide-cursor");
745 if (s && strcmp(s, "yes") == 0)
749 m = vmark_first(focus, rl->typenum, p);
750 s = pane_attr_get(focus, "background");
751 if (s && strncmp(s, "call:", 5) == 0) {
752 home_call(focus, "pane-clear", p);
753 home_call(focus, s+5, p, 0, m);
754 } else if (rl->background_drawn)
757 home_call(focus, "pane-clear", p);
758 else if (strncmp(s, "color:", 6) == 0) {
762 home_call(focus, "pane-clear", p, 0, NULL, a);
764 } else if (strncmp(s, "image:", 6) == 0) {
765 home_call(focus, "pane-clear", p);
766 home_call(focus, "Draw:image", p, 1, NULL, s+6);
768 home_call(focus, "pane-clear", p);
769 rl->background_drawn = True;
771 if (rl->header && vmark_is_valid(rl->header)) {
772 draw_line(p, focus, rl->header, -1);
773 y = rl->header->mdata->h;
775 y -= rl->skip_height;
780 while (m && m->mdata) {
782 if (!hide_cursor && p->cx <= 0 && pm &&
783 mark_ordered_or_same(m, pm) &&
784 (!(m2 && doc_following(focus, m2) != WEOF) ||
785 mark_ordered_not_same(pm, m2))) {
786 short len = call_render_line_to_point(focus, pm,
788 draw_line(p, focus, m, len);
789 rl->cursor_line = m->mdata->y + m->mdata->cy;
793 draw_line(p, focus, m, -1);
796 y = m->mdata->y + m->mdata->h;
799 if (!cursor_drawn && !hide_cursor) {
800 /* Place cursor in bottom right */
801 struct pane *cp = rl->cursor_pane;
806 cp = pane_register(p, -1, &cursor_handle);
807 rl->cursor_pane = cp;
812 m2 = vmark_last(focus, rl->typenum, p);
814 while (m2 && mwidth <= 0) {
816 mwidth = pane_attr_get_int(
817 m2->mdata, "curs_width", -1);
818 lineheight = pane_attr_get_int(
819 m2->mdata, "line-height", -1);
834 home_call(focus, "pane-clear", cp);
835 home_call(focus, "Draw:text", cp, 0, NULL, " ",
839 } else if (rl->cursor_pane) {
840 pane_close(rl->cursor_pane);
841 rl->cursor_pane = NULL;
846 DEF_CMD(render_lines_get_attr)
848 struct rl_data *rl = ci->home->data;
850 if (ci->str && strcmp(ci->str, "shift_left") == 0) {
853 return comm_call(ci->comm2, "cb", ci->focus,
855 snprintf(ret, sizeof(ret), "%d", rl->shift_left);
856 return comm_call(ci->comm2, "cb", ci->focus, 0, NULL, ret);
861 DEF_CMD(render_lines_point_moving)
863 struct pane *p = ci->home;
864 struct rl_data *rl = p->data;
865 struct mark *pt = call_ret(mark, "doc:point", ci->home);
869 /* Stop igoring point, because it is probably relevant now */
870 rl->ignore_point = 0;
872 /* Someone else moved the point, so reset target column */
877 static int revalidate_start(struct rl_data *rl safe,
878 struct pane *p safe, struct pane *focus safe,
879 struct mark *start safe, struct mark *pm,
883 bool on_screen = False;
885 bool found_end = False;
887 if (pm && !rl->do_wrap) {
890 /* Need to check if side-shift is needed on cursor line */
892 call("doc:render-line-prev", focus, 0, m2);
894 m = vmark_at_or_before(focus, m2, rl->typenum, p);
898 (!vmark_is_valid(m) || refresh_all)) {
899 pane_damaged(p, DAMAGED_REFRESH);
900 call("doc:render-line-prev", focus, 0, m);
901 call_render_line(p, focus, m, &start);
904 struct pane *hp = m->mdata;
905 int offset = call_render_line_to_point(focus,
907 measure_line(p, focus, m, offset);
908 prefix_len = pane_attr_get_int(
909 m->mdata, "prefix_len", -1);
910 curs_width = pane_attr_get_int(
911 m->mdata, "curs_width", 1);
913 while (hp->cx + curs_width >= p->w) {
914 int shift = 8 * curs_width;
917 rl->shift_left += shift;
918 measure_line(p, focus, m, offset);
921 while (hp->cx < prefix_len &&
922 rl->shift_left > 0 &&
923 hp->cx + curs_width * 8*curs_width < p->w) {
924 int shift = 8 * curs_width;
925 if (shift > rl->shift_left)
926 shift = rl->shift_left;
927 rl->shift_left -= shift;
928 measure_line(p, focus, m, offset);
935 struct pane *hp = rl->header->mdata;
937 measure_line(p, focus, rl->header, -1);
939 pane_resize(hp, hp->x, y, hp->w, hp->h);
944 y -= rl->skip_height;
945 for (m = start; m && !found_end && y < p->h; m = vmark_next(m)) {
947 if (refresh_all || !vmark_is_valid(m)) {
948 pane_damaged(p, DAMAGED_REFRESH);
949 call_render_line(p, focus, m, NULL);
951 found_end = measure_line(p, focus, m, -1);
957 pane_damaged(p, DAMAGED_REFRESH);
958 pane_resize(hp, hp->x, y, hp->w, hp->h);
962 if (pm && m == start && rl->skip_height > 0 && m2 &&
963 mark_ordered_not_same(pm, m2)) {
964 /* Point might be in this line, but off top
967 int offset = call_render_line_to_point(focus,
970 measure_line(p, focus, m, offset);
971 if (hp->cy >= rl->skip_height + rl->margin)
972 /* Cursor is visible on this line */
975 } else if (pm && y >= p->h && m->seq < pm->seq) {
976 /* point might be in this line, but off end
979 int offset = call_render_line_to_point(focus,
983 measure_line(p, focus, m, offset);
984 lh = attr_find_int(hp->attrs,
988 if (y - hp->h + hp->cy <= p->h - lh - rl->margin) {
989 /* Cursor is on screen */
993 } else if (pm && mark_ordered_or_same(m, pm) && m2 &&
994 mark_ordered_or_same(pm, m2)) {
998 int offset = call_render_line_to_point(
1003 measure_line(p, focus, m, offset);
1004 lh = attr_find_int(hp->attrs,
1006 cy = y - hp->h + hp->cy;
1007 if (cy >= rl->margin && cy <= p->h - rl->margin - lh)
1008 /* Cursor at least margin from edge */
1015 rl->tail_height = p->h - y;
1017 rl->tail_height = 0;
1020 while ((m2 = vmark_next(m)) != NULL) {
1021 /* end of view has clearly changed */
1022 rl->repositioned = 1;
1026 if (!pm || on_screen) {
1027 if (rl->repositioned) {
1028 rl->repositioned = 0;
1029 call("render:reposition", focus,
1030 rl->lines, vmark_first(focus,
1033 rl->cols, vmark_last(focus,
1043 DEF_CMD(render_lines_revise)
1045 struct pane *p = ci->home;
1046 struct pane *focus = ci->focus;
1047 struct rl_data *rl = p->data;
1048 struct mark *pm = NULL;
1049 struct mark *m1, *m2;
1050 bool refresh_all = False;
1054 a = pane_attr_get(focus, "render-wrap");
1055 if (rl->do_wrap != (!a || strcmp(a, "yes") ==0)) {
1056 rl->do_wrap = (!a || strcmp(a, "yes") ==0);
1060 rl->margin = pane_attr_get_int(focus, "render-vmargin", 0);
1061 if (rl->margin >= p->h/2)
1064 hdr = pane_attr_get(focus, "heading");
1070 rl->header = vmark_new(focus, MARK_UNGROUPED, NULL);
1072 vmark_set(p, rl->header, hdr);
1073 measure_line(p, focus, rl->header, -1);
1075 } else if (rl->header) {
1076 vmark_free(rl->header);
1080 if (!rl->ignore_point)
1081 pm = call_ret(mark, "doc:point", focus);
1082 m1 = vmark_first(focus, rl->typenum, p);
1083 m2 = vmark_last(focus, rl->typenum, p);
1085 if (m1 && !vmark_is_valid(m1))
1086 /* newline before might have been deleted, better check */
1087 call("doc:render-line-prev", focus, 0, m1);
1088 // FIXME double check that we invalidate line before any change...
1091 (!pm || (mark_ordered_or_same(m1,pm)))) {
1092 /* We maybe be able to keep m1 as start, if things work out.
1093 * So check all sub-panes are still valid and properly
1096 if (revalidate_start(rl, p, focus, m1, pm, refresh_all))
1099 /* Need to find a new top-of-display */
1101 pm = call_ret(mark, "doc:point", focus);
1103 /* Don't know what to do here... */
1105 find_lines(pm, p, focus, NO_NUMERIC);
1106 rl->repositioned = 0;
1107 call("render:reposition", focus,
1108 rl->lines, vmark_first(focus, rl->typenum, p), NULL,
1109 rl->cols, vmark_last(focus, rl->typenum, p), NULL,
1114 DEF_CMD(render_lines_refresh)
1116 struct pane *p = ci->home;
1117 struct pane *focus = ci->focus;
1118 struct rl_data *rl = p->data;
1119 struct mark *m, *pm = NULL;
1121 //pane_damaged(p, DAMAGED_VIEW);
1123 pm = call_ret(mark, "doc:point", focus);
1125 m = vmark_first(focus, rl->typenum, p);
1130 rl->lines = render(pm, p, focus);
1135 DEF_CMD(render_lines_close)
1137 struct pane *p = ci->home;
1138 struct rl_data *rl = p->data;
1141 while ((m = vmark_first(p, rl->typenum, p)) != NULL)
1144 vmark_free(rl->header);
1147 call("doc:del-view", p, rl->typenum);
1151 DEF_CMD(render_lines_abort)
1153 struct pane *p = ci->home;
1154 struct rl_data *rl = p->data;
1156 rl->ignore_point = 0;
1159 pane_damaged(p, DAMAGED_VIEW);
1161 /* Allow other handlers to complete the Abort */
1162 return Efallthrough;
1165 DEF_CMD(render_lines_move)
1168 * Find a new 'top' for the displayed region so that render()
1169 * will draw from there.
1170 * When moving backwards we move back a line and render it.
1171 * When moving forwards we render and then step forward
1172 * At each point we count the number of display lines that result.
1173 * When we choose a new start, we delete all earlier marks.
1174 * We also delete marks before current top when moving forward
1175 * where there are more than a page full.
1177 struct pane *p = ci->home;
1178 struct pane *focus = ci->focus;
1179 int rpt = RPT_NUM(ci);
1180 struct rl_data *rl = p->data;
1181 struct mark *top, *old_top;
1182 int pagesize = p->h / 10;
1184 top = vmark_first(focus, rl->typenum, p);
1186 return Efallthrough;
1188 old_top = mark_dup(top);
1189 if (strcmp(ci->key, "Move-View-Large") == 0)
1190 pagesize = p->h * 9 / 10;
1191 rpt *= pagesize ?: 1;
1193 rl->ignore_point = 1;
1195 if (rl->skip_line_height <= 0)
1196 rl->skip_line_height = 1;
1199 /* Need to add new lines at the top and remove
1205 struct mark *prevtop = top;
1207 if (rl->skip_height) {
1208 rl->skip_height -= rl->skip_line_height;
1209 if (rl->skip_height < rl->skip_line_height/2)
1210 rl->skip_height = 0;
1211 rpt += rl->skip_line_height;
1217 m = mark_dup_view(top);
1218 top = call_render_line_prev(focus, m,
1220 if (!top && doc_prior(focus, prevtop) != WEOF) {
1221 /* Double check - maybe a soft top-of-file */
1222 m = mark_dup(prevtop);
1224 top = call_render_line_prev(focus, m,
1230 while (m && m->seq < prevtop->seq &&
1231 !mark_same(m, prevtop)) {
1232 if (!vmark_is_valid(m))
1233 call_render_line(p, focus, m, NULL);
1234 if (m->mdata == NULL) {
1238 measure_line(p, focus, m, -1);
1242 /* FIXME remove extra lines, maybe add */
1243 rl->skip_height = y;
1246 /* Need to remove lines from top */
1247 if (!vmark_is_valid(top))
1248 call_render_line(p, focus, top, NULL);
1249 measure_line(p, focus, top, -1);
1250 while (top && top->mdata && rpt > 0) {
1254 if (rpt < y - rl->skip_height) {
1255 rl->skip_height += rpt;
1258 rpt -= y - rl->skip_height;
1259 rl->skip_height = 0;
1260 top = vmark_next(top);
1263 if (!vmark_is_valid(top))
1264 call_render_line(p, focus, top, NULL);
1265 measure_line(p, focus, top, -1);
1267 if (top && top->mdata) {
1268 /* We didn't fall off the end, so it is OK to remove
1269 * everything before 'top'
1272 while ((old = vmark_first(focus, rl->typenum, p)) != NULL &&
1277 rl->repositioned = 1;
1278 pane_damaged(ci->home, DAMAGED_VIEW);
1279 top = vmark_first(focus, rl->typenum, p);
1280 if (top && mark_same(top, old_top)) {
1288 static char *get_active_tag(const char *a)
1295 t = strstr(a, ",active-tag:");
1300 return strndup(t, c?c-t: (int)strlen(t));
1303 DEF_CMD(render_lines_set_cursor)
1306 * 1 if this resulted from a click
1307 * 2 if from a release
1309 * 0 any other reason.
1311 struct pane *p = ci->home;
1312 struct pane *focus = ci->focus;
1313 struct rl_data *rl = p->data;
1315 struct mark *m2 = NULL;
1319 cih = pane_mapxy(ci->focus, ci->home,
1320 ci->x >= 0 ? ci->x : p->cx >= 0 ? p->cx : 0,
1321 ci->y >= 0 ? ci->y : p->cy >= 0 ? p->cx : 0,
1324 m = vmark_first(p, rl->typenum, p);
1326 while (m && m->mdata && m->mdata->y + m->mdata->h <= cih.y)
1330 /* There is nothing rendered? */
1333 /* chi is after the last visible content, and m is the end
1334 * of that content (possible EOF) so move there
1337 if (cih.y < m->mdata->y)
1338 cih.y = m->mdata->y;
1339 xypos = find_xy_line(p, focus, m, cih.x, cih.y);
1341 m2 = call_render_line_offset(focus, m, xypos);
1346 if (ci->num == 2) { /* Mouse release */
1347 xyattr = pane_attr_get(m->mdata, "xyattr");
1348 tag = get_active_tag(xyattr);
1351 asprintf(&c, "Mouse-Activate:%s", tag);
1353 call(c, focus, 0, m2, tag,
1354 0, ci->mark, xyattr);
1360 /* m is the closest we'll get */
1364 mark_to_mark(ci->mark, m);
1366 call("Move-to", focus, 0, m);
1372 DEF_CMD(render_lines_move_pos)
1374 struct pane *p = ci->home;
1375 struct pane *focus = ci->focus;
1376 struct rl_data *rl = p->data;
1377 struct mark *pm = ci->mark;
1378 struct mark *top, *bot;
1382 rl->ignore_point = 1;
1383 top = vmark_first(focus, rl->typenum, p);
1384 bot = vmark_last(focus, rl->typenum, p);
1385 if (top && rl->skip_height)
1386 /* top line not fully displayed, being in that line is
1388 top = vmark_next(top);
1390 /* last line might not be fully displayed, so don't assume */
1391 bot = vmark_prev(bot);
1393 !mark_ordered_or_same(top, pm) ||
1394 !mark_ordered_not_same(pm, bot))
1395 /* pos not displayed */
1396 find_lines(pm, p, focus, NO_NUMERIC);
1397 pane_damaged(p, DAMAGED_REFRESH);
1398 /* FIXME this should only be done in the above find_lines(), but
1399 * search currently depends on this incorrect behaviour
1401 rl->repositioned = 1;
1405 DEF_CMD(render_lines_view_line)
1407 struct pane *p = ci->home;
1408 struct pane *focus = ci->focus;
1409 struct rl_data *rl = p->data;
1410 struct mark *pm = ci->mark;
1416 if (line == NO_NUMERIC)
1419 while ((top = vmark_first(focus, rl->typenum, p)) != NULL)
1422 rl->ignore_point = 1;
1423 find_lines(pm, p, focus, line);
1424 pane_damaged(p, DAMAGED_REFRESH);
1428 DEF_CMD(render_lines_move_line)
1430 /* FIXME should be able to select between display lines
1431 * and content lines - different when a line wraps.
1432 * For now just content lines.
1433 * target_x and target_y are the target location in a line
1434 * relative to the start of line.
1435 * We use doc:EOL to find a suitable start of line, then
1436 * render that line and find the last location not after x,y
1438 struct pane *p = ci->home;
1439 struct pane *focus = ci->focus;
1440 struct rl_data *rl = p->data;
1443 struct mark *m = ci->mark;
1447 m = call_ret(mark, "doc:point", focus);
1451 if (rl->target_x < 0) {
1452 rl->target_x = p->cx;
1453 rl->target_y = p->cy - rl->cursor_line;
1455 if (rl->target_x < 0)
1456 /* maybe not displayed yet */
1457 rl->target_x = rl->target_y = 0;
1465 if (!call("doc:EOL", ci->focus, num, m)) {
1469 if (RPT_NUM(ci) > 0) {
1470 /* at end of target line, move to start */
1471 if (!call("doc:EOL", ci->focus, -1, m)) {
1477 start = vmark_new(focus, rl->typenum, p);
1480 mark_to_mark(start, m);
1481 start = call_render_line_prev(focus, start, 0, NULL);
1485 pane_damaged(p, DAMAGED_VIEW);
1489 if (vmark_first(focus, rl->typenum, p) == start) {
1490 /* New first mark, so view will have changed */
1491 rl->repositioned = 1;
1494 if (rl->target_x == 0 && rl->target_y == 0) {
1495 /* No need to move to target column - already there.
1496 * This simplifies life for render-complete which is
1497 * always at col 0, and messes with markup a bit.
1502 /* FIXME only do this if point is active/volatile, or
1503 * if start->mdata is NULL
1505 call_render_line(p, focus, start, NULL);
1507 xypos = find_xy_line(p, focus, start, rl->target_x,
1508 rl->target_y + start->mdata->y);
1510 /* xypos is the distance from start-of-line to the target */
1512 struct mark *m2 = call_render_line_offset(
1513 focus, start, xypos);
1515 mark_to_mark(m, m2);
1522 DEF_CMD(render_lines_notify_replace)
1524 struct pane *p = ci->home;
1525 struct rl_data *rl = p->data;
1526 struct mark *start = ci->mark;
1527 struct mark *end = ci->mark2;
1530 if (strcmp(ci->key, "doc:replaced") == 0) {
1531 struct mark *pt = call_ret(mark, "doc:point", ci->home);
1533 /* If anyone changes the doc, reset the target. This might
1534 * be too harsh, but I mainly want target tracking for
1535 * close-in-time movement, so it probably doesn't matter.
1539 /* If the replacement happened at 'point', then stop
1542 if (ci->mark2 == pt)
1543 rl->ignore_point = 0;
1546 if (!start && !end) {
1547 /* No marks given - assume everything changed */
1549 for (m = vmark_first(p, rl->typenum, p);
1552 vmark_invalidate(m);
1554 pane_damaged(p, DAMAGED_VIEW);
1555 return Efallthrough;
1558 if (start && end && start->seq > end->seq) {
1563 if (strcmp(ci->key, "doc:replaced") == 0) {
1564 first = vmark_first(ci->home, rl->typenum, p);
1565 if (first && start && end && mark_same(first, end))
1566 /* Insert just before visible region */
1567 mark_to_mark(first, start);
1571 start = vmark_at_or_before(ci->home, start, rl->typenum, p);
1573 start = vmark_first(ci->home, rl->typenum, p);
1575 start = vmark_at_or_before(ci->home, end, rl->typenum, p);
1577 /* change is before visible region */
1578 return Efallthrough;
1579 /* FIXME check 'start' is at least 'num' before end */
1582 end = vmark_at_or_before(ci->home, end, rl->typenum, p);
1584 end = vmark_last(ci->home, rl->typenum, p);
1585 } else if (start) { /* smatch needs to know start in not NULL */
1586 end = vmark_at_or_before(ci->home, start, rl->typenum, p);
1588 end = vmark_first(ci->home, rl->typenum, p);
1590 return Efallthrough;
1591 if (vmark_next(end))
1592 end = vmark_next(end);
1593 /* FIXME check that 'end' is at least 'num' after start */
1597 /* Change outside visible region */
1598 return Efallthrough;
1600 while (end && mark_ordered_or_same(start, end)) {
1601 vmark_invalidate(end);
1602 end = vmark_prev(end);
1604 /* Must be sure to invalidate the line *before* the change */
1606 vmark_invalidate(end);
1608 pane_damaged(p, DAMAGED_VIEW);
1610 return Efallthrough;
1613 DEF_CMD(render_lines_clip)
1615 struct rl_data *rl = ci->home->data;
1617 marks_clip(ci->home, ci->mark, ci->mark2, rl->typenum, ci->home,
1620 mark_clip(rl->header, ci->mark, ci->mark2, !!ci->num);
1621 return Efallthrough;
1624 DEF_CMD(render_lines_attach);
1625 DEF_CMD(render_lines_clone)
1627 struct pane *parent = ci->focus;
1629 render_lines_attach.func(ci);
1630 pane_clone_children(ci->home, parent->focus);
1634 DEF_CMD(render_lines_resize)
1636 struct pane *p = ci->home;
1637 struct rl_data *rl = p->data;
1640 for (m = vmark_first(p, rl->typenum, p);
1643 vmark_invalidate(m);
1644 rl->background_drawn = False;
1645 pane_damaged(p, DAMAGED_VIEW);
1647 /* Allow propagation to children */
1651 static struct map *rl_map;
1653 DEF_LOOKUP_CMD(render_lines_handle, rl_map);
1655 static void render_lines_register_map(void)
1657 rl_map = key_alloc();
1659 key_add(rl_map, "Move-View-Small", &render_lines_move);
1660 key_add(rl_map, "Move-View-Large", &render_lines_move);
1661 key_add(rl_map, "Move-View-Pos", &render_lines_move_pos);
1662 key_add(rl_map, "Move-View-Line", &render_lines_view_line);
1663 key_add(rl_map, "Move-CursorXY", &render_lines_set_cursor);
1664 key_add(rl_map, "Move-Line", &render_lines_move_line);
1666 /* Make it easy to stop ignoring point */
1667 key_add(rl_map, "Abort", &render_lines_abort);
1669 key_add(rl_map, "Close", &render_lines_close);
1670 key_add(rl_map, "Free", &edlib_do_free);
1671 key_add(rl_map, "Clone", &render_lines_clone);
1672 key_add(rl_map, "Refresh", &render_lines_refresh);
1673 key_add(rl_map, "Refresh:view", &render_lines_revise);
1674 key_add(rl_map, "Refresh:size", &render_lines_resize);
1675 key_add(rl_map, "Notify:clip", &render_lines_clip);
1676 key_add(rl_map, "get-attr", &render_lines_get_attr);
1677 key_add(rl_map, "point:moving", &render_lines_point_moving);
1679 key_add(rl_map, "doc:replaced", &render_lines_notify_replace);
1680 /* view:changed is sent to a tile when the display might need
1681 * to change, even though the doc may not have*/
1682 key_add(rl_map, "view:changed", &render_lines_notify_replace);
1685 REDEF_CMD(render_lines_attach)
1691 render_lines_register_map();
1698 if (strcmp(ci->key, "attach-render-text") == 0)
1699 p = call_ret(pane, "attach-markup", p);
1700 p = pane_register(p, 0, &render_lines_handle.c, rl);
1705 rl->typenum = home_call(ci->focus, "doc:add-view", p) - 1;
1706 call("doc:request:doc:replaced", p);
1707 call("doc:request:point:moving", p);
1709 return comm_call(ci->comm2, "callback:attach", p);
1712 void edlib_init(struct pane *ed safe)
1714 call_comm("global-set-command", ed, &render_lines_attach, 0, NULL,
1715 "attach-render-lines");
1716 call_comm("global-set-command", ed, &render_lines_attach, 0, NULL,
1717 "attach-render-text");