Coverage Report

Created: 2026-08-13 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/grid.c
Line
Count
Source
1
/* $OpenBSD: grid.c,v 1.156 2026/08/03 12:58:53 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2008 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
#ifdef __APPLE__
22
#include <assert.h>
23
#endif
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include "tmux.h"
28
29
/*
30
 * Grid data. This is the basic data structure that represents what is shown on
31
 * screen.
32
 *
33
 * A grid is a grid of cells (struct grid_cell). Lines are not allocated until
34
 * cells in that line are written to. The grid is split into history and
35
 * viewable data with the history starting at row (line) 0 and extending to
36
 * (hsize - 1); from hsize to hsize + (sy - 1) is the viewable data. All
37
 * functions in this file work on absolute coordinates, grid-view.c has
38
 * functions which work on the screen data.
39
 */
40
41
/* Default grid cell data. */
42
const struct grid_cell grid_default_cell = {
43
  { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 8, 0
44
};
45
46
/*
47
 * Padding grid cell data. Padding cells are the only zero width cell that
48
 * appears in the grid - because of this, they are always extended cells.
49
 */
50
static const struct grid_cell grid_padding_cell = {
51
  { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 8, 0
52
};
53
54
/* Cleared grid cell data. */
55
static const struct grid_cell grid_cleared_cell = {
56
  { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 8, 0
57
};
58
static const struct grid_cell_entry grid_cleared_entry = {
59
  { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED
60
};
61
62
#ifdef __APPLE__
63
void
64
grid_check_is_clear(struct grid *gd)
65
{
66
  struct grid_line  *gl;
67
  u_int      yy, ny;
68
69
  assert(gd != NULL);
70
71
  if (gd->sy == 0) {
72
    assert(gd->linedata == NULL);
73
    return;
74
  }
75
76
  assert(gd->linedata != NULL);
77
78
  ny = gd->hsize + gd->sy;
79
  for (yy = 0; yy < ny; yy++) {
80
    gl = &gd->linedata[yy];
81
82
    assert(gl->celldata == NULL);
83
    assert(gl->cellused == 0);
84
    assert(gl->cellsize == 0);
85
    assert(gl->extddata == NULL);
86
    assert(gl->extdsize == 0);
87
    assert(gl->flags == 0);
88
    assert(gl->time == 0);
89
  }
90
}
91
#else
92
void
93
grid_check_is_clear(__unused struct grid *gd)
94
46.0k
{
95
46.0k
}
96
#endif
97
98
/* Store cell in entry. */
99
static void
100
grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc,
101
    u_char c)
102
2.37M
{
103
2.37M
  gce->flags = (gc->flags & ~GRID_FLAG_CLEARED);
104
105
2.37M
  gce->data.fg = gc->fg & 0xff;
106
2.37M
  if (gc->fg & COLOUR_FLAG_256)
107
701
    gce->flags |= GRID_FLAG_FG256;
108
109
2.37M
  gce->data.bg = gc->bg & 0xff;
110
2.37M
  if (gc->bg & COLOUR_FLAG_256)
111
559
    gce->flags |= GRID_FLAG_BG256;
112
113
2.37M
  gce->data.attr = gc->attr;
114
2.37M
  gce->data.data = c;
115
2.37M
}
116
117
/* Check if a cell should be an extended cell. */
118
static int
119
grid_need_extended_cell(const struct grid_cell_entry *gce,
120
    const struct grid_cell *gc)
121
2.41M
{
122
2.41M
  if (gce->flags & GRID_FLAG_EXTENDED)
123
5.16k
    return (1);
124
2.41M
  if (gc->attr > 0xff)
125
669
    return (1);
126
2.41M
  if (gc->data.size > 1 || gc->data.width > 1)
127
7.89k
    return (1);
128
2.40M
  if ((gc->fg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) ||
129
2.40M
      (gc->bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)))
130
1.47k
    return (1);
131
2.40M
  if (gc->us != 8) /* only supports 256 or RGB */
132
6.37k
    return (1);
133
2.39M
  if (gc->link != 0)
134
15.0k
    return (1);
135
2.37M
  if (gc->flags & GRID_FLAG_TAB)
136
224
    return (1);
137
2.37M
  return (0);
138
2.37M
}
139
140
/* Get an extended cell. */
141
static void
142
grid_get_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce,
143
    int flags)
144
81.9k
{
145
81.9k
  u_int at = gl->extdsize + 1;
146
147
81.9k
  gl->extddata = xreallocarray(gl->extddata, at, sizeof *gl->extddata);
148
81.9k
  gl->extdsize = at;
149
150
81.9k
  gce->offset = at - 1;
151
81.9k
  gce->flags = (flags | GRID_FLAG_EXTENDED);
152
81.9k
}
153
154
/* Set cell as extended. */
155
static struct grid_extd_entry *
156
grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce,
157
    const struct grid_cell *gc)
