Coverage Report

Created: 2026-10-05 06:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/fuzzy.c
Line
Count
Source
1
/* $OpenBSD: fuzzy.c,v 1.2 2026/09/28 16:55:08 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2026 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 <ctype.h>
22
#include <stdlib.h>
23
#include <string.h>
24
25
#include "tmux.h"
26
27
/*
28
 * Fuzzy matching in the style of fzf. The pattern is split into groups by |
29
 * and each group is split on spaces into terms. A row matches if any group
30
 * matches; within a group all positive terms must match and all inverse terms
31
 * must not match.
32
 *
33
 * Plain positive terms are fuzzy subsequences. A leading ' makes a term an
34
 * exact substring match, ^ anchors a term at the start and $ anchors it at
35
 * the end. A leading ! inverts the term. Plain inverse terms are exact
36
 * substring matches rather than inverse fuzzy matches, like fzf.
37
 *
38
 * Both the pattern and the text are UTF-8. The text may contain tmux style
39
 * directives (#[...]); these and their contents are invisible to matching and
40
 * occupy no columns, but align= styles do move the surrounding text and are
41
 * accounted for exactly as format_draw lays it out (the no-list layout, see
42
 * format_draw_none). Matching is smart-case: case is ignored unless the pattern
43
 * contains an uppercase character (ASCII case folding only; other characters
44
 * are compared exactly by their UTF-8 data).
45
 *
46
 * On a match a bitstr_t of the requested display width is returned with a bit
47
 * set for every column occupied by a matched character, so the caller can
48
 * highlight them; NULL is returned if there is no match. A cheap fzf-style
49
 * score (matches at the start, after word boundaries and in contiguous runs
50
 * score higher) is also produced so callers can rank best-match-first.
51
 */
52
53
0
#define FUZZY_BONUS_EXACT 1000
54
0
#define FUZZY_BONUS_PREFIX 200
55
0
#define FUZZY_BONUS_SUFFIX 100
56
0
#define FUZZY_BONUS_START 12
57
0
#define FUZZY_BONUS_BOUNDARY 8
58
0
#define FUZZY_BONUS_CONSECUTIVE 6
59
0
#define FUZZY_PENALTY_LEADING 1
60
0
#define FUZZY_PENALTY_LEADING_MAX 10
61
0
#define FUZZY_PENALTY_GAP 1
62
63
/* A single visible character of the text. */
64
struct fuzzy_char {
65
  enum style_align   align;
66
  struct utf8_data   ud;    /* original UTF-8 data */
67
  u_int      width;   /* display width */
68
  u_int      offset;  /* within its alignment */
69
};
70
71
/* One parsed query term. */
72
struct fuzzy_term {
73
  int      inverse;
74
  int      exact;
75
  int      prefix;
76
  int      suffix;
77
  const char    *text;
78
  size_t       len;
79
};
80
81
/* Is this character a word boundary, so a match after it scores higher? */
82
static int
83
fuzzy_is_boundary(const struct utf8_data *ud)
84
0
{
85
0
  static const char *boundary = " -_/.:";
86
87
0
  if (ud->size != 1)
88
0
    return (0);
89
0
  return (strchr(boundary, ud->data[0]) != NULL);
90
0
}
91
92
/*
93
 * Compare two characters, folding ASCII case if wanted. UTF-8 is compared
94
 * directly without case folding.
95
 */
96
static int
97
fuzzy_char_equal(const struct utf8_data *a, const struct utf8_data *b, int fold)
98
0
{
99
0
  if (fold &&
100
0
      a->size == 1 &&
101
0
      b->size == 1 &&
102
0
      a->data[0] < 0x80 &&
103
0
      b->data[0] < 0x80)
104
0
    return (tolower(a->data[0]) == tolower(b->data[0]));
105
0
  return (a->size == b->size && memcmp(a->data, b->data, a->size) == 0);
106
0
}
107
108
/* Map a style alignment onto one of the four layout columns. */
109
static enum style_align
110
fuzzy_align(enum style_align align)
111
0
{
112
0
  if (align == STYLE_ALIGN_DEFAULT)
113
0
    return (STYLE_ALIGN_LEFT);
114
0
  return (align);
115
0
}
116
117
/* Add a visible character to the array, updating the alignment width. */
118
static void
119
fuzzy_add(struct fuzzy_char **cs, u_int *ncs, u_int *alloc, enum style_align a,
120
    const struct utf8_data *ud, u_int *widths)
