/*
- * Copyright Neil Brown ©2015-2021 <neil@brown.name>
+ * Copyright Neil Brown ©2015-2023 <neil@brown.name>
* May be distributed under terms of GPLv2 - see file:COPYING
*
* Rendering for any document which presents as a sequence of lines.
* This takes a mark and moves it to the end of the rendered line
* so that another call will produce another line.
* "doc:render-line" must always return a full line including '\n'
- * unless the result would be bigger than the 'max' passed in ->num or,
- * when ->num==-1, unless the rendering would go beyond the location in
- * ->mark2. In these cases it can stop before a '\n'. In each case,
+ * unless the result would be bigger than the 'max' passed in ->num or
+ * ->num < 0. In these cases it can stop before a '\n'. In each case,
* the mark is moved to the end of the region that was rendered;
- * This allows a mark to be found for a given character position, or a display
- * position found for a given mark.
+ * This allows a mark to be found for a given character position.
+ * If mark2 is given, the offset in the rendering when mark2 is reached
+ * is reported as ->num in the callback.
* For the standard 'render the whole line' functionality, ->num should
- * be NO_NUMERIC
+ * be negative.
*
* The document or filter must also provide "doc:render-line-prev" which
* moves mark to a start-of-line. If num is 0, then don't skip over any
#define MARK_DATA_PTR struct pane
#define _GNU_SOURCE /* for asprintf */
+#define PANE_DATA_TYPE struct rl_data
#include "core.h"
#include "misc.h"
#include <stdio.h> /* snprintf */
short i_moved; /* I moved cursor, so don't clear
* target
*/
- int do_wrap;
+ short do_wrap;
+ short shift_locked;
short shift_left;
short shift_left_last_refresh;
struct mark *header;
short cols; /* columns used for longest line */
short margin; /* distance from top/bottom required for cursor */
bool background_drawn;
+ bool background_uniform;
/* If cursor not visible, we add this pane in bottom-right and place
* cursor there.
*/
struct pane *cursor_pane;
};
+#include "core-pane.h"
static void vmark_clear(struct mark *m safe)
{
mark_free(m);
}
-static void vmark_set(struct pane *p safe, struct mark *m safe, char *line safe)
+static void vmark_set(struct pane *p safe, struct pane *focus safe,
+ struct mark *m safe, char *line safe)
{
if (!m->mdata)
- m->mdata = call_ret(pane, "attach-renderline", p);
+ m->mdata = call_ret(pane, "attach-renderline", p, -1);
if (m->mdata)
- pane_call(m->mdata, "render-line:set", p, 0, NULL, line);
+ pane_call(m->mdata, "render-line:set", focus, -1, NULL, line);
}
static void vmark_invalidate(struct mark *m safe)
static bool vmark_is_valid(struct mark *m safe)
{
- return m->mdata && !(m->mdata->damaged & DAMAGED_VIEW);
+ return mark_valid(m) && m->mdata && !(m->mdata->damaged & DAMAGED_VIEW);
}
/* Returns 'true' at end-of-page */
-static bool measure_line(struct pane *p safe, struct pane *focus safe,
- struct mark *mk safe, short cursor_offset)
+static int _measure_line(struct pane *p safe, struct pane *focus safe,
+ struct mark *mk safe, short cursor_offset,
+ char **cursor_attr)
{
+ struct rl_data *rl = p->data;
struct pane *hp = mk->mdata;
- int ret = 0;
-
- if (hp) {
- pane_resize(hp, hp->x, hp->y, p->w, p->h);
- ret = pane_call(hp, "render-line:measure",
- focus, cursor_offset);
+ struct call_return cr;
+
+ if (!mark_valid(mk) || !hp)
+ return False;
+ pane_resize(hp, hp->x, hp->y, p->w, p->h);
+ if (!rl->shift_locked) {
+ int sl = pane_attr_get_int(focus, "render-wrap", -2);
+ if (sl != rl->shift_left) {
+ char *sla = NULL;
+ asprintf(&sla, "%d auto", rl->shift_left);
+ attr_set_str(&focus->attrs, "render-wrap", sla);
+ free(sla);
+ }
}
+ cr = pane_call_ret(all, hp, "render-line:measure",
+ focus, cursor_offset);
+ if (cursor_attr)
+ *cursor_attr = cr.s;
/* end-of-page flag */
- return ret == 2;
+ return cr.ret & 3;
+}
+#define measure_line(...) VFUNC(measure_line, __VA_ARGS__)
+static inline int measure_line3(struct pane *p safe, struct pane *focus safe,
+ struct mark *mk safe)
+{
+ return _measure_line(p, focus, mk, -1, NULL);
+}
+static inline int measure_line4(struct pane *p safe, struct pane *focus safe,
+ struct mark *mk safe, short cursor_offset)
+{
+ return _measure_line(p, focus, mk, cursor_offset, NULL);
+}
+static inline int measure_line5(struct pane *p safe, struct pane *focus safe,
+ struct mark *mk safe, short cursor_offset,
+ char **cursor_attr)
+{
+ return _measure_line(p, focus, mk, cursor_offset, cursor_attr);
}
/* Returns offset of posx,posy */
static int find_xy_line(struct pane *p safe, struct pane *focus safe,
- struct mark *mk safe, short posx, short posy)
+ struct mark *mk safe, short posx, short posy,
+ const char **xyattr)
{
struct pane *hp = mk->mdata;
- int ret = 0;
-
- if (hp) {
- ret = pane_call(hp,
- "render-line:findxy",
- focus,
- -1, NULL, NULL,
- 0, NULL, NULL,
- posx - hp->x, posy - hp->y);
- }
+ struct call_return cr;
+
+ if (!hp)
+ return -1;
+ cr = pane_call_ret(all, hp,
+ "render-line:findxy",
+ focus,
+ -1, NULL, NULL,
+ 0, NULL, NULL,
+ posx - hp->x, posy - hp->y);
+ if (xyattr)
+ *xyattr = cr.s;
/* xypos */
- return ret > 0 ? (ret - 1) : -1;
+ return cr.ret > 0 ? (cr.ret - 1) : -1;
}
static void draw_line(struct pane *p safe, struct pane *focus safe,
int ret;
struct mark *m2;
- if (m->viewnum < 0)
+ if (m->viewnum < 0) {
+ mark_free(m);
return NULL;
+ }
ret = call("doc:render-line-prev", p, n, m);
if (ret <= 0) {
/* if n>0 we can fail because start-of-file was found before
}
s = "";
} else
- s = call_ret(strsave, "doc:render-line", p, NO_NUMERIC, m);
+ s = call_ret(strsave, "doc:render-line", p, -1, m);
+ if (!mark_valid(start)) {
+ mark_free(m);
+ return;
+ }
if (s)
- vmark_set(home, start, s);
+ vmark_set(home, p, start, s);
m2 = vmark_matching(m);
if (m2)
*end = m2;
vmark_free(m);
}
+ /* Any mark at same location as start must go too. */
+ while ((m = vmark_prev(start)) != NULL &&
+ mark_same(m, start)) {
+ vmark_free(m);
+ }
}
DEF_CMD(no_save)
return m;
}
-DEF_CMD(get_len)
+DEF_CMD(get_offset)
{
- if (ci->str) {
- int l = strlen(ci->str);
- while (l >=4 && strncmp(ci->str+l-3, "</>", 3) == 0 &&
- ci->str[l-4] != '<')
- l -= 3;
- return l + 1;
- } else
+ if (ci->num < 0)
return 1;
+ else
+ return ci->num + 1;
}
static int call_render_line_to_point(struct pane *p safe, struct mark *pm safe,
int len;
struct mark *m = mark_dup_view(start);
- len = call_comm("doc:render-line", p, &get_len, -1, m, NULL, 0, pm);
+ len = call_comm("doc:render-line", p, &get_offset, -1, m, NULL, 0, pm);
mark_free(m);
if (len <= 0)
return 0;
short h = 0;
start = m;
call_render_line(p, focus, start, endp);
- measure_line(p, focus, start, -1);
+ measure_line(p, focus, start);
h = start->mdata ? start->mdata->h : 0;
if (h) {
*y_pre = h;
short *y_post safe, short *line_height_post safe)
{
struct mark *end = *endp;
- bool found_end = False;
if (!end)
return True;
call_render_line(p, focus, end, startp);
- found_end = measure_line(p, focus, end, -1);
+ measure_line(p, focus, end);
if (end->mdata)
*y_post = end->mdata->h;
if (*y_post > 0 && end->mdata)
else
end = vmark_next(end);
if (!end) {
- found_end = 1;
if (p->h >= *line_height_post *2)
*y_post = p->h / 10;
}
*endp = end;
- return found_end;
+ return False;
}
static int consume_space(struct pane *p safe, int y,
short *y_prep safe, short *y_postp safe,
short *lines_above safe, short *lines_below safe,
int found_start, int found_end,
- int line_height_pre, int line_height_post)
+ int line_height_pre, int line_height_post,
+ bool line_at_a_time)
{
int y_pre = *y_prep;
int y_post = *y_postp;
- if (y_pre > 0 && y_post > 0) {
+ if (y_pre > 0 && y_post > 0 && !found_start && !found_end) {
int consume = (y_post < y_pre
? y_post : y_pre) * 2;
int above, below;
if (consume > p->h - y)
consume = p->h - y;
+ if (line_at_a_time && consume > 2*line_height_pre &&
+ line_height_pre > 0)
+ consume = 2*line_height_pre;
+ if (line_at_a_time && consume > 2*line_height_post &&
+ line_height_post > 0)
+ consume = 2*line_height_post;
if (y_pre > y_post) {
above = consume - (consume/2);
below = consume/2;
* both > 0
*/
}
- if (found_end && y_pre) {
+ if (found_end && y_pre && !found_start) {
int consume = p->h - y;
if (consume > y_pre)
consume = y_pre;
+ if (line_at_a_time && consume > line_height_pre &&
+ line_height_pre > 0)
+ consume = line_height_pre;
y_pre -= consume;
y += consume;
*lines_above += consume / (line_height_pre?:1);
}
- if (found_start && y_post) {
+ if (found_start && y_post && !found_end) {
int consume = p->h - y;
if (consume > y_post)
consume = y_post;
+ if (line_at_a_time && consume > line_height_post &&
+ line_height_post > 0)
+ consume = line_height_post;
y_post -= consume;
y += consume;
*lines_below += consume / (line_height_post?:1);
*
* If we decide to stop moving in both directions, but have not
* reached EOF or full height of display, keep moving downwards.
+ *
+ * "start" is a mark at the start of the first line we currently
+ * intend to display, and y_pre is the number of pixel from the top
+ * of the display of that line, to the top pixel that will be displayed.
+ * We only move 'start' backward when y_pre is zero, and initially y_pre
+ * is the full height of that line.
+ *
+ * Similarly "end" is the start of the last line we currently intend
+ * to display, and y_post is the number of pixel from the bottom of that display
+ * up to the point we currently intend to display. We only move "end" forward
+ * when y_post is zero, and when we do we set y_post to the full height of the
+ * line.
+ *
+ * Until we decide on the start or end (found_start, found_end), we
+ * repeatedly add equal parts of y_pre and y_post into the total to
+ * be display - consume_space() does this. The space removed from y_pre
+ * and y_post is added to 'y' - the total height.
+ * It is also included into lines_above and lines_below which count text lines,
+ * rather than pixels, using line_height_pre and line_height_post as scale
+ * factors. These are used to determine when vline or rl->margin requirements
+ * have been met.
*/
static void find_lines(struct mark *pm safe, struct pane *p safe,
struct pane *focus safe,
int vline)
{
struct rl_data *rl = p->data;
+ /* orig_top/bot bound what is currently displayed and
+ * are used to determine if the display has been repositioned.
+ * orig_bot is *after* the last displayed line. Its ->mdata
+ * will be NULL.
+ */
struct mark *orig_top, *orig_bot;
- struct mark *top, *bot; // boundary of previous display
+ /* top and bot are used to enhance stability. They are NULL
+ * if vline is given, else they match orig_top/bot.
+ */
+ struct mark *top, *bot;
struct mark *m;
+ /* Current estimate of new display. From y_pre pixels down
+ * from the top of line at 'start', to y_post pixels up
+ * from the end of the line before 'end' there are 'y'
+ * pixel lines that we have committed to display.
+ */
struct mark *start, *end; // current estimate for new display
+ short y_pre = 0, y_post = 0;
short y = 0;
+ /* Number of text-lines in the committed region above or below
+ * the baseline of the line containing pm. These lines might not
+ * all be the same height. line_height_pre/post are the heights of
+ * start and end-1 so changes in y_pre/y_post can be merged into these
+ * counts.
+ */
short lines_above = 0, lines_below = 0; /* distance from start/end
* to pm.
*/
- short offset; // pos of pm in rendering of that line
- bool found_start = False, found_end = False;
- /* y_pre and y_post are measurement from start/end that
- * haven't yet been included into lines_above/lines_below.
- */
- short y_pre = 0, y_post = 0;
short line_height_pre = 1, line_height_post = 1;
+ short offset; // pos of pm while measureing the line holding the cursor.
+ /* We set found_start we we don't want to consider anything above the
+ * top that we currently intend to display. Once it is set,
+ * 'start', y_pre, lines_above are all frozen.
+ * Similarly once found_end is set we freeze end, y_pos, lines_below,
+ * but we mught unfreeze those if there is room for more text at end of
+ * display.
+ * found_start is set:
+ * - when y_pre is zero and start is at top of file
+ * - when lines_above reaches positive vline
+ * - when intended display has grown down into the previous
+ * display. This means we have added enough lines above and
+ * don't want to scroll the display more than we need.
+ * - When we hit unexpected errors moving backwards
+ * found_end is set:
+ * - when we hit end-of-file
+ * - when lines_below reached -vline
+ * - when the top of the intended display overlaps the
+ * previous display.
+ */
+ bool found_start = False, found_end = False;
+
orig_top = vmark_first(focus, rl->typenum, p);
orig_bot = vmark_last(focus, rl->typenum, p);
/* Protect top/bot from being freed by call_render_line */
start = vmark_new(focus, rl->typenum, p);
if (!start)
goto abort;
+ /* FIXME why is this here. We set ->repositioned at the end
+ * if the marks move. Maybe we need to check if y_pre moves too.
+ */
rl->repositioned = 1;
mark_to_mark(start, pm);
start = call_render_line_prev(focus, start, 0, &rl->top_sol);
if (!start)
goto abort;
+
+ /* Render the cursor line, and find where the cursor is. */
offset = call_render_line_to_point(focus, pm, start);
call_render_line(p, focus, start, NULL);
end = vmark_next(start);
* call_render_line() will have created 'end' if it didn't exist.
*/
- rl->shift_left = 0;
+ if (!rl->shift_locked)
+ rl->shift_left = 0;
- /* ->cy is top of cursor, we want to measure from bottom */
if (start->mdata) {
struct pane *hp = start->mdata;
int curs_width;
- found_end = measure_line(p, focus, start, offset);
+ int shifts = 0;
+ found_end = measure_line(p, focus, start, offset) & 2;
curs_width = pane_attr_get_int(
start->mdata, "curs_width", 1);
- while (!rl->do_wrap && curs_width > 0 &&
+ if (curs_width <= 0)
+ curs_width = 1;
+ // FIXME this loops indefinitely if cursor after
+ // right-justified text.
+ while (!rl->do_wrap && !rl->shift_locked &&
hp->cx + curs_width >= p->w) {
int shift = 8 * curs_width;
if (shift > hp->cx)
shift = hp->cx;
rl->shift_left += shift;
measure_line(p, focus, start, offset);
+ if (shifts++ > 100)
+ break;
}
+ /* ->cy is top of cursor, we want to measure from bottom */
line_height_pre = attr_find_int(start->mdata->attrs, "line-height");
if (line_height_pre < 1)
line_height_pre = 1;
+ /* We now have a better estimate than '1' */
+ line_height_post = line_height_pre;
y_pre = start->mdata->cy + line_height_pre;
y_post = start->mdata->h - y_pre;
} else {
y_post = 0;
}
if (!end) {
- found_end = True;
+ /* When cursor at EOF, leave 10% height of display
+ * blank at bottom to make this more obvious - unless
+ * the display is so small that might push the last line partly
+ * off display at the top.
+ */
if (p->h > line_height_pre * 2)
y_post += p->h / 10;
- else
+ else {
/* Small display, no space at EOF */
y_post = 0;
+ found_end = True;
+ }
}
y = 0;
if (rl->header && rl->header->mdata)
y = rl->header->mdata->h;
/* We have start/end of the focus line. When rendered this,
- * plus header and eof-footed would use y_pre + y + y_post
+ * plus header and eof-footer would use y_pre + y + y_post
* vertical space.
*/
while ((!found_start || !found_end) && y < p->h) {
if (vline != NO_NUMERIC) {
+ /* As lines_above/below measure from the baseline
+ * of the cursor line, and as we want to see the top
+ * of he cursor line as well, these two cases are
+ * asymmetric.
+ */
if (!found_start && vline > 0 &&
- lines_above >= vline-1)
+ lines_above >= vline)
found_start = True;
if (!found_end && vline < 0 &&
lines_below >= -vline-1)
found_end = step_fore(p, focus, &start, &end,
&y_post, &line_height_post);
+ /* This test has "> rl->margin" while found_end test has
+ * ">= rl->margin" due to the asymmetry of measuring from the
+ * baseline, not the centreling, of the target text.
+ */
if (!found_start && top && end &&
mark_ordered_or_same(start, top) &&
- lines_above >= rl->margin)
+ lines_above > rl->margin)
if (mark_ordered_not_same(top, end) ||
(mark_same(top, end) &&
y_post - rl->tail_height >= y_pre))
y = consume_space(p, y, &y_pre, &y_post,
&lines_above, &lines_below,
found_start, found_end,
- line_height_pre, line_height_post);
+ line_height_pre, line_height_post,
+ vline && vline != NO_NUMERIC);
}
/* We might need to continue downwards even after found_end
* if there is more space.
y = consume_space(p, y, &y_pre, &y_post,
&lines_above, &lines_below,
found_start, found_end,
- line_height_pre, line_height_post);
+ line_height_pre, line_height_post,
+ vline && vline != NO_NUMERIC);
}
if (start->mdata && start->mdata->h <= y_pre) {
}
y = 0;
- if (rl->header && rl->header->mdata)
+ rl->cols = 0;
+ if (rl->header && rl->header->mdata) {
y = rl->header->mdata->h;
+ rl->cols = pane_attr_get_int(rl->header->mdata, "width", 0);
+ }
y -= rl->skip_height;
for (m = vmark_first(focus, rl->typenum, p);
m && m->mdata ; m = vmark_next(m)) {
struct pane *hp = m->mdata;
- pane_resize(hp, hp->x, y, hp->w, hp->h);
+ int cols;
+ if (pane_resize(hp, hp->x, y, hp->w, hp->h) &&
+ !rl->background_uniform)
+ pane_damaged(hp, DAMAGED_REFRESH);
y += hp->h;
+ cols = pane_attr_get_int(hp, "width", 0);
+ if (cols > rl->cols)
+ rl->cols = cols;
}
+ rl->lines = y;
pane_damaged(p, DAMAGED_REFRESH);
m = vmark_first(focus, rl->typenum, p);
if (!m || !orig_top || !mark_same(m, orig_top))
struct mark *m, *m2;
struct xy scale = pane_scale(focus);
char *s;
- int hide_cursor = 0;
- int cursor_drawn = 0;
+ bool hide_cursor = False;
+ bool cursor_drawn = False;
bool refresh_all = rl->shift_left != rl->shift_left_last_refresh;
rl->shift_left_last_refresh = rl->shift_left;
s = pane_attr_get(focus, "hide-cursor");
if (s && strcmp(s, "yes") == 0)
- hide_cursor = 1;
+ hide_cursor = True;
rl->cols = 0;
m = vmark_first(focus, rl->typenum, p);
- if (!rl->background_drawn)
+ if (!rl->background_drawn) {
refresh_all = True;
+ rl->background_uniform = True;
+ }
s = pane_attr_get(focus, "background");
- if (s && strncmp(s, "call:", 5) == 0) {
- home_call(focus, "Draw:clear", p);
+ if (s && strstarts(s, "call:")) {
+ home_call(focus, "Draw:clear", p, 0, NULL, "");
home_call(focus, s+5, p, 0, m);
refresh_all = True;
+ rl->background_uniform = False;
} else if (rl->background_drawn)
;
else if (!s)
- home_call(focus, "Draw:clear", p);
- else if (strncmp(s, "color:", 6) == 0) {
+ home_call(focus, "Draw:clear", p, 0, NULL, "");
+ else if (strstarts(s, "color:")) {
char *a = strdup(s);
strcpy(a, "bg:");
strcpy(a+3, s+6);
home_call(focus, "Draw:clear", p, 0, NULL, a);
free(a);
- } else if (strncmp(s, "image:", 6) == 0) {
+ } else if (strstarts(s, "image:")) {
home_call(focus, "Draw:clear", p);
- home_call(focus, "Draw:image", p, 1, NULL, s+6);
+ home_call(focus, "Draw:image", p, 0, NULL, s+6, 0, NULL, "S");
+ rl->background_uniform = False;
} else
- home_call(focus, "Draw:clear", p);
+ home_call(focus, "Draw:clear", p, 0, NULL, "");
rl->background_drawn = True;
if (rl->header && vmark_is_valid(rl->header)) {
struct pane *hp = rl->header->mdata;
draw_line(p, focus, rl->header, -1, refresh_all);
y = hp->h;
- rl->cols = hp->x + hp->w;
+ rl->cols = pane_attr_get_int(hp, "width", 0);
}
y -= rl->skip_height;
draw_line(p, focus, m, len, True);
rl->cursor_line = hp->y + hp->cy;
curs = pane_mapxy(hp, p, hp->cx, hp->cy, False);
- if (hp->cx < 0) {
+ if (hp->cx < 0 || hp->cx >= hp->w) {
p->cx = -1;
p->cy = -1;
} else {
p->cx = curs.x;
p->cy = curs.y;
+ cursor_drawn = True;
}
- cursor_drawn = 1;
} else {
draw_line(p, focus, m, -1, refresh_all);
}
if (m->mdata) {
- int cols = m->mdata->x + m->mdata->w;
+ int cols = pane_attr_get_int(m->mdata, "width", 0);
if (cols > rl->cols)
rl->cols = cols;
y = m->mdata->y + m->mdata->h;
}
m = m2;
}
+ if (m && !m->mdata && vmark_next(m))
+ LOG("render-lines: break in vmark sequence");
if (!cursor_drawn && !hide_cursor) {
/* Place cursor in bottom right */
struct pane *cp = rl->cursor_pane;
short lineheight;
if (!cp) {
+ /* Note: this pane will container rl_data
+ * which isn't used.
+ */
cp = pane_register(p, -1, &cursor_handle);
rl->cursor_pane = cp;
}
return y;
}
-DEF_CMD(render_lines_get_attr)
-{
- struct rl_data *rl = ci->home->data;
-
- if (ci->str && strcmp(ci->str, "shift_left") == 0) {
- char ret[10];
- if (rl->do_wrap)
- return comm_call(ci->comm2, "cb", ci->focus,
- 0, NULL, "-1");
- snprintf(ret, sizeof(ret), "%d", rl->shift_left);
- return comm_call(ci->comm2, "cb", ci->focus, 0, NULL, ret);
- }
- return Efallthrough;
-}
-
DEF_CMD(render_lines_point_moving)
{
struct pane *p = ci->home;
/* Someone else moved the point, so reset target column */
rl->target_x = -1;
m = vmark_at_or_before(ci->focus, pt, rl->typenum, p);
- if (m && vmark_is_valid(m)) {
+ if (m && !m->mdata)
+ /* End marker is no use, want to refresh last line */
+ m = vmark_prev(m);
+ if (m && m->mdata) {
pane_damaged(m->mdata, DAMAGED_REFRESH);
pane_damaged(m->mdata->parent, DAMAGED_REFRESH);
}
struct mark *m, *m2;
bool found_end = False;
bool start_of_file;
+ int shifts = 0;
- if (pm && !rl->do_wrap) {
+ /* This loop is fragile and sometimes spins. So ensure we
+ * never loop more than 100 times.
+ */
+ if (pm && !rl->do_wrap && !rl->shift_locked && shifts++ < 100) {
int prefix_len;
int curs_width;
/* Need to check if side-shift is needed on cursor line */
}
if (m && m->mdata) {
struct pane *hp = m->mdata;
+ int cols;
int offset = call_render_line_to_point(focus,
pm, m);
measure_line(p, focus, m, offset);
curs_width = pane_attr_get_int(
m->mdata, "curs_width", 1);
- while (hp->cx + curs_width >= p->w) {
+ while (hp->cx + curs_width > p->w && shifts++ < 100) {
int shift = 8 * curs_width;
if (shift > hp->cx)
shift = hp->cx;
measure_line(p, focus, m, offset);
refresh_all = 1;
}
- while (hp->cx < prefix_len &&
+ /* We shift right is cursor is off the left end, or if
+ * doing so wouldn't hide anything on the right end
+ */
+ cols = pane_attr_get_int(hp, "width", 0);
+ while ((hp->cx < prefix_len
+ || (cols-rl->shift_left) + curs_width * 8 + curs_width < p->w) &&
rl->shift_left > 0 &&
- hp->cx + curs_width * 8*curs_width < p->w) {
+ shifts++ < 100 &&
+ hp->cx + curs_width * 8 < p->w) {
int shift = 8 * curs_width;
if (shift > rl->shift_left)
shift = rl->shift_left;
rl->shift_left -= shift;
measure_line(p, focus, m, offset);
+ cols = pane_attr_get_int(hp, "width", 0);
refresh_all = 1;
}
}
if (rl->header) {
struct pane *hp = rl->header->mdata;
if (refresh_all) {
- measure_line(p, focus, rl->header, -1);
+ measure_line(p, focus, rl->header);
if (hp)
pane_resize(hp, hp->x, y, hp->w, hp->h);
}
start_of_file = doc_prior(focus, start) == WEOF;
for (m = start; m && !found_end && y < p->h; m = vmark_next(m)) {
struct pane *hp;
+ int found = 0;
+ int offset = -1;
if (refresh_all)
vmark_invalidate(m);
call_render_line(p, focus, m, NULL);
- found_end = measure_line(p, focus, m, -1);
+ m2 = vmark_next(m);
+ /* The "found & 1" handles case when EOF is at the end
+ * of a non-empty line.
+ */
+ if (pm && m2 && mark_same(pm, m2))
+ /* Cursor at end shouldn't affect appearance */
+ found = measure_line(p, focus, m);
+ if (pm && m2 && mark_ordered_or_same(m, pm) &&
+ (mark_ordered_not_same(pm, m2) ||
+ (mark_same(pm, m2) && !(found & 1))))
+ /* Cursor is on this line */
+ offset = call_render_line_to_point(focus,
+ pm, m);
+ if (pm && mark_same(m, pm))
+ /* Probably EOF - cursor is here */
+ offset = 0;
+ found = measure_line(p, focus, m, offset);
+
hp = m->mdata;
- if (!hp)
+ if (!mark_valid(m) || !hp)
break;
+ found_end = found & 2;
+
if (y != hp->y) {
pane_damaged(p, DAMAGED_REFRESH);
+ hp->damaged &= ~DAMAGED_SIZE;
pane_resize(hp, hp->x, y, hp->w, hp->h);
+ if (hp->damaged & DAMAGED_SIZE && !rl->background_uniform)
+ pane_damaged(hp, DAMAGED_REFRESH);
}
y += hp->h;
- m2 = vmark_next(m);
- if (pm && m == start && rl->skip_height > 0 && m2 &&
- mark_ordered_not_same(pm, m2)) {
- /* Point might be in this line, but off top
- * of the screen
- */
- int offset = call_render_line_to_point(focus,
- pm, m);
- if (offset >= 0) {
- measure_line(p, focus, m, offset);
+ if (offset >= 0) {
+ /* Cursor is on this line */
+ int lh = attr_find_int(hp->attrs,
+ "line-height");
+ int cy = y - hp->h + hp->cy;
+ if (lh < 1)
+ lh = 1;
+ if (m == start && rl->skip_height > 0) {
+ /* Point might be in this line, but off top
+ * of the screen
+ */
if (hp->cy >= rl->skip_height + rl->margin)
/* Cursor is visible on this line
* and after margin from top.
else if (start_of_file && rl->skip_height == 0)
/* Cannot make more margin space */
on_screen = True;
- }
- } else if (pm && y >= p->h && m->seq < pm->seq) {
- /* point might be in this line, but off end
- * of the screen
- */
- int offset = call_render_line_to_point(focus,
- pm, m);
- if (offset > 0) {
- int lh;
- measure_line(p, focus, m, offset);
- lh = attr_find_int(hp->attrs,
- "line-height");
- if (lh <= 0)
- lh = 1;
- if (y - hp->h + hp->cy <= p->h - lh - rl->margin) {
+ } else if (y >= p->h) {
+ /* point might be in this line, but off end
+ * of the screen
+ */
+ if (hp->cy >= 0 &&
+ y - hp->h + hp->cy <= p->h - lh - rl->margin) {
/* Cursor is on screen */
on_screen = True;
}
- }
- } else if (pm && mark_ordered_or_same(m, pm) && m2 &&
- mark_ordered_or_same(pm, m2)) {
- if (rl->margin == 0)
+ } else if (rl->margin == 0)
+ on_screen = True;
+ else if (cy >= rl->margin &&
+ cy <= p->h - rl->margin - lh)
+ /* Cursor at least margin from edge */
on_screen = True;
- else {
- int offset = call_render_line_to_point(
- focus, pm, m);
- if (offset > 0) {
- int lh;
- int cy;
- measure_line(p, focus, m, offset);
- lh = attr_find_int(hp->attrs,
- "line-height");
- cy = y - hp->h + hp->cy;
- if (cy >= rl->margin &&
- cy <= p->h - rl->margin - lh)
- /* Cursor at least margin from edge */
- on_screen = True;
- }
- }
}
}
if (y >= p->h)
rl->tail_height = p->h - y;
else
rl->tail_height = 0;
- if (m) {
+ if (mark_valid(m)) {
vmark_clear(m);
- while ((m2 = vmark_next(m)) != NULL) {
+ while (mark_valid(m2 = vmark_next(m)) && m2) {
/* end of view has clearly changed */
rl->repositioned = 1;
vmark_free(m2);
struct mark *pm = NULL;
struct mark *m1, *m2;
bool refresh_all = False;
+ bool wrap;
char *hdr;
char *a;
+ int shift;
a = pane_attr_get(focus, "render-wrap");
- if (rl->do_wrap != (!a || strcmp(a, "yes") ==0)) {
- rl->do_wrap = (!a || strcmp(a, "yes") ==0);
+ wrap = (!a || strcmp(a, "yes") == 0);
+ if (rl->do_wrap != wrap) {
+ rl->do_wrap = wrap;
refresh_all = True;
+ rl->shift_left = 0;
+ }
+ if (wrap)
+ rl->shift_locked = True;
+ if (!a)
+ /* avoid any ambiguity */
+ attr_set_str(&focus->attrs, "render-wrap", "yes");
+
+ if (a) {
+ char *end;
+ shift = strtol(a, &end, 10);
+ if (end == a || (end && *end && *end != ' '))
+ shift = -1;
+ }
+ else
+ shift = -1;
+
+ if (a && shift >= 0) {
+ if (rl->shift_left != shift)
+ refresh_all = True;
+
+ rl->shift_left = shift;
+ rl->shift_locked = strstr(a, "auto") == NULL;
+ } else if (!wrap) {
+ /* unrecognised - no wrap, not locked */
+ if (rl->shift_locked)
+ refresh_all = True;
+ rl->shift_left = 0;
+ rl->shift_locked = 0;
+ attr_set_str(&focus->attrs, "render-wrap", "0 auto");
+ }
+ if (refresh_all) {
+ struct mark *v;
+ for (v = vmark_first(focus, rl->typenum, p);
+ (v && v->mdata) ; v = vmark_next(v))
+ pane_damaged(v->mdata, DAMAGED_REFRESH);
}
rl->margin = pane_attr_get_int(focus, "render-vmargin", 0);
if (hdr) {
if (!rl->header)
- rl->header = vmark_new(focus, MARK_UNGROUPED, NULL);
+ rl->header = mark_new(focus);
if (rl->header) {
- vmark_set(p, rl->header, hdr);
- measure_line(p, focus, rl->header, -1);
+ vmark_set(p, focus, rl->header, hdr);
+ measure_line(p, focus, rl->header);
}
} else if (rl->header) {
vmark_free(rl->header);
struct pane *focus = ci->focus;
struct rl_data *rl = p->data;
struct mark *m, *pm = NULL;
+ int cols = rl->cols;
+ int lines = rl->lines;
//pane_damaged(p, DAMAGED_VIEW);
return 1;
rl->lines = render(pm, p, focus);
+ if (rl->lines != lines || rl->cols != cols)
+ call("render:reposition", focus, rl->lines, NULL, NULL, rl->cols);
return 1;
}
-DEF_CMD(render_lines_close)
+DEF_CMD_CLOSED(render_lines_close)
{
struct rl_data *rl = ci->home->data;
return 1;
}
-DEF_CMD(render_lines_close_mark)
+DEF_CMD_CLOSED(render_lines_close_mark)
{
struct mark *m = ci->mark;
rpt = 0;
break;
}
- measure_line(p, focus, m, -1);
+ measure_line(p, focus, m);
y += m->mdata->h;
m = vmark_next(m);
}
} else {
/* Need to remove lines from top */
call_render_line(p, focus, top, NULL);
- measure_line(p, focus, top, -1);
+ measure_line(p, focus, top);
while (top && top->mdata && rpt > 0) {
short y = 0;
if (!top)
break;
call_render_line(p, focus, top, NULL);
- measure_line(p, focus, top, -1);
+ measure_line(p, focus, top);
}
if (top && top->mdata) {
/* We didn't fall off the end, so it is OK to remove
return 1;
}
-static char *get_active_tag(const char *a)
+static char *get_action_tag(const char *tag safe, const char *a)
{
+ int taglen = strlen(tag);
char *t;
char *c;
if (!a)
return NULL;
- t = strstr(a, ",active-tag:");
- if (!t)
- return NULL;
- t += 12;
+ do {
+ t = strstr(a, ",action-");
+ if (!t)
+ return NULL;
+ a = t+1;
+ } while (!(strncmp(t+8, tag, taglen) == 0 &&
+ t[8+taglen] == ':'));
+
+ t += 8 + taglen + 1;
c = strchr(t, ',');
return strndup(t, c?c-t: (int)strlen(t));
}
DEF_CMD(render_lines_set_cursor)
{
- /* ->num is
- * 1 if this resulted from a click
- * 2 if from a release
- * 3 if from motion
- * 0 any other reason.
+ /* ->str gives a context specific action to perform
+ * If the attributes at the location include
+ * action-$str then the value of that attribute
+ * is send as a command
*/
struct pane *p = ci->home;
struct pane *focus = ci->focus;
struct rl_data *rl = p->data;
+ const char *action = ci->str;
+ const char *xyattr = NULL;
struct mark *m;
struct mark *m2 = NULL;
struct xy cih;
int xypos;
cih = pane_mapxy(ci->focus, ci->home,
- ci->x >= 0 ? ci->x : p->cx >= 0 ? p->cx : 0,
- ci->y >= 0 ? ci->y : p->cy >= 0 ? p->cy : 0,
+ ci->x == INT_MAX ? p->cx : ci->x,
+ ci->y == INT_MAX ? p->cy : ci->y,
False);
m = vmark_first(p, rl->typenum, p);
if (!m)
/* There is nothing rendered? */
return 1;
- if (!m->mdata) {
- /* chi is after the last visible content, and m is the end
- * of that content (possible EOF) so move there
- */
- } else {
- if (cih.y < m->mdata->y)
+ if (m->mdata) {
+ /* might be able to find a position in the line */
+ if (cih.y < m->mdata->y) {
+ /* action only permitted in precise match */
+ action = NULL;
cih.y = m->mdata->y;
- xypos = find_xy_line(p, focus, m, cih.x, cih.y);
- if (xypos >= 0)
- m2 = call_render_line_offset(focus, m, xypos);
- }
- if (m2) {
- char *tag, *xyattr;
-
- if (ci->num == 2) { /* Mouse release */
- xyattr = pane_attr_get(m->mdata, "xyattr");
- tag = get_active_tag(xyattr);
- if (tag) {
- char *c = NULL;
- asprintf(&c, "Mouse-Activate:%s", tag);
- if (c)
- call(c, focus, 0, m2, tag,
- 0, ci->mark, xyattr);
- free(c);
+ }
+ xypos = find_xy_line(p, focus, m, cih.x, cih.y, &xyattr);
+ if (xypos >= 0 &&
+ (m2 = call_render_line_offset(focus, m, xypos)) != NULL) {
+ char *tag;
+ wint_t c = doc_following(focus, m2);
+ if (c == WEOF || is_eol(c))
+ /* after last char on line - no action. */
+ action = NULL;
+ if (action && xyattr &&
+ (tag = get_action_tag(action, xyattr)) != NULL) {
+ int x, y;
+ /* This is a hack to get the
+ * start of these attrs so menu
+ * can be placed correctly.
+ * Only works for menus below
+ * the line.
+ */
+ if (!strstr(xyattr, ",menu-at-mouse,") &&
+ sscanf(xyattr, "%dx%d,", &x, &y) == 2) {
+ cih.x = x;
+ cih.y = m->mdata->y + y +
+ attr_find_int(m->mdata->attrs,
+ "line-height");
+ ;
+ }
+ call(tag, focus, 0, m2, xyattr,
+ 0, ci->mark, action,
+ cih.x, cih.y);
}
+ m = m2;
}
- m = m2;
- } else {
- /* m is the closest we'll get */
}
-
if (ci->mark)
mark_to_mark(ci->mark, m);
else
return 1;
}
+DEF_CMD(render_lines_action)
+{
+ /* If there is an action-$str: at '->mark', send the command
+ * to the focus
+ */
+ struct mark *m = ci->mark;
+ struct pane *p = ci->home;
+ struct rl_data *rl = p->data;
+ struct pane *focus = ci->focus;
+ struct mark *v, *n;
+ int offset;
+ char *attr = NULL, *tag;
+
+ if (!m || !ci->str)
+ return Enoarg;
+ v = vmark_first(p, rl->typenum, p);
+
+ while (v && v->mdata && (n = vmark_next(v)) &&
+ mark_ordered_or_same(n, m))
+ v = n;
+
+ if (!v || !v->mdata || !mark_ordered_or_same(v, m))
+ return Efallthrough;
+ offset = call_render_line_to_point(focus, m, v);
+ measure_line(p, focus, v, offset, &attr);
+ if (!attr)
+ return Efallthrough;
+ tag = get_action_tag(ci->str, attr);
+ if (!tag)
+ return Efallthrough;
+ call(tag, focus, 0, m, attr, 0, NULL, ci->str);
+ return 1;
+}
+
DEF_CMD(render_lines_move_pos)
{
struct pane *p = ci->home;
/* top line not fully displayed, being in that line is
* not sufficient */
top = vmark_next(top);
- if (bot)
+ if (bot && rl->tail_height)
/* last line might not be fully displayed, so don't assume */
bot = vmark_prev(bot);
if (!top || !bot ||
struct pane *focus = ci->focus;
struct rl_data *rl = p->data;
struct mark *pm = ci->mark;
- struct mark *top;
int line = ci->num;
if (!pm)
if (line == NO_NUMERIC)
return Einval;
- while ((top = vmark_first(focus, rl->typenum, p)) != NULL)
- vmark_free(top);
-
rl->ignore_point = 1;
find_lines(pm, p, focus, line);
pane_damaged(p, DAMAGED_REFRESH);
int num;
int xypos = -1;
struct mark *m = ci->mark;
- struct mark *start;
+ struct mark *start, *m2;
if (!m)
m = call_ret(mark, "doc:point", focus);
rl->i_moved = 1;
num = RPT_NUM(ci);
- if (num < 0)
- num -= 1;
- else
- num += 1;
- if (call("doc:EOL", ci->focus, num, m) <= 0) {
+ if (call("doc:EOL", ci->focus, num, m, NULL, 1) <= 0) {
rl->i_moved = 0;
- return Efail;
+ return Efalse;
}
- if (RPT_NUM(ci) > 0) {
+ if (RPT_NUM(ci) < 0) {
/* at end of target line, move to start */
if (call("doc:EOL", ci->focus, -1, m) <= 0) {
rl->i_moved = 0;
- return Efail;
+ return Efalse;
}
}
+ /* We are at the start of the target line. We might
+ * like to find the target_x column, but if anything
+ * goes wrong it isn't a failure.
+ * Need to ensure there is a vmark here. call_render_line_prev()
+ * wil only move the mark if it is in a multi-line rendering,
+ * such as an image which acts as though it is multiple lines.
+ * It will check if there is already a mark at the target location.
+ * It will free the mark passed in unless it returns it.
+ */
start = vmark_new(focus, rl->typenum, p);
if (start) {
if (!start) {
pane_damaged(p, DAMAGED_VIEW);
- rl->i_moved = 0;
- return 1;
+ goto done;
}
- if (vmark_first(focus, rl->typenum, p) == start) {
+ if (vmark_first(focus, rl->typenum, p) == start &&
+ !vmark_is_valid(start))
/* New first mark, so view will have changed */
rl->repositioned = 1;
- }
- if (rl->target_x == 0 && rl->target_y == 0) {
+ if (rl->target_x == 0 && rl->target_y == 0)
/* No need to move to target column - already there.
* This simplifies life for render-complete which is
* always at col 0, and messes with markup a bit.
*/
- rl->i_moved = 0;
- return 1;
- }
+ goto done;
+
/* FIXME only do this if point is active/volatile, or
* if start->mdata is NULL
*/
vmark_invalidate(start);
call_render_line(p, focus, start, NULL);
- if (start->mdata)
- xypos = find_xy_line(p, focus, start, rl->target_x,
- rl->target_y + start->mdata->y);
+ if (!start->mdata)
+ goto done;
+ xypos = find_xy_line(p, focus, start, rl->target_x,
+ rl->target_y + start->mdata->y, NULL);
+
+ if (xypos < 0)
+ goto done;
/* xypos is the distance from start-of-line to the target */
- if (xypos >= 0) {
- struct mark *m2 = call_render_line_offset(
- focus, start, xypos);
- if (m2)
- mark_to_mark(m, m2);
- mark_free(m2);
+
+ m2 = call_render_line_offset(focus, start, xypos);
+ if (!m2)
+ goto done;
+
+ if (!mark_same(start, m)) {
+ /* This is a multi-line render and we aren't on
+ * the first line. We might need a larger 'y'.
+ * For now, ensure that we move in the right
+ * direction.
+ * FIXME this loses target_x and can move up
+ * too far. How to fix??
+ */
+ if (num > 0 && mark_ordered_not_same(m2, m))
+ mark_to_mark(m2, m);
+ if (num < 0 && mark_ordered_not_same(m, m2))
+ mark_to_mark(m2, m);
}
+ mark_to_mark(m, m2);
+
+ mark_free(m2);
+
+done:
rl->i_moved = 0;
return 1;
}
rl->target_x = -1;
/* If the replacement happened at 'point', then stop
- * ignoring it.
+ * ignoring it, and handle the fact that point moved.
*/
if (ci->mark2 == pt)
- rl->ignore_point = 0;
+ pane_call(p, "mark:moving", ci->focus, 0, pt);
}
if (strcmp(ci->key, "view:changed") == 0)
pane_damaged(m->mdata, DAMAGED_REFRESH);
}
rl->background_drawn = False;
- pane_damaged(p, DAMAGED_VIEW);
+ pane_damaged(p, DAMAGED_VIEW | DAMAGED_REFRESH);
/* Allow propagation to children */
return 0;
/* Make it easy to stop ignoring point */
key_add(rl_map, "Abort", &render_lines_abort);
+ key_add(rl_map, "Action", &render_lines_action);
+
key_add(rl_map, "Close", &render_lines_close);
key_add(rl_map, "Close:mark", &render_lines_close_mark);
- key_add(rl_map, "Free", &edlib_do_free);
key_add(rl_map, "Clone", &render_lines_clone);
key_add(rl_map, "Refresh", &render_lines_refresh);
key_add(rl_map, "Refresh:view", &render_lines_revise);
key_add(rl_map, "Refresh:size", &render_lines_resize);
key_add(rl_map, "Notify:clip", &render_lines_clip);
- key_add(rl_map, "get-attr", &render_lines_get_attr);
- key_add(rl_map, "point:moving", &render_lines_point_moving);
+ key_add(rl_map, "mark:moving", &render_lines_point_moving);
key_add(rl_map, "doc:replaced", &render_lines_notify_replace);
+ key_add(rl_map, "doc:replaced-attr", &render_lines_notify_replace);
/* view:changed is sent to a tile when the display might need
* to change, even though the doc may not have*/
key_add(rl_map, "view:changed", &render_lines_notify_replace);
if (!rl_map)
render_lines_register_map();
- alloc(rl, pane);
- rl->target_x = -1;
- rl->target_y = -1;
- rl->do_wrap = 1;
p = ci->focus;
- if (strcmp(ci->key, "attach-render-text") == 0)
+ if (strcmp(ci->key, "attach-render-text") == 0) {
p = call_ret(pane, "attach-markup", p);
- p = pane_register(p, 0, &render_lines_handle.c, rl);
- if (!p) {
- free(rl);
- return Efail;
+ if (!p)
+ p = ci->focus;
}
+ p = pane_register(p, 0, &render_lines_handle.c);
+ if (!p)
+ return Efail;
+ rl = p->data;
+ rl->target_x = -1;
+ rl->target_y = -1;
+ rl->do_wrap = 1;
rl->typenum = home_call(ci->focus, "doc:add-view", p) - 1;
call("doc:request:doc:replaced", p);
- call("doc:request:point:moving", p);
+ call("doc:request:doc:replaced-attr", p);
+ call("doc:request:mark:moving", p);
return comm_call(ci->comm2, "callback:attach", p);
}