Coverage Report

Created: 2026-10-17 06:18

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