Coverage Report

Created: 2026-06-10 06:31

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 1 if window has only two panes. */
102
static int
103
screen_redraw_two_panes(struct window *w, enum layout_type *type)
104
0
{
105
0
  struct window_pane  *wp;
106
0
  u_int      count = 0;
107
108
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
109
0
    if (window_pane_is_floating(wp) || wp->layout_cell == NULL)
110
0
      continue;
111
0
    count++;
112
0
    if (count > 2 || wp->layout_cell->parent == NULL)
113
0
      return (0);
114
0
    *type = wp->layout_cell->parent->type;
115
0
  }
116
0
  if (count <= 1)
117
0
    return (0);
118
0
  return (1);
119
0
}
120
121
/* Check if cell is on the border of a pane. */
122
static enum screen_redraw_border_type
123
screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp,
124
    int px, int py)
125
0
{
126
0
  struct options  *oo = wp->window->options;
127
0
  int    ex = wp->xoff + wp->sx, ey = wp->yoff + wp->sy;
128
0
  int    hsplit = 0, vsplit = 0, pane_status = ctx->pane_status;
129
0
  int    pane_scrollbars = ctx->pane_scrollbars, sb_w = 0;
130
0
  int    sb_pos, sx = wp->sx, sy = wp->sy;
131
0
  enum layout_type split_type;
132
133
0
  if (pane_scrollbars != 0)
134
0
    sb_pos = ctx->pane_scrollbars_pos;
135
0
  else
136
0
    sb_pos = 0;
137
138
  /* Inside pane. */
139
0
  if (px >= wp->xoff && px < ex && py >= wp->yoff && py < ey)
140
0
    return (SCREEN_REDRAW_INSIDE);
141
142
  /* Are scrollbars enabled? */
143
0
  if (window_pane_show_scrollbar(wp, pane_scrollbars))
144
0
    sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
145
146
  /* Floating pane borders. */
147
0
  if (window_pane_is_floating(wp)) {
148
0
    if (py >= wp->yoff - 1 && py <= wp->yoff + sy) {
149
0
      if (sb_pos == PANE_SCROLLBARS_LEFT) {
150
0
        if (px == wp->xoff - 1 - sb_w)
151
0
          return (SCREEN_REDRAW_BORDER_LEFT);
152
0
        if (px == wp->xoff + sx)
153
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
154
0
      } else { /* PANE_SCROLLBARS_RIGHT or none. */
155
0
        if (px == wp->xoff - 1)
156
0
          return (SCREEN_REDRAW_BORDER_LEFT);
157
0
        if (px == wp->xoff + sx + sb_w)
158
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
159
0
      }
160
0
    }
161
0
    if (px >= wp->xoff && px <= wp->xoff + sx) {
162
0
      if (py == wp->yoff - 1)
163
0
        return (SCREEN_REDRAW_BORDER_TOP);
164
0
      if (py == wp->yoff + sy)
165
0
        return (SCREEN_REDRAW_BORDER_BOTTOM);
166
0
    }
167
0
    return (SCREEN_REDRAW_OUTSIDE);
168
0
  }
169
170
  /* Get pane indicator. */
171
0
  switch (options_get_number(oo, "pane-border-indicators")) {
172
0
  case PANE_BORDER_COLOUR:
173
0
  case PANE_BORDER_BOTH:
174
0
    if (screen_redraw_two_panes(wp->window, &split_type)) {
175
0
      hsplit = (split_type == LAYOUT_LEFTRIGHT);
176
0
      vsplit = (split_type == LAYOUT_TOPBOTTOM);
177
0
    }
178
0
    break;
179
0
  }
180
181
  /*
182
   * Left/right borders. The sy / 2 test is to colour only half the
183
   * active window's border when there are two panes.
184
   */
185
0
  if ((wp->yoff == 0 || py >= wp->yoff - 1) && py <= ey) {
186
0
    if (sb_pos == PANE_SCROLLBARS_LEFT) {
187
0
      if (wp->xoff - sb_w == 0 && px == sx + sb_w) {
188
0
        if (!hsplit || (hsplit && py <= sy / 2))
189
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
190
0
      }
191
0
      if (wp->xoff - sb_w != 0) {
192
0
        if (px == wp->xoff - sb_w - 1 &&
193
0
            (!hsplit || (hsplit && py > sy / 2)))
194
0
          return (SCREEN_REDRAW_BORDER_LEFT);
195
0
        if (px == wp->xoff + sx + sb_w - 1)
196
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
197
0
      }
198
0
    } else { /* sb_pos == PANE_SCROLLBARS_RIGHT or disabled */
199
0
      if (wp->xoff == 0 && px == sx + sb_w) {
200
0
        if (!hsplit || (hsplit && py <= sy / 2))
201
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
202
0
      }
203
0
      if (wp->xoff != 0) {
204
0
        if (px == wp->xoff - 1 &&
205
0
            (!hsplit || (hsplit && py > sy / 2)))
206
0
          return (SCREEN_REDRAW_BORDER_LEFT);
207
0
        if (px == wp->xoff + sx + sb_w)
208
0
          return (SCREEN_REDRAW_BORDER_RIGHT);
209
0
      }
210
0
    }
211
0
  }
212
213
  /* Top/bottom borders. */
214
0
  if (vsplit && pane_status == PANE_STATUS_OFF && sb_w == 0) {
215
0
    if (wp->yoff == 0 && py == sy && px <= sx / 2)
216
0
      return (SCREEN_REDRAW_BORDER_BOTTOM);
217
0
    if (wp->yoff != 0 && py == wp->yoff - 1 && px > sx / 2)
218
0
      return (SCREEN_REDRAW_BORDER_TOP);
219
0
  } else {
220
0
    if (sb_pos == PANE_SCROLLBARS_LEFT) {
221
0
      if ((wp->xoff - sb_w == 0 || px >= wp->xoff - sb_w) &&
222
0
          (px <= ex || (sb_w != 0 && px < ex + sb_w))) {
223
0
        if (wp->yoff != 0 && py == wp->yoff - 1)
224
0
          return (SCREEN_REDRAW_BORDER_TOP);
225
0
        if (py == ey)
226
0
          return (SCREEN_REDRAW_BORDER_BOTTOM);
227
0
      }
228
0
    } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */
229
0
      if ((wp->xoff == 0 || px >= wp->xoff) &&
230
0
          (px <= ex || (sb_w != 0 && px < ex + sb_w))) {
231
0
        if (pane_status != PANE_STATUS_BOTTOM &&
232
0
            wp->yoff != 0 &&
233
0
            py == wp->yoff - 1)
234
0
          return (SCREEN_REDRAW_BORDER_TOP);
235
0
        if (pane_status != PANE_STATUS_TOP && py == ey)
236
0
          return (SCREEN_REDRAW_BORDER_BOTTOM);
237
0
      }
238
0
    }
239
0
  }
240
241
  /* Outside pane. */
242
0
  return (SCREEN_REDRAW_OUTSIDE);
243
0
}
244
245
/* Check a single cell position. */
246
static int
247
screen_redraw_cell_border1(struct screen_redraw_ctx *ctx, int sb_pos, int sb_w,
248
    struct window_pane *wp, int px, int py)
249
0
{
250
0
  if (sb_pos == PANE_SCROLLBARS_LEFT) {
251
0
    if ((px < wp->xoff - 1 - sb_w ||
252
0
        px > wp->xoff + (int)wp->sx) &&
253
0
        (py < wp->yoff - 1 ||
254
0
        py > wp->yoff + (int)wp->sy))
255
0
      return (-1);
256
0
  } else { /* PANE_SCROLLBARS_RIGHT or off. */
257
0
    if ((px < wp->xoff - 1 ||
258
0
        px > wp->xoff + (int)wp->sx + sb_w) &&
259
0
        (py < wp->yoff - 1 ||
260
0
        py > wp->yoff + (int)wp->sy))
261
0
      return (-1);
262
0
  }
263
0
  switch (screen_redraw_pane_border(ctx, wp, px, py)) {
264
0
  case SCREEN_REDRAW_INSIDE:
265
0
    return (0);
266
0
  case SCREEN_REDRAW_OUTSIDE:
267
0
    return (-1);
268
0
  default:
269
0
    return (1);
270
0
  }
271
0
}
272
273
/* Check if a cell is on a border. */
274
static int
275
screen_redraw_cell_border(struct screen_redraw_ctx *ctx, struct window_pane *wp,
276
    int px, int py)
