Coverage Report

Created: 2026-08-13 06:07

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.1 2026/06/26 14:40:30 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
72
#define FUZZY_BONUS_EXACT 1000
54
0
#define FUZZY_BONUS_PREFIX 200
55
0
#define FUZZY_BONUS_SUFFIX 100
56
2
#define FUZZY_BONUS_START 12
57
3
#define FUZZY_BONUS_BOUNDARY 8
58
78
#define FUZZY_BONUS_CONSECUTIVE 6
59
101
#define FUZZY_PENALTY_LEADING 1
60
137
#define FUZZY_PENALTY_LEADING_MAX 10
61
31
#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
101
{
85
101
  static const char *boundary = " -_/.:";
86
87
101
  if (ud->size != 1)
88
0
    return (0);
89
101
  return (strchr(boundary, ud->data[0]) != NULL);
90
101
}
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
17.9k
{
99
17.9k
  if (fold &&
100
9.07k
      a->size == 1 &&
101
9.07k
      b->size == 1 &&
102
9.07k
      a->data[0] < 0x80 &&
103
4.17k
      b->data[0] < 0x80)
104
4.17k
    return (tolower(a->data[0]) == tolower(b->data[0]));
105
13.8k
  return (a->size == b->size && memcmp(a->data, b->data, a->size) == 0);
106
17.9k
}
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
163
{
112
163
  if (align == STYLE_ALIGN_DEFAULT)
113
163
    return (STYLE_ALIGN_LEFT);
114
0
  return (align);
115
163
}
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
278k
{
122
278k
  struct fuzzy_char *fc;
123
124
278k
  if (*ncs == *alloc) {
125
5.36k
    *alloc = (*alloc == 0) ? 64 : *alloc * 2;
126
5.36k
    *cs = xreallocarray(*cs, *alloc, sizeof **cs);
127
5.36k
  }
128
278k
  fc = &(*cs)[(*ncs)++];
129
278k
  fc->align = a;
130
278k
  memcpy(&fc->ud, ud, sizeof fc->ud);
131
278k
  fc->width = ud->width;
132
278k
  fc->offset = widths[a];
133
278k
  widths[a] += ud->width;
134
278k
}
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
321k
{
140
321k
  enum utf8_state  more;
141
321k
  const char  *start = cp;
142
143
321k
  if ((more = utf8_open(ud, (u_char)*cp)) == UTF8_MORE) {
144
51.1k
    while (++cp != end && more == UTF8_MORE)
145
29.6k
      more = utf8_append(ud, (u_char)*cp);
146
21.4k
    if (more == UTF8_DONE)
147
0
      return (cp);
148
21.4k
    cp = start;
149
21.4k
  }
150
321k
  utf8_set(ud, (u_char)*cp);
151
321k
  return (cp + 1);
152
321k
}
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
2.80k
{
162
2.80k
  struct fuzzy_char *cs = NULL;
163
2.80k
  u_int      alloc = 0, n, leading, i;
164
2.80k
  enum style_align   current = STYLE_ALIGN_LEFT;
165
2.80k
  struct style     sy;
166
2.80k
  const char    *cp = text, *textend = text + strlen(text);
167
2.80k
  const char    *end;
168
2.80k
  struct utf8_data   ud, hash, bracket;
169
2.80k
  char      *tmp;
170
171
2.80k
  *ncs = 0;
172
2.80k
  memset(widths, 0, sizeof *widths * (STYLE_ALIGN_ABSOLUTE_CENTRE + 1));
173
2.80k
  style_set(&sy, &grid_default_cell);
174
2.80k
  utf8_set(&hash, '#');
175
2.80k
  utf8_set(&bracket, '[');
176
177
334k
  while (*cp != '\0') {
178
    /* Handle a run of #s, which may introduce a style. */
179
331k
    if (*cp == '#') {
180
27.0k
      for (n = 0; cp[n] == '#'; n++)
181
13.6k
        /* nothing */;
182
13.3k
      if (cp[n] != '[') {
183
        /* Escaped #s: ##->#, so half (rounded up). */
184
8.13k
        leading = (n % 2 == 0) ? n / 2 : n / 2 + 1;
185
16.2k
        for (i = 0; i < leading; i++) {
186
8.13k
          fuzzy_add(&cs, ncs, &alloc, current,
187
8.13k
              &hash, widths);
188
8.13k
        }
189
8.13k
        cp += n;
190
8.13k
        continue;
191
8.13k
      }
192
193
      /* Even count: all #s escaped, the [ is literal. */
194
5.36k
      for (i = 0; i < n / 2; i++)
195
116
        fuzzy_add(&cs, ncs, &alloc, current, &hash,
196
116
            widths);
197
5.25k
      if (n % 2 == 0) {
198
18
        fuzzy_add(&cs, ncs, &alloc, current, &bracket,
199
18
            widths);
200
18
        cp += n + 1;
201
18
        continue;
202
18
      }
203
204
      /* Odd count: this is a style, find and parse it. */
205
5.23k
      end = format_skip(cp + n + 1, "]");
206
5.23k
      if (end == NULL)
207
78
        break;
208
5.15k
      tmp = xstrndup(cp + n + 1, end - (cp + n + 1));
209
5.15k
      if (style_parse(&sy, &grid_default_cell, tmp) == 0)
210
163
        current = fuzzy_align(sy.align);
211
5.15k
      free(tmp);
212
5.15k
      cp = end + 1;
213
5.15k
      continue;
214
5.23k
    }
215
216
    /* Decode one character, multibyte or single byte. */
217
318k
    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
318k
    if (ud.size == 1 && (ud.data[0] <= 0x1f || ud.data[0] >= 0x7f))
225
48.3k
      continue;
226
269k
    fuzzy_add(&cs, ncs, &alloc, current, &ud, widths);
227
269k
  }
228
2.80k
  return (cs);
229
2.80k
}
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
20
{
240
20
  enum style_align  a = fc->align;
241
242
20
  if (fc->offset < src[a] || fc->offset >= src[a] + vis[a])
243
0
    return (-1);
244
20
  *column = start[a] + (fc->offset - src[a]);
245
20
  return (0);
246
20
}
247
248
/* Decode a UTF-8 term into an array of characters. */
249
static u_int
250
fuzzy_decode(const char *tok, size_t len, struct utf8_data *out)
251
449
{
252
449
  const char  *cp = tok, *end = tok + len;
253
449
  u_int    n = 0;
254
255
3.24k
  while (cp != end)
256
2.79k
    cp = fuzzy_decode_one(cp, end, &out[n++]);
257
449
  return (n);
258
449
}
259
260
/* Add the score for a fuzzy token matched at the given positions. */
261
static int
262
fuzzy_score_positions(const u_int *pos, u_int npos, const struct fuzzy_char *cs)
263
31
{
264
31
  u_int i, gap, span;
265
31
  int score = 0;
266
267
31
  if (npos == 0)
268
0
    return (0);
269
31
  if (pos[0] == 0)
270
2
    score += FUZZY_BONUS_START;
271
29
  else {
272
29
    if (fuzzy_is_boundary(&cs[pos[0] - 1].ud))
273
0
      score += FUZZY_BONUS_BOUNDARY;
274
29
    if (pos[0] < FUZZY_PENALTY_LEADING_MAX)
275
27
      score -= pos[0] * FUZZY_PENALTY_LEADING;
276
2
    else {
277
2
      score -= FUZZY_PENALTY_LEADING_MAX *
278
2
          FUZZY_PENALTY_LEADING;
279
2
    }
280
29
  }
281
37
  for (i = 1; i < npos; i++) {
282
6
    if (pos[i] == pos[i - 1] + 1)
283
6
      score += FUZZY_BONUS_CONSECUTIVE;
284
0
    else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud))