158
88.1k
{
159
88.1k
  struct grid_extd_entry  *gee;
160
88.1k
  int      flags = (gc->flags & ~GRID_FLAG_CLEARED);
161
88.1k
  utf8_char    uc;
162
163
88.1k
  if (~gce->flags & GRID_FLAG_EXTENDED)
164
31.6k
    grid_get_extended_cell(gl, gce, flags);
165
56.4k
  else if (gce->offset >= gl->extdsize)
166
0
    fatalx("offset too big");
167
88.1k
  gl->flags |= GRID_LINE_EXTENDED;
168
88.1k
  if (gc->link != 0)
169
15.5k
    gl->flags |= GRID_LINE_HYPERLINK;
170
171
88.1k
  if (gc->flags & GRID_FLAG_TAB)
172
3.51k
    uc = gc->data.width;
173
84.6k
  else
174
84.6k
    utf8_from_data(&gc->data, &uc);
175
176
88.1k
  gee = &gl->extddata[gce->offset];
177
88.1k
  gee->data = uc;
178
88.1k
  gee->attr = gc->attr;
179
88.1k
  gee->flags = flags;
180
88.1k
  gee->fg = gc->fg;
181
88.1k
  gee->bg = gc->bg;
182
88.1k
  gee->us = gc->us;
183
88.1k
  gee->link = gc->link;
184
88.1k
  return (gee);
185
88.1k
}
186
187
/* Free up unused extended cells. */
188
static void
189
grid_compact_line(struct grid_line *gl)
190
0
{
191
0
  int      new_extdsize = 0;
192
0
  struct grid_extd_entry  *new_extddata;
193
0
  struct grid_cell_entry  *gce;
194
0
  struct grid_extd_entry  *gee;
195
0
  u_int      px, idx;
196
197
0
  if (gl->extdsize == 0)
198
0
    return;
199
200
0
  for (px = 0; px < gl->cellsize; px++) {
201
0
    gce = &gl->celldata[px];
202
0
    if (gce->flags & GRID_FLAG_EXTENDED)
203
0
      new_extdsize++;
204
0
  }
205
206
0
  if (new_extdsize == 0) {
207
0
    free(gl->extddata);
208
0
    gl->extddata = NULL;
209
0
    gl->extdsize = 0;
210
0
    return;
211
0
  }
212
0
  new_extddata = xreallocarray(NULL, new_extdsize, sizeof *gl->extddata);
213
214
0
  idx = 0;
215
0
  for (px = 0; px < gl->cellsize; px++) {
216
0
    gce = &gl->celldata[px];
217
0
    if (gce->flags & GRID_FLAG_EXTENDED) {
218
0
      gee = &gl->extddata[gce->offset];
219
0
      memcpy(&new_extddata[idx], gee, sizeof *gee);
220
0
      gce->offset = idx++;
221
0
    }
222
0
  }
223
224
0
  free(gl->extddata);
225
0
  gl->extddata = new_extddata;
226
0
  gl->extdsize = new_extdsize;
227
0
}
228
229
/* Get line data. */
230
struct grid_line *
231
grid_get_line(struct grid *gd, u_int line)
232
52.6k
{
233
52.6k
  return (&gd->linedata[line]);
234
52.6k
}
235
236
/* Get line time. */
237
time_t
238
grid_line_time(const struct grid_line *gl)
239
0
{
240
0
  if (gl->time == 0)
241
0
    return (0);
242
0
  return (start_time.tv_sec + gl->time - 1);
243
0
}
244
245
/* Set line time. */
246
static void
247
grid_line_set_time(struct grid_line *gl)
248
0
{
249
0
  if (current_time == 0)
250
0
    gl->time = 0;
251
0
  else
252
0
    gl->time = current_time - start_time.tv_sec + 1;
253
0
}
254
255
/* Adjust number of lines. */
256
void
257
grid_adjust_lines(struct grid *gd, u_int lines)
258
0
{
259
0
  gd->linedata = xreallocarray(gd->linedata, lines, sizeof *gd->linedata);
260
0
}
261
262
/* Copy default into a cell. */
263
static void
264
grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved)
265
944k
{
266
944k
  struct grid_line  *gl = &gd->linedata[py];
267
944k
  struct grid_cell_entry  *gce = &gl->celldata[px];
268
944k
  struct grid_extd_entry  *gee;
269
944k
  u_int      old_offset = gce->offset;
270
944k
  int      had_extd = (gce->flags & GRID_FLAG_EXTENDED);
271
272
944k
  memcpy(gce, &grid_cleared_entry, sizeof *gce);
273
944k
  if (!moved && had_extd && old_offset < gl->extdsize) {
274
1.03k
    gce->flags |= GRID_FLAG_EXTENDED;
275
1.03k
    gce->offset = old_offset;
276
1.03k
    gee = grid_extended_cell(gl, gce, &grid_cleared_cell);
277
1.03k
    if (bg != 8)
278
341
      gee->bg = bg;
279
943k
  } else if (bg != 8) {
280
267k
    if (bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) {
281
50.2k
      grid_get_extended_cell(gl, gce, gce->flags);
282
50.2k
      gee = grid_extended_cell(gl, gce, &grid_cleared_cell);
283
50.2k
      gee->bg = bg;
284
217k
    } else {
285
217k
      if (bg & COLOUR_FLAG_256)
286
3.53k
        gce->flags |= GRID_FLAG_BG256;
287
217k
      gce->data.bg = bg;
288
217k
    }
289
267k
  }
290
944k
}
291
292
/* Check grid y position. */
293
static int
294
grid_check_y(struct grid *gd, const char *from, u_int py)
295
2.82M
{
296
2.82M
  if (py >= gd->hsize + gd->sy) {
297
0
    log_debug("%s: y out of range: %u", from, py);
298
0
    return (-1);
299
0
  }
300
2.82M
  return (0);
301
2.82M
}
302
303
/* Check if two styles are (visibly) the same. */
304
int
305
grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2)
306
156k
{
307
156k
  int flags1 = gc1->flags, flags2 = gc2->flags;
308
309
156k
  if (gc1->fg != gc2->fg || gc1->bg != gc2->bg)
310
1.00k
    return (0);
311
155k
  if (gc1->attr != gc2->attr)
312
852
    return (0);
313
154k
  if ((flags1 & ~GRID_FLAG_CLEARED) != (flags2 & ~GRID_FLAG_CLEARED))
314
402
    return (0);
315
154k
  if (gc1->link != gc2->link)
316
402
    return (0);
317
153k
  return (1);
318
154k
}
319
320
/* Compare grid cells. Return 1 if equal, 0 if not. */
321
int
322
grid_cells_equal(const struct grid_cell *gc1, const struct grid_cell *gc2)
323
3.78k
{
324
3.78k
  if (!grid_cells_look_equal(gc1, gc2))
325
1.62k
    return (0);
326
2.15k
  if (gc1->data.width != gc2->data.width)
327
0
    return (0);
328
2.15k
  if (gc1->data.size != gc2->data.size)
329
984
    return (0);
330
1.17k
  return (memcmp(gc1->data.data, gc2->data.data, gc1->data.size) == 0);
331
2.15k
}
332
333
/* Set grid cell to a tab. */
334
void
335
grid_set_tab(struct grid_cell *gc, u_int width)
336
6.62k
{
337
6.62k
  memset(gc->data.data, 0, sizeof gc->data.data);
338
6.62k
  gc->flags |= GRID_FLAG_TAB;
339
6.62k
  gc->flags &= ~GRID_FLAG_PADDING;
340
6.62k
  gc->data.width = gc->data.size = gc->data.have = width;
341
6.62k
  memset(gc->data.data, ' ', gc->data.size);
342
6.62k
}
343
344
/* Free one line. */
345
static void
346
grid_free_line(struct grid *gd, u_int py)
347
742k
{
348
742k
  struct grid_line  *gl = &gd->linedata[py];
349
350
#ifdef __APPLE__
351
  assert(gl->cellused <= gl->cellsize);
352
  assert(gl->extdsize == 0 || gl->extddata != NULL);
353
  assert(gl->cellsize == 0 || gl->celldata != NULL);
354
#endif
355
356
742k
  free(gl->celldata);
357
742k
  free(gl->extddata);
358
742k
  memset(gl, 0, sizeof *gl);
359
742k
}
360
361
/* Free several lines. */
362
void
363
grid_free_lines(struct grid *gd, u_int py, u_int ny)
364
24.2k
{
365
24.2k
  u_int yy;
366
367
348k
  for (yy = py; yy < py + ny; yy++)
368
324k
    grid_free_line(gd, yy);
369
24.2k
}
370
371
/* Create a new grid. */
372
struct grid *
373
grid_create(u_int sx, u_int sy, u_int hlimit)
374
23.2k
{
375
23.2k
  struct grid *gd;
376
377
23.2k
  gd = xcalloc(1, sizeof *gd);
378
23.2k
  gd->sx = sx;
379
23.2k
  gd->sy = sy;
380
381
23.2k
  if (hlimit != 0)
382
0
    gd->flags = GRID_HISTORY;
383
23.2k
  gd->hlimit = hlimit;
384
385
23.2k
  if (gd->sy != 0)
386
23.2k
    gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata);
387
388
#ifdef __APPLE__
389
  assert(gd->hsize == 0);
390
#endif
391
23.2k
  grid_check_is_clear(gd);
392
23.2k
  return (gd);
393
23.2k
}
394
395
/* Destroy grid. */
396
void
397
grid_destroy(struct grid *gd)
398
23.2k
{
399
23.2k
  grid_free_lines(gd, 0, gd->hsize + gd->sy);
400
23.2k
  free(gd->linedata);
401
23.2k
  free(gd);
402
23.2k
}
403
404
/* Compare grids. */
405
int
406
grid_compare(struct grid *ga, struct grid *gb)
407
0
{
408
0
  struct grid_line  *gla, *glb;
409
0
  struct grid_cell   gca, gcb;
410
0
  u_int      xx, yy;
411
412
0
  if (ga->sx != gb->sx || ga->sy != gb->sy)
413
0
    return (1);
414
415
0
  for (yy = 0; yy < ga->sy; yy++) {
416
0
    gla = &ga->linedata[yy];
417
0
    glb = &gb->linedata[yy];
418
0
    if (gla->cellsize != glb->cellsize)
419
0
      return (1);
420
0
    for (xx = 0; xx < gla->cellsize; xx++) {
421
0
      grid_get_cell(ga, xx, yy, &gca);
422
0
      grid_get_cell(gb, xx, yy, &gcb);
423
0
      if (!grid_cells_equal(&gca, &gcb))
424
0
        return (1);
425
0
    }
426
0
  }
427
428
0
  return (0);
429
0
}
430
431
/* Trim lines from the history. */
432
static void
433
grid_trim_history(struct grid *gd, u_int ny)
434
276
{
435
276
  u_int remaining;
436
437
276
  grid_free_lines(gd, 0, ny);
438
276
  remaining = gd->hsize + gd->sy - ny;
439
276
  memmove(&gd->linedata[0], &gd->linedata[ny],
440
276
      remaining * (sizeof *gd->linedata));
441
276
  memset(&gd->linedata[remaining], 0, ny * (sizeof *gd->linedata));
442
276
}
443
444
/*
445
 * Collect lines from the history if at the limit. Free the top (oldest) 10%
446
 * and shift up.
447
 */
448
void
449
grid_collect_history(struct grid *gd, int all)
450
0
{
451
0
  u_int ny;
452
453
0
  if (gd->hsize == 0 || gd->hsize < gd->hlimit)
454
0
    return;
455
456
0
  if (all)
457
0
    ny = gd->hsize - gd->hlimit;
458
0
  else
459
0
    ny = gd->hlimit / 10;
460
0
  if (ny < 1)
461
0
    ny = 1;
462
0
  if (ny > gd->hsize)
463
0
    ny = gd->hsize;
464
465
  /*
466
   * Free the lines from 0 to ny then move the remaining lines over
467
   * them.
468
   */
469
0
  grid_trim_history(gd, ny);
470
471
0
  gd->hsize -= ny;
472
0
  gd->scroll_collected += ny;
473
0
  if (gd->hscrolled > gd->hsize)
474
0
    gd->hscrolled = gd->hsize;
475
0
}
476
477
/* Remove lines from the bottom of the history. */
478
void
479
grid_remove_history(struct grid *gd, u_int ny)
480
0
{
481
0
  u_int yy, start;
482
483
0
  if (ny > gd->hsize)
484
0
    return;
485
0
  start = gd->hsize + gd->sy - ny;
486
0
  for (yy = 0; yy < ny; yy++)
487
0
    grid_free_line(gd, start + yy);
488
0
  memset(&gd->linedata[start], 0, ny * sizeof *gd->linedata);
489
0
  gd->hsize -= ny;
490
0
}
491
492
/*
493
 * Scroll the entire visible screen, moving one line into the history. Just
494
 * allocate a new line at the bottom and move the history size indicator.
495
 */