121
0
{
122
0
  struct fuzzy_char *fc;
123
124
0
  if (*ncs == *alloc) {
125
0
    *alloc = (*alloc == 0) ? 64 : *alloc * 2;
126
0
    *cs = xreallocarray(*cs, *alloc, sizeof **cs);
127
0
  }
128
0
  fc = &(*cs)[(*ncs)++];
129
0
  fc->align = a;
130
0
  memcpy(&fc->ud, ud, sizeof fc->ud);
131
0
  fc->width = ud->width;
132
0
  fc->offset = widths[a];
133
0
  widths[a] += ud->width;
134
0
}
135
136
/* Decode a character as UTF-8. */
137
static const char *
138
fuzzy_decode_one(const char *cp, const char *end, struct utf8_data *ud)
139
0
{
140
0
  enum utf8_state  more;
141
0
  const char  *start = cp;
142
143
0
  if ((more = utf8_open(ud, (u_char)*cp)) == UTF8_MORE) {
144
0
    while (++cp != end && more == UTF8_MORE)
145
0
      more = utf8_append(ud, (u_char)*cp);
146
0
    if (more == UTF8_DONE)
147
0
      return (cp);
148
0
    cp = start;
149
0
  }
150
0
  utf8_set(ud, (u_char)*cp);
151
0
  return (cp + 1);
152
0
}
153
154
/*
155
 * Scan the text into an array of visible characters, skipping styles and
156
 * recording the alignment and intra-alignment offset of each. Returns the
157
 * array and its length and fills in the per-alignment widths.
158
 */
159
static struct fuzzy_char *
160
fuzzy_scan(const char *text, u_int *ncs, u_int *widths)
161
0
{
162
0
  struct fuzzy_char *cs = NULL;
163
0
  u_int      alloc = 0, n, leading, i;
164
0
  enum style_align   current = STYLE_ALIGN_LEFT;
165
0
  struct style     sy;
166
0
  const char    *cp = text, *textend = text + strlen(text);
167
0
  const char    *end;
168
0
  struct utf8_data   ud, hash, bracket;
169
0
  char      *tmp;
170
171
0
  *ncs = 0;
172
0
  memset(widths, 0, sizeof *widths * (STYLE_ALIGN_ABSOLUTE_CENTRE + 1));
173
0
  style_set(&sy, &grid_default_cell);
174
0
  utf8_set(&hash, '#');
175
0
  utf8_set(&bracket, '[');
176
177
0
  while (*cp != '\0') {
178
    /* Handle a run of #s, which may introduce a style. */
179
0
    if (*cp == '#') {
180
0
      for (n = 0; cp[n] == '#'; n++)
181
0
        /* nothing */;
182
0
      if (cp[n] != '[') {
183
        /* Escaped #s: ##->#, so half (rounded up). */
184
0
        leading = (n % 2 == 0) ? n / 2 : n / 2 + 1;
185
0
        for (i = 0; i < leading; i++) {
186
0
          fuzzy_add(&cs, ncs, &alloc, current,
187
0
              &hash, widths);
188
0
        }
189
0
        cp += n;
190
0
        continue;
191
0
      }
192
193
      /* Even count: all #s escaped, the [ is literal. */
194
0
      for (i = 0; i < n / 2; i++)
195
0
        fuzzy_add(&cs, ncs, &alloc, current, &hash,
196
0
            widths);
197
0
      if (n % 2 == 0) {
198
0
        fuzzy_add(&cs, ncs, &alloc, current, &bracket,
199
0
            widths);
200
0
        cp += n + 1;
201
0
        continue;
202
0
      }
203
204
      /* Odd count: this is a style, find and parse it. */
205
0
      end = format_skip(cp + n + 1, "]");
206
0
      if (end == NULL)
207
0
        break;
208
0
      tmp = xstrndup(cp + n + 1, end - (cp + n + 1));
209
0
      if (style_parse(&sy, &grid_default_cell, tmp) == 0)
210
0
        current = fuzzy_align(sy.align);
211
0
      free(tmp);
212
0
      cp = end + 1;
213
0
      continue;
214
0
    }
215
216
    /* Decode one character, multibyte or single byte. */
217
0
    cp = fuzzy_decode_one(cp, textend, &ud);
218
219
    /*
220
     * Skip non-printable single bytes (control characters and raw
221
     * bytes left over from a failed decode); keep printable ASCII
222
     * and any decoded UTF-8.
223
     */
224
0
    if (ud.size == 1 && (ud.data[0] <= 0x1f || ud.data[0] >= 0x7f))
225
0
      continue;
226
0
    fuzzy_add(&cs, ncs, &alloc, current, &ud, widths);
227
0
  }
228
0
  return (cs);
229
0
}
230
231
/*
232
 * Work out the display column of a visible character given the trimmed widths
233
 * and start columns of each alignment. Returns 0 and sets the column if the
234
 * character is visible, otherwise returns -1.
235
 */