285
0
      score += FUZZY_BONUS_BOUNDARY;
286
6
  }
287
31
  span = pos[npos - 1] - pos[0] + 1;
288
31
  gap = span - npos;
289
31
  score -= gap * FUZZY_PENALTY_GAP;
290
31
  return (score);
291
31
}
292
293
/*
294
 * Match a token as a subsequence of the visible characters. Returns if the
295
 * token matches.
296
 */
297
static int
298
fuzzy_match_fuzzy(const struct utf8_data *tok, u_int toklen,
299
    struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
300
259
{
301
259
  u_int pi, ci, *pos;
302
259
  int found, value;
303
304
259
  if (toklen == 0 || ncs == 0)
305
16
    return (0);
306
243
  pos = xcalloc(toklen, sizeof *pos);
307
308
  /* First find a subsequence from the start. */
309
243
  ci = 0;
310
296
  for (pi = 0; pi < toklen; pi++) {
311
9.16k
    while (ci != ncs &&
312
8.95k
        !fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold))
313
8.90k
      ci++;
314
265
    if (ci == ncs) {
315
212
      free(pos);
316
212
      return (0);
317
212
    }
318
53
    pos[pi] = ci++;
319
53
  }
320
321
  /* Then compact it backwards to prefer a shorter span. */
322
31
  ci = pos[toklen - 1];
323
68
  for (pi = toklen; pi > 0; pi--) {
324
37
    found = 0;
325
37
    for (;;) {
326
37
      if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) {
327
37
        pos[pi - 1] = ci;
328
37
        found = 1;
329
37
        break;
330
37
      }
331
0
      if (ci == 0)
332
0
        break;
333
0
      ci--;
334
0
    }
335
37
    if (!found) {
336
0
      free(pos);
337
0
      return (0);
338
0
    }
339
37
    if (pi != 1)
340
6
      ci--;
341
37
  }