277
0
{
278
0
  struct client   *c = ctx->c;
279
0
  struct window   *w = c->session->curw->window;
280
0
  struct window_pane  *wp2;
281
0
  int      sx = w->sx, sy = w->sy, sb_w, sb_pos, n;
282
283
0
  if (ctx->pane_scrollbars != 0)
284
0
    sb_pos = ctx->pane_scrollbars_pos;
285
0
  else
286
0
    sb_pos = 0;
287
0
  sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
288
289
  /* For floating panes, only check the pane itself. */
290
0
  if (window_pane_is_floating(wp)) {
291
0
    n = screen_redraw_cell_border1(ctx, sb_pos, sb_w, wp, px, py);
292
0
    if (n == -1)
293
0
      return (0);
294
0
    return (n);
295
0
  }
296
297
  /* Outside the window or on the window border? */
298
0
  if (ctx->pane_status == PANE_STATUS_BOTTOM)
299
0
    sy--;
300
0
  if (px > sx || py > sy)
301
0
    return (0);
302
0
  if (px == sx || py == sy)
303
0
    return (1);
304
305
  /*
306
   * If checking a cell from a tiled pane, ignore floating panes because
307
   * two side-by-side or top-bottom panes share a border which is used to
308
   * do split colouring. Essentially, treat all tiled panes as being in a
309
   * single z-index.
310
   */
311
0
  TAILQ_FOREACH(wp2, &w->z_index, zentry) {
312
0
    if (!window_pane_visible(wp2) || window_pane_is_floating(wp2))
313
0
      continue;
314
0
    n = screen_redraw_cell_border1(ctx, sb_pos, sb_w, wp2, px, py);
315
0
    if (n != -1)
316
0
      return (n);
317
0
  }
318
319
0
  return (0);
320
0
}
321
322
/* Work out type of border cell from surrounding cells. */
323
static int
324
screen_redraw_type_of_cell(struct screen_redraw_ctx *ctx,
325
    struct window_pane *wp, int px, int py)
326
0
{
327
0
  struct client *c = ctx->c;
328
0
  struct window *w = c->session->curw->window;
329
0
  int    pane_status = ctx->pane_status, borders = 0;
330
0
  int    sx = w->sx, sy = w->sy;
331
332
  /* Is this outside the window? */
333
0
  if (pane_status == PANE_STATUS_BOTTOM)
334
0
    sy--;
335
0
  if (px > sx || py > sy)
336
0
    return (CELL_OUTSIDE);
337
338
  /*
339
   * Construct a bitmask of whether the cells to the left (bit 8), right,
340
   * top, and bottom (bit 1) of this cell are borders.
341
   *
342
   * bits 8 4 2 1:     2
343
   *       8 + 4
344
   *         1
345
   */
346
0
  if (!window_pane_is_floating(wp)) {
347
0
    if (px == 0 || screen_redraw_cell_border(ctx, wp, px - 1, py))
348
0
      borders |= 8;
349
0
    if (px <= sx && screen_redraw_cell_border(ctx, wp, px + 1, py))
350
0
      borders |= 4;
351
0
    if (pane_status == PANE_STATUS_TOP) {
352
0
      if (py != 0 &&
353
0
          screen_redraw_cell_border(ctx, wp, px, py - 1))
354
0
        borders |= 2;
355
0
      if (screen_redraw_cell_border(ctx, wp, px, py + 1))
356
0
        borders |= 1;
357
0
    } else if (pane_status == PANE_STATUS_BOTTOM) {
358
0
      if (py == 0 ||
359
0
          screen_redraw_cell_border(ctx, wp, px, py - 1))
360
0
        borders |= 2;
361
0
      if (py != sy &&
362
0
          screen_redraw_cell_border(ctx, wp, px, py + 1))
363
0
        borders |= 1;
364
0
    } else {
365
0
      if (py == 0 ||
366
0
          screen_redraw_cell_border(ctx, wp, px, py - 1))
367
0
        borders |= 2;
368
0
      if (screen_redraw_cell_border(ctx, wp, px, py + 1))
369
0
        borders |= 1;
370
0
    }
371
0
  } else {
372
0
    if (screen_redraw_cell_border(ctx, wp, px - 1, py))
373
0
      borders |= 8;
374
0
    if (px <= sx && screen_redraw_cell_border(ctx, wp, px + 1, py))
375
0
      borders |= 4;
376
0
    if (pane_status == PANE_STATUS_TOP) {
377
0
      if (py != 0 &&
378
0
          screen_redraw_cell_border(ctx, wp, px, py - 1))
379
0
        borders |= 2;
380
0
      if (screen_redraw_cell_border(ctx, wp, px, py + 1))
381
0
        borders |= 1;
382
0
    } else if (pane_status == PANE_STATUS_BOTTOM) {
383
0
      if (screen_redraw_cell_border(ctx, wp, px, py - 1))
384
0
        borders |= 2;
385
0
      if (py != sy &&
386
0
          screen_redraw_cell_border(ctx, wp, px, py + 1))
387
0
        borders |= 1;
388
0
    } else {
389
0
      if (screen_redraw_cell_border(ctx, wp, px, py - 1))
390
0
        borders |= 2;
391
0
      if (screen_redraw_cell_border(ctx, wp, px, py + 1))
392
0
        borders |= 1;
393
0
    }
394
0
  }
395
396
  /*
397
   * Figure out what kind of border this cell is. Only one bit set
398
   * doesn't make sense (can't have a border cell with no others
399
   * connected).
400
   */
401
0
  switch (borders) {
402
0
  case 15:  /* 1111, left right top bottom */
403
0
    return (CELL_JOIN);
404
0
  case 14:  /* 1110, left right top */
405
0
    return (CELL_BOTTOMJOIN);
406
0
  case 13:  /* 1101, left right bottom */
407
0
    return (CELL_TOPJOIN);
408
0
  case 12:  /* 1100, left right */
409
0
    return (CELL_LEFTRIGHT);
410
0
  case 11:  /* 1011, left top bottom */
411
0
    return (CELL_RIGHTJOIN);
412
0
  case 10:  /* 1010, left top */
413
0
    return (CELL_BOTTOMRIGHT);
414
0
  case 9:   /* 1001, left bottom */
415
0
    return (CELL_TOPRIGHT);
416
0
  case 7:   /* 0111, right top bottom */
417
0
    return (CELL_LEFTJOIN);
418
0
  case 6:   /* 0110, right top */
419
0
    return (CELL_BOTTOMLEFT);
420
0
  case 5:   /* 0101, right bottom */
421
0
    return (CELL_TOPLEFT);
422
0
  case 3:   /* 0011, top bottom */
423
0
    return (CELL_TOPBOTTOM);
424
0
  }
425
0
  return (CELL_OUTSIDE);
426
0
}
427
428
/* Check if cell inside a pane. */
429
static int
430
screen_redraw_check_cell(struct screen_redraw_ctx *ctx, int px, int py,
431
    struct window_pane **wpp)
