Coverage Report

Created: 2025-11-19 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/screen-redraw.c
Line
Count
Source
1
/* $OpenBSD$ */
2
3
/*
4
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
21
#include <stdlib.h>
22
#include <string.h>
23
24
#include "tmux.h"
25
26
static void screen_redraw_draw_borders(struct screen_redraw_ctx *);
27
static void screen_redraw_draw_panes(struct screen_redraw_ctx *);
28
static void screen_redraw_draw_status(struct screen_redraw_ctx *);
29
static void screen_redraw_draw_pane(struct screen_redraw_ctx *,
30
        struct window_pane *);
31
static void screen_redraw_set_context(struct client *,
32
        struct screen_redraw_ctx *);
33
static void screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *);
34
static void screen_redraw_draw_scrollbar(struct screen_redraw_ctx *,
35
        struct window_pane *, int, int, int, u_int, u_int, u_int);
36
static void screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *,
37
        struct window_pane *);
38
39
0
#define START_ISOLATE "\342\201\246"
40
0
#define END_ISOLATE   "\342\201\251"
41
42
/* Border in relation to a pane. */
43
enum screen_redraw_border_type {
44
  SCREEN_REDRAW_OUTSIDE,
45
  SCREEN_REDRAW_INSIDE,
46
  SCREEN_REDRAW_BORDER_LEFT,
47
  SCREEN_REDRAW_BORDER_RIGHT,
48
  SCREEN_REDRAW_BORDER_TOP,
49
  SCREEN_REDRAW_BORDER_BOTTOM
50
};
51
0
#define BORDER_MARKERS "  +,.-"
52
53
/* Get cell border character. */
54
static void
55
screen_redraw_border_set(struct window *w, struct window_pane *wp,
56
    enum pane_lines pane_lines, int cell_type, struct grid_cell *gc)
57
0
{
58
0
  u_int idx;
59
60
0
  if (cell_type == CELL_OUTSIDE && w->fill_character != NULL) {
61
0
    utf8_copy(&gc->data, &w->fill_character[0]);
62
0
    return;
63
0
  }
64
65
0
  switch (pane_lines) {
66
0
  case PANE_LINES_NUMBER:
67
0
    if (cell_type == CELL_OUTSIDE) {
68
0
      gc->attr |= GRID_ATTR_CHARSET;
69
0
      utf8_set(&gc->data, CELL_BORDERS[CELL_OUTSIDE]);
70
0
      break;
71
0
    }
72
0
    gc->attr &= ~GRID_ATTR_CHARSET;
73
0
    if (wp != NULL && window_pane_index(wp, &idx) == 0)
74
0
      utf8_set(&gc->data, '0' + (idx % 10));
75
0
    else
76
0
      utf8_set(&gc->data, '*');
77
0
    break;
78
0
  case PANE_LINES_DOUBLE:
79
0
    gc->attr &= ~GRID_ATTR_CHARSET;
80
0
    utf8_copy(&gc->data, tty_acs_double_borders(cell_type));
81
0
    break;
82
0
  case PANE_LINES_HEAVY:
83
0
    gc->attr &= ~GRID_ATTR_CHARSET;
84
0
    utf8_copy(&gc->data, tty_acs_heavy_borders(cell_type));
85
0
    break;
86
0
  case PANE_LINES_SIMPLE:
87
0
    gc->attr &= ~GRID_ATTR_CHARSET;
88
0
    utf8_set(&gc->data, SIMPLE_BORDERS[cell_type]);
89
0
    break;
90
0
  case PANE_LINES_SPACES:
91
0
    gc->attr &= ~GRID_ATTR_CHARSET;
92
0
    utf8_set(&gc->data, ' ');
93
0
    break;
94
0
  default:
95
0
    gc->attr |= GRID_ATTR_CHARSET;
96
0
    utf8_set(&gc->data, CELL_BORDERS[cell_type]);
97
0
    break;
98
0
  }
99
0
}
100
101
/* Return if window has only two panes. */
102
static int
103
screen_redraw_two_panes(struct window *w, int direction)
104
0
{
105
0
  struct window_pane  *wp;
106
107
0
  wp = TAILQ_NEXT(TAILQ_FIRST(&w->panes), entry);
108
0
  if (wp == NULL)
109
0
    return (0); /* one pane */
110
0
  if (TAILQ_NEXT(wp, entry) != NULL)
111
0
    return (0); /* more than two panes */
112
0
  if (direction == 0 && wp->xoff == 0)
113
0
    return (0);
114
0
  if (direction == 1 && wp->yoff == 0)
115
0
    return (0);
116
0
  return (1);
117
0
}
118
119
/* Check if cell is on the border of a pane. */
120
static enum screen_redraw_border_type
121
screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp,
122
    u_int px, u_int py)
123
0
{
124
0
  struct options  *oo = wp->window->options;
125
0
  u_int    ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy;
126
0
  int    hsplit = 0, vsplit = 0, pane_status = ctx->pane_status;
127
0
  int    pane_scrollbars = ctx->pane_scrollbars, sb_w = 0;
128
0
  int    sb_pos;
129
130
0
  if (pane_scrollbars != 0)
131
0
    sb_pos = ctx->pane_scrollbars_pos;
132
0
  else
133
0
    sb_pos = 0;
134
135
  /* Inside pane. */
136
0
  if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey)
137
0
    return (SCREEN_REDRAW_INSIDE);
138
139
  /* Get pane indicator. */
