Coverage Report

Created: 2026-04-12 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/format-draw.c
Line
Count
Source
1
/* $OpenBSD$ */
2
3
/*
4
 * Copyright (c) 2019 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
/* Format range. */
27
struct format_range {
28
  u_int        index;
29
  struct screen     *s;
30
31
  u_int        start;
32
  u_int        end;
33
34
  enum style_range_type    type;
35
  u_int        argument;
36
  char                             string[16];
37
38
  TAILQ_ENTRY(format_range)  entry;
39
};
40
TAILQ_HEAD(format_ranges, format_range);
41
42
/* Does this range match this style? */
43
static int
44
format_is_type(struct format_range *fr, struct style *sy)
45
0
{
46
0
  if (fr->type != sy->range_type)
47
0
    return (0);
48
0
  switch (fr->type) {
49
0
  case STYLE_RANGE_NONE:
50
0
  case STYLE_RANGE_LEFT:
51
0
  case STYLE_RANGE_RIGHT:
52
0
  case STYLE_RANGE_CONTROL:
53
0
    return (1);
54
0
  case STYLE_RANGE_PANE:
55
0
  case STYLE_RANGE_WINDOW:
56
0
  case STYLE_RANGE_SESSION:
57
0
    return (fr->argument == sy->range_argument);
58
0
  case STYLE_RANGE_USER:
59
0
    return (strcmp(fr->string, sy->range_string) == 0);
60
0
  }
61
0
  return (1);
62
0
}
63
64
/* Free a range. */
65
static void
66
format_free_range(struct format_ranges *frs, struct format_range *fr)
67
0
{
68
0
  TAILQ_REMOVE(frs, fr, entry);
69
0
  free(fr);
70
0
}
71
72
/* Fix range positions. */
73
static void
74
format_update_ranges(struct format_ranges *frs, struct screen *s, u_int offset,
75
    u_int start, u_int width)
76
0
{
77
0
  struct format_range *fr, *fr1;
78
79
0
  if (frs == NULL)
80
0
    return;
81
82
0
  TAILQ_FOREACH_SAFE(fr, frs, entry, fr1) {
83
0
    if (fr->s != s)
84
0
      continue;
85
86
0
    if (fr->end <= start || fr->start >= start + width) {
87
0
      format_free_range(frs, fr);
88
0
      continue;
89
0
    }
90
91
0
    if (fr->start < start)
92
0
      fr->start = start;
93
0
    if (fr->end > start + width)
94
0
      fr->end = start + width;
95
0
    if (fr->start == fr->end) {
96
0
      format_free_range(frs, fr);
97
0
      continue;
98
0
    }
99
100
0
    fr->start -= start;
101
0
    fr->end -= start;
102
103
0
    fr->start += offset;
104
0
    fr->end += offset;
105
0
  }
106
0
}
107
108
/* Draw a part of the format. */
109
static void
110
format_draw_put(struct screen_write_ctx *octx, u_int ocx, u_int ocy,
111
    struct screen *s, struct format_ranges *frs, u_int offset, u_int start,
112
    u_int width)
113
0
{
114
  /*
115
   * The offset is how far from the cursor on the target screen; start
116
   * and width how much to copy from the source screen.
117
   */
118
0
  screen_write_cursormove(octx, ocx + offset, ocy, 0);
119
0
  screen_write_fast_copy(octx, s, start, 0, width, 1);
120
0
  format_update_ranges(frs, s, offset, start, width);
121
0
}
122
123
/* Draw list part of format. */
124
static void
125
format_draw_put_list(struct screen_write_ctx *octx,
126
    u_int ocx, u_int ocy, u_int offset, u_int width, struct screen *list,
127
    struct screen *list_left, struct screen *list_right, int focus_start,
128
    int focus_end, struct format_ranges *frs)
129
0
{
130
0
  u_int start, focus_centre;
131
132
  /* If there is enough space for the list, draw it entirely. */
133
0
  if (width >= list->cx) {
134
0
    format_draw_put(octx, ocx, ocy, list, frs, offset, 0, width);
135
0
    return;
136
0
  }
137
138
  /* The list needs to be trimmed. Try to keep the focus visible. */
139
0
  focus_centre = focus_start + (focus_end - focus_start) / 2;
140
0
  if (focus_centre < width / 2)
141
0
    start = 0;
142
0
  else
143
0
    start = focus_centre - width / 2;
144
0
  if (start + width > list->cx)
145
0
    start = list->cx - width;
146
147
  /* Draw <> markers at either side if needed. */
148
0
  if (start != 0 && width > list_left->cx) {
149
0
    screen_write_cursormove(octx, ocx + offset, ocy, 0);
150
0
    screen_write_fast_copy(octx, list_left, 0, 0, list_left->cx, 1);
151
0
    offset += list_left->cx;
152
0
    start += list_left->cx;
153
0
    width -= list_left->cx;
154
0
  }
155
0
  if (start + width < list->cx && width > list_right->cx) {
156
0
    screen_write_cursormove(octx, ocx + offset + width -
157
0
        list_right->cx, ocy, 0);
158
0
    screen_write_fast_copy(octx, list_right, 0, 0, list_right->cx,
159
0
        1);
160
0
    width -= list_right->cx;
161
0
  }
162
163
  /* Draw the list screen itself. */
164
0
  format_draw_put(octx, ocx, ocy, list, frs, offset, start, width);
165
0
}
166
167
/* Draw format with no list. */
168
static void
169
format_draw_none(struct screen_write_ctx *octx, u_int available, u_int ocx,
170
    u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
171
    struct screen *abs_centre, struct format_ranges *frs)