342
343
31
  value = fuzzy_score_positions(pos, toklen, cs);
344
31
  *score += value;
345
68
  for (pi = 0; pi < toklen; pi++)
346
37
    matched[pos[pi]] = 1;
347
31
  free(pos);
348
31
  return (1);
349
31
}
350
351
/* Score an exact, prefix or suffix match. */
352
static int
353
fuzzy_score_exact(u_int start, u_int toklen, u_int ncs,
354
    const struct fuzzy_char *cs, int prefix, int suffix)
355
72
{
356
72
  int score;
357
358
72
  score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE;
359
72
  if (prefix)
360
0
    score += FUZZY_BONUS_PREFIX;
361
72
  if (suffix)
362
0
    score += FUZZY_BONUS_SUFFIX;
363
72
  if (start == 0)
364
0
    score += FUZZY_BONUS_START;
365
72
  else if (fuzzy_is_boundary(&cs[start - 1].ud))
366
3
    score += FUZZY_BONUS_BOUNDARY;
367
72
  if (start < FUZZY_PENALTY_LEADING_MAX)
368
38
    score -= start * FUZZY_PENALTY_LEADING;
369
34
  else
370
34
    score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING;
371
72
  if (!prefix && !suffix)
372
72
    score -= ncs - (start + toklen);
373
72
  return (score);
374
72
}
375
376
/* Match an exact, prefix or suffix term against the visible characters. */
377
static int
378
fuzzy_match_exact(const struct utf8_data *tok, u_int toklen,
379
    struct fuzzy_char *cs, u_int ncs, int fold, int prefix, int suffix,
380
    int *score, char *matched)
381
190
{
382
190
  u_int start, end, i, j, best = 0;
383
190
  int ok, found = 0, value, bestscore = 0;
384
385
190
  if (toklen == 0 || toklen > ncs)
386
2
    return (0);
387
388
188
  if (prefix && suffix) {
389
64
    if (toklen != ncs)
390
0
      return (0);
391
64
    start = 0;
392
64
    end = 1;
393
124
  } else if (prefix) {
394
0
    start = 0;
395
0
    end = 1;
396
124
  } else if (suffix) {
397
0
    start = ncs - toklen;
398
0
    end = start + 1;
399
124
  } else {
400
124
    start = 0;
401
124
    end = ncs - toklen + 1;
402
124
  }
403
404
9.18k
  for (i = start; i < end; i++) {
405
8.99k
    ok = 1;
406
9.06k
    for (j = 0; j < toklen; j++) {
407
8.99k
      if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) {
408
8.92k
        ok = 0;
409
8.92k
        break;
410
8.92k
      }
411
8.99k
    }
412
8.99k
    if (!ok)
413
8.92k
      continue;
414
72
    value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix);
415
72
    if (!found || value > bestscore) {
416
39
      found = 1;
417
39
      best = i;
418
39
      bestscore = value;
419
39
    }
420
72
  }
421
188
  if (!found)
422
153
    return (0);
423
35
  *score += bestscore;
424
35
  if (matched != NULL) {
425
2
    for (i = 0; i < toklen; i++)
426
1
      matched[best + i] = 1;
427
1
  }
