Coverage Report

Created: 2026-08-13 06:04

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
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 into an array of characters. */
249
static u_int
250
fuzzy_decode(const char *tok, size_t len, struct utf8_data *out)
251
0
{
252
0
  const char  *cp = tok, *end = tok + len;
253
0
  u_int    n = 0;
254
255
0
  while (cp != end)
256
0
    cp = fuzzy_decode_one(cp, end, &out[n++]);
257
0
  return (n);
258
0
}
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
0
{
264
0
  u_int i, gap, span;
265
0
  int score = 0;
266
267
0
  if (npos == 0)
268
0
    return (0);
269
0
  if (pos[0] == 0)
270
0
    score += FUZZY_BONUS_START;
271
0
  else {
272
0
    if (fuzzy_is_boundary(&cs[pos[0] - 1].ud))
273
0
      score += FUZZY_BONUS_BOUNDARY;
274
0
    if (pos[0] < FUZZY_PENALTY_LEADING_MAX)
275
0
      score -= pos[0] * FUZZY_PENALTY_LEADING;
276
0
    else {
277
0
      score -= FUZZY_PENALTY_LEADING_MAX *
278
0
          FUZZY_PENALTY_LEADING;
279
0
    }
280
0
  }
281
0
  for (i = 1; i < npos; i++) {
282
0
    if (pos[i] == pos[i - 1] + 1)
283
0
      score += FUZZY_BONUS_CONSECUTIVE;
284
0
    else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud))
285
0
      score += FUZZY_BONUS_BOUNDARY;
286
0
  }
287
0
  span = pos[npos - 1] - pos[0] + 1;
288
0
  gap = span - npos;
289
0
  score -= gap * FUZZY_PENALTY_GAP;
290
0
  return (score);
291
0
}
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
0
{
301
0
  u_int pi, ci, *pos;
302
0
  int found, value;
303
304
0
  if (toklen == 0 || ncs == 0)
305
0
    return (0);
306
0
  pos = xcalloc(toklen, sizeof *pos);
307
308
  /* First find a subsequence from the start. */
309
0
  ci = 0;
310
0
  for (pi = 0; pi < toklen; pi++) {
311
0
    while (ci != ncs &&
312
0
        !fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold))
313
0
      ci++;
314
0
    if (ci == ncs) {
315
0
      free(pos);
316
0
      return (0);
317
0
    }
318
0
    pos[pi] = ci++;
319
0
  }
320
321
  /* Then compact it backwards to prefer a shorter span. */
322
0
  ci = pos[toklen - 1];
323
0
  for (pi = toklen; pi > 0; pi--) {
324
0
    found = 0;
325
0
    for (;;) {
326
0
      if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) {
327
0
        pos[pi - 1] = ci;
328
0
        found = 1;
329
0
        break;
330
0
      }
331
0
      if (ci == 0)
332
0
        break;
333
0
      ci--;
334
0
    }
335
0
    if (!found) {
336
0
      free(pos);
337
0
      return (0);
338
0
    }
339
0
    if (pi != 1)
340
0
      ci--;
341
0
  }
342
343
0
  value = fuzzy_score_positions(pos, toklen, cs);
344
0
  *score += value;
345
0
  for (pi = 0; pi < toklen; pi++)
346
0
    matched[pos[pi]] = 1;
347
0
  free(pos);
348
0
  return (1);
349
0
}
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
0
{
356
0
  int score;
357
358
0
  score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE;
359
0
  if (prefix)
360
0
    score += FUZZY_BONUS_PREFIX;
361
0
  if (suffix)
362
0
    score += FUZZY_BONUS_SUFFIX;
363
0
  if (start == 0)
364
0
    score += FUZZY_BONUS_START;
365
0
  else if (fuzzy_is_boundary(&cs[start - 1].ud))
366
0
    score += FUZZY_BONUS_BOUNDARY;
367
0
  if (start < FUZZY_PENALTY_LEADING_MAX)
368
0
    score -= start * FUZZY_PENALTY_LEADING;
369
0
  else
370
0
    score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING;
371
0
  if (!prefix && !suffix)
372
0
    score -= ncs - (start + toklen);
373
0
  return (score);
374
0
}
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
0
{
382
0
  u_int start, end, i, j, best = 0;
383
0
  int ok, found = 0, value, bestscore = 0;
384
385
0
  if (toklen == 0 || toklen > ncs)
386
0
    return (0);
387
388
0
  if (prefix && suffix) {
389
0
    if (toklen != ncs)
390
0
      return (0);
391
0
    start = 0;
392
0
    end = 1;
393
0
  } else if (prefix) {
394
0
    start = 0;
395
0
    end = 1;
396
0
  } else if (suffix) {
397
0
    start = ncs - toklen;
398
0
    end = start + 1;
399
0
  } else {
400
0
    start = 0;
401
0
    end = ncs - toklen + 1;
402
0
  }
403
404
0
  for (i = start; i < end; i++) {
405
0
    ok = 1;
406
0
    for (j = 0; j < toklen; j++) {
407
0
      if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) {
408
0
        ok = 0;
409
0
        break;
410
0
      }
411
0
    }
412
0
    if (!ok)
413
0
      continue;
414
0
    value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix);
