Coverage Report

Created: 2026-05-30 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/tty-draw.c
Line
Count
Source
1
/* $OpenBSD$ */
2
3
/*
4
 * Copyright (c) 2026 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 <string.h>
22
23
#include "tmux.h"
24
25
enum tty_draw_line_state {
26
  TTY_DRAW_LINE_FIRST,
27
  TTY_DRAW_LINE_FLUSH,
28
  TTY_DRAW_LINE_NEW1,
29
  TTY_DRAW_LINE_NEW2,
30
  TTY_DRAW_LINE_EMPTY,
31
  TTY_DRAW_LINE_SAME,
32
  TTY_DRAW_LINE_DONE
33
};
34
static const char* tty_draw_line_states[] = {
35
  "FIRST",
36
  "FLUSH",
37
  "NEW1",
38
  "NEW2",
39
  "EMPTY",
40
  "SAME",
41
  "DONE"
42
};
43
44
/* Clear part of the line. */
45
static void
46
tty_draw_line_clear(struct tty *tty, u_int px, u_int py, u_int nx,
47
    const struct grid_cell *defaults, u_int bg, int wrapped)
48
0
{
49
  /* Nothing to clear. */
50
0
  if (nx == 0)
51
0
    return;
52
53
  /* If genuine BCE is available, can try escape sequences. */
54
0
  if (tty->client->overlay_check == NULL &&
55
0
      !wrapped &&
56
0
      nx >= 10 &&
57
0
      !tty_fake_bce(tty, defaults, bg)) {
58
    /* Off the end of the line, use EL if available. */
59
0
    if (px + nx >= tty->sx && tty_term_has(tty->term, TTYC_EL)) {
60
0
      tty_cursor(tty, px, py);
61
0
      tty_putcode(tty, TTYC_EL);
62
0
      return;
63
0
    }
64
65
    /* At the start of the line. Use EL1. */
66
0
    if (px == 0 && tty_term_has(tty->term, TTYC_EL1)) {
67
0
      tty_cursor(tty, px + nx - 1, py);
68
0
      tty_putcode(tty, TTYC_EL1);
69
0
      return;
70
0
    }
71
72
    /* Section of line. Use ECH if possible. */
73
0
    if (tty_term_has(tty->term, TTYC_ECH)) {
74
0
      tty_cursor(tty, px, py);
75
0
      tty_putcode_i(tty, TTYC_ECH, nx);
76
0
      return;
77
0
    }
78
0
  }
79
80
        /* Couldn't use an escape sequence, use spaces. */
81
0
  if (px != 0 || !wrapped)
82
0
    tty_cursor(tty, px, py);
83
0
  if (nx == 1)
84
0
    tty_putc(tty, ' ');
85
0
  else if (nx == 2)
86
0
    tty_putn(tty, "  ", 2, 2);
87
0
  else
88
0
    tty_repeat_space(tty, nx);
89
0
}
90
91
/* Draw a line from screen to tty. */
92
void
93
tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
94
    u_int atx, u_int aty, const struct grid_cell *defaults,
95
    struct colour_palette *palette)