172
0
{
173
0
  u_int width_left, width_centre, width_right, width_abs_centre;
174
175
0
  width_left = left->cx;
176
0
  width_centre = centre->cx;
177
0
  width_right = right->cx;
178
0
  width_abs_centre = abs_centre->cx;
179
180
  /*
181
   * Try to keep as much of the left and right as possible at the expense
182
   * of the centre.
183
   */
184
0
  while (width_left + width_centre + width_right > available) {
185
0
    if (width_centre > 0)
186
0
      width_centre--;
187
0
    else if (width_right > 0)
188
0
      width_right--;
189
0
    else
190
0
      width_left--;
191
0
  }
192
193
  /* Write left. */
194
0
  format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
195
196
  /* Write right at available - width_right. */
197
0
  format_draw_put(octx, ocx, ocy, right, frs,
198
0
      available - width_right,
199
0
      right->cx - width_right,
200
0
      width_right);
201
202
  /*
203
   * Write centre halfway between
204
   *     width_left
205
   * and
206
   *     available - width_right.
207
   */
208
0
  format_draw_put(octx, ocx, ocy, centre, frs,
209
0
      width_left
210
0
      + ((available - width_right) - width_left) / 2
211
0
      - width_centre / 2,
212
0
      centre->cx / 2 - width_centre / 2,
213
0
      width_centre);
214
215
  /*
216
   * Write abs_centre in the perfect centre of all horizontal space.
217
   */
218
0
  if (width_abs_centre > available)
219
0
    width_abs_centre = available;
220
0
  format_draw_put(octx, ocx, ocy, abs_centre, frs,
221
0
      (available - width_abs_centre) / 2,
222
0
      0,
223
0
      width_abs_centre);
224
0
}
225
226
/* Draw format with list on the left. */
227
static void
228
format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx,
229
    u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
230
    struct screen *abs_centre, struct screen *list, struct screen *list_left,
231
    struct screen *list_right, struct screen *after, int focus_start,
232
    int focus_end, struct format_ranges *frs)
233
0
{
234
0
  u_int     width_left, width_centre, width_right;
235
0
  u_int     width_list, width_after, width_abs_centre;
236
0
  struct screen_write_ctx ctx;
237
238
0
  width_left = left->cx;
239
0
  width_centre = centre->cx;
240
0
  width_right = right->cx;
241
0
  width_abs_centre = abs_centre->cx;
242
0
  width_list = list->cx;
243
0
  width_after = after->cx;
244
245
  /*
246
   * Trim first the centre, then the list, then the right, then after the
247
   * list, then the left.
248
   */
249
0
  while (width_left +
250
0
      width_centre +
251
0
      width_right +
252
0
      width_list +
253
0
      width_after > available) {
254
0
    if (width_centre > 0)
255
0
      width_centre--;
256
0
    else if (width_list > 0)
257
0
      width_list--;
258
0
    else if (width_right > 0)
259
0
      width_right--;
260
0
    else if (width_after > 0)
261
0
      width_after--;
262
0
    else
263
0
      width_left--;
264
0
  }
265
266
  /* If there is no list left, pass off to the no list function. */
267
0
  if (width_list == 0) {
268
0
    screen_write_start(&ctx, left);
269
0
    screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
270
0
    screen_write_stop(&ctx);
271
272
0
    format_draw_none(octx, available, ocx, ocy, left, centre,
273
0
        right, abs_centre, frs);
274
0
    return;
275
0
  }
276
277
  /* Write left at 0. */
278
0
  format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
279
280
  /* Write right at available - width_right. */
281
0
  format_draw_put(octx, ocx, ocy, right, frs,
282
0
      available - width_right,
283
0
      right->cx - width_right,
284
0
      width_right);
285
286
  /* Write after at width_left + width_list. */
287
0
  format_draw_put(octx, ocx, ocy, after, frs,
288
0
      width_left + width_list,
289
0
      0,
290
0
      width_after);
291
292
  /*
293
   * Write centre halfway between
294
   *     width_left + width_list + width_after
295
   * and
296
   *     available - width_right.
297
   */
298
0
  format_draw_put(octx, ocx, ocy, centre, frs,
299
0
      (width_left + width_list + width_after)
300
0
      + ((available - width_right)
301
0
    - (width_left + width_list + width_after)) / 2
302
0
      - width_centre / 2,
303
0
      centre->cx / 2 - width_centre / 2,
304
0
      width_centre);
305
306
  /*
307
   * The list now goes from
308
   *     width_left
309
   * to
310
   *     width_left + width_list.
311
   * If there is no focus given, keep the left in focus.
312
   */
313
0
  if (focus_start == -1 || focus_end == -1)
314
0
    focus_start = focus_end = 0;
315
0
  format_draw_put_list(octx, ocx, ocy, width_left, width_list, list,
316
0
      list_left, list_right, focus_start, focus_end, frs);
317
318
  /*
319
   * Write abs_centre in the perfect centre of all horizontal space.
320
   */
321
0
  if (width_abs_centre > available)
322
0
    width_abs_centre = available;
323
0
  format_draw_put(octx, ocx, ocy, abs_centre, frs,
324
0
      (available - width_abs_centre) / 2,
325
0
      0,
326
0
      width_abs_centre);
327
0
}
328
329
/* Draw format with list in the centre. */
330
static void
331
format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx,
332
    u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
333
    struct screen *abs_centre, struct screen *list, struct screen *list_left,
334
    struct screen *list_right, struct screen *after, int focus_start,
335
    int focus_end, struct format_ranges *frs)