415
0
    if (!found || value > bestscore) {
416
0
      found = 1;
417
0
      best = i;
418
0
      bestscore = value;
419
0
    }
420
0
  }
421
0
  if (!found)
422
0
    return (0);
423
0
  *score += bestscore;
424
0
  if (matched != NULL) {
425
0
    for (i = 0; i < toklen; i++)
426
0
      matched[best + i] = 1;
427
0
  }
428
0
  return (1);
429
0
}
430
431
/* Parse one term. */
432
static int
433
fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term)
434
0
{
435
0
  memset(term, 0, sizeof *term);
436
0
  if (start == end)
437
0
    return (0);
438
0
  if (*start == '!') {
439
0
    term->inverse = 1;
440
0
    start++;
441
0
  }
442
0
  if (start == end)
443
0
    return (0);
444
0
  if (*start == '\'') {
445
0
    term->exact = 1;
446
0
    start++;
447
0
  } else if (*start == '^') {
448
0
    term->exact = 1;
449
0
    term->prefix = 1;
450
0
    start++;
451
0
  }
452
0
  if (start == end)
453
0
    return (0);
454
0
  if (end[-1] == '$') {
455
0
    term->exact = 1;
456
0
    term->suffix = 1;
457
0
    end--;
458
0
  }
459
0
  if (start == end)
460
0
    return (0);
461
462
0
  if (term->inverse)
463
0
    term->exact = 1;
464
0
  term->text = start;
465
0
  term->len = end - start;
466
0
  return (1);
467
0
}
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
0
{
474
0
  u_int toklen;
475
0
  int value = 0, matched_term;
476
477
0
  toklen = fuzzy_decode(term->text, term->len, tok);
478
0
  if (term->exact) {
479
0
    matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold,
480
0
        term->prefix, term->suffix, &value,
481
0
        term->inverse ? NULL : matched);
482
0
  } else {
483
0
    matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold,
484
0
        &value, term->inverse ? NULL : matched);
485
0
  }
486
487
0
  if (term->inverse)
488
0
    return (!matched_term);
489
0
  if (!matched_term)
490
0
    return (0);
491
0
  *score += value;
492
0
  return (1);
493
0
}
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
0
{
500
0
  const char    *cp = start, *sp;
501
0
  struct fuzzy_term  term;
502
0
  int      any = 0;
503
504
0
  *score = 0;
505
0
  while (cp != end) {
506
0
    while (cp != end && *cp == ' ')
507
0
      cp++;
508
0
    if (cp == end)
509
0
      break;
510
0
    sp = cp;
511
0
    while (cp != end && *cp != ' ')
512
0
      cp++;
513
0
    if (!fuzzy_parse_term(sp, cp, &term))
514
0
      return (0);
515
0
    any = 1;
516
0
    if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score,
517
0
        matched))
518
0
      return (0);
519
0
  }
520
0
  return (any);