432
0
{
433
0
  struct client   *c = ctx->c;
434
0
  struct window   *w = c->session->curw->window;
435
0
  struct window_pane  *wp, *start;
436
0
  int      sx = w->sx, sy = w->sy;
437
0
  int      pane_status = ctx->pane_status;
438
0
  int      border, pane_scrollbars = ctx->pane_scrollbars;
439
0
  int      pane_status_line, tiled_only = 0, left, right;
440
0
  int      sb_pos = ctx->pane_scrollbars_pos, sb_w;
441
442
0
  *wpp = NULL;
443
444
  /* Outside the pane. */
445
0
  if (px > sx || py > sy)
446
0
    return (CELL_OUTSIDE);
447
448
  /* Find pane highest in z-index at this point. */
449
0
  TAILQ_FOREACH(wp, &w->z_index, zentry) {
450
0
    if (window_pane_is_floating(wp) && (px >= sx || py >= sy)) {
451
      /* Clip floating panes to window. */
452
0
      continue;
453
0
    }
454
0
    sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
455
0
    if (sb_pos == PANE_SCROLLBARS_LEFT) {
456
0
      if ((px >= wp->xoff - 1 - sb_w &&
457
0
          px <= wp->xoff + (int)wp->sx) &&
458
0
          (py >= wp->yoff - 1 &&
459
0
          py <= wp->yoff + (int)wp->sy))
460
0
        break;
461
0
    } else { /* PANE_SCROLLBARS_RIGHT or none. */
462
0
      if ((px >= wp->xoff - 1 &&
463
0
          px <= wp->xoff + (int)wp->sx + sb_w) &&
464
0
          (py >= wp->yoff - 1 &&
465
0
          py <= wp->yoff + (int)wp->sy))
466
0
        break;
467
0
    }
468
0
  }
469
0
  if (wp != NULL)
470
0
    start = wp;
471
0
  else
472
0
    start = wp = server_client_get_pane(c);
473
0
  if (wp == NULL)
474
0
    return (CELL_OUTSIDE);
475
476
  /* On the window border. */
477
0
  if (px == sx || py == sy)
478
0
    return (screen_redraw_type_of_cell(ctx, wp, px, py));
479
480
  /*
481
   * If this is a tiled pane, then only check other tiled panes. This is
482
   * necessary if there are two side-by-side or top-bottom panes with a
483
   * shared border and half the shared border is the active border.
484
   */
485
0
  if (!window_pane_is_floating(wp))
486
0
    tiled_only = 1;
487
0
  do { /* loop until back to wp == start */
488
0
    if (!window_pane_visible(wp))
489
0
      goto next;
490
0
    if (tiled_only && window_pane_is_floating(wp))
491
0
      goto next;
492
0
    *wpp = wp;
493
494
0
    sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
495
0
    if (sb_pos == PANE_SCROLLBARS_LEFT) {
496
0
      if ((px < wp->xoff - 1 - sb_w ||
497
0
           px > wp->xoff + (int)wp->sx) &&
498
0
          (py < wp->yoff - 1 ||
499
0
          py > wp->yoff + (int)wp->sy))
500
0
        goto next;
501
0
    } else { /* PANE_SCROLLBARS_RIGHT or none */
502
0
      if ((px < wp->xoff - 1 ||
503
0
           px > wp->xoff + (int)wp->sx + sb_w) &&
504
0
          (py < wp->yoff - 1 ||
505
0
           py > wp->yoff + (int)wp->sy))
506
0
        goto next;
507
0
    }
508
509
    /*
510
     * Pane border status inside top/bottom border is CELL_INSIDE
511
     * so it doesn't get overdrawn by a border line.
512
     */
513
0
    if (pane_status != PANE_STATUS_OFF) {
514
0
      if (pane_status == PANE_STATUS_TOP)
515
0
        pane_status_line = wp->yoff - 1;
516
0
      else
517
0
        pane_status_line = wp->yoff + (int)wp->sy;
518
0
      left = wp->xoff + 2;
519
0
      right = wp->xoff + 2 + (int)wp->status_size - 1;
520
0
      if (py == pane_status_line + (int)ctx->oy &&
521
0
          px >= left &&
522
0
          px <= right)
523
0
        return (CELL_INSIDE);
524
0
    }
525
526
    /* Check if CELL_SCROLLBAR. */
527
0
    if (window_pane_show_scrollbar(wp, pane_scrollbars)) {
528
      /*
529
       * Check if py could lie within a scrollbar. If the
530
       * pane is at the top then py == 0 to sy; if the pane
531
       * is not at the top, then yoff to yoff + sy.
532
       */
533
0
      sb_w = wp->scrollbar_style.width +
534
0
          wp->scrollbar_style.pad;
535
0
      if ((wp->yoff == 0 && py < (int)wp->sy) ||
536
0
          (py >= wp->yoff &&
537
0
           py < wp->yoff + (int)wp->sy)) {
538
        /* Check if px lies within a scrollbar. */
539
0
        if ((sb_pos == PANE_SCROLLBARS_RIGHT &&
540
0
            (px >= wp->xoff + (int)wp->sx &&
541
0
            px < wp->xoff + (int)wp->sx + sb_w)) ||
542
0
            (sb_pos == PANE_SCROLLBARS_LEFT &&
543
0
            (px >= wp->xoff - sb_w &&
544
0
            px < wp->xoff)))
545
0
          return (CELL_SCROLLBAR);
546
0
      }
547
0
    }
548
549
    /*
550
     * If definitely inside, return. If not on border, skip.
551
     * Otherwise work out the cell.
552
     */
553
0
    border = screen_redraw_pane_border(ctx, wp, px, py);
554
0
    if (border == SCREEN_REDRAW_INSIDE)
555
0
      return (CELL_INSIDE);
556
0
    if (border == SCREEN_REDRAW_OUTSIDE)
557
0
      goto next;
558
0
    return (screen_redraw_type_of_cell(ctx, wp, px, py));
559
560
0
  next:
561
0
    wp = TAILQ_NEXT(wp, zentry);
562
0
    if (wp == NULL)
563
0
      wp = TAILQ_FIRST(&w->z_index);
564
0
  } while (wp != start);
565
566
0
  return (CELL_OUTSIDE);
567
0
}
568
569
/* Check if the border of a particular pane. */
570
static int
571
screen_redraw_check_is(struct screen_redraw_ctx *ctx, int px, int py,
572
    struct window_pane *wp)
573
0
{
574
0
  enum screen_redraw_border_type  border;
575
576
0
  if (wp == NULL)
577
0
    return (0); /* no active pane */
578
0
  border = screen_redraw_pane_border(ctx, wp, px, py);
579
0
  if (border != SCREEN_REDRAW_INSIDE && border != SCREEN_REDRAW_OUTSIDE)
580
0
    return (1);
581
0
  return (0);
582
0
}
583
584
/* Update pane status. */
585
static int
586
screen_redraw_make_pane_status(struct client *c, struct window_pane *wp,
587
    struct screen_redraw_ctx *rctx, enum pane_lines pane_lines)