496
void
497
grid_scroll_history(struct grid *gd, u_int bg)
498
0
{
499
0
  u_int yy;
500
501
0
  yy = gd->hsize + gd->sy;
502
0
  gd->linedata = xreallocarray(gd->linedata, yy + 1,
503
0
      sizeof *gd->linedata);
504
0
  grid_empty_line(gd, yy, bg);
505
506
0
  gd->hscrolled++;
507
0
  grid_compact_line(&gd->linedata[gd->hsize]);
508
0
  grid_line_set_time(&gd->linedata[gd->hsize]);
509
0
  gd->hsize++;
510
0
  gd->scroll_added++;
511
0
}
512
513
/* Clear the history. */
514
void
515
grid_clear_history(struct grid *gd)
516
276
{
517
276
  grid_trim_history(gd, gd->hsize);
518
519
276
  gd->hscrolled = 0;
520
276
  gd->hsize = 0;
521
276
  gd->scroll_generation++;
522
523
276
  gd->linedata = xreallocarray(gd->linedata, gd->sy,
524
276
      sizeof *gd->linedata);
525
276
}
526
527
/* Scroll a region up, moving the top line into the history. */
528
void
529
grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg)
530
0
{
531
0
  struct grid_line  *gl_history, *gl_upper;
532
0
  u_int      yy;
533
534
  /* Create a space for a new line. */
535
0
  yy = gd->hsize + gd->sy;
536
0
  gd->linedata = xreallocarray(gd->linedata, yy + 1,
537
0
      sizeof *gd->linedata);
538
539
  /* Move the entire screen down to free a space for this line. */
540
0
  gl_history = &gd->linedata[gd->hsize];
541
0
  memmove(gl_history + 1, gl_history, gd->sy * sizeof *gl_history);
542
543
  /* Adjust the region and find its start and end. */
544
0
  upper++;
545
0
  gl_upper = &gd->linedata[upper];
546
0
  lower++;
547
548
  /* Move the line into the history. */
549
0
  memcpy(gl_history, gl_upper, sizeof *gl_history);
550
0
  grid_line_set_time(gl_history);
551
552
  /* Then move the region up and clear the bottom line. */
553
0
  memmove(gl_upper, gl_upper + 1, (lower - upper) * sizeof *gl_upper);
554
0
  grid_empty_line(gd, lower, bg);
555
556
  /* Move the history offset down over the line. */
557
0
  gd->hscrolled++;
558
0
  gd->hsize++;
559
0
  gd->scroll_added++;
560
0
}
561
562
/* Expand line to fit to cell. */
563
static void
564
grid_expand_line(struct grid *gd, u_int py, u_int sx, u_int bg)
565
2.46M
{
566
2.46M
  struct grid_line  *gl;
567
2.46M
  u_int      xx;
568
569
2.46M
  gl = &gd->linedata[py];
570
2.46M
  if (sx <= gl->cellsize)
571
2.44M
    return;
572
573
21.1k
  if (sx < gd->sx / 4)
574
7.28k
    sx = gd->sx / 4;
575
13.8k
  else if (sx < gd->sx / 2)
576
4.04k
    sx = gd->sx / 2;
577
9.84k
  else if (gd->sx > sx)
578
4.24k
    sx = gd->sx;
579
580
21.1k
  gl->celldata = xreallocarray(gl->celldata, sx,
581
21.1k
      sizeof *gl->celldata);
582
21.1k
  if (gl->cellsize < sx) {
583
21.1k
    memset(gl->celldata + gl->cellsize, 0,
584
21.1k
        (sx - gl->cellsize) * sizeof *gl->celldata);
585
21.1k
  }
586
890k
  for (xx = gl->cellsize; xx < sx; xx++)
587
869k
    grid_clear_cell(gd, xx, py, bg, 0);
588
21.1k
  gl->cellsize = sx;
589
21.1k
}
590
591
/* Empty a line and set background colour if needed. */
592
void
593
grid_empty_line(struct grid *gd, u_int py, u_int bg)
594
418k
{
595
418k
  memset(&gd->linedata[py], 0, sizeof gd->linedata[py]);
596
418k
  if (!COLOUR_DEFAULT(bg))
597
2.98k
    grid_expand_line(gd, py, gd->sx, bg);
598
418k
}
599
600
/* Peek at grid line. */
601
const struct grid_line *
602
grid_peek_line(struct grid *gd, u_int py)
603
0
{
604
0
  if (grid_check_y(gd, __func__, py) != 0)
605
0
    return (NULL);
606
0
  return (&gd->linedata[py]);
607
0
}
608
609
/* Get cell from line. */
610
static void
611
grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
612
136k
{
613
136k
  struct grid_cell_entry  *gce = &gl->celldata[px];
614
136k
  struct grid_extd_entry  *gee;
615
616
136k
  if (gce->flags & GRID_FLAG_EXTENDED) {
617
10.2k
    if (gce->offset >= gl->extdsize)
618
0
      memcpy(gc, &grid_default_cell, sizeof *gc);
619
10.2k
    else {
620
10.2k
      gee = &gl->extddata[gce->offset];
621
10.2k
      gc->flags = gee->flags;
622
10.2k
      gc->attr = gee->attr;
623
10.2k
      gc->fg = gee->fg;
624
10.2k
      gc->bg = gee->bg;
625
10.2k
      gc->us = gee->us;
626
10.2k
      gc->link = gee->link;
627
628
10.2k
      if (gc->flags & GRID_FLAG_TAB)
629
3.11k
        grid_set_tab(gc, gee->data);
630
7.16k
      else
631
7.16k
        utf8_to_data(gee->data, &gc->data);
632
10.2k
    }
633
10.2k
    return;
634
10.2k
  }
635
636
125k
  gc->flags = gce->flags & ~(GRID_FLAG_FG256|GRID_FLAG_BG256);
637
125k
  gc->attr = gce->data.attr;
638
125k
  gc->fg = gce->data.fg;
639
125k
  if (gce->flags & GRID_FLAG_FG256)
640
38
    gc->fg |= COLOUR_FLAG_256;
641
125k
  gc->bg = gce->data.bg;
642
125k
  if (gce->flags & GRID_FLAG_BG256)
643
33
    gc->bg |= COLOUR_FLAG_256;
644
125k
  gc->us = 8;
645
125k
  utf8_set(&gc->data, gce->data.data);
646
125k
  gc->link = 0;
647
125k
}
648
649
/* Get cell for reading. */
650
void
651
grid_get_cell(struct grid *gd, u_int px, u_int py, struct grid_cell *gc)
652
245k
{
653
245k
  if (grid_check_y(gd, __func__, py) != 0 ||
654
245k
      px >= gd->linedata[py].cellsize)
655
109k
    memcpy(gc, &grid_default_cell, sizeof *gc);
656
136k
  else
657
136k
    grid_get_cell1(&gd->linedata[py], px, gc);
658
245k
}
659
660
/* Set cell at position. */
661
void
662
grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
663
2.40M
{
664
2.40M
  struct grid_line  *gl;
665
2.40M
  struct grid_cell_entry  *gce;
666
667
2.40M
  if (grid_check_y(gd, __func__, py) != 0)
668
0
    return;
669
670
2.40M
  grid_expand_line(gd, py, px + 1, 8);
671
672
2.40M
  gl = &gd->linedata[py];
673
2.40M
  if (px + 1 > gl->cellused)
674
242k
    gl->cellused = px + 1;
675
676
2.40M
  gce = &gl->celldata[px];
677
2.40M
  if (grid_need_extended_cell(gce, gc))
678
34.3k
    grid_extended_cell(gl, gce, gc);
679
2.36M
  else
680
2.36M
    grid_store_cell(gce, gc, gc->data.data[0]);
681
2.40M
}
682
683
/* Set padding at position. */
684
void
685
grid_set_padding(struct grid *gd, u_int px, u_int py, int bg)
686
21.0k
{
687
21.0k
  struct grid_cell  gc;
688
689
21.0k
  memcpy(&gc, &grid_padding_cell, sizeof gc);
690
21.0k
  gc.bg = bg;
691
21.0k
  grid_set_cell(gd, px, py, &gc);
692
21.0k
}
693
694
/* Set cells at position. */
695
void
696
grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc,
697
    const char *s, size_t slen)