140
0
  switch (options_get_number(oo, "pane-border-indicators")) {
141
0
  case PANE_BORDER_COLOUR:
142
0
  case PANE_BORDER_BOTH:
143
0
    hsplit = screen_redraw_two_panes(wp->window, 0);
144
0
    vsplit = screen_redraw_two_panes(wp->window, 1);
145
0
    break;
146
0
  }
147
148
  /* Are scrollbars enabled? */
149
0
  if (window_pane_show_scrollbar(wp, pane_scrollbars))
150
0
    sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
151
152
  /*
153
   * Left/right borders. The wp->sy / 2 test is to colour only half the
154
   * active window's border when there are two panes.
155
   */
156
0
  if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) {
157
0
    if (sb_pos == PANE_SCROLLBARS_LEFT) {
158
0
      if (wp->xoff - sb_w == 0 && px == wp->sx + sb_w)
159
0
        if (!hsplit || (hsplit && py <= wp->sy / 2))
160
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
161
0
      if (wp->xoff - sb_w != 0) {
162
0
        if (px == wp->xoff - sb_w - 1 &&
163
0
            (!hsplit || (hsplit && py > wp->sy / 2)))
164
0
          return (SCREEN_REDRAW_BORDER_LEFT);
165
0
        if (px == wp->xoff + wp->sx + sb_w - 1)
166
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
167
0
      }
168
0
    } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled*/
169
0
      if (wp->xoff == 0 && px == wp->sx + sb_w)
170
0
        if (!hsplit || (hsplit && py <= wp->sy / 2))
171
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
172
0
      if (wp->xoff != 0) {
173
0
        if (px == wp->xoff - 1 &&
174
0
            (!hsplit || (hsplit && py > wp->sy / 2)))
175
0
          return (SCREEN_REDRAW_BORDER_LEFT);
176
0
        if (px == wp->xoff + wp->sx + sb_w)
177
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
178
0
      }
179
0
    }
180
0
  }
181
182
  /* Top/bottom borders. */
183
0
  if (vsplit && pane_status == PANE_STATUS_OFF && sb_w == 0) {
184
0
    if (wp->yoff == 0 && py == wp->sy && px <= wp->sx / 2)
185
0
      return (SCREEN_REDRAW_BORDER_BOTTOM);
186
0
    if (wp->yoff != 0 && py == wp->yoff - 1 && px > wp->sx / 2)
187
0
      return (SCREEN_REDRAW_BORDER_TOP);
188
0
  } else {
189
0
    if (sb_pos == PANE_SCROLLBARS_LEFT) {
190
0
      if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) &&
191
0
          (px <= ex || (sb_w != 0 && px < ex + sb_w))) {
192
0
        if (wp->yoff != 0 && py == wp->yoff - 1)
193
0
          return (SCREEN_REDRAW_BORDER_TOP);
194
0
        if (py == ey)
195
0
          return (SCREEN_REDRAW_BORDER_BOTTOM);
196
0
      }
197
0
    } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */
198
0
      if ((wp->xoff == 0 || px >= wp->xoff) &&
199
0
          (px <= ex || (sb_w != 0 && px < ex + sb_w))) {
200
0
        if (wp->yoff != 0 && py == wp->yoff - 1)
201
0
          return (SCREEN_REDRAW_BORDER_TOP);
202
0
        if (py == ey)
203
0
          return (SCREEN_REDRAW_BORDER_BOTTOM);
204
0
      }
205
0
    }
206
0
  }
207
208
  /* Outside pane. */
209
0
  return (SCREEN_REDRAW_OUTSIDE);
210
0
}
211
212
/* Check if a cell is on a border. */
213
static int
214
screen_redraw_cell_border(struct screen_redraw_ctx *ctx, u_int px, u_int py)
215
0
{
216
0
  struct client   *c = ctx->c;
217
0
  struct window   *w = c->session->curw->window;
218
0
  struct window_pane  *wp;
219
0
  u_int      sy = w->sy;
220
221
0
  if (ctx->pane_status == PANE_STATUS_BOTTOM)
222
0
    sy--;
223
224
  /* Outside the window? */
225
0
  if (px > w->sx || py > sy)
226
0
    return (0);
227
228
  /* On the window border? */
229
0
  if (px == w->sx || py == sy)
230
0
    return (1);
231
232
  /* Check all the panes. */
233
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
234
0
    if (!window_pane_visible(wp))
235
0
      continue;
236
0
    switch (screen_redraw_pane_border(ctx, wp, px, py)) {
237
0
    case SCREEN_REDRAW_INSIDE:
238
0
      return (0);
239
0
    case SCREEN_REDRAW_OUTSIDE:
240
0
      break;
241
0
    default:
242
0
      return (1);
243
0
    }
244
0
  }
245
246
0
  return (0);