588
0
{
589
0
  struct window   *w = wp->window;
590
0
  struct grid_cell   gc;
591
0
  const char    *fmt, *border_option;
592
0
  struct format_tree  *ft;
593
0
  struct style_line_entry *sle = &wp->border_status_line;
594
0
  char      *expanded;
595
0
  int      pane_status = rctx->pane_status, sb_w = 0;
596
0
  int      pane_scrollbars = rctx->pane_scrollbars;
597
0
  int      max_width;
598
0
  u_int      width, i, cell_type, px, py;
599
0
  struct screen_write_ctx  ctx;
600
0
  struct screen    old;
601
602
0
  if (window_pane_show_scrollbar(wp, pane_scrollbars))
603
0
    sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
604
605
0
  ft = format_create(c, NULL, FORMAT_PANE|wp->id, FORMAT_STATUS);
606
0
  format_defaults(ft, c, c->session, c->session->curw, wp);
607
608
0
  if (wp == server_client_get_pane(c))
609
0
    border_option = "pane-active-border-style";
610
0
  else
611
0
    border_option = "pane-border-style";
612
0
  style_apply(&gc, wp->options, border_option, ft);
613
0
  fmt = options_get_string(wp->options, "pane-border-format");
614
615
0
  expanded = format_expand_time(ft, fmt);
616
0
  if (wp->sx < 4)
617
0
    width = 0;
618
0
  else
619
0
    width = wp->sx + sb_w - 2;
620
0
  max_width = (int)w->sx - (wp->xoff + 2) - sb_w;
621
0
  if (max_width < 0)
622
0
    max_width = 0;
623
0
  if (width > (u_int)max_width)
624
0
    width = (u_int)max_width;
625
0
  wp->status_size = width;
626
627
0
  memcpy(&old, &wp->status_screen, sizeof old);
628
0
  screen_init(&wp->status_screen, width, 1, 0);
629
0
  wp->status_screen.mode = 0;
630
631
0
  screen_write_start(&ctx, &wp->status_screen);
632
633
0
  for (i = 0; i < width; i++) {
634
0
    px = wp->xoff + 2 + i;
635
0
    if (pane_status == PANE_STATUS_TOP)
636
0
      py = wp->yoff - 1;
637
0
    else
638
0
      py = wp->yoff + wp->sy;
639
0
    cell_type = screen_redraw_type_of_cell(rctx, wp, px, py);
640
0
    screen_redraw_border_set(w, wp, pane_lines, cell_type, &gc);
641
0
    screen_write_cell(&ctx, &gc);
642
0
  }
643
0
  gc.attr &= ~GRID_ATTR_CHARSET;
644
645
0
  screen_write_cursormove(&ctx, 0, 0, 0);
646
0
  style_ranges_free(&sle->ranges);
647
0
  format_draw(&ctx, &gc, width, expanded, &sle->ranges, 0);
648
649
0
  screen_write_stop(&ctx);
650
0
  format_free(ft);
651
652
0
  free(sle->expanded);
653
0
  sle->expanded = expanded;
654
655
0
  if (grid_compare(wp->status_screen.grid, old.grid) == 0) {
656
0
    screen_free(&old);
657
0
    return (0);
658
0
  }
659
0
  screen_free(&old);
660
0
  return (1);
661
0
}
662
663
/* Draw pane status. */
664
static void
665
screen_redraw_draw_pane_status(struct screen_redraw_ctx *ctx)
666
0
{
667
0
  struct client   *c = ctx->c;
668
0
  struct window   *w = c->session->curw->window;
669
0
  struct tty    *tty = &c->tty;
670
0
  struct window_pane  *wp;
671
0
  struct screen   *s;
672
0
  struct visible_ranges *r;
673
0
  struct visible_range  *ri;
674
0
  u_int      i, l, x, width, size;
675
0
  int      xoff, yoff;
676
677
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
678
679
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
680
0
    if (!window_pane_visible(wp))
681
0
      continue;
682
0
    s = &wp->status_screen;
683
684
0
    size = wp->status_size;
685
0
    if (ctx->pane_status == PANE_STATUS_TOP)
686
0
      yoff = wp->yoff - 1;
687
0
    else
688
0
      yoff = wp->yoff + wp->sy;
689
0
    xoff = wp->xoff + 2;
690
691
0
    if (xoff + (int)size <= ctx->ox ||
692
0
        xoff >= ctx->ox + (int)ctx->sx ||
693
0
        yoff < ctx->oy ||
694
0
        yoff >= ctx->oy + (int)ctx->sy)
695
0
      continue;
696
697
0
    if (xoff >= ctx->ox && xoff + size <= ctx->ox + ctx->sx) {
698
      /* All visible. */
699
0
      l = 0;
700
0
      x = xoff - ctx->ox;
701
0
      width = size;
702
0
    } else if (xoff < ctx->ox && xoff + size > ctx->ox + ctx->sx) {
703
      /* Both left and right not visible. */
704
0
      l = ctx->ox;
705
0
      x = 0;
706
0
      width = ctx->sx;
707
0
    } else if (xoff < ctx->ox) {
708
      /* Left not visible. */
709
0
      l = ctx->ox - xoff;
710
0
      x = 0;
711
0
      width = size - l;
712
0
    } else {
713
      /* Right not visible. */
714
0
      l = 0;
715
0
      x = xoff - ctx->ox;
716
0
      width = size - x;
717
0
    }
718
719
0
    r = tty_check_overlay_range(tty, x, yoff, width);
720
0
    r = screen_redraw_get_visible_ranges(wp, x, yoff, width, r);
721
0
    if (ctx->statustop)
722
0
      yoff += ctx->statuslines;
723
0
    for (i = 0; i < r->used; i++) {
724
0
      ri = &r->ranges[i];
725
0
      if (ri->nx == 0)
726
0
        continue;
727
0
      tty_draw_line(tty, s, l + (ri->px - x), 0, ri->nx,
728
0
          ri->px, yoff - ctx->oy,
729
0
          &grid_default_cell, NULL);
730
0
    }
731
0
  }
732
0
  tty_cursor(tty, 0, 0);
733
0
}
734
735
/* Update status line and change flags if unchanged. */
736
static uint64_t
737
screen_redraw_update(struct screen_redraw_ctx *ctx, uint64_t flags)
738
0
{
739
0
  struct client     *c = ctx->c;
740
0
  struct window     *w = c->session->curw->window;
741
0
  struct window_pane    *wp;
742
0
  int        redraw;
743
0
  enum pane_lines      lines;
744
745
0
  if (c->message_string != NULL)
746
0
    redraw = status_message_redraw(c);
747
0
  else if (c->prompt_string != NULL)
748
0
    redraw = status_prompt_redraw(c);
749
0
  else
750
0
    redraw = status_redraw(c);
751
0
  if (!redraw && (~flags & CLIENT_REDRAWSTATUSALWAYS))
752
0
    flags &= ~CLIENT_REDRAWSTATUS;
753
754
0
  if (c->overlay_draw != NULL)
755
0
    flags |= CLIENT_REDRAWOVERLAY;
756
757
0
  if (ctx->pane_status != PANE_STATUS_OFF) {
758
0
    lines = ctx->pane_lines;
759
0
    redraw = 0;
760
0
    TAILQ_FOREACH(wp, &w->panes, entry) {
761
0
      if (screen_redraw_make_pane_status(c, wp, ctx, lines))
762
0
        redraw = 1;
763
0
    }
764
0
    if (redraw)
765
0
      flags |= CLIENT_REDRAWBORDERS;
766
0
  }
767
768
0
  return (flags);
769
0
}
770
771
/* Set up redraw context. */
772
static void
773
screen_redraw_set_context(struct client *c, struct screen_redraw_ctx *ctx)
774
0
{
775
0
  struct session  *s = c->session;
776
0
  struct options  *oo = s->options;
777
0
  struct window *w = s->curw->window;
778
0
  struct options  *wo = w->options;
779
0
  u_int    lines;
780
781
0
  memset(ctx, 0, sizeof *ctx);
782
0
  ctx->c = c;
783
784
0
  lines = status_line_size(c);
785
0
  if (c->message_string != NULL || c->prompt_string != NULL)
786
0
    lines = (lines == 0) ? 1 : lines;
787
0
  if (lines != 0 && options_get_number(oo, "status-position") == 0)
788
0
    ctx->statustop = 1;
789
0
  ctx->statuslines = lines;
790
791
0
  ctx->pane_status = options_get_number(wo, "pane-border-status");
792
0
  ctx->pane_lines = options_get_number(wo, "pane-border-lines");
793
794
0
  ctx->pane_scrollbars = options_get_number(wo, "pane-scrollbars");
795
0
  ctx->pane_scrollbars_pos = options_get_number(wo,
796
0
      "pane-scrollbars-position");
797
798
0
  tty_window_offset(&c->tty, &ctx->ox, &ctx->oy, &ctx->sx, &ctx->sy);
799
800
0
  log_debug("%s: %s @%u ox=%u oy=%u sx=%u sy=%u %u/%d", __func__, c->name,
801
0
      w->id, ctx->ox, ctx->oy, ctx->sx, ctx->sy, ctx->statuslines,
802
0
      ctx->statustop);
803
0
}
804
805
/* Redraw entire screen. */
806
void
807
screen_redraw_screen(struct client *c)
808
0
{
809
0
  struct screen_redraw_ctx  ctx;
810
0
  uint64_t      flags;
811
812
0
  if (c->flags & CLIENT_SUSPENDED)
813
0
    return;
814
815
0
  screen_redraw_set_context(c, &ctx);
816
817
0
  flags = screen_redraw_update(&ctx, c->flags);
818
0
  if ((flags & CLIENT_ALLREDRAWFLAGS) == 0)
819
0
    return;
820
821
0
  tty_sync_start(&c->tty);
822
0
  tty_update_mode(&c->tty, c->tty.mode, NULL);
823
824
0
  if (flags & (CLIENT_REDRAWWINDOW|CLIENT_REDRAWBORDERS)) {
825
0
    log_debug("%s: redrawing borders", c->name);
826
0
    screen_redraw_draw_borders(&ctx);
827
0
    if (ctx.pane_status != PANE_STATUS_OFF)
828
0
      screen_redraw_draw_pane_status(&ctx);
829
0
    screen_redraw_draw_pane_scrollbars(&ctx);
830
0
  }
831
0
  if (flags & CLIENT_REDRAWWINDOW) {
832
0
    log_debug("%s: redrawing panes", c->name);
833
0
    screen_redraw_draw_panes(&ctx);
834
0
    screen_redraw_draw_pane_scrollbars(&ctx);
835
0
  }
836
0
  if (ctx.statuslines != 0 &&
837
0
      (flags & (CLIENT_REDRAWSTATUS|CLIENT_REDRAWSTATUSALWAYS))) {
838
0
    log_debug("%s: redrawing status", c->name);
839
0
    screen_redraw_draw_status(&ctx);
840
0
  }
841
0
  if (c->overlay_draw != NULL && (flags & CLIENT_REDRAWOVERLAY)) {
842
0
    log_debug("%s: redrawing overlay", c->name);
843
0
    c->overlay_draw(c, c->overlay_data, &ctx);
844
0
  }
845
846
0
  tty_reset(&c->tty);
847
0
}
848
849
/* Redraw a single pane and its scrollbar. */
850
void
851
screen_redraw_pane(struct client *c, struct window_pane *wp,
852
    int redraw_scrollbar_only)
