diff --git a/popup.c b/popup.c index f7c812e48..a0a3cfbde 100644 --- a/popup.c +++ b/popup.c @@ -321,32 +321,50 @@ popup_resize_cb(__unused struct client *c, void *data) } /* - * Report damage on the current window for a popup's rectangle, given in raw - * client/tty coordinates - translated into window coordinates by removing - * any top status lines and adding the client's pan/window offset, the same + * Report damage for a popup's rectangle, given in raw client/tty + * coordinates. Status-line cells are outside the window scene - they have no + * corresponding window content and redraw_damage_window() can't reach them - + * so if the popup's rectangle overlaps the status line, force it to redraw + * separately. The rest of the rectangle is clipped to the actual pane area + * (above or below the status line, whichever side it's on) before being + * translated into window coordinates and reported the normal way, the same * way mouse coordinates are translated elsewhere (e.g. cmd-join-pane.c, - * cmd-split-window.c). redraw_damage_window() safely clips or drops - * anything that ends up out of the window's own bounds (a popup can cover - * the status line, which has no corresponding window content), so this - * does not need to be exact for those edge cases. + * cmd-split-window.c). */ static void popup_damage(struct client *c, u_int px, u_int py, u_int sx, u_int sy) { struct window *w; - u_int ox, oy, osx, osy, wy; + int statusat; + u_int ox, oy, osx, osy, lines, top, bottom, y0, y1; if (c->session == NULL) return; w = c->session->curw->window; - tty_window_offset(&c->tty, &ox, &oy, &osx, &osy); - if (status_at_line(c) == 0 && py >= status_line_size(c)) - wy = py - status_line_size(c); - else - wy = py; + lines = status_line_size(c); + statusat = status_at_line(c); + if (statusat >= 0 && py < (u_int)statusat + lines && + py + sy > (u_int)statusat) + c->flags |= (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS); - redraw_damage_window(w, px + ox, wy + oy, sx, sy); + if (statusat == 0) { + top = lines; + bottom = c->tty.sy; + } else if (statusat > 0) { + top = 0; + bottom = statusat; + } else { + top = 0; + bottom = c->tty.sy; + } + y0 = (py > top) ? py : top; + y1 = (py + sy < bottom) ? py + sy : bottom; + if (y0 >= y1) + return; + + tty_window_offset(&c->tty, &ox, &oy, &osx, &osy); + redraw_damage_window(w, px + ox, y0 - top + oy, sx, y1 - y0); } static void diff --git a/screen-redraw.c b/screen-redraw.c index 2a2156d86..76bdcde39 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1073,6 +1073,13 @@ redraw_free_scene(struct redraw_scene *scene) free(scene); } +/* Does a client's cached scene show this window? */ +int +redraw_client_has_window(struct client *c, struct window *w) +{ + return (c->redraw_scene != NULL && c->redraw_scene->w == w); +} + /* Mark a window's cached redraw scenes as out of date. */ void redraw_invalidate_scene(struct window *w) @@ -1758,6 +1765,24 @@ redraw_set_draw_context(struct redraw_draw_ctx *dctx, dctx->flags |= REDRAW_ISOLATES; } +/* Build a pane prompt into a one-line screen. */ +static void +redraw_make_pane_prompt(struct window_pane *wp, struct screen *screen) +{ + struct screen_write_ctx ctx; + struct prompt_draw_data pdd; + + screen_init(screen, wp->sx, 1, 0); + screen_write_start(&ctx, screen); + pdd.ctx = &ctx; + pdd.cursor_x = &wp->prompt_cx; + pdd.area_x = 0; + pdd.area_width = wp->sx; + pdd.prompt_line = 0; + prompt_draw(wp->prompt, &pdd); + screen_write_stop(&ctx); +} + /* Draw a pane's prompt over its content. */ static void redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) @@ -1766,8 +1791,6 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) struct client *c = scene->c; struct tty *tty = &c->tty; struct screen screen; - struct screen_write_ctx ctx; - struct prompt_draw_data pdd; int ox = scene->ox, oy = scene->oy; int sx = scene->sx, sy = scene->sy; int line, cy, px, offset, width, wy; @@ -1800,16 +1823,7 @@ redraw_draw_pane_prompt(struct redraw_draw_ctx *dctx, struct window_pane *wp) if (px + width > sx) width = sx - px; - screen_init(&screen, wp->sx, 1, 0); - screen_write_start(&ctx, &screen); - pdd.ctx = &ctx; - pdd.cursor_x = &wp->prompt_cx; - pdd.area_x = 0; - pdd.area_width = wp->sx; - pdd.prompt_line = 0; - prompt_draw(wp->prompt, &pdd); - screen_write_stop(&ctx); - + redraw_make_pane_prompt(wp, &screen); tty_draw_line(tty, &screen, 0, offset, width, px, cy, NULL); screen_free(&screen); } @@ -1841,7 +1855,8 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) redraw = status_prompt_redraw(c); else redraw = status_redraw(c); - if (!redraw && !REDRAW_IS_ALL(flags)) { + if (!redraw && (~c->flags & CLIENT_REDRAWSTATUSALWAYS) && + !REDRAW_IS_ALL(flags)) { flags &= ~REDRAW_STATUS; if (flags == 0) return; @@ -2014,7 +2029,7 @@ redraw_screen(struct client *c) } else { if (c->flags & CLIENT_REDRAWBORDERS) flags |= (REDRAW_PANE_BORDER|REDRAW_PANE_STATUS); - if (c->flags & CLIENT_REDRAWSTATUS) + if (c->flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS)) flags |= (REDRAW_STATUS|REDRAW_PANE_STATUS); if (c->flags & CLIENT_REDRAWOVERLAY) flags |= REDRAW_OVERLAY; @@ -2071,6 +2086,64 @@ redraw_damage_refresh_status(struct redraw_draw_ctx *dctx, wp->flags |= PANE_NEWSTATUS; } +/* + * Grow a clipped span range by one cell on either edge that isn't already at + * the span's own boundary. A clip edge that lands mid-character (this is a + * damage rectangle, so its edges are geometric and have no idea what's in + * the grid) may be sitting on the second, padding half of a wide character + * whose first half falls just outside the requested range - growing by one + * cell is always enough to pull the whole character back in, since no grid + * cell is ever wider than two columns, and clamping to the span's own x and + * width keeps this from bleeding into a neighbouring span. + */ +static void +redraw_damage_grow_span_clip(struct redraw_span *span, u_int *xp, u_int *endp) +{ + if (*xp > span->x) + (*xp)--; + if (*endp < span->x + span->width) + (*endp)++; +} + +/* Recompose a pane's prompt over a damaged section of its display row. */ +static void +redraw_damage_draw_pane_prompt(struct redraw_draw_ctx *dctx, + struct redraw_span *span, u_int y, u_int x, u_int n) +{ + struct redraw_scene *scene = dctx->scene; + struct window_pane *wp = span->data.p.wp; + struct tty *tty = &scene->c->tty; + struct visible_ranges *r; + struct visible_range *rr; + struct screen screen; + u_int i, px, width, prompt_y; + + if (wp->prompt == NULL || wp->sx == 0 || wp->sy == 0) + return; + if (dctx->flags & REDRAW_STATUS_TOP) + prompt_y = 0; + else + prompt_y = wp->sy - 1; + if (span->data.p.py != prompt_y) + return; + + redraw_make_pane_prompt(wp, &screen); + r = tty_check_overlay_range(tty, x, y, n); + for (i = 0; i < r->used; i++) { + rr = &r->ranges[i]; + if (rr->nx == 0) + continue; + px = span->data.p.px + (rr->px - span->x); + if (px >= screen_size_x(&screen)) + continue; + width = rr->nx; + if (width > screen_size_x(&screen) - px) + width = screen_size_x(&screen) - px; + tty_draw_line(tty, &screen, px, 0, width, rr->px, y, NULL); + } + screen_free(&screen); +} + /* * Compose exactly the cells within a damaged rectangle (already in this * client's own scene coordinates), rather than a whole pane. For each row @@ -2114,8 +2187,15 @@ redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y, redraw_damage_refresh_status(dctx, span->data.st.wp); } + redraw_damage_grow_span_clip(span, &clip_x, + &clip_end); redraw_draw_span(dctx, span, cy, clip_x, clip_end - clip_x); + if (type == REDRAW_SPAN_PANE) { + redraw_damage_draw_pane_prompt(dctx, + span, cy, clip_x, + clip_end - clip_x); + } } } } diff --git a/server-client.c b/server-client.c index f4b8f23b0..5486fb732 100644 --- a/server-client.c +++ b/server-client.c @@ -488,8 +488,17 @@ server_client_set_session(struct client *c, struct session *s) * just an active-pane change, already handled narrowly by * window_set_active_pane() and window_redraw_active_switch() * before this is reached. + * + * old and s may be the same session object, whose curw was + * already updated to the new window before this function was + * called - old->curw and s->curw would then read the same, + * already-current value, so comparing them can never detect + * a same-session window change. Compare against the client's + * own cached scene instead, which only reflects what it has + * actually drawn. */ - if (old == NULL || old != s || old->curw != s->curw) + if (old == NULL || old != s || + !redraw_client_has_window(c, s->curw->window)) server_redraw_client(c); } diff --git a/tmux.h b/tmux.h index d2a857fa1..0cd008bbd 100644 --- a/tmux.h +++ b/tmux.h @@ -3658,6 +3658,7 @@ void redraw_screen(struct client *); void redraw_pane(struct client *, struct window_pane *); void redraw_pane_scrollbar(struct client *, struct window_pane *); void redraw_free_scene(struct redraw_scene *); +int redraw_client_has_window(struct client *, struct window *); void redraw_invalidate_scene(struct window *); void redraw_invalidate_all_scenes(void); void redraw_damage_window(struct window *, u_int, u_int, u_int, u_int);