336
0
{
337
0
  u_int     width_left, width_centre, width_right, middle;
338
0
  u_int     width_list, width_after, width_abs_centre;
339
0
  struct screen_write_ctx ctx;
340
341
0
  width_left = left->cx;
342
0
  width_centre = centre->cx;
343
0
  width_right = right->cx;
344
0
  width_abs_centre = abs_centre->cx;
345
0
  width_list = list->cx;
346
0
  width_after = after->cx;
347
348
  /*
349
   * Trim first the list, then after the list, then the centre, then the
350
   * right, then the left.
351
   */
352
0
  while (width_left +
353
0
      width_centre +
354
0
      width_right +
355
0
      width_list +
356
0
      width_after > available) {
357
0
    if (width_list > 0)
358
0
      width_list--;
359
0
    else if (width_after > 0)
360
0
      width_after--;
361
0
    else if (width_centre > 0)
362
0
      width_centre--;
363
0
    else if (width_right > 0)
364
0
      width_right--;
365
0
    else
366
0
      width_left--;
367
0
  }
368
369
  /* If there is no list left, pass off to the no list function. */
370
0
  if (width_list == 0) {
371
0
    screen_write_start(&ctx, centre);
372
0
    screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
373
0
    screen_write_stop(&ctx);
374
375
0
    format_draw_none(octx, available, ocx, ocy, left, centre,
376
0
        right, abs_centre, frs);
377
0
    return;
378
0
  }
379
380
  /* Write left at 0. */
381
0
  format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
382
383
  /* Write right at available - width_right. */
384
0
  format_draw_put(octx, ocx, ocy, right, frs,
385
0
      available - width_right,
386
0
      right->cx - width_right,
387
0
      width_right);
388
389
  /*
390
   * All three centre sections are offset from the middle of the
391
   * available space.
392
   */
393
0
  middle = (width_left + ((available - width_right) - width_left) / 2);
394
395
  /*
396
   * Write centre at
397
   *     middle - width_list / 2 - width_centre.
398
   */
399
0
  format_draw_put(octx, ocx, ocy, centre, frs,
400
0
      middle - width_list / 2 - width_centre,
401
0
      0,
402
0
      width_centre);
403
404
  /*
405
   * Write after at
406
   *     middle - width_list / 2 + width_list
407
   */
408
0
  format_draw_put(octx, ocx, ocy, after, frs,
409
0
      middle - width_list / 2 + width_list,
410
0
      0,
411
0
      width_after);
412
413
  /*
414
   * The list now goes from
415
   *     middle - width_list / 2
416
   * to
417
   *     middle + width_list / 2
418
   * If there is no focus given, keep the centre in focus.
419
   */
420
0
  if (focus_start == -1 || focus_end == -1)
421
0
    focus_start = focus_end = list->cx / 2;
422
0
  format_draw_put_list(octx, ocx, ocy, middle - width_list / 2,
423
0
      width_list, list, list_left, list_right, focus_start, focus_end,
424
0
      frs);
425
426
  /*
427
   * Write abs_centre in the perfect centre of all horizontal space.
428
   */
429
0
  if (width_abs_centre > available)
430
0
    width_abs_centre = available;
431
0
  format_draw_put(octx, ocx, ocy, abs_centre, frs,
432
0
      (available - width_abs_centre) / 2,
433
0
      0,
434
0
      width_abs_centre);
435
0
}
436
437
/* Draw format with list on the right. */
438
static void
439
format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx,
440
    u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
441
    struct screen *abs_centre,     struct screen *list,
442
    struct screen *list_left, struct screen *list_right, struct screen *after,
443
    int focus_start, int focus_end, struct format_ranges *frs)
444
0
{
445
0
  u_int     width_left, width_centre, width_right;
446
0
  u_int     width_list, width_after, width_abs_centre;
447
0
  struct screen_write_ctx ctx;
448
449
0
  width_left = left->cx;
450
0
  width_centre = centre->cx;
451
0
  width_right = right->cx;
452
0
  width_abs_centre = abs_centre->cx;
453
0
  width_list = list->cx;
454
0
  width_after = after->cx;
455
456
  /*
457
   * Trim first the centre, then the list, then the right, then
458
   * after the list, then the left.
459
   */
460
0
  while (width_left +
461
0
      width_centre +
462
0
      width_right +
463
0
      width_list +
464
0
      width_after > available) {
465
0
    if (width_centre > 0)
466
0
      width_centre--;
467
0
    else if (width_list > 0)
468
0
      width_list--;
469
0
    else if (width_right > 0)
470
0
      width_right--;
471
0
    else if (width_after > 0)
472
0
      width_after--;
473
0
    else
474
0
      width_left--;
475
0
  }
476
477
  /* If there is no list left, pass off to the no list function. */
478
0
  if (width_list == 0) {
479
0
    screen_write_start(&ctx, right);
480
0
    screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
481
0
    screen_write_stop(&ctx);
482
483
0
    format_draw_none(octx, available, ocx, ocy, left, centre,
484
0
        right, abs_centre, frs);
485
0
    return;
486
0
  }
487
488
  /* Write left at 0. */
489
0
  format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
490
491
  /* Write after at available - width_after. */
492
0
  format_draw_put(octx, ocx, ocy, after, frs,
493
0
      available - width_after,
494
0
      after->cx - width_after,
495
0
      width_after);
496
497
  /*
498
   * Write right at
499
   *     available - width_right - width_list - width_after.
500
   */
501
0
  format_draw_put(octx, ocx, ocy, right, frs,
502
0
      available - width_right - width_list - width_after,
503
0
      0,
504
0
      width_right);
505
506
  /*
507
   * Write centre halfway between
508
   *     width_left
509
   * and
510
   *     available - width_right - width_list - width_after.
511
   */
512
0
  format_draw_put(octx, ocx, ocy, centre, frs,
513
0
      width_left
514
0
      + ((available - width_right - width_list - width_after)
515
0
    - width_left) / 2
516
0
      - width_centre / 2,
517
0
      centre->cx / 2 - width_centre / 2,
518
0
      width_centre);
519
520
  /*
521
   * The list now goes from
522
   *     available - width_list - width_after
523
   * to
524
   *     available - width_after
525
   * If there is no focus given, keep the right in focus.
526
   */
527
0
  if (focus_start == -1 || focus_end == -1)
528
0
    focus_start = focus_end = 0;
529
0
  format_draw_put_list(octx, ocx, ocy, available - width_list -
530
0
      width_after, width_list, list, list_left, list_right, focus_start,
531
0
      focus_end, frs);
532
533
  /*
534
   * Write abs_centre in the perfect centre of all horizontal space.
535
   */
536
0
  if (width_abs_centre > available)
537
0
    width_abs_centre = available;
538
0
  format_draw_put(octx, ocx, ocy, abs_centre, frs,
539
0
      (available - width_abs_centre) / 2,
540
0
      0,
541
0
      width_abs_centre);
542
0
}
543
544
static void
545
format_draw_absolute_centre(struct screen_write_ctx *octx, u_int available,
546
    u_int ocx, u_int ocy, struct screen *left, struct screen *centre,
547
    struct screen *right, struct screen *abs_centre, struct screen *list,
548
    struct screen *list_left, struct screen *list_right, struct screen *after,
549
    int focus_start, int focus_end, struct format_ranges *frs)