853
0
{
854
0
  struct screen_redraw_ctx  ctx;
855
856
0
  if (!window_pane_visible(wp))
857
0
    return;
858
859
0
  screen_redraw_set_context(c, &ctx);
860
0
  tty_sync_start(&c->tty);
861
0
  tty_update_mode(&c->tty, c->tty.mode, NULL);
862
863
0
  if (!redraw_scrollbar_only)
864
0
    screen_redraw_draw_pane(&ctx, wp);
865
866
0
  if (window_pane_show_scrollbar(wp, ctx.pane_scrollbars))
867
0
    screen_redraw_draw_pane_scrollbar(&ctx, wp);
868
869
0
  tty_reset(&c->tty);
870
0
}
871
872
/* Get border cell style. */
873
static void
874
screen_redraw_draw_borders_style(struct screen_redraw_ctx *ctx, u_int x,
875
    u_int y, struct window_pane *wp, struct grid_cell *ngc)
876
0
{
877
0
  struct client   *c = ctx->c;
878
0
  struct session    *s = c->session;
879
0
  struct window_pane  *active = server_client_get_pane(c);
880
0
  struct grid_cell  *gc;
881
0
  const char    *border_option;
882
0
  struct format_tree  *ft;
883
0
  int     *flag;
884
885
0
  if ((window_pane_is_floating(wp) && wp == active) ||
886
0
      (!window_pane_is_floating(wp) &&
887
0
      screen_redraw_check_is(ctx, x, y, active))) {
888
0
    flag = &wp->active_border_gc_set;
889
0
    gc = &wp->active_border_gc;
890
0
    border_option = "pane-active-border-style";
891
0
  } else {
892
0
    flag = &wp->border_gc_set;
893
0
    gc = &wp->border_gc;
894
0
    border_option = "pane-border-style";
895
0
  }
896
897
0
  if (!*flag) {
898
0
    ft = format_create_defaults(NULL, c, s, s->curw, wp);
899
0
    style_apply(gc, wp->options, border_option, ft);
900
0
    format_free(ft);
901
0
    *flag = 1;
902
0
  }
903
0
  memcpy(ngc, gc, sizeof *ngc);
904
0
}
905
906
/* Draw arrow indicator if enabled. */
907
static void
908
screen_redraw_draw_border_arrows(struct screen_redraw_ctx *ctx, int i,
909
    int j, u_int cell_type, struct window_pane *wp,
910
    struct window_pane *active, struct grid_cell *gc)
911
0
{
912
0
  struct client   *c = ctx->c;
913
0
  struct session    *s = c->session;
914
0
  struct window   *w = s->curw->window;
915
0
  struct options    *oo = w->options;
916
0
  u_int      x = ctx->ox + i, y = ctx->oy + j;
917
0
  int      value, arrows = 0, border;
918
0
  enum layout_type   type;
919
920
0
  if (wp == NULL)
921
0
    return;
922
0
  if (i != wp->xoff + 1 && j != wp->yoff + 1)
923
0
    return;
924
925
0
  if (wp != active) {
926
0
    if (window_pane_is_floating(active))
927
0
      return;
928
0
    if (window_pane_is_floating(wp))
929
0
      return;
930
0
  }
931
932
0
  value = options_get_number(oo, "pane-border-indicators");
933
0
  if (value != PANE_BORDER_ARROWS && value != PANE_BORDER_BOTH)
934
0
    return;
935
936
0
  border = screen_redraw_pane_border(ctx, active, x, y);
937
0
  if (border == SCREEN_REDRAW_INSIDE)
938
0
    return;
939
940
0
  if (i == wp->xoff + 1) {
941
0
    if (border == SCREEN_REDRAW_OUTSIDE) {
942
0
      if (screen_redraw_two_panes(wp->window, &type)) {
943
0
        if (active == TAILQ_FIRST(&w->panes))
944
0
          border = SCREEN_REDRAW_BORDER_BOTTOM;
945
0
        else
946
0
          border = SCREEN_REDRAW_BORDER_TOP;
947
0
        arrows = 1;
948
0
      }
949
0
    } else {
950
0
      if (cell_type == CELL_LEFTRIGHT)
951
0
        arrows = 1;
952
0
      else if (cell_type == CELL_TOPJOIN &&
953
0
          border == SCREEN_REDRAW_BORDER_BOTTOM)
954
0
        arrows = 1;
955
0
      else if (cell_type == CELL_BOTTOMJOIN &&
956
0
          border == SCREEN_REDRAW_BORDER_TOP)
957
0
        arrows = 1;
958
0
    }
959
0
  }
960
0
  if (j == wp->yoff + 1) {
961
0
    if (border == SCREEN_REDRAW_OUTSIDE) {
962
0
      if (screen_redraw_two_panes(wp->window, &type)) {
963
0
        if (active == TAILQ_FIRST(&w->panes))
964
0
          border = SCREEN_REDRAW_BORDER_RIGHT;
965
0
        else
966
0
          border = SCREEN_REDRAW_BORDER_LEFT;
967
0
        arrows = 1;
968
0
      }
969
0
    } else {
970
0
      if (cell_type == CELL_TOPBOTTOM)
971
0
        arrows = 1;
972
0
      else if (cell_type == CELL_LEFTJOIN &&
973
0
          border == SCREEN_REDRAW_BORDER_RIGHT)
974
0
        arrows = 1;
975
0
      else if (cell_type == CELL_RIGHTJOIN &&
976
0
          border == SCREEN_REDRAW_BORDER_LEFT)
977
0
        arrows = 1;
978
0
    }
979
0
  }
980
0
  if (arrows) {
981
0
    gc->attr |= GRID_ATTR_CHARSET;
982
0
    utf8_set(&gc->data, BORDER_MARKERS[border]);
983
0
  }
984
0
}
985
986
/* Draw a border cell. */
987
static void
988
screen_redraw_draw_borders_cell(struct screen_redraw_ctx *ctx, u_int i, u_int j)
989
0
{
990
0
  struct client   *c = ctx->c;
991
0
  struct session    *s = c->session;
992
0
  struct window   *w = s->curw->window;
993
0
  struct options    *oo = w->options;
994
0
  struct tty    *tty = &c->tty;
995
0
  struct format_tree  *ft;
996
0
  struct window_pane  *wp, *active = server_client_get_pane(c);
997
0
  struct grid_cell   gc;
998
0
  u_int      cell_type;
999
0
  u_int      x = ctx->ox + i, y = ctx->oy + j;
1000
0
  int      isolates;
1001
0
  struct visible_ranges *r;
1002
1003
0
  if (c->overlay_check != NULL) {
1004
0
    r = c->overlay_check(c, c->overlay_data, x, y, 1);
1005
0
    if (server_client_ranges_is_empty(r))
1006
0
      return;
1007
0
  }
1008
1009
0
  cell_type = screen_redraw_check_cell(ctx, x, y, &wp);
1010
0
  if (cell_type == CELL_INSIDE || cell_type == CELL_SCROLLBAR)
1011
0
    return;
1012
1013
0
  if (wp == NULL || cell_type == CELL_OUTSIDE) {
1014
0
    if (!ctx->no_pane_gc_set) {
1015
0
      ft = format_create_defaults(NULL, c, s, s->curw, NULL);
1016
0
      memcpy(&ctx->no_pane_gc, &grid_default_cell, sizeof gc);
1017
0
      style_add(&ctx->no_pane_gc, oo, "pane-border-style",
1018
0
          ft);
1019
0
      format_free(ft);
1020
0
      ctx->no_pane_gc_set = 1;
1021
0
    }
1022
0
    memcpy(&gc, &ctx->no_pane_gc, sizeof gc);
1023
0
  } else {
1024
0
    screen_redraw_draw_borders_style(ctx, x, y, wp, &gc);
1025
0
    if (server_is_marked(s, s->curw, marked_pane.wp) &&
1026
0
        screen_redraw_check_is(ctx, x, y, marked_pane.wp))
1027
0
      gc.attr ^= GRID_ATTR_REVERSE;
1028
0
  }
1029
0
  screen_redraw_border_set(w, wp, ctx->pane_lines, cell_type, &gc);
1030
1031
0
  if (cell_type == CELL_TOPBOTTOM &&
1032
0
      (c->flags & CLIENT_UTF8) &&
1033
0
      tty_term_has(tty->term, TTYC_BIDI))
1034
0
    isolates = 1;
1035
0
  else
1036
0
    isolates = 0;
1037
1038
0
  if (ctx->statustop)
1039
0
    tty_cursor(tty, i, ctx->statuslines + j);
1040
0
  else
1041
0
    tty_cursor(tty, i, j);
1042
0
  if (isolates)
1043
0
    tty_puts(tty, END_ISOLATE);
1044
1045
0
  screen_redraw_draw_border_arrows(ctx, i, j, cell_type, wp, active, &gc);
1046
1047
0
  tty_cell(tty, &gc, &grid_default_cell, NULL, NULL);
1048
0
  if (isolates)
1049
0
    tty_puts(tty, START_ISOLATE);
1050
0
}
1051
1052
/* Draw the borders. */
1053
static void
1054
screen_redraw_draw_borders(struct screen_redraw_ctx *ctx)
1055
0
{
1056
0
  struct client   *c = ctx->c;
1057
0
  struct session    *s = c->session;
1058
0
  struct window   *w = s->curw->window;
1059
0
  struct window_pane  *wp;
1060
0
  u_int      i, j;
1061
1062
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
1063
1064
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
1065
0
    wp->border_gc_set = 0;
1066
0
    wp->active_border_gc_set = 0;
1067
0
  }