428
35
  return (1);
429
188
}
430
431
/* Parse one term. */
432
static int
433
fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term)
434
2.94k
{
435
2.94k
  memset(term, 0, sizeof *term);
436
2.94k
  if (start == end)
437
0
    return (0);
438
2.94k
  if (*start == '!') {
439
131
    term->inverse = 1;
440
131
    start++;
441
131
  }
442
2.94k
  if (start == end)
443
6
    return (0);
444
2.94k
  if (*start == '\'') {
445
3
    term->exact = 1;
446
3
    start++;
447
2.93k
  } else if (*start == '^') {
448
2.48k
    term->exact = 1;
449
2.48k
    term->prefix = 1;
450
2.48k
    start++;
451
2.48k
  }
452
2.94k
  if (start == end)
453
2.42k
    return (0);
454
521
  if (end[-1] == '$') {
455
136
    term->exact = 1;
456
136
    term->suffix = 1;
457
136
    end--;
458
136
  }
459
521
  if (start == end)
460
72
    return (0);
461
462
449
  if (term->inverse)
463
125
    term->exact = 1;
464
449
  term->text = start;
465
449
  term->len = end - start;
466
449
  return (1);
467
521
}
468
469
/* Match one parsed term. */
470
static int
471
fuzzy_match_term(const struct fuzzy_term *term, struct utf8_data *tok,
472
    struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
473
449
{
474
449
  u_int toklen;
475
449
  int value = 0, matched_term;
476
477
449
  toklen = fuzzy_decode(term->text, term->len, tok);
478
449
  if (term->exact) {
479
190
    matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold,
480
190
        term->prefix, term->suffix, &value,
481
190
        term->inverse ? NULL : matched);
482
259
  } else {
483
259
    matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold,
484
259
        &value, term->inverse ? NULL : matched);
485
259
  }
486
487
449
  if (term->inverse)
488
125
    return (!matched_term);
489
324
  if (!matched_term)
490
292
    return (0);
491
32
  *score += value;
492
32
  return (1);
493
324
}
494
495
/* Match one AND group of terms. */
496
static int
497
fuzzy_match_group(const char *start, const char *end, struct utf8_data *tok,
498
    struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
499
2.93k
{
500
2.93k
  const char    *cp = start, *sp;
501
2.93k
  struct fuzzy_term  term;
502
2.93k
  int      any = 0;
503
504
2.93k
  *score = 0;
505
3.06k
  while (cp != end) {
506
2.97k
    while (cp != end && *cp == ' ')
507
18
      cp++;
508
2.95k
    if (cp == end)
509
10
      break;
510
2.94k
    sp = cp;
511
8.49k
    while (cp != end && *cp != ' ')
512
5.55k
      cp++;
513
2.94k
    if (!fuzzy_parse_term(sp, cp, &term))
514
2.49k
      return (0);
515
449
    any = 1;
516
449
    if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score,
517
449
        matched))
518
326
      return (0);
519
449
  }
520
115
  return (any);
521
2.93k
}
522
523
/*
524
 * Fuzzy match pattern against text, which is drawn into a region of the given
525
 * display width. Returns a bitstr_t of width bits with a bit set for each
526
 * column occupied by a matched character, or NULL if there is no match. A
527
 * higher returned score is better.
528
 */
529
bitstr_t *
530
fuzzy_match(const char *pattern, const char *text, u_int width, u_int *score)
531
2.82k
{
532
2.82k
  struct fuzzy_char *cs;
533
2.82k
  char      *matched = NULL, *best = NULL, *groupmatched;
534
2.82k
  struct utf8_data  *tok;
535
2.82k
  bitstr_t    *mask;
536
2.82k
  u_int      ncs, i, j, column;
537
2.82k
  u_int      widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
538
2.82k
  u_int      start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
539
2.82k
  u_int      src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
540
2.82k
  u_int      vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
541
2.82k
  u_int      wl, wc, wr, wa;
542
2.82k
  const char    *cp, *sp;
543
2.82k
  int      bestscore = 0, groupscore, found = 0, fold;
544
545
2.82k
  if (width == 0)
546
0
    return (NULL);
547
548
  /* An empty query matches everything, with nothing highlighted. */
549
2.82k
  for (cp = pattern; *cp == ' ' || *cp == '|'; cp++)
550
0
    /* nothing */;
551
2.82k
  if (*cp == '\0') {
552
16
    if (score != NULL)
553
0
      *score = 0;
554
16
    return (bit_alloc(width));
555
16
  }
556
557
  /* Smart-case: fold unless the pattern has an uppercase character. */
558
2.80k
  fold = 1;
559
10.1k
  for (cp = pattern; *cp != '\0'; cp++) {
560
7.42k
    if (*cp >= 'A' && *cp <= 'Z') {
561
106
      fold = 0;
562
106
      break;
563
106
    }
564
7.42k
  }
565
566
  /* Scan the text into visible characters. */
567
2.80k
  cs = fuzzy_scan(text, &ncs, widths);
568
2.80k
  matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched);