550
0
{
551
0
  u_int width_left, width_centre, width_right, width_abs_centre;
552
0
  u_int width_list, width_after, middle, abs_centre_offset;
553
554
0
  width_left = left->cx;
555
0
  width_centre = centre->cx;
556
0
  width_right = right->cx;
557
0
  width_abs_centre = abs_centre->cx;
558
0
  width_list = list->cx;
559
0
  width_after = after->cx;
560
561
  /*
562
   * Trim first centre, then the right, then the left.
563
   */
564
0
  while (width_left +
565
0
      width_centre +
566
0
      width_right > available) {
567
0
    if (width_centre > 0)
568
0
      width_centre--;
569
0
    else if (width_right > 0)
570
0
      width_right--;
571
0
    else
572
0
      width_left--;
573
0
  }
574
575
  /*
576
   * We trim list after and abs_centre independently, as we are drawing
577
   * them over the rest. Trim first the list, then after the list, then
578
   * abs_centre.
579
   */
580
0
  while (width_list + width_after + width_abs_centre > available) {
581
0
    if (width_list > 0)
582
0
      width_list--;
583
0
    else if (width_after > 0)
584
0
      width_after--;
585
0
    else
586
0
      width_abs_centre--;
587
0
  }
588
589
  /* Write left at 0. */
590
0
  format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
591
592
  /* Write right at available - width_right. */
593
0
  format_draw_put(octx, ocx, ocy, right, frs,
594
0
      available - width_right,
595
0
      right->cx - width_right,
596
0
      width_right);
597
598
  /*
599
   * Keep writing centre at the relative centre. Only the list is written
600
   * in the absolute centre of the horizontal space.
601
   */
602
0
  middle = (width_left + ((available - width_right) - width_left) / 2);
603
604
  /*
605
   * Write centre at
606
   *     middle - width_centre.
607
   */
608
0
  format_draw_put(octx, ocx, ocy, centre, frs,
609
0
    middle - width_centre,
610
0
    0,
611
0
    width_centre);
612
613
  /*
614
   * If there is no focus given, keep the centre in focus.
615
   */
616
0
  if (focus_start == -1 || focus_end == -1)
617
0
    focus_start = focus_end = list->cx / 2;
618
619
  /*
620
   * We centre abs_centre and the list together, so their shared centre is
621
   * in the perfect centre of horizontal space.
622
   */
623
0
  abs_centre_offset = (available - width_list - width_abs_centre) / 2;
624
625
  /*
626
   * Write abs_centre before the list.
627
   */
628
0
  format_draw_put(octx, ocx, ocy, abs_centre, frs, abs_centre_offset,
629
0
      0, width_abs_centre);
630
0
  abs_centre_offset += width_abs_centre;
631
632
  /*
633
   * Draw the list in the absolute centre
634
   */
635
0
  format_draw_put_list(octx, ocx, ocy, abs_centre_offset, width_list,
636
0
      list, list_left, list_right, focus_start, focus_end, frs);
637
0
  abs_centre_offset += width_list;
638
639
  /*
640
   * Write after at the end of the centre
641
   */
642
0
  format_draw_put(octx, ocx, ocy, after, frs, abs_centre_offset, 0,
643
0
      width_after);
644
0
}
645
646
/* Get width and count of any leading #s. */
647
static const char *
648
format_leading_hashes(const char *cp, u_int *n, u_int *width)
649
0
{
650
0
  for (*n = 0; cp[*n] == '#'; (*n)++)
651
0
    /* nothing */;
652
0
  if (*n == 0) {
653
0
    *width = 0;
654
0
    return (cp);
655
0
  }
656
0
  if (cp[*n] != '[') {
657
0
    if ((*n % 2) == 0)
658
0
      *width = (*n / 2);
659
0
    else
660
0
      *width = (*n / 2) + 1;
661
0
    return (cp + *n);
662
0
  }
663
0
  *width = (*n / 2);
664
0
  if ((*n % 2) == 0) {
665
    /*
666
     * An even number of #s means that all #s are escaped, so not a
667
     * style. The caller should not skip this. Return pointing to
668
     * the [.
669
     */
670
0
    return (cp + *n);
671
0
  }
672
  /* This is a style, so return pointing to the #. */
673
0
  return (cp + *n - 1);
674
0
}
675
676
/* Draw multiple characters. */
677
static void
678
format_draw_many(struct screen_write_ctx *ctx, struct style *sy, char ch,
679
    u_int n)
680
0
{
681
0
  u_int i;
682
683
0
  utf8_set(&sy->gc.data, ch);
684
0
  for (i = 0; i < n; i++)
685
0
    screen_write_cell(ctx, &sy->gc);
686
0
}
687
688
/* Draw a format to a screen. */
689
void
690
format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
691
    u_int available, const char *expanded, struct style_ranges *srs,