1068
1069
0
  for (j = 0; j < c->tty.sy - ctx->statuslines; j++) {
1070
0
    for (i = 0; i < c->tty.sx; i++)
1071
0
      screen_redraw_draw_borders_cell(ctx, i, j);
1072
0
  }
1073
0
}
1074
1075
/* Draw the panes. */
1076
static void
1077
screen_redraw_draw_panes(struct screen_redraw_ctx *ctx)
1078
0
{
1079
0
  struct client   *c = ctx->c;
1080
0
  struct window   *w = c->session->curw->window;
1081
0
  struct window_pane  *wp;
1082
1083
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
1084
1085
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
1086
0
    if (window_pane_visible(wp))
1087
0
      screen_redraw_draw_pane(ctx, wp);
1088
0
  }
1089
0
}
1090
1091
/* Draw the status line. */
1092
static void
1093
screen_redraw_draw_status(struct screen_redraw_ctx *ctx)
1094
0
{
1095
0
  struct client *c = ctx->c;
1096
0
  struct window *w = c->session->curw->window;
1097
0
  struct tty  *tty = &c->tty;
1098
0
  struct screen *s = c->status.active;
1099
0
  u_int    i, y;
1100
1101
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
1102
1103
0
  if (ctx->statustop)
1104
0
    y = 0;
1105
0
  else
1106
0
    y = c->tty.sy - ctx->statuslines;
1107
0
  for (i = 0; i < ctx->statuslines; i++) {
1108
0
    tty_draw_line(tty, s, 0, i, UINT_MAX, 0, y + i,
1109
0
        &grid_default_cell, NULL);
1110
0
  }
1111
0
}
1112
1113
/*
1114
 * Check if a single character is within a visible range (not obscured by a
1115
 * floating pane).
1116
 */
1117
int
1118
screen_redraw_is_visible(struct visible_ranges *r, u_int px)
1119
0
{
1120
0
  u_int      i;
1121
0
  struct visible_range  *ri;
1122
1123
0
  if (r == NULL)
1124
0
    return (1);
1125
0
  for (i = 0; i < r->used; i++) {
1126
0
    ri = &r->ranges[i];
1127
0
    if (ri->nx != 0 && px >= ri->px && px <= ri->px + ri->nx)
1128
0
      return (1);
1129
0
  }
1130
0
  return (0);
1131
0
}
1132
1133
/*
1134
 * Construct ranges array for the line at starting at px,py of width cells of
1135
 * base_wp that are unobsructed. All ranges are in window coordinates.
1136
 */
1137
struct visible_ranges *
1138
screen_redraw_get_visible_ranges(struct window_pane *base_wp, int px,
1139
    int py, u_int width, struct visible_ranges *r)