247
0
}
248
249
/* Work out type of border cell from surrounding cells. */
250
static int
251
screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py)
252
0
{
253
0
  struct client *c = ctx->c;
254
0
  int    pane_status = ctx->pane_status;
255
0
  struct window *w = c->session->curw->window;
256
0
  u_int    sx = w->sx, sy = w->sy;
257
0
  int    borders = 0;
258
259
0
  if (pane_status == PANE_STATUS_BOTTOM)
260
0
    sy--;
261
262
  /* Is this outside the window? */
263
0
  if (px > sx || py > sy)
264
0
    return (CELL_OUTSIDE);
265
266
  /*
267
   * Construct a bitmask of whether the cells to the left (bit 8), right,
268
   * top, and bottom (bit 1) of this cell are borders.
269
   *
270
   * bits 8 4 2 1:     2
271
   *       8 + 4
272
   *         1
273
   */
274
0
  if (px == 0 || screen_redraw_cell_border(ctx, px - 1, py))
275
0
    borders |= 8;
276
0
  if (px <= sx && screen_redraw_cell_border(ctx, px + 1, py))
277
0
    borders |= 4;
278
0
  if (pane_status == PANE_STATUS_TOP) {
279
0
    if (py != 0 &&
280
0
        screen_redraw_cell_border(ctx, px, py - 1))
281
0
      borders |= 2;
282
0
    if (screen_redraw_cell_border(ctx, px, py + 1))
283
0
      borders |= 1;
284
0
  } else if (pane_status == PANE_STATUS_BOTTOM) {
285
0
    if (py == 0 ||
286
0
        screen_redraw_cell_border(ctx, px, py - 1))
287
0
      borders |= 2;
288
0
    if (py != sy &&
289
0
        screen_redraw_cell_border(ctx, px, py + 1))
290
0
      borders |= 1;
291
0
  } else {
292
0
    if (py == 0 ||
293
0
        screen_redraw_cell_border(ctx, px, py - 1))
294
0
      borders |= 2;
295
0
    if (screen_redraw_cell_border(ctx, px, py + 1))
296
0
      borders |= 1;
297
0
  }
298
299
  /*
300
   * Figure out what kind of border this cell is. Only one bit set
301
   * doesn't make sense (can't have a border cell with no others
302
   * connected).
303
   */
304
0
  switch (borders) {
305
0
  case 15:  /* 1111, left right top bottom */
306
0
    return (CELL_JOIN);
307
0
  case 14:  /* 1110, left right top */
308
0
    return (CELL_BOTTOMJOIN);
309
0
  case 13:  /* 1101, left right bottom */
310
0
    return (CELL_TOPJOIN);
311
0
  case 12:  /* 1100, left right */
312
0
    return (CELL_LEFTRIGHT);
313
0
  case 11:  /* 1011, left top bottom */
314
0
    return (CELL_RIGHTJOIN);
315
0
  case 10:  /* 1010, left top */
316
0
    return (CELL_BOTTOMRIGHT);
317
0
  case 9:   /* 1001, left bottom */
318
0
    return (CELL_TOPRIGHT);
319
0
  case 7:   /* 0111, right top bottom */
320
0
    return (CELL_LEFTJOIN);
321
0
  case 6:   /* 0110, right top */
322
0
    return (CELL_BOTTOMLEFT);
323
0
  case 5:   /* 0101, right bottom */
324
0
    return (CELL_TOPLEFT);
325
0
  case 3:   /* 0011, top bottom */
326
0
    return (CELL_TOPBOTTOM);
327
0
  }
328
0
  return (CELL_OUTSIDE);
329
0
}
330
331
/* Check if cell inside a pane. */
332
static int
333
screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py,
334
    struct window_pane **wpp)
335
0
{
336
0
  struct client   *c = ctx->c;
337
0
  struct window   *w = c->session->curw->window;
338
0
  struct window_pane  *wp, *active;
339
0
  int      pane_status = ctx->pane_status;
340
0
  u_int      sx = w->sx, sy = w->sy;
341
0
  int      border, pane_scrollbars = ctx->pane_scrollbars;
342
0
  u_int      right, line;
343
0
  int      sb_pos = ctx->pane_scrollbars_pos;
344
0
  int      sb_w;
345
346
0
  *wpp = NULL;
347
348
0
  if (px > sx || py > sy)
349
0
    return (CELL_OUTSIDE);
350
0
  if (px == sx || py == sy) /* window border */
351
0
    return (screen_redraw_type_of_cell(ctx, px, py));
352
353
0
  if (pane_status != PANE_STATUS_OFF) {
354
0
    active = wp = server_client_get_pane(c);
355
0
    do {
356
0
      if (!window_pane_visible(wp))
357
0
        goto next1;
358
359
0
      if (pane_status == PANE_STATUS_TOP)
360
0
        line = wp->yoff - 1;
361
0
      else
362
0
        line = wp->yoff + sy;
363
0
      right = wp->xoff + 2 + wp->status_size - 1;
364
365
0
      if (py == line && px >= wp->xoff + 2 && px <= right)
366
0
        return (CELL_INSIDE);
367
368
0
    next1:
369
0
      wp = TAILQ_NEXT(wp, entry);
370
0
      if (wp == NULL)
371
0
        wp = TAILQ_FIRST(&w->panes);
372
0
    } while (wp != active);
373
0
  }
374
375
0
  active = wp = server_client_get_pane(c);
376
0
  do {
377
0
    if (!window_pane_visible(wp))
378
0
      goto next2;
379
0
    *wpp = wp;
380
381
    /* Check if CELL_SCROLLBAR */
382
0
    if (window_pane_show_scrollbar(wp, pane_scrollbars)) {
383
384
0
      if (pane_status == PANE_STATUS_TOP)
385
0
        line = wp->yoff - 1;
386
0
      else
387
0
        line = wp->yoff + wp->sy;
388
389
      /*
390
       * Check if py could lie within a scrollbar. If the
391
       * pane is at the top then py == 0 to sy; if the pane
392
       * is not at the top, then yoff to yoff + sy.
393
       */
394
0
      sb_w = wp->scrollbar_style.width +
395
0
          wp->scrollbar_style.pad;
396
0
      if ((pane_status && py != line) ||
397
0
          (wp->yoff == 0 && py < wp->sy) ||
398
0
           (py >= wp->yoff && py < wp->yoff + wp->sy)) {
399
        /* Check if px lies within a scrollbar. */
400
0
        if ((sb_pos == PANE_SCROLLBARS_RIGHT &&
401
0
             (px >= wp->xoff + wp->sx &&
402
0
              px < wp->xoff + wp->sx + sb_w)) ||
403
0
            (sb_pos == PANE_SCROLLBARS_LEFT &&
404
0
             (px >= wp->xoff - sb_w &&
405
0
              px < wp->xoff)))
406
0
          return (CELL_SCROLLBAR);
407
0
      }
408
0
    }
409
410
    /*
411
     * If definitely inside, return. If not on border, skip.
412
     * Otherwise work out the cell.
413
     */
414
0
    border = screen_redraw_pane_border(ctx, wp, px, py);
415
0
    if (border == SCREEN_REDRAW_INSIDE)
416
0
      return (CELL_INSIDE);
417
0
    if (border == SCREEN_REDRAW_OUTSIDE)
418
0
      goto next2;
419
0
    return (screen_redraw_type_of_cell(ctx, px, py));
420
421
0
  next2:
422
0
    wp = TAILQ_NEXT(wp, entry);
423
0
    if (wp == NULL)
424
0
      wp = TAILQ_FIRST(&w->panes);
425
0
  } while (wp != active);
426
427
0
  return (CELL_OUTSIDE);
428
0
}
429
430
/* Check if the border of a particular pane. */
431
static int
432
screen_redraw_check_is(struct screen_redraw_ctx *ctx, u_int px, u_int py,
433
    struct window_pane *wp)