692
    int default_colours)
693
0
{
694
0
  enum { LEFT,
695
0
         CENTRE,
696
0
         RIGHT,
697
0
         ABSOLUTE_CENTRE,
698
0
         LIST,
699
0
         LIST_LEFT,
700
0
         LIST_RIGHT,
701
0
         AFTER,
702
0
         TOTAL } current = LEFT, last = LEFT;
703
0
  const char          *names[] = { "LEFT",
704
0
               "CENTRE",
705
0
               "RIGHT",
706
0
               "ABSOLUTE_CENTRE",
707
0
               "LIST",
708
0
               "LIST_LEFT",
709
0
               "LIST_RIGHT",
710
0
               "AFTER" };
711
0
  size_t       size = strlen(expanded);
712
0
  struct screen   *os = octx->s, s[TOTAL];
713
0
  struct screen_write_ctx  ctx[TOTAL];
714
0
  u_int      ocx = os->cx, ocy = os->cy, n, i, width[TOTAL];
715
0
  u_int      map[] = { LEFT,
716
0
             LEFT,
717
0
             CENTRE,
718
0
             RIGHT,
719
0
             ABSOLUTE_CENTRE };
720
0
  int      focus_start = -1, focus_end = -1;
721
0
  int      list_state = -1, fill = -1, even;
722
0
  enum style_align   list_align = STYLE_ALIGN_DEFAULT;
723
0
  struct grid_cell   gc, current_default, base_default;
724
0
  struct style     sy, saved_sy;
725
0
  struct utf8_data  *ud = &sy.gc.data;
726
0
  const char    *cp, *end;
727
0
  enum utf8_state    more;
728
0
  char      *tmp;
729
0
  struct format_range *fr = NULL, *fr1;
730
0
  struct format_ranges   frs;
731
0
  struct style_range  *sr;
732
733
0
  memcpy(&base_default, base, sizeof base_default);
734
0
  memcpy(&current_default, base, sizeof current_default);
735
0
  base = &base_default;
736
0
  style_set(&sy, &current_default);
737
0
  TAILQ_INIT(&frs);
738
0
  log_debug("%s: %s", __func__, expanded);
739
740
  /*
741
   * We build three screens for left, right, centre alignment, one for
742
   * the list, one for anything after the list and two for the list left
743
   * and right markers.
744
   */
745
0
  for (i = 0; i < TOTAL; i++) {
746
0
    screen_init(&s[i], size, 1, 0);
747
0
    screen_write_start(&ctx[i], &s[i]);
748
0
    screen_write_clearendofline(&ctx[i], current_default.bg);
749
0
    width[i] = 0;
750
0
  }
751
752
  /*
753
   * Walk the string and add to the corresponding screens,
754
   * parsing styles as we go.
755
   */
756
0
  cp = expanded;
757
0
  while (*cp != '\0') {
758
    /* Handle sequences of #. */
759
0
    if (cp[0] == '#' && cp[1] != '[' && cp[1] != '\0') {
760
0
      for (n = 1; cp[n] == '#'; n++)
761
0
         /* nothing */;
762
0
      even = ((n % 2) == 0);
763
0
      if (cp[n] != '[') {
764
0
        cp += n;
765
0
        if (even)
766
0
          n = (n / 2);
767
0
        else
768
0
          n = (n / 2) + 1;
769
0
        width[current] += n;
770
0
        format_draw_many(&ctx[current], &sy, '#', n);
771
0
        continue;
772
0
      }
773
0
      if (even)
774
0
        cp += (n + 1);
775
0
      else
776
0
        cp += (n - 1);
777
0
      if (sy.ignore)
778
0
        continue;
779
0
      format_draw_many(&ctx[current], &sy, '#', n / 2);
780
0
      width[current] += (n / 2);
781
0
      if (even) {
782
0
        utf8_set(ud, '[');
783
0
        screen_write_cell(&ctx[current], &sy.gc);
784
0
        width[current]++;
785
0
      }
786
0
      continue;
787
0
    }
788
789
    /* Is this not a style? */
790
0
    if (cp[0] != '#' || cp[1] != '[' || sy.ignore) {
791
      /* See if this is a UTF-8 character. */
792
0
      if ((more = utf8_open(ud, *cp)) == UTF8_MORE) {
793
0
        while (*++cp != '\0' && more == UTF8_MORE)
794
0
          more = utf8_append(ud, *cp);
795
0
        if (more != UTF8_DONE)
796
0
          cp -= ud->have;
797
0
      }
798
799
      /* Not a UTF-8 character - ASCII or not valid. */
800
0
      if (more != UTF8_DONE) {
801
0
        if (*cp < 0x20 || *cp > 0x7e) {
802
          /* Ignore nonprintable characters. */
803
0
          cp++;
804
0
          continue;
805
0
        }
806
0
        utf8_set(ud, *cp);
807
0
        cp++;
808
0
      }
809
810
      /* Draw the cell to the current screen. */
811
0
      screen_write_cell(&ctx[current], &sy.gc);
812
0
      width[current] += ud->width;
813
0
      continue;
814
0
    }
815
816
    /* This is a style. Work out where the end is and parse it. */
817
0
    end = format_skip(cp + 2, "]");
818
0
    if (end == NULL) {
819
0
      log_debug("%s: no terminating ] at '%s'", __func__,
820
0
          cp + 2);
821
0
      TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1)
822
0
          format_free_range(&frs, fr);
823
0
      goto out;
824
0
    }
825
0
    tmp = xstrndup(cp + 2, end - (cp + 2));
826
0
    style_copy(&saved_sy, &sy);
827
0
    if (style_parse(&sy, &current_default, tmp) != 0) {
828
0
      log_debug("%s: invalid style '%s'", __func__, tmp);
829
0
      free(tmp);
830
0
      cp = end + 1;
831
0
      continue;
832
0
    }
833
0
    log_debug("%s: style '%s' -> '%s'", __func__, tmp,
834
0
        style_tostring(&sy));
835
0
    free(tmp);
836
0
    if (default_colours) {
837
0
      sy.gc.bg = base->bg;
838
0
      sy.gc.fg = base->fg;
839
0
    }
840
841
    /* If this style has a fill colour, store it for later. */
842
0
    if (sy.fill != 8)
843
0
      fill = sy.fill;
844
845
    /* If this style pushed or popped the default, update it. */
846
0
    if (sy.default_type == STYLE_DEFAULT_PUSH) {
847
0
      memcpy(&current_default, &saved_sy.gc,
848
0
          sizeof current_default);
849
0
      sy.default_type = STYLE_DEFAULT_BASE;
850
0
    } else if (sy.default_type == STYLE_DEFAULT_POP) {
851
0
      memcpy(&current_default, base, sizeof current_default);
852
0
      sy.default_type = STYLE_DEFAULT_BASE;
853
0
    } else if (sy.default_type == STYLE_DEFAULT_SET) {
854
0
      memcpy(&base_default, &saved_sy.gc,
855
0
          sizeof base_default);
856
0
      memcpy(&current_default, &saved_sy.gc,
857
0
          sizeof current_default);
858
0
      sy.default_type = STYLE_DEFAULT_BASE;
859
0
    }
860
861
    /* Check the list state. */
862
0
    switch (sy.list) {
863
0
    case STYLE_LIST_ON:
864
      /*
865
       * Entering the list, exiting a marker, or exiting the
866
       * focus.
867
       */
868
0
      if (list_state != 0) {
869
0
        if (fr != NULL) { /* abort any region */
870
0
          free(fr);
871
0
          fr = NULL;
872
0
        }
873
0
        list_state = 0;
874
0
        list_align = sy.align;
875
0
      }
876
877
      /* End the focus if started. */
878
0
      if (focus_start != -1 && focus_end == -1)
879
0
        focus_end = s[LIST].cx;
880
881
0
      current = LIST;
882
0
      break;
883
0
    case STYLE_LIST_FOCUS:
884
      /* Entering the focus. */
885
0
      if (list_state != 0) /* not inside the list */
886
0
        break;
887
0
      if (focus_start == -1) /* focus already started */
888
0
        focus_start = s[LIST].cx;
889
0
      break;
890
0
    case STYLE_LIST_OFF:
891
      /* Exiting or outside the list. */
892
0
      if (list_state == 0) {
893
0
        if (fr != NULL) { /* abort any region */
894
0
          free(fr);
895
0
          fr = NULL;
896
0
        }
897
0
        if (focus_start != -1 && focus_end == -1)
898
0
          focus_end = s[LIST].cx;
899
900
0
        map[list_align] = AFTER;
901
0
        if (list_align == STYLE_ALIGN_LEFT)
902
0
          map[STYLE_ALIGN_DEFAULT] = AFTER;
903
0
        list_state = 1;
904
0
      }
905
0
      current = map[sy.align];
906
0
      break;
907
0
    case STYLE_LIST_LEFT_MARKER:
908
      /* Entering left marker. */
909
0
      if (list_state != 0) /* not inside the list */
910
0
        break;
911
0
      if (s[LIST_LEFT].cx != 0) /* already have marker */
912
0
        break;
913
0
      if (fr != NULL) { /* abort any region */
914
0
        free(fr);
915
0
        fr = NULL;
916
0
      }
917
0
      if (focus_start != -1 && focus_end == -1)
918
0
        focus_start = focus_end = -1;
919
0
      current = LIST_LEFT;
920
0
      break;
921
0
    case STYLE_LIST_RIGHT_MARKER:
922
      /* Entering right marker. */
923
0
      if (list_state != 0) /* not inside the list */
924
0
        break;
925
0
      if (s[LIST_RIGHT].cx != 0) /* already have marker */
926
0
        break;
927
0
      if (fr != NULL) { /* abort any region */
928
0
        free(fr);
929
0
        fr = NULL;
930
0
      }
931
0
      if (focus_start != -1 && focus_end == -1)
932
0
        focus_start = focus_end = -1;
933
0
      current = LIST_RIGHT;
934
0
      break;
935
0
    }
936
0
    if (current != last) {
937
0
      log_debug("%s: change %s -> %s", __func__,
938
0
          names[last], names[current]);
939
0
      last = current;
940
0
    }
941
942
    /*
943
     * Check if the range style has changed and if so end the
944
     * current range and start a new one if needed.
945
     */
946
0
    if (srs != NULL) {
947
0
      if (fr != NULL && !format_is_type(fr, &sy)) {
948
0
        if (s[current].cx != fr->start) {
949
0
          fr->end = s[current].cx + 1;
950
0
          TAILQ_INSERT_TAIL(&frs, fr, entry);
951
0
        } else
952
0
          free(fr);
953
0
        fr = NULL;
954
0
      }
955
0
      if (fr == NULL && sy.range_type != STYLE_RANGE_NONE) {
956
0
        fr = xcalloc(1, sizeof *fr);
957
0
        fr->index = current;
958
959
0
        fr->s = &s[current];
960
0
        fr->start = s[current].cx;
961
962
0
        fr->type = sy.range_type;
963
0
        fr->argument = sy.range_argument;
964
0
        strlcpy(fr->string, sy.range_string,
965
0
            sizeof fr->string);
966
0
      }
967
0
    }
968
969
0
    cp = end + 1;
970
0
  }