96
0
{
97
0
  struct grid   *gd = s->grid;
98
0
  const struct grid_cell  *gcp;
99
0
  struct grid_cell   gc, ngc, last;
100
0
  struct grid_line  *gl;
101
0
  u_int      i, j, last_i, cx, ex, width;
102
0
  u_int      cellsize, bg;
103
0
  int      flags, empty, wrapped = 0;
104
0
  char       buf[1000];
105
0
  size_t       len;
106
0
  enum tty_draw_line_state current_state, next_state;
107
108
  /*
109
   * py is the line in the screen to draw. px is the start x and nx is
110
   * the width to draw. atx,aty is the line on the terminal to draw it.
111
   */
112
0
  log_debug("%s: px=%u py=%u nx=%u atx=%u aty=%u", __func__, px, py, nx,
113
0
      atx, aty);
114
115
  /* There is no point in drawing more than the end of the terminal. */
116
0
  if (atx >= tty->sx)
117
0
    return;
118
0
  if (atx + nx >= tty->sx)
119
0
    nx = tty->sx - atx;
120
0
  if (nx == 0)
121
0
    return;
122
123
  /*
124
   * Clamp the width to cellsize - note this is not cellused, because
125
   * there may be empty background cells after it (from BCE).
126
   */
127
0
  cellsize = grid_get_line(gd, gd->hsize + py)->cellsize;
128
0
  if (screen_size_x(s) > cellsize)
129
0
    ex = cellsize;
130
0
  else
131
0
    ex = screen_size_x(s);
132
0
  log_debug("%s: drawing %u-%u,%u (end %u) at %u,%u; defaults: fg=%d, "
133
0
      "bg=%d", __func__, px, px + nx, py, ex, atx, aty, defaults->fg,
134
0
      defaults->bg);
135
136
  /* Turn off cursor while redrawing and reset region and margins. */
137
0
  flags = (tty->flags & TTY_NOCURSOR);
138
0
  tty->flags |= TTY_NOCURSOR;
139
0
  tty_update_mode(tty, tty->mode, s);
140
0
  tty_region_off(tty);
141
0
  tty_margin_off(tty);
142
143
  /* Start with the default cell as the last cell. */
144
0
  memcpy(&last, &grid_default_cell, sizeof last);
145
0
  last.bg = defaults->bg;
146
0
  tty_default_attributes(tty, defaults, palette, 8, s->hyperlinks);
147
148
  /*
149
   * If there is padding at the start, we must have truncated a wide
150
   * character. Clear it.
151
   */
152
0
  cx = 0;
153
0
  for (i = px; i < px + nx; i++) {
154
0
    grid_view_get_cell(gd, i, py, &gc);
155
0
    if (~gc.flags & GRID_FLAG_PADDING)
156
0
      break;
157
0
    cx++;
158
0
  }
159
0
  if (cx != 0) {
160
    /* Find the previous cell for the background colour. */
161
0
    for (i = px + 1; i > 0; i--) {
162
0
      grid_view_get_cell(gd, i - 1, py, &gc);
163
0
      if (~gc.flags & GRID_FLAG_PADDING)
164
0
        break;
165
0
    }
166
0
    if (i == 0)
167
0
      bg = defaults->bg;
168
0
    else {
169
0
      bg = gc.bg;
170
0
      if (gc.flags & GRID_FLAG_SELECTED) {
171
0
        memcpy(&ngc, &gc, sizeof ngc);
172
0
        if (screen_select_cell(s, &ngc, &gc))
173
0
          bg = ngc.bg;
174
0
      }
175
0
    }
176
0
    tty_attributes(tty, &last, defaults, palette, s->hyperlinks);
177
0
    log_debug("%s: clearing %u padding cells", __func__, cx);
178
0
    tty_draw_line_clear(tty, atx, aty, cx, defaults, bg, 0);
179
0
    if (cx == ex)
180
0
      goto out;
181
0
    atx += cx;
182
0
    px += cx;
183
0
    nx -= cx;
184
0
  }
185
186
  /* Did the previous line wrap on to this one? */
187
0
  if (py != 0 && atx == 0 && tty->cx >= tty->sx && nx == tty->sx) {
188
0
    gl = grid_get_line(gd, gd->hsize + py - 1);
189
0
    if (gl->flags & GRID_LINE_WRAPPED)
190
0
      wrapped = 1;
191
0
  }
192
193
  /* Loop over each character in the range. */
194
0
  last_i = i = 0;
195
0
  len = 0;
196
0
  width = 0;
197
0
  current_state = TTY_DRAW_LINE_FIRST;
198
0
  for (;;) {
199
    /* Work out the next state. */
200
0
    if (i == nx) {
201
      /*
202
       * If this is the last cell, we are done. But we need to
203
       * go through the loop again to flush anything in
204
       * the buffer.
205
       */
206
0
      empty = 0;
207
0
      next_state = TTY_DRAW_LINE_DONE;
208
0
      gcp = &grid_default_cell;
209
0
    } else {
210
0
      if (i > nx)
211
0
        fatalx("position %u > width %u", i, nx);
212
213
      /* Get the current cell. */
214
0
      grid_view_get_cell(gd, px + i, py, &gc);
215
216
      /* Update for codeset if needed. */
217
0
      gcp = tty_check_codeset(tty, &gc);
218
219
      /* And for selection. */
220
0
      if (gcp->flags & GRID_FLAG_SELECTED) {
221
0
        memcpy(&ngc, gcp, sizeof ngc);
222
0
        if (screen_select_cell(s, &ngc, gcp))
223
0
          gcp = &ngc;
224
0
      }
225
226
      /* Work out the the empty width. */
227
0
      empty = 0;
228
0
      if (px >= ex || i >= ex - px) {
229
        /* Outside the area being drawn. */
230
0
        empty = 1;
231
0
      } else if (gcp->data.width > nx - i) {
232
        /* Wide character that has been truncated. */
233
0
        empty = nx - i;
234
0
      } else if (gcp->flags & GRID_FLAG_PADDING) {
235
        /* Orphan padding cell. */
236
0
        empty = 1;
237
0
      } else if (gcp->bg == last.bg && gcp->attr == 0 &&
238
0
          gcp->link == 0) {
239
        /*
240
         * No attributes - empty if cleared, tab or
241
         * space.
242
         */
243
0
        if (gcp->flags & GRID_FLAG_CLEARED)
244
0
          empty = 1;
245
0
        else if (gcp->flags & GRID_FLAG_TAB)
246
0
          empty = gcp->data.width;
247
0
        else if (gcp->data.size == 1 &&
248
0
            *gcp->data.data == ' ')
249
0
          empty = 1;
250
0
      }
251
252
      /* Work out the next state. */
253
0
      if (empty != 0)
254
0
        next_state = TTY_DRAW_LINE_EMPTY;
255
0
      else if (current_state == TTY_DRAW_LINE_FIRST)
256
0
        next_state = TTY_DRAW_LINE_SAME;
257
0
      else if (grid_cells_look_equal(gcp, &last)) {
258
0
        if (gcp->data.size > (sizeof buf) - len)
259
0
          next_state = TTY_DRAW_LINE_FLUSH;
260
0
        else
261
0
          next_state = TTY_DRAW_LINE_SAME;
262
0
      } else if (current_state == TTY_DRAW_LINE_NEW1)
263
0
        next_state = TTY_DRAW_LINE_NEW2;
264
0
      else
265
0
        next_state = TTY_DRAW_LINE_NEW1;
266
0
    }
267
0
    if (log_get_level() != 0) {
268
0
      log_debug("%s: cell %u empty %u, bg %u; state: "
269
0
          "current %s, next %s", __func__, px + i, empty,
270
0
          gcp->bg, tty_draw_line_states[current_state],
271
0
          tty_draw_line_states[next_state]);
272
0
    }
273
274
    /* If the state has changed, flush any collected data. */
275
0
    if (next_state != current_state) {
276
0
      if (current_state == TTY_DRAW_LINE_EMPTY) {
277
0
        tty_attributes(tty, &last, defaults, palette,
278
0
            s->hyperlinks);
279
0
        tty_draw_line_clear(tty, atx + last_i, aty,
280
0
            i - last_i, defaults, last.bg, wrapped);
281
0
        wrapped = 0;
282
0
      } else if (next_state != TTY_DRAW_LINE_SAME &&
283
0
          len != 0) {
284
0
        tty_attributes(tty, &last, defaults, palette,
285
0
            s->hyperlinks);
286
0
        if (atx + i - width != 0 || !wrapped)
287
0
          tty_cursor(tty, atx + i - width, aty);
288
0
        if (~last.attr & GRID_ATTR_CHARSET)
289
0
          tty_putn(tty, buf, len, width);
290
0
        else {
291
0
          for (j = 0; j < len; j++)
292
0
            tty_putc(tty, buf[j]);
293
0
        }
294
0
        len = 0;
295
0
        width = 0;
296
0
        wrapped = 0;
297
0
      }
298
0
      last_i = i;
299
0
    }
300
301
    /* Append the cell if it is not empty and not padding. */
302
0
    if (next_state != TTY_DRAW_LINE_EMPTY) {
303
0
      memcpy(buf + len, gcp->data.data, gcp->data.size);
304
0
      len += gcp->data.size;
305
0
      width += gcp->data.width;
306
0
    }
307
308
    /* If this is the last cell, we are done. */
309
0
    if (next_state == TTY_DRAW_LINE_DONE)
310
0
      break;
311
312
    /* Otherwise move to the next. */
313
0
    current_state = next_state;
314
0
    memcpy(&last, gcp, sizeof last);
315
0
    if (empty != 0)
316
0
      i += empty;
317
0
    else
318
0
      i += gcp->data.width;
319
0
  }
320
321
0
out:
322
0
  tty->flags = (tty->flags & ~TTY_NOCURSOR)|flags;
323
0
  tty_update_mode(tty, tty->mode, s);
324
0
}
325