434
0
{
435
0
  enum screen_redraw_border_type  border;
436
437
0
  border = screen_redraw_pane_border(ctx, wp, px, py);
438
0
  if (border != SCREEN_REDRAW_INSIDE && border != SCREEN_REDRAW_OUTSIDE)
439
0
    return (1);
440
0
  return (0);
441
0
}
442
443
/* Update pane status. */
444
static int
445
screen_redraw_make_pane_status(struct client *c, struct window_pane *wp,
446
    struct screen_redraw_ctx *rctx, enum pane_lines pane_lines)
447
0
{
448
0
  struct window   *w = wp->window;
449
0
  struct grid_cell   gc;
450
0
  const char    *fmt;
451
0
  struct format_tree  *ft;
452
0
  char      *expanded;
453
0
  int      pane_status = rctx->pane_status, sb_w = 0;
454
0
  int      pane_scrollbars = rctx->pane_scrollbars;
455
0
  u_int      width, i, cell_type, px, py;
456
0
  struct screen_write_ctx  ctx;
457
0
  struct screen    old;
458
459
0
  if (window_pane_show_scrollbar(wp, pane_scrollbars))
460
0
    sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
461
462
0
  ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS);
463
0
  format_defaults(ft, c, c->session, c->session->curw, wp);
464
465
0
  if (wp == server_client_get_pane(c))
466
0
    style_apply(&gc, w->options, "pane-active-border-style", ft);
467
0
  else
468
0
    style_apply(&gc, w->options, "pane-border-style", ft);
469
0
  fmt = options_get_string(wp->options, "pane-border-format");
470
471
0
  expanded = format_expand_time(ft, fmt);
472
0
  if (wp->sx < 4)
473
0
    wp->status_size = width = 0;
474
0
  else
475
0
    wp->status_size = width = wp->sx + sb_w - 2;
476
477
0
  memcpy(&old, &wp->status_screen, sizeof old);
478
0
  screen_init(&wp->status_screen, width, 1, 0);
479
0
  wp->status_screen.mode = 0;
480
481
0
  screen_write_start(&ctx, &wp->status_screen);
482
483
0
  for (i = 0; i < width; i++) {
484
0
    px = wp->xoff + 2 + i;
485
0
    if (pane_status == PANE_STATUS_TOP)
486
0
      py = wp->yoff - 1;
487
0
    else
488
0
      py = wp->yoff + wp->sy;
489
0
    cell_type = screen_redraw_type_of_cell(rctx, px, py);
490
0
    screen_redraw_border_set(w, wp, pane_lines, cell_type, &gc);
491
0
    screen_write_cell(&ctx, &gc);
492
0
  }
493
0
  gc.attr &= ~GRID_ATTR_CHARSET;
494
495
0
  screen_write_cursormove(&ctx, 0, 0, 0);
496
0
  format_draw(&ctx, &gc, width, expanded, NULL, 0);
497
0
  screen_write_stop(&ctx);
498
499
0
  free(expanded);
500
0
  format_free(ft);
501
502
0
  if (grid_compare(wp->status_screen.grid, old.grid) == 0) {
503
0
    screen_free(&old);
504
0
    return (0);
505
0
  }
506
0
  screen_free(&old);
507
0
  return (1);
508
0
}
509
510
/* Draw pane status. */
511
static void
512
screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx)
513
0
{
514
0
  struct client   *c = ctx->c;
515
0
  struct window   *w = c->session->curw->window;
516
0
  struct tty    *tty = &c->tty;
517
0
  struct window_pane  *wp;
518
0
  struct screen   *s;
519
0
  u_int      i, x, width, xoff, yoff, size;
520
521
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
522
523
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
524
0
    if (!window_pane_visible(wp))
525
0
      continue;
526
0
    s = &wp->status_screen;
527
528
0
    size = wp->status_size;
529
0
    if (ctx->pane_status == PANE_STATUS_TOP)
530
0
      yoff = wp->yoff - 1;
531
0
    else
532
0
      yoff = wp->yoff + wp->sy;
533
0
    xoff = wp->xoff + 2;
534
535
0
    if (xoff + size <= ctx->ox ||
536
0
        xoff >= ctx->ox + ctx->sx ||
537
0
        yoff < ctx->oy ||
538
0
        yoff >= ctx->oy + ctx->sy)
539
0
      continue;
540
541
0
    if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) {
542
      /* All visible. */
543
0
      i = 0;
544
0
      x = xoff - ctx->ox;
545
0
      width = size;
546
0
    } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) {
547
      /* Both left and right not visible. */
548
0
      i = ctx->ox;
549
0
      x = 0;
550
0
      width = ctx->sx;
551
0
    } else if (xoff < ctx->ox) {
552
      /* Left not visible. */
553
0
      i = ctx->ox - xoff;
554
0
      x = 0;
555
0
      width = size - i;
556
0
    } else {
557
      /* Right not visible. */
558
0
      i = 0;
559
0
      x = xoff - ctx->ox;
560
0
      width = size - x;
561
0
    }