971
0
  free(fr);
972
973
0
  for (i = 0; i < TOTAL; i++) {
974
0
    screen_write_stop(&ctx[i]);
975
0
    log_debug("%s: width %s is %u", __func__, names[i], width[i]);
976
0
  }
977
0
  if (focus_start != -1 && focus_end != -1)
978
0
    log_debug("%s: focus %d-%d", __func__, focus_start, focus_end);
979
0
  TAILQ_FOREACH(fr, &frs, entry) {
980
0
    log_debug("%s: range %d|%u is %s %u-%u", __func__, fr->type,
981
0
        fr->argument, names[fr->index], fr->start, fr->end);
982
0
  }
983
984
  /* Clear the available area. */
985
0
  if (fill != -1) {
986
0
    memcpy(&gc, &grid_default_cell, sizeof gc);
987
0
    gc.bg = fill;
988
0
    for (i = 0; i < available; i++)
989
0
      screen_write_putc(octx, &gc, ' ');
990
0
  }
991
992
  /*
993
   * Draw the screens. How they are arranged depends on where the list
994
   * appears.
995
   */
996
0
  switch (list_align) {
997
0
  case STYLE_ALIGN_DEFAULT:
998
    /* No list. */
999
0
    format_draw_none(octx, available, ocx, ocy, &s[LEFT],
1000
0
        &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &frs);
1001
0
    break;
1002
0
  case STYLE_ALIGN_LEFT:
1003
    /* List is part of the left. */
1004
0
    format_draw_left(octx, available, ocx, ocy, &s[LEFT],
1005
0
        &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
1006
0
        &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
1007
0
        focus_start, focus_end, &frs);
1008
0
    break;
1009
0
  case STYLE_ALIGN_CENTRE:
1010
    /* List is part of the centre. */
1011
0
    format_draw_centre(octx, available, ocx, ocy, &s[LEFT],
1012
0
        &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
1013
0
        &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
1014
0
        focus_start, focus_end, &frs);
1015
0
    break;
1016
0
  case STYLE_ALIGN_RIGHT:
1017
    /* List is part of the right. */
1018
0
    format_draw_right(octx, available, ocx, ocy, &s[LEFT],
1019
0
        &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
1020
0
        &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
1021
0
        focus_start, focus_end, &frs);
1022
0
    break;
1023
0
  case STYLE_ALIGN_ABSOLUTE_CENTRE:
1024
    /* List is in the centre of the entire horizontal space. */
1025
0
    format_draw_absolute_centre(octx, available, ocx, ocy, &s[LEFT],
1026
0
        &s[CENTRE], &s[RIGHT], &s[ABSOLUTE_CENTRE], &s[LIST],
1027
0
        &s[LIST_LEFT], &s[LIST_RIGHT], &s[AFTER],
1028
0
        focus_start, focus_end, &frs);
1029
0
    break;
1030
0
  }