1140
1.89M
{
1141
1.89M
  struct window_pane    *wp;
1142
1.89M
  struct window     *w;
1143
1.89M
  struct visible_range    *ri;
1144
1.89M
  static struct visible_ranges   sr = { NULL, 0, 0 };
1145
1.89M
  int        found_self, sb, sb_w, sb_pos;
1146
1.89M
  u_int        lb, rb, tb, bb;
1147
1.89M
  u_int        i, s;
1148
1149
1.89M
  if (px + width <= 0 || py < 0)
1150
0
    goto empty;
1151
1.89M
  if (px < 0) {
1152
0
    px = 0;
1153
0
    width += px;
1154
0
  }
1155
1156
1.89M
  if (base_wp == NULL) {
1157
0
    if (r != NULL)
1158
0
      return (r);
1159
0
    if (sr.ranges == NULL)
1160
0
      sr.ranges = xcalloc(1, sizeof *sr.ranges);
1161
0
    sr.ranges[0].px = px;
1162
0
    sr.ranges[0].nx = width;
1163
0
    sr.size = 1;
1164
0
    sr.used = 1;
1165
0
    return (&sr);
1166
0
  }
1167
1168
1.89M
  w = base_wp->window;
1169
1.89M
  if ((u_int)py >= w->sy)
1170
0
    goto empty;
1171
1.89M
  if (px + width > w->sx)
1172
0
    width = w->sx - px;
1173
1174
1.89M
  if (r == NULL) {
1175
    /* Start with the entire width of the range. */
1176
1.89M
    server_client_ensure_ranges(&base_wp->r, 1);
1177
1.89M
    r = &base_wp->r;
1178
1.89M
    r->ranges[0].px = px;
1179
1.89M
    r->ranges[0].nx = width;
1180
1.89M
    r->used = 1;
1181
1.89M
  }
1182
1183
1.89M
  sb = options_get_number(w->options, "pane-scrollbars");
1184
1.89M
  sb_pos = options_get_number(w->options, "pane-scrollbars-position");
1185
1186
1.89M
  found_self = 0;
1187
1.89M
  TAILQ_FOREACH_REVERSE(wp, &w->z_index, window_panes_zindex, zentry) {
1188
1.89M
    if (wp == base_wp) {
1189
1.89M
      found_self = 1;
1190
1.89M
      continue;
1191
1.89M
    }
1192
1193
0
    tb = wp->yoff > 0 ? wp->yoff - 1 : 0;
1194
0
    bb = wp->yoff + wp->sy;
1195
0
    if (!found_self ||
1196
0
        !window_pane_visible(wp) ||
1197
0
        (u_int)py < tb ||
1198
0
        (u_int)py > bb)
1199
0
      continue;
1200
0
    if (!window_pane_is_floating(wp) && (u_int)py == bb)
1201
0
      continue;
1202
1203
0
    sb_w = wp->scrollbar_style.width + wp->scrollbar_style.pad;
1204
0
    if (!window_pane_show_scrollbar(wp, sb))
1205
0
      sb_w = sb_pos = 0;
1206
1207
0
    for (i = 0; i < r->used; i++) {
1208
0
      ri = &r->ranges[i];
1209
0
      if (sb_pos == PANE_SCROLLBARS_LEFT) {
1210
0
        if (wp->xoff > sb_w)
1211
0
          lb = wp->xoff - 1 - sb_w;
1212
0
        else
1213
0
          lb = 0;
1214
0
      } else { /* PANE_SCROLLBARS_RIGHT or none. */
1215
0
        if (wp->xoff > 0)
1216
0
          lb = wp->xoff - 1;
1217
0
        else
1218
0
          lb = 0;
1219
0
      }
1220
0
      if (sb_pos == PANE_SCROLLBARS_LEFT)
1221
0
        rb = wp->xoff + wp->sx;
1222
0
      else /* PANE_SCROLLBARS_RIGHT or none. */
1223
0
        rb = wp->xoff + wp->sx + sb_w;
1224
0
      if (rb > w->sx)
1225
0
        rb = w->sx - 1;
1226
1227
0
      if (lb > ri->px &&
1228
0
          lb < ri->px + ri->nx &&
1229
0
          rb >= ri->px + ri->nx)
1230
0
      {
1231
        /*
1232
         * If the left edge of floating pane falls
1233
         * inside this range and right edge covers up
1234
         * to right of range, then shrink left edge of
1235
         * range.
1236
         */
1237
0
        ri->nx = lb - ri->px;
1238
0
      }
1239
0
      else if (rb >= ri->px &&
1240
0
          rb < ri->px + ri->nx &&
1241
0
          lb <= ri->px) {
1242
        /*
1243
         * Else if the right edge of floating pane falls
1244
         * inside of this range and left edge covers
1245
         * the left of range, then move px forward to
1246
         * right edge of pane.
1247
         */
1248
0
        ri->nx = ri->nx - (rb + 1 - ri->px);
1249
0
        ri->px = ri->px + (rb + 1 - ri->px);
1250
0
      }
1251
0
      else if (lb > ri->px && rb < ri->px + ri->nx) {
1252
        /*
1253
         * Else if pane fully inside range then split
1254
         * into 2 ranges.
1255
         */
1256
0
        server_client_ensure_ranges(r, r->used + 1);
1257
0
        for (s = r->used; s > i; s--) {
1258
0
          memcpy(&r->ranges[s], &r->ranges[s - 1],
1259
0
              sizeof *r->ranges);
1260
0
        }
1261
0
        ri = &r->ranges[i];
1262
0
        r->ranges[i + 1].px = rb + 1;
1263
0
        r->ranges[i + 1].nx = ri->px + ri->nx - (rb + 1);
1264
        /* ri->px was copied, unchanged. */
1265
0
        ri->nx = lb - ri->px;
1266
0
        r->used++;
1267
0
      } else if (lb <= ri->px && rb >= ri->px + ri->nx) {
1268
        /*
1269
         * If floating pane completely covers this range
1270
         * then delete it (make it 0 length).
1271
         */
1272
0
        ri->nx = 0;
1273
0
      } else {
1274
        /*
1275
         * The range is already obscured, do
1276
         * nothing.
1277
         */
1278
0
      }
1279
0
    }
1280
0
  }
1281
1.89M
  return (r);
1282
1283
0
empty:
1284
0
  if (r == NULL) {
1285
0
    if (sr.ranges == NULL)
1286
0
      sr.ranges = xcalloc(1, sizeof *sr.ranges);
1287
0
    sr.size = 1;
1288
0
    sr.used = 0;
1289
0
    return (&sr);
1290
0
  }
1291
0
  r->used = 0;
1292
0
  return (r);
1293
0
}
1294
1295
/* Draw one pane. */
1296
static void
1297
screen_redraw_draw_pane(struct screen_redraw_ctx *ctx, struct window_pane *wp)
1298
0
{
1299
0
  struct client   *c = ctx->c;
1300
0
  struct window   *w = c->session->curw->window;
1301
0
  struct tty    *tty = &c->tty;
1302
0
  struct screen   *s = wp->screen;
1303
0
  struct colour_palette *palette = &wp->palette;
1304
0
  struct grid_cell   defaults;
1305
0
  u_int      j, k, woy, wx, wy, py, width;
1306
0
  struct visible_ranges *r;
1307
0
  struct visible_range  *ri;
1308
1309
  /*
1310
   * There are 3 coordinate spaces:
1311
   *
1312
   * window: (0 to w->sx-1, 0 to w->sy-1)
1313
   * tty: (0 to tty->sx-1, 0 to tty->sy-1)
1314
   * pane: (0 to wp->sx-1, 0 to wp->sy-1)
1315
   *
1316
   * Transformations:
1317
   * window <-> tty (x-axis):
1318
   *   window_x = tty_x + ctx->ox
1319
   *   tty_x = window_x - ctx->ox
1320
   *
1321
   * window <-> tty (y-axis):
1322
   *   woy = ctx->statustop ? ctx->statuslines : 0
1323
   *   window_y = tty_y + ctx->oy - woy
1324
   *   tty_y = woy + window_y - ctx->oy
1325
   *
1326
   * window <-> pane (x-axis):
1327
   *   window_x = pane_x + wp->xoff
1328
   *   pane_x = window_x - wp->xoff
1329
   *
1330
   * window <-> pane (y-axis):
1331
   *   window_y = pane_y + wp->yoff
1332
   *   pane_y = window_y - wp->yoff
1333
   */
1334
1335
0
  if (wp->base.mode & MODE_SYNC)
1336
0
    screen_write_stop_sync(wp);
1337
1338
0
  log_debug("%s: %s @%u %%%u", __func__, c->name, w->id, wp->id);
1339
1340
  /* Check if pane completely not visible. */
1341
0
  if (wp->xoff + (int)wp->sx <= ctx->ox ||
1342
0
      wp->xoff >= (int)ctx->ox + (int)ctx->sx)
1343
0
    return;
1344
1345
0
  if (ctx->statustop)
1346
0
    woy = ctx->statuslines;
1347
0
  else
1348
0
    woy = 0;
1349
0
  for (j = 0; j < wp->sy; j++) {
1350
0
    if (wp->yoff + (int)j < (int)ctx->oy ||
1351
0
        wp->yoff + (int)j >= (int)ctx->oy + (int)ctx->sy)
1352
0
      continue;
1353
0
    wy = wp->yoff + j; /* y line within window w */
1354
0
    py = woy + wy - ctx->oy; /* y line within tty */
1355
0
    if (py > tty->sy) {
1356
      /* Continue if this line is off of tty. */
1357
0
      continue;
1358
0
    }
1359
0
    if (wp->xoff >= (int)ctx->ox &&
1360
0
        wp->xoff + (int)wp->sx <= (int)ctx->ox + (int)ctx->sx) {
1361
      /* All visible. */
1362
0
      wx = (u_int)(wp->xoff - (int)ctx->ox);
1363
0
      width = wp->sx;
1364
0
    } else if (wp->xoff < (int)ctx->ox &&
1365
0
        wp->xoff + (int)wp->sx > (int)ctx->ox + (int)ctx->sx) {
1366
      /* Both left and right not visible. */
1367
0
      wx = 0;
1368
0
      width = ctx->sx;
1369
0
    } else if (wp->xoff < (int)ctx->ox) {
1370
      /* Left not visible. */
1371
0
      wx = 0;
1372
0
      width = wp->sx - ((u_int)((int)ctx->ox - wp->xoff));
1373
0
    } else {
1374
      /* Right not visible. */
1375
0
      wx = (u_int)(wp->xoff - (int)ctx->ox);
1376
0
      width = ctx->sx - wx;
1377
0
    }
1378
1379
    /* Get visible ranges of line before we draw it. */
1380
0
    r = tty_check_overlay_range(tty, wx, wy, width);
1381
0
    r = screen_redraw_get_visible_ranges(wp, wx, wy, width, r);
1382
0
    tty_default_colours(&defaults, wp);
1383
0
    for (k = 0; k < r->used; k++) {
1384
0
      ri = &r->ranges[k];
1385
0
      if (ri->nx == 0)
1386
0
        continue;
1387
0
      log_debug("%s: %s %%%u range %u (%u,%u) width %u, "
1388
0
          "tty (%u,%u) width %u",
1389
0
          __func__, c->name, wp->id, k,
1390
0
          ri->px + (int)ctx->ox - wp->xoff, j, ri->nx,
1391
0
          ri->px, py, ri->nx);
1392
0
      tty_draw_line(tty, s, ri->px + (int)ctx->ox - wp->xoff,
1393
0
          j, ri->nx, ri->px, py, &defaults, palette);
1394
0
    }
1395
0
  }
1396
1397
#ifdef ENABLE_SIXEL
1398
  tty_draw_images(c, wp, s);
1399
#endif
1400
0
}
1401
1402
/* Draw the panes scrollbars */
1403
static void
1404
screen_redraw_draw_pane_scrollbars(struct screen_redraw_ctx *ctx)
1405
0
{
1406
0
  struct client   *c = ctx->c;
1407
0
  struct window   *w = c->session->curw->window;
1408
0
  struct window_pane  *wp;
1409
1410
0
  log_debug("%s: %s @%u", __func__, c->name, w->id);
1411
1412
0
  TAILQ_FOREACH(wp, &w->panes, entry) {
1413
0
    if (window_pane_show_scrollbar(wp, ctx->pane_scrollbars) &&
1414
0
        window_pane_visible(wp))
1415
0
      screen_redraw_draw_pane_scrollbar(ctx, wp);
1416
0
  }
1417
0
}
1418
1419
/* Calculate position and size of pane scrollbar. */
1420
void
1421
screen_redraw_draw_pane_scrollbar(struct screen_redraw_ctx *ctx,
1422
    struct window_pane *wp)