562
563
0
    if (ctx->statustop)
564
0
      yoff += ctx->statuslines;
565
0
    tty_draw_line(tty, s, i, 0, width, x, yoff - ctx->oy,
566
0
        &grid_default_cell, NULL);
567
0
  }
568
0
  tty_cursor(tty, 0, 0);
569
0
}
570
571
/* Update status line and change flags if unchanged. */
572
static uint64_t
573
screen_redraw_update(struct screen_redraw_ctx *ctx, uint64_t flags)
574
0
{
575
0
  struct client     *c = ctx->c;
576
0
  struct window     *w = c->session->curw->window;
577
0
  struct window_pane    *wp;
578
0
  int        redraw;
579
0
  enum pane_lines      lines;
580
581
0
  if (c->message_string != NULL)
582
0
    redraw = status_message_redraw(c);
583
0
  else if (c->prompt_string != NULL)
584
0
    redraw = status_prompt_redraw(c);
585
0
  else
586
0
    redraw = status_redraw(c);
587
0
  if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS))
588
0
    flags &= ~CLIENT_REDRAWSTATUS;
589
590
0
  if (c->overlay_draw != NULL)
591
0
    flags |= CLIENT_REDRAWOVERLAY;
592
593
0
  if (ctx->pane_status != PANE_STATUS_OFF) {
594
0
    lines = ctx->pane_lines;
595
0
    redraw = 0;
596
0
    TAILQ_FOREACH(wp, &w->panes, entry) {
597
0
      if (screen_redraw_make_pane_status(c, wp, ctx, lines))
598
0
        redraw = 1;
599
0
    }
600
0
    if (redraw)
601
0
      flags |= CLIENT_REDRAWBORDERS;
602
0
  }
603
604
0
  return (flags);
605
0
}
606
607
/* Set up redraw context. */
608
static void
609
screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx)
610
0
{
611
0
  struct session  *s = c->session;
612
0
  struct options  *oo = s->options;
613
0
  struct window *w = s->curw->window;
614
0
  struct options  *wo = w->options;
615
0
  u_int    lines;
616
617
0
  memset(ctx, 0, sizeof *ctx);
618
0
  ctx->c = c;
619
620
0
  lines = status_line_size(c);
621
0
  if (c->message_string != NULL || c->prompt_string != NULL)
622
0
    lines = (lines == 0) ? 1 : lines;
623
0
  if (lines != 0 && options_get_number(oo, "status-position") == 0)
624
0
    ctx->statustop = 1;
625
0
  ctx->statuslines = lines;
626
627
0
  ctx->pane_status = options_get_number(wo, "pane-border-status");
628
0
  ctx->pane_lines = options_get_number(wo, "pane-border-lines");
629
630
0
  ctx->pane_scrollbars = options_get_number(wo, "pane-scrollbars");
631
0
  ctx->pane_scrollbars_pos = options_get_number(wo,
632
0
      "pane-scrollbars-position");
633
634
0
  tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy);
635
636
0
  log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name,
637
0
      w->id, ctx->ox, ctx->oy, ctx->sx, ctx->sy, ctx->statuslines,
638
0
      ctx->statustop);
639
0
}
640
641
/* Redraw entire screen. */
642
void
643
screen_redraw_screen(struct client *c)
644
0
{
645
0
  struct screen_redraw_ctx  ctx;
646
0
  uint64_t      flags;
647
648
0
  if (c->flags & CLIENT_SUSPENDED)
649
0
    return;
650
651
0
  screen_redraw_set_context(c, &ctx);
652
653
0
  flags = screen_redraw_update(&ctx, c->flags);
654
0
  if ((flags & CLIENT_ALLREDRAWFLAGS) == 0)
655
0
    return;
656
657
0
  tty_sync_start(&c->tty);
658
0
  tty_update_mode(&c->tty, c->tty.mode, NULL);
659
660
0
  if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) {
661
0
    log_debug("%s: redrawing borders", c->name);
662
0
    screen_redraw_draw_borders(&ctx);
663
0
    if (ctx.pane_status != PANE_STATUS_OFF)
664
0
      screen_redraw_draw_pane_status(&ctx);
665
0
    screen_redraw_draw_pane_scrollbars(&ctx);
666
0
  }
667
0
  if (flags & CLIENT_REDRAWWINDOW) {
668
0
    log_debug("%s: redrawing panes", c->name);
669
0
    screen_redraw_draw_panes(&ctx);
670
0
    screen_redraw_draw_pane_scrollbars(&ctx);
671
0
  }
672
0
  if (ctx.statuslines != 0 &&
673
0
      (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) {
674
0
    log_debug("%s: redrawing status", c->name);
675
0
    screen_redraw_draw_status(&ctx);
676
0
  }
677
0
  if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) {
678
0
    log_debug("%s: redrawing overlay", c->name);
679
0
    c->overlay_draw(c, c->overlay_data, &ctx);
680
0
  }