1031
1032
  /* Create ranges to return. */
1033
0
  TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1) {
1034
0
    sr = xcalloc(1, sizeof *sr);
1035
0
    sr->type = fr->type;
1036
0
    sr->argument = fr->argument;
1037
0
    strlcpy(sr->string, fr->string, sizeof sr->string);
1038
0
    sr->start = fr->start;
1039
0
    sr->end = fr->end;
1040
0
    TAILQ_INSERT_TAIL(srs, sr, entry);
1041
1042
0
    switch (sr->type) {
1043
0
    case STYLE_RANGE_NONE:
1044
0
      break;
1045
0
    case STYLE_RANGE_LEFT:
1046
0
      log_debug("%s: range left at %u-%u", __func__,
1047
0
          sr->start, sr->end);
1048
0
      break;
1049
0
    case STYLE_RANGE_RIGHT:
1050
0
      log_debug("%s: range right at %u-%u", __func__,
1051
0
          sr->start, sr->end);
1052
0
      break;
1053
0
    case STYLE_RANGE_PANE:
1054
0
      log_debug("%s: range pane|%%%u at %u-%u", __func__,
1055
0
          sr->argument, sr->start, sr->end);
1056
0
      break;
1057
0
    case STYLE_RANGE_WINDOW:
1058
0
      log_debug("%s: range window|%u at %u-%u", __func__,
1059
0
          sr->argument, sr->start, sr->end);
1060
0
      break;
1061
0
    case STYLE_RANGE_SESSION:
1062
0
      log_debug("%s: range session|$%u at %u-%u", __func__,
1063
0
          sr->argument, sr->start, sr->end);
1064
0
      break;
1065
0
    case STYLE_RANGE_USER:
1066
0
      log_debug("%s: range user|%u at %u-%u", __func__,
1067
0
          sr->argument, sr->start, sr->end);
1068
0
      break;
1069
0
    case STYLE_RANGE_CONTROL:
1070
0
      log_debug("%s: range control|%u at %u-%u", __func__,
1071
0
          sr->argument, sr->start, sr->end);
1072
0
      break;
1073
0
    }
1074
0
    format_free_range(&frs, fr);
1075
0
  }
1076
1077
0
out:
1078
  /* Free the screens. */
1079
0
  for (i = 0; i < TOTAL; i++)
1080
0
    screen_free(&s[i]);
1081
1082
  /* Restore the original cursor position. */
1083
0
  screen_write_cursormove(octx, ocx, ocy, 0);
1084
0
}
1085
1086
/* Get width, taking #[] into account. */
1087
u_int
1088
format_width(const char *expanded)
1089
0
{
1090
0
  const char    *cp, *end;
1091
0
  u_int      n, leading_width, width = 0;
1092
0
  struct utf8_data   ud;
1093
0
  enum utf8_state    more;
1094
1095
0
  cp = expanded;
1096
0
  while (*cp != '\0') {
1097
0
    if (*cp == '#') {
1098
0
      end = format_leading_hashes(cp, &n, &leading_width);
1099
0
      width += leading_width;
1100
0
      cp = end;
1101
0
      if (*cp == '#') {
1102
0
        end = format_skip(cp + 2, "]");
1103
0
        if (end == NULL)
1104
0
          return (0);
1105
0
        cp = end + 1;
1106
0
      }
1107
0
    } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
1108
0
      while (*++cp != '\0' && more == UTF8_MORE)
1109
0
        more = utf8_append(&ud, *cp);
1110
0
      if (more == UTF8_DONE)
1111
0
        width += ud.width;
1112
0
    } else if (*cp > 0x1f && *cp < 0x7f) {
1113
0
      width++;
1114
0
      cp++;
1115
0
    } else
1116
0
      cp++;
1117
0
  }