236
static int
237
fuzzy_column(const struct fuzzy_char *fc, const u_int *start, const u_int *src,
238
    const u_int *vis, u_int *column)
239
0
{
240
0
  enum style_align  a = fc->align;
241
242
0
  if (fc->offset < src[a] || fc->offset >= src[a] + vis[a])
243
0
    return (-1);
244
0
  *column = start[a] + (fc->offset - src[a]);
245
0
  return (0);
246
0
}
247
248
/* Decode a UTF-8 term, stopping if it cannot fit in the given limit. */
249
static int
250
fuzzy_decode(const char *tok, size_t len, struct utf8_data *out, u_int limit,
251
    u_int *n)
252
0
{
253
0
  const char  *cp = tok, *end = tok + len;
254
255
0
  *n = 0;
256
0
  while (cp != end) {
257
0
    if (*n == limit)
258
0
      return (-1);
259
0
    cp = fuzzy_decode_one(cp, end, &out[(*n)++]);
260
0
  }
261
0
  return (0);
262
0
}
263
264
/* Add the score for a fuzzy token matched at the given positions. */
265
static int
266
fuzzy_score_positions(const u_int *pos, u_int npos, const struct fuzzy_char *cs)
267
0
{
268
0
  u_int i, gap, span;
269
0
  int score = 0;
270
271
0
  if (npos == 0)
272
0
    return (0);
273
0
  if (pos[0] == 0)
274
0
    score += FUZZY_BONUS_START;
275
0
  else {
276
0
    if (fuzzy_is_boundary(&cs[pos[0] - 1].ud))
277
0
      score += FUZZY_BONUS_BOUNDARY;
278
0
    if (pos[0] < FUZZY_PENALTY_LEADING_MAX)
279
0
      score -= pos[0] * FUZZY_PENALTY_LEADING;
280
0
    else {
281
0
      score -= FUZZY_PENALTY_LEADING_MAX *
282
0
          FUZZY_PENALTY_LEADING;
283
0
    }
284
0
  }
285
0
  for (i = 1; i < npos; i++) {
286
0
    if (pos[i] == pos[i - 1] + 1)
287
0
      score += FUZZY_BONUS_CONSECUTIVE;
288
0
    else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud))
289
0
      score += FUZZY_BONUS_BOUNDARY;
290
0
  }
291
0
  span = pos[npos - 1] - pos[0] + 1;
292
0
  gap = span - npos;
293
0
  score -= gap * FUZZY_PENALTY_GAP;
294
0
  return (score);
295
0
}
296
297
/*
298
 * Match a token as a subsequence of the visible characters. Returns if the
299
 * token matches.
300
 */