698
9.34k
{
699
9.34k
  struct grid_line  *gl;
700
9.34k
  struct grid_cell_entry  *gce;
701
9.34k
  struct grid_extd_entry  *gee;
702
9.34k
  u_int      i;
703
704
9.34k
  if (grid_check_y(gd, __func__, py) != 0)
705
0
    return;
706
707
9.34k
  grid_expand_line(gd, py, px + slen, 8);
708
709
9.34k
  gl = &gd->linedata[py];
710
9.34k
  if (px + slen > gl->cellused)
711
6.14k
    gl->cellused = px + slen;
712
713
25.3k
  for (i = 0; i < slen; i++) {
714
15.9k
    gce = &gl->celldata[px + i];
715
15.9k
    if (grid_need_extended_cell(gce, gc)) {
716
2.55k
      gee = grid_extended_cell(gl, gce, gc);
717
2.55k
      gee->data = utf8_build_one(s[i]);
718
2.55k
    } else
719
13.4k
      grid_store_cell(gce, gc, s[i]);
720
15.9k
  }
721
9.34k
}
722
723
/* Clear area. */
724
void
725
grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
726
11.8k
{
727
11.8k
  struct grid_line  *gl;
728
11.8k
  u_int      xx, yy, ox, sx;
729
730
11.8k
  if (nx == 0 || ny == 0)
731
323
    return;
732
733
11.5k
  if (px == 0 && nx == gd->sx) {
734
7.76k
    grid_clear_lines(gd, py, ny, bg);
735
7.76k
    return;
736
7.76k
  }
737
738
3.80k
  if (grid_check_y(gd, __func__, py) != 0)
739
0
    return;
740
3.80k
  if (grid_check_y(gd, __func__, py + ny - 1) != 0)
741
0
    return;
742
743
7.60k
  for (yy = py; yy < py + ny; yy++) {
744
3.80k
    gl = &gd->linedata[yy];
745
746
3.80k
    sx = gd->sx;
747
3.80k
    if (sx > gl->cellsize)
748
2.34k
      sx = gl->cellsize;
749
3.80k
    ox = nx;
750
3.80k
    if (COLOUR_DEFAULT(bg)) {
751
3.15k
      if (px > sx)
752
423
        continue;
753
2.73k
      if (px + nx > sx)
754
1.68k
        ox = sx - px;
755
2.73k
    }
756
757
3.38k
    grid_expand_line(gd, yy, px + ox, 8); /* default bg first */
758
42.6k
    for (xx = px; xx < px + ox; xx++)
759
39.2k
      grid_clear_cell(gd, xx, yy, bg, 0);
760
3.38k
  }
761
3.80k
}
762
763
/* Clear lines. This just frees and truncates the lines. */
764
void
765
grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
766
30.6k
{
767
30.6k
  u_int yy;
768
769
30.6k
  if (ny == 0)
770
0
    return;
771
772
30.6k
  if (grid_check_y(gd, __func__, py) != 0)
773
0
    return;
774
30.6k
  if (grid_check_y(gd, __func__, py + ny - 1) != 0)
775
0
    return;
776
777
429k
  for (yy = py; yy < py + ny; yy++) {
778
398k
    grid_free_line(gd, yy);
779
398k
    grid_empty_line(gd, yy, bg);
780
398k
  }
781
30.6k
  if (py != 0)
782
4.15k
    gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
783
30.6k
}
784
785
/* Move a group of lines. */
786
void
787
grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg)
788
19.8k
{
789
19.8k
  u_int yy;
790
791
19.8k
  if (ny == 0 || py == dy)
792
646
    return;
793
794
19.2k
  if (grid_check_y(gd, __func__, py) != 0)
795
0
    return;
796
19.2k
  if (grid_check_y(gd, __func__, py + ny - 1) != 0)
797
0
    return;
798
19.2k
  if (grid_check_y(gd, __func__, dy) != 0)
799
0
    return;
800
19.2k
  if (grid_check_y(gd, __func__, dy + ny - 1) != 0)
801
0
    return;
802
803
  /* Free any lines which are being replaced. */
804
464k
  for (yy = dy; yy < dy + ny; yy++) {
805
445k
    if (yy >= py && yy < py + ny)
806
425k
      continue;
807
19.6k
    grid_free_line(gd, yy);
808
19.6k
  }
809
19.2k
  if (dy != 0)
810
4.58k
    gd->linedata[dy - 1].flags &= ~GRID_LINE_WRAPPED;
811
812
19.2k
  memmove(&gd->linedata[dy], &gd->linedata[py],
813
19.2k
      ny * (sizeof *gd->linedata));
814
815
  /*
816
   * Wipe any lines that have been moved (without freeing them - they are
817
   * still present).
818
   */
819
464k
  for (yy = py; yy < py + ny; yy++) {
820
445k
    if (yy < dy || yy >= dy + ny)
821
19.6k
      grid_empty_line(gd, yy, bg);
822
445k
  }
823
19.2k
  if (py != 0 && (py < dy || py >= dy + ny))
824
975
    gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
825
19.2k
}
826
827
/* Move a group of cells. */
828
void
829
grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
830
    u_int bg)
831
24.3k
{
832
24.3k
  struct grid_line  *gl;
833
24.3k
  u_int      xx;
834
835
24.3k
  if (nx == 0 || px == dx)
836
302
    return;
837
838
24.0k
  if (grid_check_y(gd, __func__, py) != 0)
839
0
    return;
840
24.0k
  gl = &gd->linedata[py];
841
842
24.0k
  grid_expand_line(gd, py, px + nx, 8);
843
24.0k
  grid_expand_line(gd, py, dx + nx, 8);
844
24.0k
  memmove(&gl->celldata[dx], &gl->celldata[px],
845
24.0k
      nx * sizeof *gl->celldata);
846
24.0k
  if (dx + nx > gl->cellused)
847
1.48k
    gl->cellused = dx + nx;
848
849
  /* Wipe any cells that have been moved. */
850
1.11M
  for (xx = px; xx < px + nx; xx++) {
851
1.08M
    if (xx >= dx && xx < dx + nx)
852
1.05M
      continue;
853
35.6k
    grid_clear_cell(gd, xx, py, bg, 1);
854
35.6k
  }
855
24.0k
}
856
857
/* Get ANSI foreground sequence. */
858
static size_t
859
grid_string_cells_fg(const struct grid_cell *gc, int *values)
860
0
{
861
0
  size_t  n;
862
0
  u_char  r, g, b;
863
0
  int c;
864
865
0
  n = 0;
866
0
  if (gc->fg & COLOUR_FLAG_THEME) {
867
0
    c = colour_theme_terminal_colour(gc->fg & 0xff);
868
0
    if (c == 8)
869
0
      values[n++] = 39;
870
0
    else
871
0
      values[n++] = c + 30;
872
0
  } else if (gc->fg & COLOUR_FLAG_256) {
873
0
    values[n++] = 38;
874
0
    values[n++] = 5;
875
0
    values[n++] = gc->fg & 0xff;
876
0
  } else if (gc->fg & COLOUR_FLAG_RGB) {
877
0
    values[n++] = 38;
878
0
    values[n++] = 2;
879
0
    colour_split_rgb(gc->fg, &r, &g, &b);
880
0
    values[n++] = r;
881
0
    values[n++] = g;
882
0
    values[n++] = b;
883
0
  } else {
884
0
    switch (gc->fg) {
885
0
    case 0:
886
0
    case 1:
887
0
    case 2:
888
0
    case 3:
889
0
    case 4:
890
0
    case 5:
891
0
    case 6:
892
0
    case 7:
893
0
      values[n++] = gc->fg + 30;
894
0
      break;
895
0
    case 8:
896
0
      values[n++] = 39;
897
0
      break;
898
0
    case 90:
899
0
    case 91:
900
0
    case 92:
901
0
    case 93:
902
0
    case 94:
903
0
    case 95:
904
0
    case 96:
905
0
    case 97:
906
0
      values[n++] = gc->fg;
907
0
      break;
908
0
    }
909
0
  }
910
0
  return (n);
911
0
}
912
913
/* Get ANSI background sequence. */
914
static size_t
915
grid_string_cells_bg(const struct grid_cell *gc, int *values)
916
0
{
917
0
  size_t  n;
918
0
  u_char  r, g, b;
919
0
  int c;
920
921
0
  n = 0;
922
0
  if (gc->bg & COLOUR_FLAG_THEME) {
923
0
    c = colour_theme_terminal_colour(gc->bg & 0xff);
924
0
    if (c == 8)
925
0
      values[n++] = 49;
926
0
    else
927
0
      values[n++] = c + 40;
928
0
  } else if (gc->bg & COLOUR_FLAG_256) {
929
0
    values[n++] = 48;
930
0
    values[n++] = 5;
931
0
    values[n++] = gc->bg & 0xff;
932
0
  } else if (gc->bg & COLOUR_FLAG_RGB) {
933
0
    values[n++] = 48;
934
0
    values[n++] = 2;
935
0
    colour_split_rgb(gc->bg, &r, &g, &b);
936
0
    values[n++] = r;
937
0
    values[n++] = g;
938
0
    values[n++] = b;
939
0
  } else {
940
0
    switch (gc->bg) {
941
0
    case 0:
942
0
    case 1:
943
0
    case 2:
944
0
    case 3:
945
0
    case 4:
946
0
    case 5:
947
0
    case 6:
948
0
    case 7:
949
0
      values[n++] = gc->bg + 40;
950
0
      break;
951
0
    case 8:
952
0
      values[n++] = 49;
953
0
      break;
954
0
    case 90:
955
0
    case 91:
956
0
    case 92:
957
0
    case 93:
958
0
    case 94:
959
0
    case 95:
960
0
    case 96:
961
0
    case 97:
962
0
      values[n++] = gc->bg + 10;
963
0
      break;
964
0
    }
965
0
  }
966
0
  return (n);
967
0
}
968
969
/* Get underscore colour sequence. */
970
static size_t
971
grid_string_cells_us(const struct grid_cell *gc, int *values)
972
0
{
973
0
  size_t  n;
974
0
  u_char  r, g, b;
975
0
  int c;
976
977
0
  n = 0;
978
0
  if (gc->us & COLOUR_FLAG_THEME) {
979
0
    c = colour_theme_terminal_colour(gc->us & 0xff);
980
0
    if (c == 8)
981
0
      values[n++] = 59;
982
0
    else {
983
0
      values[n++] = 58;
984
0
      values[n++] = 5;
985
0
      values[n++] = c;
986
0
    }
987
0
  } else if (gc->us & COLOUR_FLAG_256) {
988
0
    values[n++] = 58;
989
0
    values[n++] = 5;
990
0
    values[n++] = gc->us & 0xff;
991
0
  } else if (gc->us & COLOUR_FLAG_RGB) {
992
0
    values[n++] = 58;
993
0
    values[n++] = 2;
994
0
    colour_split_rgb(gc->us, &r, &g, &b);
995
0
    values[n++] = r;
996
0
    values[n++] = g;
997
0
    values[n++] = b;
998
0
  }
999
0
  return (n);
1000
0
}
1001
1002
/* Add on SGR code. */
1003
static void
1004
grid_string_cells_add_code(char *buf, size_t len, u_int n, int *s, int *newc,
1005
    int *oldc, size_t nnewc, size_t noldc, int flags)
