xref: /netbsd/external/bsd/tmux/dist/format-draw.c (revision 7ee36356)
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 
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