681
682
0
  tty_reset(&c->tty);
683
0
}
684
685
/* Redraw a single pane and its scrollbar. */
686
void
687
screen_redraw_pane(struct client *c, struct window_pane *wp,
688
    int redraw_scrollbar_only)
689
0
{
690
0
  struct screen_redraw_ctx  ctx;
691
692
0
  if (!window_pane_visible(wp))
693
0
    return;
694
695
0
  screen_redraw_set_context(c, &ctx);
696
0
  tty_sync_start(&c->tty);
697
0
  tty_update_mode(&c->tty, c->tty.mode, NULL);
698
699
0
  if (!redraw_scrollbar_only)
700
0
    screen_redraw_draw_pane(&ctx, wp);
701
702
0
  if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars))
703
0
    screen_redraw_draw_pane_scrollbar(&ctx, wp);
704
705
0
  tty_reset(&c->tty);
706
0
}
707
708
/* Get border cell style. */
709
static const struct grid_cell *
710
screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x,
711
    u_int y, struct window_pane *wp)
712
0
{
713
0
  struct client   *c = ctx->c;
714
0
  struct session    *s = c->session;
715
0
  struct window   *w = s->curw->window;
716
0
  struct window_pane  *active = server_client_get_pane(c);
717
0
  struct options    *oo = w->options;
718
0
  struct format_tree  *ft;
719
720
0
  if (wp->border_gc_set)
721
0
    return (&wp->border_gc);
722
0
  wp->border_gc_set = 1;
723
724
0
  ft = format_create_defaults(NULL, c, s, s->curw, wp);
725
0
  if (screen_redraw_check_is(ctx, x, y, active))
726
0
    style_apply(&wp->border_gc, oo, "pane-active-border-style", ft);
727
0
  else
728
0
    style_apply(&wp->border_gc, oo, "pane-border-style", ft);
729
0
  format_free(ft);
730
731
0
  return (&wp->border_gc);
732
0
}
733
734
/* Draw a border cell. */
735
static void
736
screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j)
737
0
{
738
0
  struct client   *c = ctx->c;
739
0
  struct session    *s = c->session;
740
0
  struct window   *w = s->curw->window;
741
0
  struct options    *oo = w->options;
742
0
  struct tty    *tty = &c->tty;
743
0
  struct format_tree  *ft;
744
0
  struct window_pane  *wp, *active = server_client_get_pane(c);
745
0
  struct grid_cell   gc;
746
0
  const struct grid_cell  *tmp;
747
0
  struct overlay_ranges  r;
748
0
  u_int      cell_type, x = ctx->ox + i, y = ctx->oy + j;
749
0
  int      arrows = 0, border, isolates;
750
751
0
  if (c->overlay_check != NULL) {
752
0
    c->overlay_check(c, c->overlay_data, x, y, 1, &r);
753
0
    if (r.nx[0] + r.nx[1] == 0)
754
0
      return;
755
0
  }
756
757
0
  cell_type = screen_redraw_check_cell(ctx, x, y, &wp);
758
0
  if (cell_type == CELL_INSIDE || cell_type == CELL_SCROLLBAR)
759
0
    return;
760
761
0
  if (wp == NULL) {
762
0
    if (!ctx->no_pane_gc_set) {
763
0
      ft = format_create_defaults(NULL, c, s, s->curw, NULL);
764
0
      memcpy(&ctx->no_pane_gc, &grid_default_cell, sizeof gc);
765
0
      style_add(&ctx->no_pane_gc, oo, "pane-border-style",
766
0
          ft);
767
0
      format_free(ft);
768
0
      ctx->no_pane_gc_set = 1;
769
0
    }
770
0
    memcpy(&gc, &ctx->no_pane_gc, sizeof gc);
771
0
  } else {
772
0
    tmp = screen_redraw_draw_borders_style(ctx, x, y, wp);
773
0
    if (tmp == NULL)
774
0
      return;
775
0
    memcpy(&gc, tmp, sizeof gc);
776
777
0
    if (server_is_marked(s, s->curw, marked_pane.wp) &&
778
0
        screen_redraw_check_is(ctx, x, y, marked_pane.wp))
779
0
      gc.attr ^= GRID_ATTR_REVERSE;
780
0
  }
781
0
  screen_redraw_border_set(w, wp, ctx->pane_lines, cell_type, &gc);
782
783
0
  if (cell_type == CELL_TOPBOTTOM &&
784
0
      (c->flags & CLIENT_UTF8) &&
785
0
      tty_term_has(tty->term, TTYC_BIDI))
786
0
    isolates = 1;
787
0
  else
788
0
    isolates = 0;
789
790
0
  if (ctx->statustop)
791
0
    tty_cursor(tty, i, ctx->statuslines + j);
792
0
  else
793
0
    tty_cursor(tty, i, j);
794
0
  if (isolates)
795
0
    tty_puts(tty, END_ISOLATE);
796
797
0
  switch (options_get_number(oo, "pane-border-indicators")) {
798
0
  case PANE_BORDER_ARROWS:
799
0
  case PANE_BORDER_BOTH:
800
0
    arrows = 1;
801
0
    break;
802
0
  }
803
804
0
  if (wp != NULL && arrows) {
805
0
    border = screen_redraw_pane_border(ctx, active, x, y);
806
0
    if (((i == wp->xoff + 1 &&
807
0
        (cell_type == CELL_LEFTRIGHT ||
808
0
        (cell_type == CELL_TOPJOIN &&
809
0
        border == SCREEN_REDRAW_BORDER_BOTTOM) ||
810
0
        (cell_type == CELL_BOTTOMJOIN &&
811
0
        border == SCREEN_REDRAW_BORDER_TOP))) ||
812
0
        (j == wp->yoff + 1 &&
813
0
        (cell_type == CELL_TOPBOTTOM ||
814
0
        (cell_type == CELL_LEFTJOIN &&
815
0
        border == SCREEN_REDRAW_BORDER_RIGHT) ||
816
0
        (cell_type == CELL_RIGHTJOIN &&
817
0
        border == SCREEN_REDRAW_BORDER_LEFT)))) &&
818
0
        screen_redraw_check_is(ctx, x, y, active)) {
819
0
      gc.attr |= GRID_ATTR_CHARSET;
820
0
      utf8_set(&gc.data, BORDER_MARKERS[border]);
821
0
    }
822
0
  }
823
824
0
  tty_cell(tty, &gc, &grid_default_cell, NULL, NULL);
825
0
  if (isolates)
826
0
    tty_puts(tty, START_ISOLATE);
827
0
}
828
829
/* Draw the borders. */
830
static void
831
screen_redraw_draw_borders(struct screen_redraw_ctx *ctx)
832
0
{
833
0
  struct client   *c = ctx->c;
834
0
  struct session    *s = c->session;
835
0
  struct window   *w = s->curw->window;
836
0
  struct window_pane  *wp;
837
0
  u_int      i, j;
838
839
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
840
841
0
  TAILQ_FOREACH(wp, &w->panes, entry)
842
0
    wp->border_gc_set = 0;
843
844
0
  for (j = 0; j < c->tty.sy - ctx->statuslines; j++) {
845
0
    for (i = 0; i < c->tty.sx; i++)
846
0
      screen_redraw_draw_borders_cell(ctx, i, j);
847
0
  }
848
0
}
849
850
/* Draw the panes. */
851
static void
852
screen_redraw_draw_panes(struct screen_redraw_ctx *ctx)
853
0
{
854
0
  struct client   *c = ctx->c;
855
0
  struct window   *w = c->session->curw->window;
856
0
  struct window_pane  *wp;
857
858
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
859
860
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
861
0
    if (window_pane_visible(wp))
862
0
      screen_redraw_draw_pane(ctx, wp);
863
0
  }