1006
0
{
1007
0
  u_int i;
1008
0
  char  tmp[64];
1009
0
  int reset = (n != 0 && s[0] == 0);
1010
1011
0
  if (nnewc == 0)
1012
0
    return; /* no code to add */
1013
0
  if (!reset &&
1014
0
      nnewc == noldc &&
1015
0
      memcmp(newc, oldc, nnewc * sizeof newc[0]) == 0)
1016
0
    return; /* no reset and colour unchanged */
1017
0
  if (reset && (newc[0] == 49 || newc[0] == 39))
1018
0
    return; /* reset and colour default */
1019
1020
0
  if (flags & GRID_STRING_ESCAPE_SEQUENCES)
1021
0
    strlcat(buf, "\\033[", len);
1022
0
  else
1023
0
    strlcat(buf, "\033[", len);
1024
0
  for (i = 0; i < nnewc; i++) {
1025
0
    if (i + 1 < nnewc)
1026
0
      xsnprintf(tmp, sizeof tmp, "%d;", newc[i]);
1027
0
    else
1028
0
      xsnprintf(tmp, sizeof tmp, "%d", newc[i]);
1029
0
    strlcat(buf, tmp, len);
1030
0
  }
1031
0
  strlcat(buf, "m", len);
1032
0
}
1033
1034
static int
1035
grid_string_cells_add_hyperlink(char *buf, size_t len, const char *id,
1036
    const char *uri, int flags)
1037
0
{
1038
0
  char  *tmp;
1039
1040
0
  if (strlen(uri) + strlen(id) + 17 >= len)
1041
0
    return (0);
1042
1043
0
  if (flags & GRID_STRING_ESCAPE_SEQUENCES)
1044
0
    strlcat(buf, "\\033]8;", len);
1045
0
  else
1046
0
    strlcat(buf, "\033]8;", len);
1047
0
  if (*id != '\0') {
1048
0
    xasprintf(&tmp, "id=%s;", id);
1049
0
    strlcat(buf, tmp, len);
1050
0
    free(tmp);
1051
0
  } else
1052
0
    strlcat(buf, ";", len);
1053
0
  strlcat(buf, uri, len);
1054
0
  if (flags & GRID_STRING_ESCAPE_SEQUENCES)
1055
0
    strlcat(buf, "\\033\\\\", len);
1056
0
  else
1057
0
    strlcat(buf, "\033\\", len);
1058
0
  return (1);
1059
0
}
1060
1061
/*
1062
 * Returns ANSI code to set particular attributes (colour, bold and so on)
1063
 * given a current state.
1064
 */
1065
static void
1066
grid_string_cells_code(const struct grid_cell *lastgc,
1067
    const struct grid_cell *gc, char *buf, size_t len, int flags,
1068
    struct screen *sc, int *has_link)
1069
0
{
1070
0
  int      oldc[64], newc[64], s[128];
1071
0
  size_t       noldc, nnewc, n, i;
1072
0
  u_int      attr = gc->attr, lastattr = lastgc->attr;
1073
0
  char       tmp[64];
1074
0
  const char    *uri, *id;
1075
1076
0
  static const struct {
1077
0
    u_int mask;
1078
0
    u_int code;
1079
0
  } attrs[] = {
1080
0
    { GRID_ATTR_BRIGHT, 1 },
1081
0
    { GRID_ATTR_DIM, 2 },
1082
0
    { GRID_ATTR_ITALICS, 3 },
1083
0
    { GRID_ATTR_UNDERSCORE, 4 },
1084
0
    { GRID_ATTR_BLINK, 5 },
1085
0
    { GRID_ATTR_REVERSE, 7 },
1086
0
    { GRID_ATTR_HIDDEN, 8 },
1087
0
    { GRID_ATTR_STRIKETHROUGH, 9 },
1088
0
    { GRID_ATTR_UNDERSCORE_2, 42 },
1089
0
    { GRID_ATTR_UNDERSCORE_3, 43 },
1090
0
    { GRID_ATTR_UNDERSCORE_4, 44 },
1091
0
    { GRID_ATTR_UNDERSCORE_5, 45 },
1092
0
    { GRID_ATTR_OVERLINE, 53 },
1093
0
  };
1094
0
  n = 0;
1095
1096
  /* If any attribute is removed, begin with 0. */
1097
0
  for (i = 0; i < nitems(attrs); i++) {
1098
0
    if (((~attr & attrs[i].mask) &&
1099
0
        (lastattr & attrs[i].mask)) ||
1100
0
        (lastgc->us != 8 && gc->us == 8)) {
1101
0
      s[n++] = 0;
1102
0
      lastattr &= GRID_ATTR_CHARSET;
1103
0
      break;
1104
0
    }
1105
0
  }
1106
  /* For each attribute that is newly set, add its code. */
1107
0
  for (i = 0; i < nitems(attrs); i++) {
1108
0
    if ((attr & attrs[i].mask) && !(lastattr & attrs[i].mask))
1109
0
      s[n++] = attrs[i].code;
1110
0
  }
1111
1112
  /* Write the attributes. */
1113
0
  *buf = '\0';
1114
0
  if (n > 0) {
1115
0
    if (flags & GRID_STRING_ESCAPE_SEQUENCES)
1116
0
      strlcat(buf, "\\033[", len);
1117
0
    else
1118
0
      strlcat(buf, "\033[", len);
1119
0
    for (i = 0; i < n; i++) {
1120
0
      if (s[i] < 10)
1121
0
        xsnprintf(tmp, sizeof tmp, "%d", s[i]);
1122
0
      else {
1123
0
        xsnprintf(tmp, sizeof tmp, "%d:%d", s[i] / 10,
1124
0
            s[i] % 10);
1125
0
      }
1126
0
      strlcat(buf, tmp, len);
1127
0
      if (i + 1 < n)
1128
0
        strlcat(buf, ";", len);
1129
0
    }
1130
0
    strlcat(buf, "m", len);
1131
0
  }
1132
1133
  /* If the foreground colour changed, write its parameters. */
1134
0
  nnewc = grid_string_cells_fg(gc, newc);
1135
0
  noldc = grid_string_cells_fg(lastgc, oldc);
1136
0
  grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc,
1137
0
      flags);
1138
1139
  /* If the background colour changed, append its parameters. */
1140
0
  nnewc = grid_string_cells_bg(gc, newc);
1141
0
  noldc = grid_string_cells_bg(lastgc, oldc);
1142
0
  grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc,
1143
0
      flags);
1144
1145
  /* If the underscore colour changed, append its parameters. */
1146
0
  nnewc = grid_string_cells_us(gc, newc);
1147
0
  noldc = grid_string_cells_us(lastgc, oldc);
1148
0
  grid_string_cells_add_code(buf, len, n, s, newc, oldc, nnewc, noldc,
1149
0
      flags);
1150
1151
  /* Append shift in/shift out if needed. */
1152
0
  if ((attr & GRID_ATTR_CHARSET) && !(lastattr & GRID_ATTR_CHARSET)) {
1153
0
    if (flags & GRID_STRING_ESCAPE_SEQUENCES)
1154
0
      strlcat(buf, "\\016", len); /* SO */
1155
0
    else
1156
0
      strlcat(buf, "\016", len);  /* SO */
1157
0
  }
1158
0
  if (!(attr & GRID_ATTR_CHARSET) && (lastattr & GRID_ATTR_CHARSET)) {
1159
0
    if (flags & GRID_STRING_ESCAPE_SEQUENCES)
1160
0
      strlcat(buf, "\\017", len); /* SI */
1161
0
    else
1162
0
      strlcat(buf, "\017", len);  /* SI */
1163
0
  }
1164
1165
  /* Add hyperlink if changed. */