521
0
}
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
0
{
532
0
  struct fuzzy_char *cs;
533
0
  char      *matched = NULL, *best = NULL, *groupmatched;
534
0
  struct utf8_data  *tok;
535
0
  bitstr_t    *mask;
536
0
  u_int      ncs, i, j, column;
537
0
  u_int      widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
538
0
  u_int      start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
539
0
  u_int      src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
540
0
  u_int      vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
541
0
  u_int      wl, wc, wr, wa;
542
0
  const char    *cp, *sp;
543
0
  int      bestscore = 0, groupscore, found = 0, fold;
544
545
0
  if (width == 0)
546
0
    return (NULL);
547
548
  /* An empty query matches everything, with nothing highlighted. */
549
0
  for (cp = pattern; *cp == ' ' || *cp == '|'; cp++)
550
0
    /* nothing */;
551
0
  if (*cp == '\0') {
552
0
    if (score != NULL)
553
0
      *score = 0;
554
0
    return (bit_alloc(width));
555
0
  }
556
557
  /* Smart-case: fold unless the pattern has an uppercase character. */
558
0
  fold = 1;
559
0
  for (cp = pattern; *cp != '\0'; cp++) {
560
0
    if (*cp >= 'A' && *cp <= 'Z') {
561
0
      fold = 0;
562
0
      break;
563
0
    }
564
0
  }
565
566
  /* Scan the text into visible characters. */
567
0
  cs = fuzzy_scan(text, &ncs, widths);
568
0
  matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched);
569
0
  best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best);
570
0
  tok = xreallocarray(NULL, strlen(pattern) + 1, sizeof *tok);
571
572
  /* Match each |-separated group and keep the best-scoring one. */
573
0
  cp = pattern;
574
0
  while (*cp != '\0') {
575
0
    while (*cp == ' ' || *cp == '|')
576
0
      cp++;
577
0
    if (*cp == '\0')
578
0
      break;
579
0
    sp = cp;
580
0
    while (*cp != '\0' && *cp != '|')
581
0
      cp++;
582
0
    memset(matched, 0, ncs == 0 ? 1 : ncs);
583
0
    groupmatched = matched;
584
0
    if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold,
585
0
        &groupscore, groupmatched)) {
586
0
      if (!found || groupscore > bestscore) {
587
0
        found = 1;
588
0
        bestscore = groupscore;
589
0
        memcpy(best, matched, ncs == 0 ? 1 : ncs);
590
0
      }
591
0
    }
592
0
  }
593
0
  free(tok);
594
0
  if (!found) {
595
0
    free(best);
596
0
    free(matched);
597
0
    free(cs);
598
0
    return (NULL);
599
0
  }
600
601
  /*
602
   * Work out the trimmed widths and start columns of each alignment,
603
   * mirroring format_draw_none.
604
   */
605
0
  wl = widths[STYLE_ALIGN_LEFT];
606
0
  wc = widths[STYLE_ALIGN_CENTRE];
607
0
  wr = widths[STYLE_ALIGN_RIGHT];
608
0
  wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE];
609
0
  while (wl + wc + wr > width) {
610
0
    if (wc > 0)
611
0
      wc--;
612
0
    else if (wr > 0)
613
0
      wr--;
614
0
    else
615
0
      wl--;
616
0
  }
617
0
  if (wa > width)
618
0
    wa = width;
619
620
0
  start[STYLE_ALIGN_LEFT] = 0;
621
0
  src[STYLE_ALIGN_LEFT] = 0;
622
0
  vis[STYLE_ALIGN_LEFT] = wl;
623
624
0
  start[STYLE_ALIGN_RIGHT] = width - wr;
625
0
  src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr;
626
0
  vis[STYLE_ALIGN_RIGHT] = wr;
627
628
0
  start[STYLE_ALIGN_CENTRE] =
629
0
      wl + ((width - wr) - wl) / 2 - wc / 2;
630
0
  src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2;
631
0
  vis[STYLE_ALIGN_CENTRE] = wc;
632
633
0
  start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2;
634
0
  src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0;
635
0
  vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa;
636
637
  /* Set a bit for each column of each matched character. */
638
0
  mask = bit_alloc(width);
639
0
  for (i = 0; i < ncs; i++) {
640
0
    if (!best[i])
641
0
      continue;
642
0
    if (fuzzy_column(&cs[i], start, src, vis, &column) != 0)
643
0
      continue;
644
0
    for (j = 0; j < cs[i].width && column + j < width; j++)
645
0
      bit_set(mask, column + j);
646
0
  }
647
648
0
  free(best);
649
0
  free(matched);
650
0
  free(cs);
651
652
0
  if (score != NULL)
653
0
    *score = (bestscore < 0) ? 0 : (u_int)bestscore;
654
0
  return (mask);
655
0
}