301
static int
302
fuzzy_match_fuzzy(const struct utf8_data *tok, u_int toklen,
303
    struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
304
0
{
305
0
  u_int pi, ci, *pos;
306
0
  int found, value;
307
308
0
  if (toklen == 0 || ncs == 0)
309
0
    return (0);
310
0
  pos = xcalloc(toklen, sizeof *pos);
311
312
  /* First find a subsequence from the start. */
313
0
  ci = 0;
314
0
  for (pi = 0; pi < toklen; pi++) {
315
0
    while (ci != ncs &&
316
0
        !fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold))
317
0
      ci++;
318
0
    if (ci == ncs) {
319
0
      free(pos);
320
0
      return (0);
321
0
    }
322
0
    pos[pi] = ci++;
323
0
  }
324
325
  /* Then compact it backwards to prefer a shorter span. */
326
0
  ci = pos[toklen - 1];
327
0
  for (pi = toklen; pi > 0; pi--) {
328
0
    found = 0;
329
0
    for (;;) {
330
0
      if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) {
331
0
        pos[pi - 1] = ci;
332
0
        found = 1;
333
0
        break;
334
0
      }
335
0
      if (ci == 0)
336
0
        break;
337
0
      ci--;
338
0
    }
339
0
    if (!found) {
340
0
      free(pos);
341
0
      return (0);
342
0
    }
343
0
    if (pi != 1)
344
0
      ci--;
345
0
  }
346
347
0
  value = fuzzy_score_positions(pos, toklen, cs);
348
0
  *score += value;
349
0
  for (pi = 0; pi < toklen; pi++)
350
0
    matched[pos[pi]] = 1;
351
0
  free(pos);
352
0
  return (1);
353
0
}
354
355
/* Score an exact, prefix or suffix match. */
356
static int
357
fuzzy_score_exact(u_int start, u_int toklen, u_int ncs,
358
    const struct fuzzy_char *cs, int prefix, int suffix)
359
0
{
360
0
  int score;
361
362
0
  score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE;
363
0
  if (prefix)
364
0
    score += FUZZY_BONUS_PREFIX;
365
0
  if (suffix)
366
0
    score += FUZZY_BONUS_SUFFIX;
367
0
  if (start == 0)
368
0
    score += FUZZY_BONUS_START;
369
0
  else if (fuzzy_is_boundary(&cs[start - 1].ud))
370
0
    score += FUZZY_BONUS_BOUNDARY;
371
0
  if (start < FUZZY_PENALTY_LEADING_MAX)
372
0
    score -= start * FUZZY_PENALTY_LEADING;
373
0
  else
374
0
    score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING;
375
0
  if (!prefix && !suffix)
376
0
    score -= ncs - (start + toklen);
377
0
  return (score);
378
0
}
379
380
/* Match an exact, prefix or suffix term against the visible characters. */
381
static int
382
fuzzy_match_exact(const struct utf8_data *tok, u_int toklen,
383
    struct fuzzy_char *cs, u_int ncs, int fold, int prefix, int suffix,
384
    int *score, char *matched)
385
0
{
386
0
  u_int start, end, i, j, best = 0;
387
0
  int ok, found = 0, value, bestscore = 0;
388
389
0
  if (toklen == 0 || toklen > ncs)
390
0
    return (0);
391
392
0
  if (prefix && suffix) {
393
0
    if (toklen != ncs)
394
0
      return (0);
395
0
    start = 0;
396
0
    end = 1;
397
0
  } else if (prefix) {
398
0
    start = 0;
399
0
    end = 1;
400
0
  } else if (suffix) {
401
0
    start = ncs - toklen;
402
0
    end = start + 1;
403
0
  } else {
404
0
    start = 0;
405
0
    end = ncs - toklen + 1;
406
0
  }
407
408
0
  for (i = start; i < end; i++) {
409
0
    ok = 1;
410
0
    for (j = 0; j < toklen; j++) {
411
0
      if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) {
412
0
        ok = 0;
413
0
        break;
414
0
      }
415
0
    }
416
0
    if (!ok)
417
0
      continue;
418
0
    value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix);
419
0
    if (!found || value > bestscore) {
420
0
      found = 1;
421
0
      best = i;
422
0
      bestscore = value;
423
0
    }
424
0
  }
425
0
  if (!found)
426
0
    return (0);
427
0
  *score += bestscore;
428
0
  if (matched != NULL) {
429
0
    for (i = 0; i < toklen; i++)
430
0
      matched[best + i] = 1;
431
0
  }