1166
0
  if (sc != NULL && sc->hyperlinks != NULL && lastgc->link != gc->link) {
1167
0
    if (hyperlinks_get(sc->hyperlinks, gc->link, &uri, &id, NULL)) {
1168
0
      *has_link = grid_string_cells_add_hyperlink(buf, len,
1169
0
          id, uri, flags);
1170
0
    } else if (*has_link) {
1171
0
      grid_string_cells_add_hyperlink(buf, len, "", "",
1172
0
          flags);
1173
0
      *has_link = 0;
1174
0
    }
1175
0
  }
1176
0
}
1177
1178
/* Convert cells into a string. */
1179
char *
1180
grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx,
1181
    struct grid_cell **lastgc, int flags, struct screen *s)
1182
0
{
1183
0
  struct grid_cell   gc;
1184
0
  static struct grid_cell  lastgc1;
1185
0
  const char    *data;
1186
0
  char      *buf, code[8192];
1187
0
  size_t       len, off, size, codelen;
1188
0
  u_int      xx, end;
1189
0
  int      has_link = 0;
1190
0
  const struct grid_line  *gl;
1191
1192
0
  if (lastgc != NULL && *lastgc == NULL) {
1193
0
    memcpy(&lastgc1, &grid_default_cell, sizeof lastgc1);
1194
0
    *lastgc = &lastgc1;
1195
0
  }
1196
1197
0
  len = 128;
1198
0
  buf = xmalloc(len);
1199
0
  off = 0;
1200
1201
0
  gl = grid_peek_line(gd, py);
1202
0
  if (gl == NULL) {
1203
0
    buf[0] = '\0';
1204
0
    return (buf);
1205
0
  }
1206
0
  if (flags & GRID_STRING_EMPTY_CELLS)
1207
0
    end = gl->cellsize;
1208
0
  else
1209
0
    end = gl->cellused;
1210
0
  for (xx = px; xx < px + nx; xx++) {
1211
0
    if (xx >= end)
1212
0
      break;
1213
0
    grid_get_cell(gd, xx, py, &gc);
1214
0
    if (gc.flags & GRID_FLAG_PADDING)
1215
0
      continue;
1216
1217
0
    if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) {
1218
0
      grid_string_cells_code(*lastgc, &gc, code, sizeof code,
1219
0
          flags, s, &has_link);
1220
0
      codelen = strlen(code);
1221
0
      memcpy(*lastgc, &gc, sizeof **lastgc);
1222
0
    } else
1223
0
      codelen = 0;
1224
1225
0
    if (gc.flags & GRID_FLAG_TAB) {
1226
0
      data = "\t";
1227
0
      size = 1;
1228
0
    } else {
1229
0
      data = gc.data.data;
1230
0
      size = gc.data.size;
1231
0
      if ((flags & GRID_STRING_ESCAPE_SEQUENCES) &&
1232
0
          size == 1 &&
1233
0
          *data == '\\') {
1234
0
        data = "\\\\";
1235
0
        size = 2;
1236
0
      }
1237
0
    }
1238
1239
0
    while (len < off + size + codelen + 1) {
1240
0
      buf = xreallocarray(buf, 2, len);
1241
0
      len *= 2;
1242
0
    }
1243
1244
0
    if (codelen != 0) {
1245
0
      memcpy(buf + off, code, codelen);
1246
0
      off += codelen;
1247
0
    }
1248
0
    memcpy(buf + off, data, size);
1249
0
    off += size;
1250
0
  }
1251
1252
0
  if (has_link) {
1253
0
    grid_string_cells_add_hyperlink(code, sizeof code, "", "",
1254
0
        flags);
1255
0
    codelen = strlen(code);
1256
0
    while (len < off + size + codelen + 1) {
1257
0
      buf = xreallocarray(buf, 2, len);
1258
0
      len *= 2;
1259
0
    }
1260
0
    memcpy(buf + off, code, codelen);
1261
0
    off += codelen;
1262
0
  }
1263
1264
0
  if (flags & GRID_STRING_TRIM_SPACES) {
1265
0
    while (off > 0 && buf[off - 1] == ' ')
1266
0
      off--;
1267
0
  }
1268
0
  buf[off] = '\0';
1269
1270
0
  return (buf);
1271
0
}
1272
1273
/*
1274
 * Duplicate a set of lines between two grids. Both source and destination
1275
 * should be big enough.
1276
 */
1277
void
1278
grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
1279
    u_int ny)
1280
708
{
1281
708
  struct grid_line  *dstl, *srcl;
1282
708
  u_int      yy;
1283
1284
708
  if (dy + ny > dst->hsize + dst->sy)
1285
0
    ny = dst->hsize + dst->sy - dy;
1286
708
  if (sy + ny > src->hsize + src->sy)
1287
0
    ny = src->hsize + src->sy - sy;
1288
708
  grid_free_lines(dst, dy, ny);
1289
1290
18.4k
  for (yy = 0; yy < ny; yy++) {
1291
17.7k
    srcl = &src->linedata[sy];
1292
17.7k
    dstl = &dst->linedata[dy];
1293
1294
17.7k
    memcpy(dstl, srcl, sizeof *dstl);
1295
17.7k
    if (srcl->cellsize != 0) {
1296
1.41k
      dstl->celldata = xreallocarray(NULL,
1297
1.41k
          srcl->cellsize, sizeof *dstl->celldata);
1298
1.41k
      memcpy(dstl->celldata, srcl->celldata,
1299
1.41k
          srcl->cellsize * sizeof *dstl->celldata);
1300
1.41k
    } else
1301
16.2k
      dstl->celldata = NULL;
1302
17.7k
    if (srcl->extdsize != 0) {
1303
447
      dstl->extdsize = srcl->extdsize;
1304
447
      dstl->extddata = xreallocarray(NULL, dstl->extdsize,
1305
447
          sizeof *dstl->extddata);
1306
447
      memcpy(dstl->extddata, srcl->extddata, dstl->extdsize *
1307
447
          sizeof *dstl->extddata);
1308
447
    } else
1309
17.2k
      dstl->extddata = NULL;
1310
1311
17.7k
    sy++;
1312
17.7k
    dy++;
1313
17.7k
  }
1314
708
}
1315
1316
/* Mark line as dead. */
1317
static void
1318
grid_reflow_dead(struct grid_line *gl)
1319
0
{
1320
0
  memset(gl, 0, sizeof *gl);
1321
0
  gl->flags = GRID_LINE_DEAD;
1322
0
}
1323
1324
/* Add lines, return the first new one. */
1325
static struct grid_line *
1326
grid_reflow_add(struct grid *gd, u_int n)
1327
0
{
1328
0
  struct grid_line  *gl;
1329
0
  u_int      sy = gd->sy + n;
1330
1331
0
  gd->linedata = xreallocarray(gd->linedata, sy, sizeof *gd->linedata);
1332
0
  gl = &gd->linedata[gd->sy];
1333
0
  memset(gl, 0, n * (sizeof *gl));
1334
0
  gd->sy = sy;
1335
0
  return (gl);
1336
0
}
1337
1338
/* Move a line across. */
1339
static struct grid_line *
1340
grid_reflow_move(struct grid *gd, struct grid_line *from)
1341
0
{
1342
0
  struct grid_line  *to;
1343
1344
0
  to = grid_reflow_add(gd, 1);
1345
0
  memcpy(to, from, sizeof *to);
1346
0
  grid_reflow_dead(from);
1347
0
  return (to);
1348
0
}
1349
1350
/* Join line below onto this one. */
1351
static void
1352
grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
1353
    u_int width, int already)