864
0
}
865
866
/* Draw the status line. */
867
static void
868
screen_redraw_draw_status(struct screen_redraw_ctx *ctx)
869
0
{
870
0
  struct client *c = ctx->c;
871
0
  struct window *w = c->session->curw->window;
872
0
  struct tty  *tty = &c->tty;
873
0
  struct screen *s = c->status.active;
874
0
  u_int    i, y;
875
876
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
877
878
0
  if (ctx->statustop)
879
0
    y = 0;
880
0
  else
881
0
    y = c->tty.sy - ctx->statuslines;
882
0
  for (i = 0; i < ctx->statuslines; i++) {
883
0
    tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i,
884
0
        &grid_default_cell, NULL);
885
0
  }
886
0
}
887
888
/* Draw one pane. */
889
static void
890
screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp)
891
0
{
892
0
  struct client   *c = ctx->c;
893
0
  struct window   *w = c->session->curw->window;
894
0
  struct tty    *tty = &c->tty;
895
0
  struct screen   *s = wp->screen;
896
0
  struct colour_palette *palette = &wp->palette;
897
0
  struct grid_cell   defaults;
898
0
  u_int      i, j, top, x, y, width;
899
900
0
  log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id);
901
902
0
  if (wp->xoff + wp->sx <= ctx->ox || wp->xoff >= ctx->ox + ctx->sx)
903
0
    return;
904
0
  if (ctx->statustop)
905
0
    top = ctx->statuslines;
906
0
  else
907
0
    top = 0;
908
0
  for (j = 0; j < wp->sy; j++) {
909
0
    if (wp->yoff + j < ctx->oy || wp->yoff + j >= ctx->oy + ctx->sy)
910
0
      continue;
911
0
    y = top + wp->yoff + j - ctx->oy;
912
913
0
    if (wp->xoff >= ctx->ox &&
914
0
        wp->xoff + wp->sx <= ctx->ox + ctx->sx) {
915
      /* All visible. */
916
0
      i = 0;
917
0
      x = wp->xoff - ctx->ox;
918
0
      width = wp->sx;
919
0
    } else if (wp->xoff < ctx->ox &&
920
0
        wp->xoff + wp->sx > ctx->ox + ctx->sx) {
921
      /* Both left and right not visible. */
922
0
      i = ctx->ox;
923
0
      x = 0;
924
0
      width = ctx->sx;
925
0
    } else if (wp->xoff < ctx->ox) {
926
      /* Left not visible. */
927
0
      i = ctx->ox - wp->xoff;
928
0
      x = 0;
929
0
      width = wp->sx - i;
930
0
    } else {
931
      /* Right not visible. */
932
0
      i = 0;
933
0
      x = wp->xoff - ctx->ox;
934
0
      width = ctx->sx - x;
935
0
    }
936
0
    log_debug("%s: %s %%%u line %u,%u at %u,%u, width %u",
937
0
        __func__, c->name, wp->id, i, j, x, y, width);
938
939
0
    tty_default_colours(&defaults, wp);
940
0
    tty_draw_line(tty, s, i, j, width, x, y, &defaults, palette);
941
0
  }
942
943
#ifdef ENABLE_SIXEL
944
  tty_draw_images(c, wp, s);