432
0
  return (1);
433
0
}
434
435
/* Parse one term. */
436
static int
437
fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term)
438
0
{
439
0
  memset(term, 0, sizeof *term);
440
0
  if (start == end)
441
0
    return (0);
442
0
  if (*start == '!') {
443
0
    term->inverse = 1;
444
0
    start++;
445
0
  }
446
0
  if (start == end)
447
0
    return (0);
448
0
  if (*start == '\'') {
449
0
    term->exact = 1;
450
0
    start++;
451
0
  } else if (*start == '^') {
452
0
    term->exact = 1;
453
0
    term->prefix = 1;
454
0
    start++;
455
0
  }
456
0
  if (start == end)
457
0
    return (0);
458
0
  if (end[-1] == '$') {
459
0
    term->exact = 1;
460
0
    term->suffix = 1;
461
0
    end--;
462
0
  }
463
0
  if (start == end)
464
0
    return (0);
465
466
0
  if (term->inverse)
467
0
    term->exact = 1;
468
0
  term->text = start;
469
0
  term->len = end - start;
470
0
  return (1);
471
0
}
472
473
/* Match one parsed term. */
474
static int
475
fuzzy_match_term(const struct fuzzy_term *term, struct utf8_data *tok,
476
    struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
477
0
{
478
0
  u_int toklen;
479
0
  int value = 0, matched_term;
480
481
0
  if (fuzzy_decode(term->text, term->len, tok, ncs, &toklen) != 0)
482
0
    return (term->inverse);
483
0
  if (term->exact) {
484
0
    matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold,
485
0
        term->prefix, term->suffix, &value,
486
0
        term->inverse ? NULL : matched);
487
0
  } else {
488
0
    matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold,
489
0
        &value, term->inverse ? NULL : matched);
490
0
  }
491
492
0
  if (term->inverse)
493
0
    return (!matched_term);
494
0
  if (!matched_term)
495
0
    return (0);
496
0
  *score += value;
497
0
  return (1);
498
0
}
499
500
/* Match one AND group of terms. */
501
static int
502
fuzzy_match_group(const char *start, const char *end, struct utf8_data *tok,
503
    struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
504
0
{
505
0
  const char    *cp = start, *sp;
506
0
  struct fuzzy_term  term;
507
0
  int      any = 0;
508
509
0
  *score = 0;
510
0
  while (cp != end) {
511
0
    while (cp != end && *cp == ' ')
512
0
      cp++;
513
0
    if (cp == end)
514
0
      break;
515
0
    sp = cp;
516
0
    while (cp != end && *cp != ' ')
517
0
      cp++;
518
0
    if (!fuzzy_parse_term(sp, cp, &term))
519
0
      return (0);
520
0
    any = 1;
521
0
    if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score,
522
0
        matched))
523
0
      return (0);
524
0
  }
525
0
  return (any);
526
0
}
527
528
/*
529
 * Fuzzy match pattern against text, which is drawn into a region of the given
530
 * display width. Returns a bitstr_t of width bits with a bit set for each
531
 * column occupied by a matched character, or NULL if there is no match. A
532
 * higher returned score is better.
533
 */
534
bitstr_t *
535
fuzzy_match(const char *pattern, const char *text, u_int width, u_int *score)
536
0
{
537
0
  struct fuzzy_char *cs;
538
0
  char      *matched = NULL, *best = NULL, *groupmatched;
539
0
  struct utf8_data  *tok;
540
0
  bitstr_t    *mask;
541
0
  u_int      ncs, i, j, column;
542
0
  u_int      widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
543
0
  u_int      start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
544
0
  u_int      src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
545
0
  u_int      vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
546
0
  u_int      wl, wc, wr, wa;
547
0
  size_t       toksize;
548
0
  const char    *cp, *sp;
549
0
  int      bestscore = 0, groupscore, found = 0, fold;
550
551
0
  if (width == 0)
552
0
    return (NULL);
553
554
  /* An empty query matches everything, with nothing highlighted. */
555
0
  for (cp = pattern; *cp == ' ' || *cp == '|'; cp++)
556
0
    /* nothing */;
557
0
  if (*cp == '\0') {
558
0
    if (score != NULL)
559
0
      *score = 0;
560
0
    return (bit_alloc(width));
561
0
  }
562
563
  /* Smart-case: fold unless the pattern has an uppercase character. */
564
0
  fold = 1;
565
0
  for (cp = pattern; *cp != '\0'; cp++) {
566
0
    if (*cp >= 'A' && *cp <= 'Z') {
567
0
      fold = 0;
568
0
      break;
569
0
    }
570
0
  }
571
572
  /* Scan the text into visible characters. */
573
0
  cs = fuzzy_scan(text, &ncs, widths);
574
0
  matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched);