1354
0
{
1355
0
  struct grid_line  *gl, *from = NULL;
1356
0
  struct grid_cell   gc;
1357
0
  u_int      lines, left, i, to, line, want = 0;
1358
0
  u_int      at;
1359
0
  int      wrapped = 1;
1360
1361
  /*
1362
   * Add a new target line.
1363
   */
1364
0
  if (!already) {
1365
0
    to = target->sy;
1366
0
    gl = grid_reflow_move(target, &gd->linedata[yy]);
1367
0
  } else {
1368
0
    to = target->sy - 1;
1369
0
    gl = &target->linedata[to];
1370
0
  }
1371
0
  at = gl->cellused;
1372
1373
  /*
1374
   * Loop until no more to consume or the target line is full.
1375
   */
1376
0
  lines = 0;
1377
0
  for (;;) {
1378
    /*
1379
     * If this is now the last line, there is nothing more to be
1380
     * done.
1381
     */
1382
0
    if (yy + 1 + lines == gd->hsize + gd->sy)
1383
0
      break;
1384
0
    line = yy + 1 + lines;
1385
1386
    /* If the next line is empty, skip it. */
1387
0
    if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
1388
0
      wrapped = 0;
1389
0
    if (gd->linedata[line].cellused == 0) {
1390
0
      if (!wrapped)
1391
0
        break;
1392
0
      lines++;
1393
0
      continue;
1394
0
    }
1395
1396
    /*
1397
     * Is the destination line now full? Copy the first character
1398
     * separately because we need to leave "from" set to the last
1399
     * line if this line is full.
1400
     */
1401
0
    grid_get_cell1(&gd->linedata[line], 0, &gc);
1402
0
    if (width + gc.data.width > sx)
1403
0
      break;
1404
0
    width += gc.data.width;
1405
0
    grid_set_cell(target, at, to, &gc);
1406
0
    at++;
1407
1408
    /* Join as much more as possible onto the current line. */
1409
0
    from = &gd->linedata[line];
1410
0
    for (want = 1; want < from->cellused; want++) {
1411
0
      grid_get_cell1(from, want, &gc);
1412
0
      if (width + gc.data.width > sx)
1413
0
        break;
1414
0
      width += gc.data.width;
1415
1416
0
      grid_set_cell(target, at, to, &gc);
1417
0
      at++;
1418
0
    }
1419
0
    lines++;
1420
1421
    /*
1422
     * If this line wasn't wrapped or we didn't consume the entire
1423
     * line, don't try to join any further lines.
1424
     */
1425
0
    if (!wrapped || want != from->cellused || width == sx)
1426
0
      break;
1427
0
  }
1428
0
  if (lines == 0 || from == NULL)
1429
0
    return;
1430
1431
  /*
1432
   * If we didn't consume the entire final line, then remove what we did
1433
   * consume. If we consumed the entire line and it wasn't wrapped,
1434
   * remove the wrap flag from this line.
1435
   */
1436
0
  left = from->cellused - want;
1437
0
  if (left != 0) {
1438
0
    grid_move_cells(gd, 0, want, yy + lines, left, 8);
1439
0
    from->cellsize = from->cellused = left;
1440
0
    lines--;
1441
0
  } else if (!wrapped)
1442
0
    gl->flags &= ~GRID_LINE_WRAPPED;
1443
1444
  /* Remove the lines that were completely consumed. */
1445
0
  for (i = yy + 1; i < yy + 1 + lines; i++) {
1446
0
    free(gd->linedata[i].celldata);
1447
0
    free(gd->linedata[i].extddata);
1448
0
    grid_reflow_dead(&gd->linedata[i]);
1449
0
  }
1450
1451
  /* Adjust scroll position. */
1452
0
  if (gd->hscrolled > to + lines)
1453
0
    gd->hscrolled -= lines;
1454
0
  else if (gd->hscrolled > to)
1455
0
    gd->hscrolled = to;
1456
0
}
1457
1458
/* Split this line into several new ones */
1459
static void
1460
grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy,
1461
    u_int at)