569
2.80k
  best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best);
570
2.80k
  tok = xreallocarray(NULL, strlen(pattern) + 1, sizeof *tok);
571
572
  /* Match each |-separated group and keep the best-scoring one. */
573
2.80k
  cp = pattern;
574
5.74k
  while (*cp != '\0') {
575
3.14k
    while (*cp == ' ' || *cp == '|')
576
210
      cp++;
577
2.93k
    if (*cp == '\0')
578
0
      break;
579
2.93k
    sp = cp;
580
10.2k
    while (*cp != '\0' && *cp != '|')
581
7.29k
      cp++;
582
2.93k
    memset(matched, 0, ncs == 0 ? 1 : ncs);
583
2.93k
    groupmatched = matched;
584
2.93k
    if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold,
585
2.93k
        &groupscore, groupmatched)) {
586
115
      if (!found || groupscore > bestscore) {
587
106
        found = 1;
588
106
        bestscore = groupscore;
589
106
        memcpy(best, matched, ncs == 0 ? 1 : ncs);
590
106
      }
591
115
    }
592
2.93k
  }
593
2.80k
  free(tok);
594
2.80k
  if (!found) {
595
2.70k
    free(best);
596
2.70k
    free(matched);
597
2.70k
    free(cs);
598
2.70k
    return (NULL);
599
2.70k
  }
600
601
  /*
602
   * Work out the trimmed widths and start columns of each alignment,
603
   * mirroring format_draw_none.
604
   */
605
105
  wl = widths[STYLE_ALIGN_LEFT];
606
105
  wc = widths[STYLE_ALIGN_CENTRE];
607
105
  wr = widths[STYLE_ALIGN_RIGHT];
608
105
  wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE];
609
6.71k
  while (wl + wc + wr > width) {
610
6.61k
    if (wc > 0)
611
0
      wc--;
612
6.61k
    else if (wr > 0)
613
0
      wr--;
614
6.61k
    else
615
6.61k
      wl--;
616
6.61k
  }
617
105
  if (wa > width)
618
0
    wa = width;
619
620
105
  start[STYLE_ALIGN_LEFT] = 0;
621
105
  src[STYLE_ALIGN_LEFT] = 0;
622
105
  vis[STYLE_ALIGN_LEFT] = wl;
623
624
105
  start[STYLE_ALIGN_RIGHT] = width - wr;
625
105
  src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr;
626
105
  vis[STYLE_ALIGN_RIGHT] = wr;
627
628
105
  start[STYLE_ALIGN_CENTRE] =
629
105
      wl + ((width - wr) - wl) / 2 - wc / 2;
630
105
  src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2;
631
105
  vis[STYLE_ALIGN_CENTRE] = wc;
632
633
105
  start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2;
634
105
  src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0;
635
105
  vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa;
636
637
  /* Set a bit for each column of each matched character. */
638
105
  mask = bit_alloc(width);
639
8.70k
  for (i = 0; i < ncs; i++) {
640
8.60k
    if (!best[i])
641
8.58k
      continue;
642
20
    if (fuzzy_column(&cs[i], start, src, vis, &column) != 0)
643
0
      continue;
644
40
    for (j = 0; j < cs[i].width && column + j < width; j++)
645
20
      bit_set(mask, column + j);
646
20
  }
647
648
105
  free(best);
649
105
  free(matched);
650
105
  free(cs);
651
652
105
  if (score != NULL)
653
0
    *score = (bestscore < 0) ? 0 : (u_int)bestscore;
654
105
  return (mask);
655
2.80k
}