575
0
  best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best);
576
577
  /* A term with more characters than the text cannot match. */
578
0
  toksize = strlen(pattern);
579
0
  if (toksize > ncs)
580
0
    toksize = ncs;
581
0
  tok = xreallocarray(NULL, toksize == 0 ? 1 : toksize, sizeof *tok);
582
583
  /* Match each |-separated group and keep the best-scoring one. */
584
0
  cp = pattern;
585
0
  while (*cp != '\0') {
586
0
    while (*cp == ' ' || *cp == '|')
587
0
      cp++;
588
0
    if (*cp == '\0')
589
0
      break;
590
0
    sp = cp;
591
0
    while (*cp != '\0' && *cp != '|')
592
0
      cp++;
593
0
    memset(matched, 0, ncs == 0 ? 1 : ncs);
594
0
    groupmatched = matched;
595
0
    if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold,
596
0
        &groupscore, groupmatched)) {
597
0
      if (!found || groupscore > bestscore) {
598
0
        found = 1;
599
0
        bestscore = groupscore;
600
0
        memcpy(best, matched, ncs == 0 ? 1 : ncs);
601
0
      }
602
0
    }
603
0
  }
604
0
  free(tok);
605
0
  if (!found) {
606
0
    free(best);
607
0
    free(matched);
608
0
    free(cs);
609
0
    return (NULL);
610
0
  }
611
612
  /*
613
   * Work out the trimmed widths and start columns of each alignment,
614
   * mirroring format_draw_none.
615
   */
616
0
  wl = widths[STYLE_ALIGN_LEFT];
617
0
  wc = widths[STYLE_ALIGN_CENTRE];
618
0
  wr = widths[STYLE_ALIGN_RIGHT];
619
0
  wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE];
620
0
  while (wl + wc + wr > width) {
621
0
    if (wc > 0)
622
0
      wc--;
623
0
    else if (wr > 0)
624
0
      wr--;
625
0
    else
626
0
      wl--;
627
0
  }
628
0
  if (wa > width)
629
0
    wa = width;
630
631
0
  start[STYLE_ALIGN_LEFT] = 0;
632
0
  src[STYLE_ALIGN_LEFT] = 0;
633
0
  vis[STYLE_ALIGN_LEFT] = wl;
634
635
0
  start[STYLE_ALIGN_RIGHT] = width - wr;
636
0
  src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr;
637
0
  vis[STYLE_ALIGN_RIGHT] = wr;
638
639
0
  start[STYLE_ALIGN_CENTRE] =
640
0
      wl + ((width - wr) - wl) / 2 - wc / 2;
641
0
  src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2;
642
0
  vis[STYLE_ALIGN_CENTRE] = wc;
643
644
0
  start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2;
645
0
  src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0;
646
0
  vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa;
647
648
  /* Set a bit for each column of each matched character. */
649
0
  mask = bit_alloc(width);
650
0
  for (i = 0; i < ncs; i++) {
651
0
    if (!best[i])
652
0
      continue;
653
0
    if (fuzzy_column(&cs[i], start, src, vis, &column) != 0)
654
0
      continue;
655
0
    for (j = 0; j < cs[i].width && column + j < width; j++)
656
0
      bit_set(mask, column + j);
657
0
  }
658
659
0
  free(best);
660
0
  free(matched);
661
0
  free(cs);
662
663
0
  if (score != NULL)
664
0
    *score = (bestscore < 0) ? 0 : (u_int)bestscore;
665
0
  return (mask);
666
0
}