1462
0
{
1463
0
  struct grid_line  *gl = &gd->linedata[yy], *first;
1464
0
  struct grid_cell   gc;
1465
0
  u_int      line, lines, width, i, xx;
1466
0
  u_int      used = gl->cellused;
1467
0
  int      flags = gl->flags;
1468
1469
  /* How many lines do we need to insert? We know we need at least two. */
1470
0
  if (~gl->flags & GRID_LINE_EXTENDED)
1471
0
    lines = 1 + (gl->cellused - 1) / sx;
1472
0
  else {
1473
0
    lines = 2;
1474
0
    width = 0;
1475
0
    for (i = at; i < used; i++) {
1476
0
      grid_get_cell1(gl, i, &gc);
1477
0
      if (width + gc.data.width > sx) {
1478
0
        lines++;
1479
0
        width = 0;
1480
0
      }
1481
0
      width += gc.data.width;
1482
0
    }
1483
0
  }
1484
1485
  /* Insert new lines. */
1486
0
  line = target->sy + 1;
1487
0
  first = grid_reflow_add(target, lines);
1488
1489
  /* Copy sections from the original line. */
1490
0
  width = 0;
1491
0
  xx = 0;
1492
0
  for (i = at; i < used; i++) {
1493
0
    grid_get_cell1(gl, i, &gc);
1494
0
    if (width + gc.data.width > sx) {
1495
0
      target->linedata[line].flags |= GRID_LINE_WRAPPED;
1496
1497
0
      line++;
1498
0
      width = 0;
1499
0
      xx = 0;
1500
0
    }
1501
0
    width += gc.data.width;
1502
0
    grid_set_cell(target, xx, line, &gc);
1503
0
    xx++;
1504
0
  }
1505
0
  if (flags & GRID_LINE_WRAPPED)
1506
0
    target->linedata[line].flags |= GRID_LINE_WRAPPED;
1507
1508
  /* Move the remainder of the original line. */
1509
0
  gl->cellsize = gl->cellused = at;
1510
0
  gl->flags |= GRID_LINE_WRAPPED;
1511
0
  memcpy(first, gl, sizeof *first);
1512
0
  grid_reflow_dead(gl);
1513
1514
  /* Adjust the scroll position. */
1515
0
  if (yy <= gd->hscrolled)
1516
0
    gd->hscrolled += lines - 1;
1517
1518
  /*
1519
   * If the original line had the wrapped flag and there is still space
1520
   * in the last new line, try to join with the next lines.
1521
   */
1522
0
  if (width < sx && (flags & GRID_LINE_WRAPPED))
1523
0
    grid_reflow_join(target, gd, sx, yy, width, 1);
1524
0
}
1525
1526
/* Reflow lines on grid to new width. */
1527
void
1528
grid_reflow(struct grid *gd, u_int sx)
1529
0
{
1530
0
  struct grid   *target;
1531
0
  struct grid_line  *gl;
1532
0
  struct grid_cell   gc;
1533
0
  u_int      yy, width, i, at;
1534
1535
  /*
1536
   * Create a destination grid. This is just used as a container for the
1537
   * line data and may not be fully valid.
1538
   */
1539
0
  target = grid_create(gd->sx, 0, 0);
1540
1541
  /*
1542
   * Loop over each source line.
1543
   */
1544
0
  for (yy = 0; yy < gd->hsize + gd->sy; yy++) {
1545
0
    gl = &gd->linedata[yy];
1546
0
    if (gl->flags & GRID_LINE_DEAD)
1547
0
      continue;
1548
1549
    /*
1550
     * Work out the width of this line. at is the point at which
1551
     * the available width is hit, and width is the full line
1552
     * width.
1553
     */
1554
0
    at = width = 0;
1555
0
    if (~gl->flags & GRID_LINE_EXTENDED) {
1556
0
      width = gl->cellused;
1557
0
      if (width > sx)
1558
0
        at = sx;
1559
0
      else
1560
0
        at = width;
1561
0
    } else {
1562
0
      for (i = 0; i < gl->cellused; i++) {
1563
0
        grid_get_cell1(gl, i, &gc);
1564
0
        if (at == 0 && width + gc.data.width > sx)
1565
0
          at = i;
1566
0
        width += gc.data.width;
1567
0
      }
1568
0
    }
1569
1570
    /*
1571
     * If the line is exactly right, just move it across
1572
     * unchanged.
1573
     */
1574
0
    if (width == sx) {
1575
0
      grid_reflow_move(target, gl);
1576
0
      continue;
1577
0
    }
1578
1579
    /*
1580
     * If the line is too big, it needs to be split, whether or not
1581
     * it was previously wrapped.
1582
     */
1583
0
    if (width > sx) {
1584
0
      grid_reflow_split(target, gd, sx, yy, at);
1585
0
      continue;
1586
0
    }
1587
1588
    /*
1589
     * If the line was previously wrapped, join as much as possible
1590
     * of the next line.
1591
     */
1592
0
    if (gl->flags & GRID_LINE_WRAPPED)
1593
0
      grid_reflow_join(target, gd, sx, yy, width, 0);
1594
0
    else
1595
0
      grid_reflow_move(target, gl);
1596
0
  }
1597
1598
  /*
1599
   * Replace the old grid with the new.
1600
   */
1601
0
  if (target->sy < gd->sy)
1602
0
    grid_reflow_add(target, gd->sy - target->sy);
1603
0
  gd->hsize = target->sy - gd->sy;
1604
0
  if (gd->hscrolled > gd->hsize)
1605
0
    gd->hscrolled = gd->hsize;
1606
0
  free(gd->linedata);
1607
0
  gd->linedata = target->linedata;
1608
0
  free(target);
1609
0
  gd->scroll_generation++;
1610
0
}
1611
1612
/* Convert to position based on wrapped lines. */
1613
void
1614
grid_wrap_position(struct grid *gd, u_int px, u_int py, u_int *wx, u_int *wy)
1615
0
{
1616
0
  u_int ax = 0, ay = 0, yy;
1617
1618
0
  for (yy = 0; yy < py; yy++) {
1619
0
    if (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
1620
0
      ax += gd->linedata[yy].cellused;
1621
0
    else {
1622
0
      ax = 0;
1623
0
      ay++;
1624
0
    }
1625
0
  }
1626
0
  if (px >= gd->linedata[yy].cellused)
1627
0
    ax = UINT_MAX;
1628
0
  else
1629
0
    ax += px;
1630
0
  *wx = ax;
1631
0
  *wy = ay;
1632
0
}
1633
1634
/* Convert position based on wrapped lines back. */
1635
void
1636
grid_unwrap_position(struct grid *gd, u_int *px, u_int *py, u_int wx, u_int wy)
1637
0
{
1638
0
  u_int yy, ay = 0;
1639
1640
0
  for (yy = 0; yy < gd->hsize + gd->sy - 1; yy++) {
1641
0
    if (ay == wy)
1642
0
      break;
1643
0
    if (~gd->linedata[yy].flags & GRID_LINE_WRAPPED)
1644
0
      ay++;
1645
0
  }
1646
1647
  /*
1648
   * yy is now 0 on the unwrapped line which contains wx. Walk forwards
1649
   * until we find the end or the line now containing wx.
1650
   */
1651
0
  if (wx == UINT_MAX) {
1652
0
    while (gd->linedata[yy].flags & GRID_LINE_WRAPPED)
1653
0
      yy++;
1654
0
    wx = gd->linedata[yy].cellused;
1655
0
  } else {
1656
0
    while (gd->linedata[yy].flags & GRID_LINE_WRAPPED) {
1657
0
      if (wx < gd->linedata[yy].cellused)
1658
0
        break;
1659
0
      wx -= gd->linedata[yy].cellused;
1660
0
      yy++;
1661
0
    }
1662
0
  }
1663
0
  *px = wx;
1664
0
  *py = yy;
1665
0
}
1666
1667
/* Get length of line. */
1668
u_int
1669
grid_line_length(struct grid *gd, u_int py)
1670
0
{
1671
0
  struct grid_cell  gc;
1672
0
  u_int     px;
1673
1674
0
  px = grid_get_line(gd, py)->cellsize;
1675
0
  if (px > gd->sx)
1676
0
    px = gd->sx;
1677
0
  while (px > 0) {
1678
0
    grid_get_cell(gd, px - 1, py, &gc);
1679
0
    if ((gc.flags & GRID_FLAG_PADDING) ||
1680
0
        gc.data.size != 1 ||
1681
0
        *gc.data.data != ' ')
1682
0
      break;
1683
0
    px--;
1684
0
  }
1685
0
  return (px);
1686
0
}
1687
1688
/* Get last position on line, not including padding. */
1689
u_int
1690
grid_line_limit(struct grid *gd, u_int py)
1691
0
{
1692
0
  struct grid_cell  gc;
1693
0
  u_int     px;
1694
1695
0
  px = grid_line_length(gd, py);
1696
0
  if (px == 0)
1697
0
    return (0);
1698
0
  px--;
1699
0
  while (px > 0) {
1700
0
    grid_get_cell(gd, px, py, &gc);
1701
0
    if (~gc.flags & GRID_FLAG_PADDING)
1702
0
      break;
1703
0
    px--;
1704
0
  }
1705
0
  return (px);
1706
0
}
1707
1708
/* Check if character is in set. */
1709
int
1710
grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
1711
0
{
1712
0
  struct grid_cell  gc, tmp_gc;
1713
0
  u_int     pxx;
1714
1715
0
  grid_get_cell(gd, px, py, &gc);
1716
0
  if (strchr(set, '\t')) {
1717
0
    if (gc.flags & GRID_FLAG_PADDING) {
1718
0
      pxx = px;
1719
0
      do
1720
0
        grid_get_cell(gd, --pxx, py, &tmp_gc);
1721
0
      while (pxx > 0 && tmp_gc.flags & GRID_FLAG_PADDING);
1722
0
      if (tmp_gc.flags & GRID_FLAG_TAB)
1723
0
        return (tmp_gc.data.width - (px - pxx));
1724
0
    } else if (gc.flags & GRID_FLAG_TAB)
1725
0
      return (gc.data.width);
1726
0
  }
1727
0
  if (gc.flags & GRID_FLAG_PADDING)
1728
0
    return (0);
1729
0
  return (utf8_cstrhas(set, &gc.data));
1730
0
}
1731
1732
/* Line flags to string. */
1733
const char *
1734
grid_line_flags_string(int flags)
1735
0
{
1736
0
  static char s[128];
1737
1738
0
  *s = '\0';
1739
0
  if (flags & GRID_LINE_WRAPPED)
1740
0
    strlcat(s, "WRAPPED,", sizeof s);
1741
0
  if (flags & GRID_LINE_EXTENDED)
1742
0
    strlcat(s, "EXTENDED,", sizeof s);
1743
0
  if (flags & GRID_LINE_DEAD)
1744
0
    strlcat(s, "DEAD,", sizeof s);
1745
0
  if (flags & GRID_LINE_START_PROMPT)
1746
0
    strlcat(s, "START_PROMPT,", sizeof s);
1747
0
  if (flags & GRID_LINE_SECOND_PROMPT)
1748
0
    strlcat(s, "SECOND_PROMPT,", sizeof s);
1749
0
  if (flags & GRID_LINE_START_COMMAND)
1750
0
    strlcat(s, "START_COMMAND,", sizeof s);
1751
0
  if (flags & GRID_LINE_START_OUTPUT)
1752
0
    strlcat(s, "START_OUTPUT,", sizeof s);
1753
0
  if (flags & GRID_LINE_END_OUTPUT)
1754
0
    strlcat(s, "END_OUTPUT,", sizeof s);
1755
0
  if (flags & GRID_LINE_HYPERLINK)
1756
0
    strlcat(s, "HYPERLINK,", sizeof s);
1757
0
  if (*s == '\0')
1758
0
    return ("NONE");
1759
0
  s[strlen(s) - 1] = '\0';
1760
0
  return (s);
1761
0
}
1762
1763
/* Cell flags to string. */
1764
const char *
1765
grid_cell_flags_string(int flags)
1766
0
{
1767
0
  static char s[128];
1768
1769
0
  *s = '\0';
1770
0
  if (flags & GRID_FLAG_FG256)
1771
0
    strlcat(s, "FG256,", sizeof s);
1772
0
  if (flags & GRID_FLAG_BG256)
1773
0
    strlcat(s, "BG256,", sizeof s);
1774
0
  if (flags & GRID_FLAG_PADDING)
1775
0
    strlcat(s, "PADDING,", sizeof s);
1776
0
  if (flags & GRID_FLAG_EXTENDED)
1777
0
    strlcat(s, "EXTENDED,", sizeof s);
1778
0
  if (flags & GRID_FLAG_SELECTED)
1779
0
    strlcat(s, "SELECTED,", sizeof s);
1780
0
  if (flags & GRID_FLAG_CLEARED)
1781
0
    strlcat(s, "CLEARED,", sizeof s);
1782
0
  if (flags & GRID_FLAG_TAB)
1783
0
    strlcat(s, "TAB,", sizeof s);
1784
0
  if (flags & GRID_FLAG_NOPALETTE)
1785
0
    strlcat(s, "NOPALETTE,", sizeof s);
1786
0
  if (*s == '\0')
1787
0
    return ("NONE");
1788
0
  s[strlen(s) - 1] = '\0';
1789
0
  return (s);
1790
0
}
1791
1792
/* Cell attributes to string. */
1793
const char *
1794
grid_cell_attr_string(int attr)
1795
0
{
1796
0
  static char s[256];
1797
1798
0
  *s = '\0';
1799
0
  if (attr & GRID_ATTR_CHARSET)
1800
0
    strlcat(s, "CHARSET,", sizeof s);
1801
0
  if (attr & GRID_ATTR_BRIGHT)
1802
0
    strlcat(s, "BRIGHT,", sizeof s);
1803
0
  if (attr & GRID_ATTR_DIM)
1804
0
    strlcat(s, "DIM,", sizeof s);
1805
0
  if (attr & GRID_ATTR_UNDERSCORE)
1806
0
    strlcat(s, "UNDERSCORE,", sizeof s);
1807
0
  if (attr & GRID_ATTR_BLINK)
1808
0
    strlcat(s, "BLINK,", sizeof s);
1809
0
  if (attr & GRID_ATTR_REVERSE)
1810
0
    strlcat(s, "REVERSE,", sizeof s);
1811
0
  if (attr & GRID_ATTR_HIDDEN)
1812
0
    strlcat(s, "HIDDEN,", sizeof s);
1813
0
  if (attr & GRID_ATTR_ITALICS)
1814
0
    strlcat(s, "ITALICS,", sizeof s);
1815
0
  if (attr & GRID_ATTR_STRIKETHROUGH)
1816
0
    strlcat(s, "STRIKETHROUGH,", sizeof s);
1817
0
  if (attr & GRID_ATTR_UNDERSCORE_2)
1818
0
    strlcat(s, "UNDERSCORE_2,", sizeof s);
1819
0
  if (attr & GRID_ATTR_UNDERSCORE_3)
1820
0
    strlcat(s, "UNDERSCORE_3,", sizeof s);
1821
0
  if (attr & GRID_ATTR_UNDERSCORE_4)
1822
0
    strlcat(s, "UNDERSCORE_4,", sizeof s);
1823
0
  if (attr & GRID_ATTR_UNDERSCORE_5)
1824
0
    strlcat(s, "UNDERSCORE_5,", sizeof s);
1825
0
  if (attr & GRID_ATTR_OVERLINE)
1826
0
    strlcat(s, "OVERLINE,", sizeof s);
1827
0
  if (*s == '\0')
1828
0
    return ("NONE");
1829
0
  s[strlen(s) - 1] = '\0';
1830
0
  return (s);
1831
0
}