945
#endif
946
0
}
947
948
/* Draw the panes scrollbars */
949
static void
950
screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *ctx)
951
0
{
952
0
  struct client   *c = ctx->c;
953
0
  struct window   *w = c->session->curw->window;
954
0
  struct window_pane  *wp;
955
956
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
957
958
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
959
0
    if (window_pane_show_scrollbar(wp, ctx->pane_scrollbars) &&
960
0
        window_pane_visible(wp))
961
0
      screen_redraw_draw_pane_scrollbar(ctx, wp);
962
0
  }
963
0
}
964
965
/* Draw pane scrollbar. */
966
void
967
screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx,
968
    struct window_pane *wp)
969
0
{
970
0
  struct screen *s = wp->screen;
971
0
  double     percent_view;
972
0
  u_int    sb = ctx->pane_scrollbars, total_height, sb_h = wp->sy;
973
0
  u_int    sb_pos = ctx->pane_scrollbars_pos, slider_h, slider_y;
974
0
  int    sb_w = wp->scrollbar_style.width;
975
0
  int    sb_pad = wp->scrollbar_style.pad;
976
0
  int    cm_y, cm_size, xoff = wp->xoff, ox = ctx->ox;
977
0
  int    sb_x, sb_y = (int)(wp->yoff - ctx->oy); /* sb top */
978
979
0
  if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) {
980
0
    if (sb == PANE_SCROLLBARS_MODAL)
981
0
      return;
982
    /* Show slider at the bottom of the scrollbar. */
983
0
    total_height = screen_size_y(s) + screen_hsize(s);
984
0
    percent_view = (double)sb_h / total_height;
985
0
    slider_h = (double)sb_h * percent_view;
986
0
    slider_y = sb_h - slider_h;
987
0
  } else {
988
0
    if (TAILQ_FIRST(&wp->modes) == NULL)
989
0
      return;
990
0
    if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0)
991
0
      return;
992
0
    total_height = cm_size + sb_h;
993
0
    percent_view = (double)sb_h / (cm_size + sb_h);
994
0
    slider_h = (double)sb_h * percent_view;
995
0
    slider_y = (sb_h + 1) * ((double)cm_y / total_height);
996
0
  }
997
998
0
  if (sb_pos == PANE_SCROLLBARS_LEFT)
999
0
    sb_x = xoff - sb_w - sb_pad - ox;
1000
0
  else
1001
0
    sb_x = xoff + wp->sx - ox;
1002
1003
0
  if (slider_h < 1)
1004
0
    slider_h = 1;
1005
0
  if (slider_y >= sb_h)
1006
0
    slider_y = sb_h - 1;
1007
1008
0
  screen_redraw_draw_scrollbar(ctx, wp, sb_pos, sb_x, sb_y, sb_h,
1009
0
      slider_h, slider_y);
1010
1011
  /* Store current position and height of the slider */
1012
0
  wp->sb_slider_y = slider_y;  /* top of slider y pos in scrollbar */
1013
0
  wp->sb_slider_h = slider_h;  /* height of slider */
1014
0
}
1015
1016
static void
1017
screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx,
1018
    struct window_pane *wp, int sb_pos, int sb_x, int sb_y, u_int sb_h,
1019
    u_int slider_h, u_int slider_y)
1020
0
{
1021
0
  struct client   *c = ctx->c;
1022
0
  struct tty    *tty = &c->tty;
1023
0
  struct grid_cell   gc, slgc, *gcp;
1024
0
  struct style    *sb_style = &wp->scrollbar_style;
1025
0
  u_int      i, j, imax, jmax;
1026
0
  u_int      sb_w = sb_style->width, sb_pad = sb_style->pad;
1027
0
  int      px, py, ox = ctx->ox, oy = ctx->oy;
1028
0
  int      sx = ctx->sx, sy = ctx->sy, xoff = wp->xoff;
1029
0
  int      yoff = wp->yoff;
1030
1031
  /* Set up style for slider. */
1032
0
  gc = sb_style->gc;
1033
0
  memcpy(&slgc, &gc, sizeof slgc);
1034
0
  slgc.fg = gc.bg;
1035
0
  slgc.bg = gc.fg;
1036
1037
0
  imax = sb_w + sb_pad;
1038
0
  if ((int)imax + sb_x > sx)
1039
0
    imax = sx - sb_x;
1040
0
  jmax = sb_h;
1041
0
  if ((int)jmax + sb_y > sy)
1042
0
    jmax = sy - sb_y;
1043
1044
0
  for (j = 0; j < jmax; j++) {
1045
0
    py = sb_y + j;
1046
0
    for (i = 0; i < imax; i++) {
1047
0
      px = sb_x + i;
1048
0
      if (px < xoff - ox - (int)sb_w - (int)sb_pad ||
1049
0
          px >= sx || px < 0 ||
1050
0
          py < yoff - oy - 1 ||
1051
0
          py >= sy || py < 0)
1052
0
        continue;
1053
0
      tty_cursor(tty, px, py);
1054
0
      if ((sb_pos == PANE_SCROLLBARS_LEFT &&
1055
0
          i >= sb_w && i < sb_w + sb_pad) ||
1056
0
          (sb_pos == PANE_SCROLLBARS_RIGHT &&
1057
0
           i < sb_pad)) {
1058
0
        tty_cell(tty, &grid_default_cell,
1059
0
            &grid_default_cell, NULL, NULL);
1060
0
      } else {
1061
0
        if (j >= slider_y && j < slider_y + slider_h)
1062
0
          gcp = &slgc;
1063
0
        else
1064
0
          gcp = &gc;
1065
0
        tty_cell(tty, gcp, &grid_default_cell, NULL,
1066
            NULL);
1067
0
      }
1068
0
    }
1069
0
  }
1070
0
}