format-draw.c revision 1.1 1 /* $OpenBSD$ */
2
3 /*
4 * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott (at) 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
37 TAILQ_ENTRY(format_range) entry;
38 };
39 TAILQ_HEAD(format_ranges, format_range);
40
41 /* Does this range match this style? */
42 static int
43 format_is_type(struct format_range *fr, struct style *sy)
44 {
45 if (fr->type != sy->range_type)
46 return (0);
47 if (fr->type == STYLE_RANGE_WINDOW &&
48 fr->argument != sy->range_argument)
49 return (0);
50 return (1);
51 }
52
53 /* Free a range. */
54 static void
55 format_free_range(struct format_ranges *frs, struct format_range *fr)
56 {
57 TAILQ_REMOVE(frs, fr, entry);
58 free(fr);
59 }
60
61 /* Fix range positions. */
62 static void
63 format_update_ranges(struct format_ranges *frs, struct screen *s, u_int offset,
64 u_int start, u_int width)
65 {
66 struct format_range *fr, *fr1;
67
68 if (frs == NULL)
69 return;
70
71 TAILQ_FOREACH_SAFE(fr, frs, entry, fr1) {
72 if (fr->s != s)
73 continue;
74
75 if (fr->end <= start || fr->start >= start + width) {
76 format_free_range(frs, fr);
77 continue;
78 }
79
80 if (fr->start < start)
81 fr->start = start;
82 if (fr->end > start + width)
83 fr->end = start + width;
84 if (fr->start == fr->end) {
85 format_free_range(frs, fr);
86 continue;
87 }
88
89 fr->start -= start;
90 fr->end -= start;
91
92 fr->start += offset;
93 fr->end += offset;
94 }
95 }
96
97 /* Draw a part of the format. */
98 static void
99 format_draw_put(struct screen_write_ctx *octx, u_int ocx, u_int ocy,
100 struct screen *s, struct format_ranges *frs, u_int offset, u_int start,
101 u_int width)
102 {
103 /*
104 * The offset is how far from the cursor on the target screen; start
105 * and width how much to copy from the source screen.
106 */
107 screen_write_cursormove(octx, ocx + offset, ocy, 0);
108 screen_write_fast_copy(octx, s, start, 0, width, 1);
109 format_update_ranges(frs, s, offset, start, width);
110 }
111
112 /* Draw list part of format. */
113 static void
114 format_draw_put_list(struct screen_write_ctx *octx,
115 u_int ocx, u_int ocy, u_int offset, u_int width, struct screen *list,
116 struct screen *list_left, struct screen *list_right, int focus_start,
117 int focus_end, struct format_ranges *frs)
118 {
119 u_int start, focus_centre;
120
121 /* If there is enough space for the list, draw it entirely. */
122 if (width >= list->cx) {
123 format_draw_put(octx, ocx, ocy, list, frs, offset, 0, width);
124 return;
125 }
126
127 /* The list needs to be trimmed. Try to keep the focus visible. */
128 focus_centre = focus_start + (focus_end - focus_start) / 2;
129 if (focus_centre < width / 2)
130 start = 0;
131 else
132 start = focus_centre - width / 2;
133 if (start + width > list->cx)
134 start = list->cx - width;
135
136 /* Draw <> markers at either side if needed. */
137 if (start != 0 && width > list_left->cx) {
138 screen_write_cursormove(octx, ocx + offset, ocy, 0);
139 screen_write_fast_copy(octx, list_left, 0, 0, list_left->cx, 1);
140 offset += list_left->cx;
141 start += list_left->cx;
142 width -= list_left->cx;
143 }
144 if (start + width < list->cx && width > list_right->cx) {
145 screen_write_cursormove(octx, ocx + offset + width - 1, ocy, 0);
146 screen_write_fast_copy(octx, list_right, 0, 0, list_right->cx,
147 1);
148 width -= list_right->cx;
149 }
150
151 /* Draw the list screen itself. */
152 format_draw_put(octx, ocx, ocy, list, frs, offset, start, width);
153 }
154
155 /* Draw format with no list. */
156 static void
157 format_draw_none(struct screen_write_ctx *octx, u_int available, u_int ocx,
158 u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
159 struct format_ranges *frs)
160 {
161 u_int width_left, width_centre, width_right;
162
163 width_left = left->cx;
164 width_centre = centre->cx;
165 width_right = right->cx;
166
167 /*
168 * Try to keep as much of the left and right as possible at the expense
169 * of the centre.
170 */
171 while (width_left + width_centre + width_right > available) {
172 if (width_centre > 0)
173 width_centre--;
174 else if (width_right > 0)
175 width_right--;
176 else
177 width_left--;
178 }
179
180 /* Write left. */
181 format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
182
183 /* Write right at available - width_right. */
184 format_draw_put(octx, ocx, ocy, right, frs,
185 available - width_right,
186 right->cx - width_right,
187 width_right);
188
189 /*
190 * Write centre halfway between
191 * width_left
192 * and
193 * available - width_right.
194 */
195 format_draw_put(octx, ocx, ocy, centre, frs,
196 width_left
197 + ((available - width_right) - width_left) / 2
198 - width_centre / 2,
199 centre->cx / 2 - width_centre / 2,
200 width_centre);
201 }
202
203 /* Draw format with list on the left. */
204 static void
205 format_draw_left(struct screen_write_ctx *octx, u_int available, u_int ocx,
206 u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
207 struct screen *list, struct screen *list_left, struct screen *list_right,
208 struct screen *after, int focus_start, int focus_end,
209 struct format_ranges *frs)
210 {
211 u_int width_left, width_centre, width_right;
212 u_int width_list, width_after;
213 struct screen_write_ctx ctx;
214
215 width_left = left->cx;
216 width_centre = centre->cx;
217 width_right = right->cx;
218 width_list = list->cx;
219 width_after = after->cx;
220
221 /*
222 * Trim first the centre, then the list, then the right, then after the
223 * list, then the left.
224 */
225 while (width_left +
226 width_centre +
227 width_right +
228 width_list +
229 width_after > available) {
230 if (width_centre > 0)
231 width_centre--;
232 else if (width_list > 0)
233 width_list--;
234 else if (width_right > 0)
235 width_right--;
236 else if (width_after > 0)
237 width_after--;
238 else
239 width_left--;
240 }
241
242 /* If there is no list left, pass off to the no list function. */
243 if (width_list == 0) {
244 screen_write_start(&ctx, NULL, left);
245 screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
246 screen_write_stop(&ctx);
247
248 format_draw_none(octx, available, ocx, ocy, left, centre,
249 right, frs);
250 return;
251 }
252
253 /* Write left at 0. */
254 format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
255
256 /* Write right at available - width_right. */
257 format_draw_put(octx, ocx, ocy, right, frs,
258 available - width_right,
259 right->cx - width_right,
260 width_right);
261
262 /* Write after at width_left + width_list. */
263 format_draw_put(octx, ocx, ocy, after, frs,
264 width_left + width_list,
265 0,
266 width_after);
267
268 /*
269 * Write centre halfway between
270 * width_left + width_list + width_after
271 * and
272 * available - width_right.
273 */
274 format_draw_put(octx, ocx, ocy, centre, frs,
275 (width_left + width_list + width_after)
276 + ((available - width_right)
277 - (width_left + width_list + width_after)) / 2
278 - width_centre / 2,
279 centre->cx / 2 - width_centre / 2,
280 width_centre);
281
282 /*
283 * The list now goes from
284 * width_left
285 * to
286 * width_left + width_list.
287 * If there is no focus given, keep the left in focus.
288 */
289 if (focus_start == -1 || focus_end == -1)
290 focus_start = focus_end = 0;
291 format_draw_put_list(octx, ocx, ocy, width_left, width_list, list,
292 list_left, list_right, focus_start, focus_end, frs);
293 }
294
295 /* Draw format with list in the centre. */
296 static void
297 format_draw_centre(struct screen_write_ctx *octx, u_int available, u_int ocx,
298 u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
299 struct screen *list, struct screen *list_left, struct screen *list_right,
300 struct screen *after, int focus_start, int focus_end,
301 struct format_ranges *frs)
302 {
303 u_int width_left, width_centre, width_right;
304 u_int width_list, width_after, middle;
305 struct screen_write_ctx ctx;
306
307 width_left = left->cx;
308 width_centre = centre->cx;
309 width_right = right->cx;
310 width_list = list->cx;
311 width_after = after->cx;
312
313 /*
314 * Trim first the list, then after the list, then the centre, then the
315 * right, then the left.
316 */
317 while (width_left +
318 width_centre +
319 width_right +
320 width_list +
321 width_after > available) {
322 if (width_list > 0)
323 width_list--;
324 else if (width_after > 0)
325 width_after--;
326 else if (width_centre > 0)
327 width_centre--;
328 else if (width_right > 0)
329 width_right--;
330 else
331 width_left--;
332 }
333
334 /* If there is no list left, pass off to the no list function. */
335 if (width_list == 0) {
336 screen_write_start(&ctx, NULL, centre);
337 screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
338 screen_write_stop(&ctx);
339
340 format_draw_none(octx, available, ocx, ocy, left, centre,
341 right, frs);
342 return;
343 }
344
345 /* Write left at 0. */
346 format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
347
348 /* Write after at available - width_after. */
349 format_draw_put(octx, ocx, ocy, after, frs,
350 available - width_after,
351 after->cx - width_after,
352 width_after);
353
354 /* Write right at available - width_right. */
355 format_draw_put(octx, ocx, ocy, right, frs,
356 available - width_right,
357 right->cx - width_right,
358 width_right);
359
360 /*
361 * All three centre sections are offset from the middle of the
362 * available space.
363 */
364 middle = (width_left + ((available - width_right) - width_left) / 2);
365
366 /*
367 * Write centre at
368 * middle - width_list / 2 - width_centre.
369 */
370 format_draw_put(octx, ocx, ocy, centre, frs,
371 middle - width_list / 2 - width_centre,
372 0,
373 width_centre);
374
375 /*
376 * Write after at
377 * middle + width_list / 2 - width_centre.
378 */
379 format_draw_put(octx, ocx, ocy, after, frs,
380 middle + width_list / 2,
381 0,
382 width_after);
383
384 /*
385 * The list now goes from
386 * middle - width_list / 2
387 * to
388 * middle + width_list / 2
389 * If there is no focus given, keep the centre in focus.
390 */
391 if (focus_start == -1 || focus_end == -1)
392 focus_start = focus_end = list->cx / 2;
393 format_draw_put_list(octx, ocx, ocy, middle - width_list / 2,
394 width_list, list, list_left, list_right, focus_start, focus_end,
395 frs);
396 }
397
398 /* Draw format with list on the right. */
399 static void
400 format_draw_right(struct screen_write_ctx *octx, u_int available, u_int ocx,
401 u_int ocy, struct screen *left, struct screen *centre, struct screen *right,
402 struct screen *list, struct screen *list_left, struct screen *list_right,
403 struct screen *after, int focus_start, int focus_end,
404 struct format_ranges *frs)
405 {
406 u_int width_left, width_centre, width_right;
407 u_int width_list, width_after;
408 struct screen_write_ctx ctx;
409
410 width_left = left->cx;
411 width_centre = centre->cx;
412 width_right = right->cx;
413 width_list = list->cx;
414 width_after = after->cx;
415
416 /*
417 * Trim first the centre, then the list, then the right, then
418 * after the list, then the left.
419 */
420 while (width_left +
421 width_centre +
422 width_right +
423 width_list +
424 width_after > available) {
425 if (width_centre > 0)
426 width_centre--;
427 else if (width_list > 0)
428 width_list--;
429 else if (width_right > 0)
430 width_right--;
431 else if (width_after > 0)
432 width_after--;
433 else
434 width_left--;
435 }
436
437 /* If there is no list left, pass off to the no list function. */
438 if (width_list == 0) {
439 screen_write_start(&ctx, NULL, right);
440 screen_write_fast_copy(&ctx, after, 0, 0, width_after, 1);
441 screen_write_stop(&ctx);
442
443 format_draw_none(octx, available, ocx, ocy, left, centre,
444 right, frs);
445 return;
446 }
447
448 /* Write left at 0. */
449 format_draw_put(octx, ocx, ocy, left, frs, 0, 0, width_left);
450
451 /* Write after at available - width_after. */
452 format_draw_put(octx, ocx, ocy, after, frs,
453 available - width_after,
454 after->cx - width_after,
455 width_after);
456
457 /*
458 * Write right at
459 * available - width_right - width_list - width_after.
460 */
461 format_draw_put(octx, ocx, ocy, right, frs,
462 available - width_right - width_list - width_after,
463 0,
464 width_right);
465
466 /*
467 * Write centre halfway between
468 * width_left
469 * and
470 * available - width_right - width_list - width_after.
471 */
472 format_draw_put(octx, ocx, ocy, centre, frs,
473 width_left
474 + ((available - width_right - width_list - width_after)
475 - width_left) / 2
476 - width_centre / 2,
477 centre->cx / 2 - width_centre / 2,
478 width_centre);
479
480 /*
481 * The list now goes from
482 * available - width_list - width_after
483 * to
484 * available - width_after
485 * If there is no focus given, keep the right in focus.
486 */
487 if (focus_start == -1 || focus_end == -1)
488 focus_start = focus_end = 0;
489 format_draw_put_list(octx, ocx, ocy, available - width_list -
490 width_after, width_list, list, list_left, list_right, focus_start,
491 focus_end, frs);
492 }
493
494 /* Draw a format to a screen. */
495 void
496 format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
497 u_int available, const char *expanded, struct style_ranges *srs)
498 {
499 enum { LEFT,
500 CENTRE,
501 RIGHT,
502 LIST,
503 LIST_LEFT,
504 LIST_RIGHT,
505 AFTER,
506 TOTAL } current = LEFT, last = LEFT;
507 const char *names[] = { "LEFT",
508 "CENTRE",
509 "RIGHT",
510 "LIST",
511 "LIST_LEFT",
512 "LIST_RIGHT",
513 "AFTER" };
514 size_t size = strlen(expanded);
515 struct screen *os = octx->s, s[TOTAL];
516 struct screen_write_ctx ctx[TOTAL];
517 u_int ocx = os->cx, ocy = os->cy, i, width[TOTAL];
518 u_int map[] = { LEFT, LEFT, CENTRE, RIGHT };
519 int focus_start = -1, focus_end = -1;
520 int list_state = -1;
521 enum style_align list_align = STYLE_ALIGN_DEFAULT;
522 struct style sy;
523 struct utf8_data *ud = &sy.gc.data;
524 const char *cp, *end;
525 enum utf8_state more;
526 char *tmp;
527 struct format_range *fr = NULL, *fr1;
528 struct format_ranges frs;
529 struct style_range *sr;
530
531 style_set(&sy, base);
532 TAILQ_INIT(&frs);
533 log_debug("%s: %s", __func__, expanded);
534
535 /*
536 * We build three screens for left, right, centre alignment, one for
537 * the list, one for anything after the list and two for the list left
538 * and right markers.
539 */
540 for (i = 0; i < TOTAL; i++) {
541 screen_init(&s[i], size, 1, 0);
542 screen_write_start(&ctx[i], NULL, &s[i]);
543 screen_write_clearendofline(&ctx[i], base->bg);
544 width[i] = 0;
545 }
546
547 /*
548 * Walk the string and add to the corresponding screens,
549 * parsing styles as we go.
550 */
551 cp = expanded;
552 while (*cp != '\0') {
553 if (cp[0] != '#' || cp[1] != '[') {
554 /* See if this is a UTF-8 character. */
555 if ((more = utf8_open(ud, *cp)) == UTF8_MORE) {
556 while (*++cp != '\0' && more == UTF8_MORE)
557 more = utf8_append(ud, *cp);
558 if (more != UTF8_DONE)
559 cp -= ud->have;
560 }
561
562 /* Not a UTF-8 character - ASCII or not valid. */
563 if (more != UTF8_DONE) {
564 if (*cp < 0x20 || *cp > 0x7e) {
565 /* Ignore nonprintable characters. */
566 cp++;
567 continue;
568 }
569 utf8_set(ud, *cp);
570 cp++;
571 }
572
573 /* Draw the cell to th current screen. */
574 screen_write_cell(&ctx[current], &sy.gc);
575 width[current] += ud->width;
576 continue;
577 }
578
579 /* This is a style. Work out where the end is and parse it. */
580 end = format_skip(cp + 2, "]");
581 if (end == NULL) {
582 log_debug("%s: no terminating ] at '%s'", __func__,
583 cp + 2);
584 TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1)
585 format_free_range(&frs, fr);
586 goto out;
587 }
588 tmp = xstrndup(cp + 2, end - (cp + 2));
589 if (style_parse(&sy, base, tmp) != 0) {
590 log_debug("%s: invalid style '%s'", __func__, tmp);
591 free(tmp);
592 cp = end + 1;
593 continue;
594 }
595 log_debug("%s: style '%s' -> '%s'", __func__, tmp,
596 style_tostring(&sy));
597 free(tmp);
598
599 /* Check the list state. */
600 switch (sy.list) {
601 case STYLE_LIST_ON:
602 /*
603 * Entering the list, exiting a marker, or exiting the
604 * focus.
605 */
606 if (list_state != 0) {
607 if (fr != NULL) { /* abort any region */
608 free(fr);
609 fr = NULL;
610 }
611 list_state = 0;
612 list_align = sy.align;
613 }
614
615 /* End the focus if started. */
616 if (focus_start != -1 && focus_end == -1)
617 focus_end = s[LIST].cx;
618
619 current = LIST;
620 break;
621 case STYLE_LIST_FOCUS:
622 /* Entering the focus. */
623 if (list_state != 0) /* not inside the list */
624 break;
625 if (focus_start == -1) /* focus already started */
626 focus_start = s[LIST].cx;
627 break;
628 case STYLE_LIST_OFF:
629 /* Exiting or outside the list. */
630 if (list_state == 0) {
631 if (fr != NULL) { /* abort any region */
632 free(fr);
633 fr = NULL;
634 }
635 if (focus_start != -1 && focus_end == -1)
636 focus_end = s[LIST].cx;
637
638 map[list_align] = AFTER;
639 if (list_align == STYLE_ALIGN_LEFT)
640 map[STYLE_ALIGN_DEFAULT] = AFTER;
641 list_state = 1;
642 }
643 current = map[sy.align];
644 break;
645 case STYLE_LIST_LEFT_MARKER:
646 /* Entering left marker. */
647 if (list_state != 0) /* not inside the list */
648 break;
649 if (s[LIST_LEFT].cx != 0) /* already have marker */
650 break;
651 if (fr != NULL) { /* abort any region */
652 free(fr);
653 fr = NULL;
654 }
655 if (focus_start != -1 && focus_end == -1)
656 focus_start = focus_end = -1;
657 current = LIST_LEFT;
658 break;
659 case STYLE_LIST_RIGHT_MARKER:
660 /* Entering right marker. */
661 if (list_state != 0) /* not inside the list */
662 break;
663 if (s[LIST_RIGHT].cx != 0) /* already have marker */
664 break;
665 if (fr != NULL) { /* abort any region */
666 free(fr);
667 fr = NULL;
668 }
669 if (focus_start != -1 && focus_end == -1)
670 focus_start = focus_end = -1;
671 current = LIST_RIGHT;
672 break;
673 }
674 if (current != last) {
675 log_debug("%s: change %s -> %s", __func__,
676 names[last], names[current]);
677 last = current;
678 }
679
680 /*
681 * Check if the range style has changed and if so end the
682 * current range and start a new one if needed.
683 */
684 if (srs != NULL) {
685 if (fr != NULL && !format_is_type(fr, &sy)) {
686 if (s[current].cx != fr->start) {
687 fr->end = s[current].cx + 1;
688 TAILQ_INSERT_TAIL(&frs, fr, entry);
689 } else
690 free(fr);
691 fr = NULL;
692 }
693 if (fr == NULL && sy.range_type != STYLE_RANGE_NONE) {
694 fr = xcalloc(1, sizeof *fr);
695 fr->index = current;
696
697 fr->s = &s[current];
698 fr->start = s[current].cx;
699
700 fr->type = sy.range_type;
701 fr->argument = sy.range_argument;
702 }
703 }
704
705 cp = end + 1;
706 }
707 free(fr);
708
709 for (i = 0; i < TOTAL; i++) {
710 screen_write_stop(&ctx[i]);
711 log_debug("%s: width %s is %u", __func__, names[i], width[i]);
712 }
713 if (focus_start != -1 && focus_end != -1)
714 log_debug("%s: focus %d-%d", __func__, focus_start, focus_end);
715 TAILQ_FOREACH(fr, &frs, entry) {
716 log_debug("%s: range %d|%u is %s %u-%u", __func__, fr->type,
717 fr->argument, names[fr->index], fr->start, fr->end);
718 }
719
720 /*
721 * Draw the screens. How they are arranged depends on where the list
722 * appearsq.
723 */
724 switch (list_align) {
725 case STYLE_ALIGN_DEFAULT:
726 /* No list. */
727 format_draw_none(octx, available, ocx, ocy, &s[LEFT],
728 &s[CENTRE], &s[RIGHT], &frs);
729 break;
730 case STYLE_ALIGN_LEFT:
731 /* List is part of the left. */
732 format_draw_left(octx, available, ocx, ocy, &s[LEFT],
733 &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT],
734 &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs);
735 break;
736 case STYLE_ALIGN_CENTRE:
737 /* List is part of the centre. */
738 format_draw_centre(octx, available, ocx, ocy, &s[LEFT],
739 &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT],
740 &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs);
741 break;
742 case STYLE_ALIGN_RIGHT:
743 /* List is part of the right. */
744 format_draw_right(octx, available, ocx, ocy, &s[LEFT],
745 &s[CENTRE], &s[RIGHT], &s[LIST], &s[LIST_LEFT],
746 &s[LIST_RIGHT], &s[AFTER], focus_start, focus_end, &frs);
747 break;
748 }
749
750 /* Create ranges to return. */
751 TAILQ_FOREACH_SAFE(fr, &frs, entry, fr1) {
752 sr = xcalloc(1, sizeof *sr);
753 sr->type = fr->type;
754 sr->argument = fr->argument;
755 sr->start = fr->start;
756 sr->end = fr->end;
757 TAILQ_INSERT_TAIL(srs, sr, entry);
758
759 log_debug("%s: range %d|%u at %u-%u", __func__, sr->type,
760 sr->argument, sr->start, sr->end);
761
762 format_free_range(&frs, fr);
763 }
764
765 out:
766 /* Free the screens. */
767 for (i = 0; i < TOTAL; i++)
768 screen_free(&s[i]);
769
770 /* Restore the original cursor position. */
771 screen_write_cursormove(octx, ocx, ocy, 0);
772 }
773
774 /* Get width, taking #[] into account. */
775 u_int
776 format_width(const char *expanded)
777 {
778 const char *cp, *end;
779 u_int width = 0;
780 struct utf8_data ud;
781 enum utf8_state more;
782
783 cp = expanded;
784 while (*cp != '\0') {
785 if (cp[0] == '#' && cp[1] == '[') {
786 end = format_skip(cp + 2, "]");
787 if (end == NULL)
788 return 0;
789 cp = end + 1;
790 } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
791 while (*++cp != '\0' && more == UTF8_MORE)
792 more = utf8_append(&ud, *cp);
793 if (more == UTF8_DONE)
794 width += ud.width;
795 else
796 cp -= ud.have;
797 } else if (*cp > 0x1f && *cp < 0x7f) {
798 width++;
799 cp++;
800 }
801 }
802 return (width);
803 }
804
805 /* Trim on the left, taking #[] into account. */
806 char *
807 format_trim_left(const char *expanded, u_int limit)
808 {
809 char *copy, *out;
810 const char *cp = expanded, *end;
811 u_int width = 0;
812 struct utf8_data ud;
813 enum utf8_state more;
814
815 out = copy = xmalloc(strlen(expanded) + 1);
816 while (*cp != '\0') {
817 if (cp[0] == '#' && cp[1] == '[') {
818 end = format_skip(cp + 2, "]");
819 if (end == NULL)
820 break;
821 memcpy(out, cp, end + 1 - cp);
822 out += (end + 1 - cp);
823 cp = end + 1;
824 } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
825 while (*++cp != '\0' && more == UTF8_MORE)
826 more = utf8_append(&ud, *cp);
827 if (more == UTF8_DONE) {
828 if (width + ud.width <= limit) {
829 memcpy(out, ud.data, ud.size);
830 out += ud.size;
831 }
832 width += ud.width;
833 } else
834 cp -= ud.have;
835 } else if (*cp > 0x1f && *cp < 0x7f) {
836 if (width + 1 <= limit)
837 *out++ = *cp;
838 width++;
839 cp++;
840 } else
841 cp++;
842 }
843 *out = '\0';
844 return (copy);
845 }
846
847 /* Trim on the right, taking #[] into account. */
848 char *
849 format_trim_right(const char *expanded, u_int limit)
850 {
851 char *copy, *out;
852 const char *cp = expanded, *end;
853 u_int width = 0, total_width, skip;
854 struct utf8_data ud;
855 enum utf8_state more;
856
857 total_width = format_width(expanded);
858 if (total_width <= limit)
859 return (xstrdup(expanded));
860 skip = total_width - limit;
861
862 out = copy = xmalloc(strlen(expanded) + 1);
863 while (*cp != '\0') {
864 if (cp[0] == '#' && cp[1] == '[') {
865 end = format_skip(cp + 2, "]");
866 if (end == NULL)
867 break;
868 memcpy(out, cp, end + 1 - cp);
869 out += (end + 1 - cp);
870 cp = end + 1;
871 } else if ((more = utf8_open(&ud, *cp)) == UTF8_MORE) {
872 while (*++cp != '\0' && more == UTF8_MORE)
873 more = utf8_append(&ud, *cp);
874 if (more == UTF8_DONE) {
875 if (width >= skip) {
876 memcpy(out, ud.data, ud.size);
877 out += ud.size;
878 }
879 width += ud.width;
880 } else
881 cp -= ud.have;
882 } else if (*cp > 0x1f && *cp < 0x7f) {
883 if (width >= skip)
884 *out++ = *cp;
885 width++;
886 cp++;
887 } else
888 cp++;
889 }
890 *out = '\0';
891 return (copy);
892 }
893