1118
0
  return (width);
1119
0
}
1120
1121
/*
1122
 * Trim on the left, taking #[] into account.  Note, we copy the whole set of
1123
 * unescaped #s, but only add their escaped size to width. This is because the
1124
 * format_draw function will actually do the escaping.
1125
 */
1126
char *
1127
format_trim_left(const char *expanded, u_int limit)
1128
0
{
1129
0
  char      *copy, *out;
1130
0
  const char    *cp = expanded, *end;
1131
0
  u_int      n, width = 0, leading_width;
1132
0
  struct utf8_data   ud;
1133
0
  enum utf8_state    more;
1134
1135
0
  out = copy = xcalloc(2, strlen(expanded) + 1);
1136
0
  while (*cp != '\0') {
1137
0
    if (width >= limit)
1138
0
      break;
1139
0
    if (*cp == '#') {
1140
0
      end = format_leading_hashes(cp, &n, &leading_width);
1141
0
      if (leading_width > limit - width)
1142
0
        leading_width = limit - width;
1143
0
      if (leading_width != 0) {
1144
0
        if (n == 1)
1145
0
          *out++ = '#';
1146
0
        else {
1147
0
          memset(out, '#', 2 * leading_width);
1148
0
          out += 2 * leading_width;
1149
0
        }
1150
0
        width += leading_width;
1151
0
      }
1152
0
      cp = end;
1153
0
      if (*cp == '#') {
1154
0
        end = format_skip(cp + 2, "]");
1155
0
        if (end == NULL)
1156
0
          break;
1157
0
        memcpy(out, cp, end + 1 - cp);
1158
0
        out += (end + 1 - cp);
1159
0
        cp = end + 1;
1160
0
      }
1161
0
    } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
1162
0
      while (*++cp != '\0' && more == UTF8_MORE)
1163
0
        more = utf8_append(&ud, *cp);
1164
0
      if (more == UTF8_DONE) {
1165
0
        if (width + ud.width <= limit) {
1166
0
          memcpy(out, ud.data, ud.size);
1167
0
          out += ud.size;
1168
0
        }
1169
0
        width += ud.width;
1170
0
      } else {
1171
0
        cp -= ud.have;
1172
0
        cp++;
1173
0
      }
1174
0
    } else if (*cp > 0x1f && *cp < 0x7f) {
1175
0
      if (width + 1 <= limit)
1176
0
        *out++ = *cp;
1177
0
      width++;
1178
0
      cp++;
1179
0
    } else
1180
0
      cp++;
1181
0
  }
1182
0
  *out = '\0';
1183
0
  return (copy);
1184
0
}
1185
1186
/* Trim on the right, taking #[] into account. */
1187
char *
1188
format_trim_right(const char *expanded, u_int limit)
1189
0
{
1190
0
  char      *copy, *out;
1191
0
  const char    *cp = expanded, *end;
1192
0
  u_int      width = 0, total_width, skip, n;
1193
0
  u_int      leading_width, copy_width;
1194
0
  struct utf8_data   ud;
1195
0
  enum utf8_state    more;
1196
1197
0
  total_width = format_width(expanded);
1198
0
  if (total_width <= limit)
1199
0
    return (xstrdup(expanded));
1200
0
  skip = total_width - limit;
1201
1202
0
  out = copy = xcalloc(2, strlen(expanded) + 1);
1203
0
  while (*cp != '\0') {
1204
0
    if (*cp == '#') {
1205
0
      end = format_leading_hashes(cp, &n, &leading_width);
1206
0
      copy_width = leading_width;
1207
0
      if (width <= skip) {
1208
0
        if (skip - width >= copy_width)
1209
0
          copy_width = 0;
1210
0
        else
1211
0
          copy_width -= (skip - width);
1212
0
      }
1213
0
      if (copy_width != 0) {
1214
0
        if (n == 1)
1215
0
          *out++ = '#';
1216
0
        else {
1217
0
          memset(out, '#', 2 * copy_width);
1218
0
          out += 2 * copy_width;
1219
0
        }
1220
0
      }
1221
0
      width += leading_width;
1222
0
      cp = end;
1223
0
      if (*cp == '#') {
1224
0
        end = format_skip(cp + 2, "]");
1225
0
        if (end == NULL)
1226
0
          break;
1227
0
        memcpy(out, cp, end + 1 - cp);
1228
0
        out += (end + 1 - cp);
1229
0
        cp = end + 1;
1230
0
      }
1231
0
    } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
1232
0
      while (*++cp != '\0' && more == UTF8_MORE)
1233
0
        more = utf8_append(&ud, *cp);
1234
0
      if (more == UTF8_DONE) {
1235
0
        if (width >= skip) {
1236
0
          memcpy(out, ud.data, ud.size);
1237
0
          out += ud.size;
1238
0
        }
1239
0
        width += ud.width;
1240
0
      } else {
1241
0
        cp -= ud.have;
1242
0
        cp++;
1243
0
      }
1244
0
    } else if (*cp > 0x1f && *cp < 0x7f) {
1245
0
      if (width >= skip)
1246
0
        *out++ = *cp;
1247
0
      width++;
1248
0
      cp++;
1249
0
    } else
1250
0
      cp++;
1251
0
  }
1252
0
  *out = '\0';
1253
0
  return (copy);
1254
0
}