1423
0
{
1424
0
  struct screen *s = wp->screen;
1425
0
  double     percent_view;
1426
0
  u_int    sb = ctx->pane_scrollbars, total_height, sb_h = wp->sy;
1427
0
  u_int    sb_pos = ctx->pane_scrollbars_pos, slider_h, slider_y;
1428
0
  int    sb_w = wp->scrollbar_style.width;
1429
0
  int    sb_pad = wp->scrollbar_style.pad;
1430
0
  int    cm_y, cm_size, xoff = wp->xoff;
1431
0
  int    sb_x, sb_y = (int)wp->yoff; /* sb top */
1432
1433
0
  if (window_pane_mode(wp) == WINDOW_PANE_NO_MODE) {
1434
0
    if (sb == PANE_SCROLLBARS_MODAL)
1435
0
      return;
1436
    /* Show slider at the bottom of the scrollbar. */
1437
0
    total_height = screen_size_y(s) + screen_hsize(s);
1438
0
    percent_view = (double)sb_h / total_height;
1439
0
    slider_h = (double)sb_h * percent_view;
1440
0
    slider_y = sb_h - slider_h;
1441
0
  } else {
1442
0
    if (TAILQ_FIRST(&wp->modes) == NULL)
1443
0
      return;
1444
0
    if (window_copy_get_current_offset(wp, &cm_y, &cm_size) == 0)
1445
0
      return;
1446
0
    total_height = cm_size + sb_h;
1447
0
    percent_view = (double)sb_h / (cm_size + sb_h);
1448
0
    slider_h = (double)sb_h * percent_view;
1449
0
    slider_y = (sb_h + 1) * ((double)cm_y / (total_height));
1450
0
  }
1451
1452
0
  if (sb_pos == PANE_SCROLLBARS_LEFT)
1453
0
    sb_x = xoff - sb_w - sb_pad;
1454
0
  else
1455
0
    sb_x = xoff + wp->sx;
1456
1457
0
  if (slider_h < 1)
1458
0
    slider_h = 1;
1459
0
  if (slider_y >= sb_h)
1460
0
    slider_y = sb_h - 1;
1461
1462
0
  screen_redraw_draw_scrollbar(ctx, wp, sb_pos, sb_x, sb_y, sb_h,
1463
0
      slider_h, slider_y);
1464
1465
  /* Store current position and height of the slider */
1466
0
  wp->sb_slider_y = slider_y;  /* top of slider y pos in scrollbar */
1467
0
  wp->sb_slider_h = slider_h;  /* height of slider */
1468
0
}
1469
1470
/* Draw pane scrollbar. */
1471
static void
1472
screen_redraw_draw_scrollbar(struct screen_redraw_ctx *ctx,
1473
    struct window_pane *wp, int sb_pos, int sb_x, int sb_y, u_int sb_h,
1474
    u_int slider_h, u_int slider_y)
1475
0
{
1476
0
  struct client   *c = ctx->c;
1477
0
  struct tty    *tty = &c->tty;
1478
0
  struct grid_cell   gc, slgc, *gcp;
1479
0
  struct style    *sb_style = &wp->scrollbar_style;
1480
0
  u_int      i, j, imin = 0, jmin = 0, imax, jmax;
1481
0
  u_int      sb_w = sb_style->width, sb_pad = sb_style->pad;
1482
0
  int      px, py, wx, wy, ox, oy, sx, sy, sb_tty_y;
1483
0
  int      xoff = wp->xoff;
1484
0
  int      yoff = wp->yoff;
1485
0
  int      sb_wy = sb_y; /* window coordinates */
1486
0
  struct visible_ranges *r;
1487
1488
  /*
1489
   * Size and offset of window relative to tty. Status at top offsets
1490
   * window downward.
1491
   */
1492
0
  sx = ctx->sx;
1493
0
  sy = tty->sy - ctx->statuslines;
1494
0
  ox = ctx->ox;
1495
0
  oy = ctx->oy;
1496
0
  if (ctx->statustop) {
1497
0
    sb_y += ctx->statuslines; /* tty coordinates */
1498
0
    sy += ctx->statuslines;
1499
0
  }
1500
1501
0
  gc = sb_style->gc;
1502
0
  memcpy(&slgc, &gc, sizeof slgc);
1503
0
  slgc.fg = gc.bg;
1504
0
  slgc.bg = gc.fg;
1505
1506
0
  if (sb_x + (int)sb_w < 0 || sb_x >= sx || sb_y >= sy) {
1507
    /* Whole scrollbar is off screen. */
1508
0
    return;
1509
0
  }
1510
0
  if (sb_x < 0) {
1511
    /* Part of scrollbar on screen. */
1512
0
    imin = - sb_x;
1513
0
  }
1514
0
  imax = sb_w + sb_pad;
1515
0
  if ((int)imax + sb_x > sx) {
1516
0
    if (sb_x > sx) {
1517
      /* Whole scrollbar off screen. */
1518
0
      return;
1519
0
    }
1520
0
    imax = sx - sb_x;
1521
0
  }
1522
0
  jmax = sb_h;
1523
0
  if ((int)jmax + sb_y > sy) {
1524
0
    if (sb_y >= sy)
1525
0
      return;
1526
0
    jmax = sy - sb_y;
1527
0
  }
1528
1529
  /*
1530
   * sb_y is in tty coordinate (window coord + statuslines when
1531
   * statustop). Subtract the pan offset oy to get the tty row.
1532
   */
1533
0
  sb_tty_y = sb_y - oy; /* scrollbar top in tty coordinates */
1534
0
  if (sb_tty_y > (int)sy) {
1535
    /* Whole scrollbar is off screen. */
1536
0
    return;
1537
0
  }
1538
0
  if (sb_tty_y < 0) {
1539
    /* Scrollbar starts above visible area; skip those rows. */
1540
0
    jmin = -sb_tty_y;
1541
0
  }
1542
0
  if (sb_tty_y + (int)sb_h <= 0) {
1543
    /* Whole scrollbar above visible area. */
1544
0
    return;
1545
0
  }
1546
0
  jmax = sb_h;
1547
0
  if (sb_tty_y + (int)jmax > (int)sy) {
1548
    /* Clip to height of tty. */
1549
0
    jmax = sy - sb_tty_y;
1550
0
  }
1551
1552
0
  for (j = jmin; j < jmax; j++) {
1553
0
    wy = sb_wy + j; /* window y coordinate */
1554
0
    py = sb_tty_y + j; /* tty y coordinate */
1555
0
    r = tty_check_overlay_range(tty, sb_x, wy, imax);
1556
0
    r = screen_redraw_get_visible_ranges(wp, sb_x, wy, imax, r);
1557
0
    for (i = imin; i < imax; i++) {
1558
0
      px = sb_x + ox + i; /* tty x coordinate */
1559
0
      wx = sb_x + i; /* window x coordinate */
1560
0
      if (wx < xoff - (int)sb_w - (int)sb_pad ||
1561
0
          px >= sx || px < 0 ||
1562
0
          wy < yoff - 1 ||
1563
0
          py >= sy || py < 0 ||
1564
0
          !screen_redraw_is_visible(r, wx))
1565
0
        continue;
1566
0
      tty_cursor(tty, px, py);
1567
0
      if ((sb_pos == PANE_SCROLLBARS_LEFT &&
1568
0
          i >= sb_w && i < sb_w + sb_pad) ||
1569
0
          (sb_pos == PANE_SCROLLBARS_RIGHT &&
1570
0
          i < sb_pad)) {
1571
0
        tty_cell(tty, &grid_default_cell,
1572
0
            &grid_default_cell, NULL, NULL);
1573
0
      } else {
1574
0
        if (j >= slider_y && j < slider_y + slider_h)
1575
0
          gcp = &slgc;
1576
0
        else
1577
0
          gcp = &gc;
1578
0
        tty_cell(tty, gcp, &grid_default_cell, NULL,
1579
            NULL);
1580
0
      }
1581
0
    }
1582
0
  }
1583
0
}