mirror of
https://github.com/tmux/tmux.git
synced 2026-07-03 10:22:29 +00:00
656 lines
17 KiB
C
656 lines
17 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/*
|
|
* Fuzzy matching in the style of fzf. The pattern is split into groups by |
|
|
* and each group is split on spaces into terms. A row matches if any group
|
|
* matches; within a group all positive terms must match and all inverse terms
|
|
* must not match.
|
|
*
|
|
* Plain positive terms are fuzzy subsequences. A leading ' makes a term an
|
|
* exact substring match, ^ anchors a term at the start and $ anchors it at
|
|
* the end. A leading ! inverts the term. Plain inverse terms are exact
|
|
* substring matches rather than inverse fuzzy matches, like fzf.
|
|
*
|
|
* Both the pattern and the text are UTF-8. The text may contain tmux style
|
|
* directives (#[...]); these and their contents are invisible to matching and
|
|
* occupy no columns, but align= styles do move the surrounding text and are
|
|
* accounted for exactly as format_draw lays it out (the no-list layout, see
|
|
* format_draw_none). Matching is smart-case: case is ignored unless the pattern
|
|
* contains an uppercase character (ASCII case folding only; other characters
|
|
* are compared exactly by their UTF-8 data).
|
|
*
|
|
* On a match a bitstr_t of the requested display width is returned with a bit
|
|
* set for every column occupied by a matched character, so the caller can
|
|
* highlight them; NULL is returned if there is no match. A cheap fzf-style
|
|
* score (matches at the start, after word boundaries and in contiguous runs
|
|
* score higher) is also produced so callers can rank best-match-first.
|
|
*/
|
|
|
|
#define FUZZY_BONUS_EXACT 1000
|
|
#define FUZZY_BONUS_PREFIX 200
|
|
#define FUZZY_BONUS_SUFFIX 100
|
|
#define FUZZY_BONUS_START 12
|
|
#define FUZZY_BONUS_BOUNDARY 8
|
|
#define FUZZY_BONUS_CONSECUTIVE 6
|
|
#define FUZZY_PENALTY_LEADING 1
|
|
#define FUZZY_PENALTY_LEADING_MAX 10
|
|
#define FUZZY_PENALTY_GAP 1
|
|
|
|
/* A single visible character of the text. */
|
|
struct fuzzy_char {
|
|
enum style_align align;
|
|
struct utf8_data ud; /* original UTF-8 data */
|
|
u_int width; /* display width */
|
|
u_int offset; /* within its alignment */
|
|
};
|
|
|
|
/* One parsed query term. */
|
|
struct fuzzy_term {
|
|
int inverse;
|
|
int exact;
|
|
int prefix;
|
|
int suffix;
|
|
const char *text;
|
|
size_t len;
|
|
};
|
|
|
|
/* Is this character a word boundary, so a match after it scores higher? */
|
|
static int
|
|
fuzzy_is_boundary(const struct utf8_data *ud)
|
|
{
|
|
static const char *boundary = " -_/.:";
|
|
|
|
if (ud->size != 1)
|
|
return (0);
|
|
return (strchr(boundary, ud->data[0]) != NULL);
|
|
}
|
|
|
|
/*
|
|
* Compare two characters, folding ASCII case if wanted. UTF-8 is compared
|
|
* directly without case folding.
|
|
*/
|
|
static int
|
|
fuzzy_char_equal(const struct utf8_data *a, const struct utf8_data *b, int fold)
|
|
{
|
|
if (fold &&
|
|
a->size == 1 &&
|
|
b->size == 1 &&
|
|
a->data[0] < 0x80 &&
|
|
b->data[0] < 0x80)
|
|
return (tolower(a->data[0]) == tolower(b->data[0]));
|
|
return (a->size == b->size && memcmp(a->data, b->data, a->size) == 0);
|
|
}
|
|
|
|
/* Map a style alignment onto one of the four layout columns. */
|
|
static enum style_align
|
|
fuzzy_align(enum style_align align)
|
|
{
|
|
if (align == STYLE_ALIGN_DEFAULT)
|
|
return (STYLE_ALIGN_LEFT);
|
|
return (align);
|
|
}
|
|
|
|
/* Add a visible character to the array, updating the alignment width. */
|
|
static void
|
|
fuzzy_add(struct fuzzy_char **cs, u_int *ncs, u_int *alloc, enum style_align a,
|
|
const struct utf8_data *ud, u_int *widths)
|
|
{
|
|
struct fuzzy_char *fc;
|
|
|
|
if (*ncs == *alloc) {
|
|
*alloc = (*alloc == 0) ? 64 : *alloc * 2;
|
|
*cs = xreallocarray(*cs, *alloc, sizeof **cs);
|
|
}
|
|
fc = &(*cs)[(*ncs)++];
|
|
fc->align = a;
|
|
memcpy(&fc->ud, ud, sizeof fc->ud);
|
|
fc->width = ud->width;
|
|
fc->offset = widths[a];
|
|
widths[a] += ud->width;
|
|
}
|
|
|
|
/* Decode a character as UTF-8. */
|
|
static const char *
|
|
fuzzy_decode_one(const char *cp, const char *end, struct utf8_data *ud)
|
|
{
|
|
enum utf8_state more;
|
|
const char *start = cp;
|
|
|
|
if ((more = utf8_open(ud, (u_char)*cp)) == UTF8_MORE) {
|
|
while (++cp != end && more == UTF8_MORE)
|
|
more = utf8_append(ud, (u_char)*cp);
|
|
if (more == UTF8_DONE)
|
|
return (cp);
|
|
cp = start;
|
|
}
|
|
utf8_set(ud, (u_char)*cp);
|
|
return (cp + 1);
|
|
}
|
|
|
|
/*
|
|
* Scan the text into an array of visible characters, skipping styles and
|
|
* recording the alignment and intra-alignment offset of each. Returns the
|
|
* array and its length and fills in the per-alignment widths.
|
|
*/
|
|
static struct fuzzy_char *
|
|
fuzzy_scan(const char *text, u_int *ncs, u_int *widths)
|
|
{
|
|
struct fuzzy_char *cs = NULL;
|
|
u_int alloc = 0, n, leading, i;
|
|
enum style_align current = STYLE_ALIGN_LEFT;
|
|
struct style sy;
|
|
const char *cp = text, *textend = text + strlen(text);
|
|
const char *end;
|
|
struct utf8_data ud, hash, bracket;
|
|
char *tmp;
|
|
|
|
*ncs = 0;
|
|
memset(widths, 0, sizeof *widths * (STYLE_ALIGN_ABSOLUTE_CENTRE + 1));
|
|
style_set(&sy, &grid_default_cell);
|
|
utf8_set(&hash, '#');
|
|
utf8_set(&bracket, '[');
|
|
|
|
while (*cp != '\0') {
|
|
/* Handle a run of #s, which may introduce a style. */
|
|
if (*cp == '#') {
|
|
for (n = 0; cp[n] == '#'; n++)
|
|
/* nothing */;
|
|
if (cp[n] != '[') {
|
|
/* Escaped #s: ##->#, so half (rounded up). */
|
|
leading = (n % 2 == 0) ? n / 2 : n / 2 + 1;
|
|
for (i = 0; i < leading; i++) {
|
|
fuzzy_add(&cs, ncs, &alloc, current,
|
|
&hash, widths);
|
|
}
|
|
cp += n;
|
|
continue;
|
|
}
|
|
|
|
/* Even count: all #s escaped, the [ is literal. */
|
|
for (i = 0; i < n / 2; i++)
|
|
fuzzy_add(&cs, ncs, &alloc, current, &hash,
|
|
widths);
|
|
if (n % 2 == 0) {
|
|
fuzzy_add(&cs, ncs, &alloc, current, &bracket,
|
|
widths);
|
|
cp += n + 1;
|
|
continue;
|
|
}
|
|
|
|
/* Odd count: this is a style, find and parse it. */
|
|
end = format_skip(cp + n + 1, "]");
|
|
if (end == NULL)
|
|
break;
|
|
tmp = xstrndup(cp + n + 1, end - (cp + n + 1));
|
|
if (style_parse(&sy, &grid_default_cell, tmp) == 0)
|
|
current = fuzzy_align(sy.align);
|
|
free(tmp);
|
|
cp = end + 1;
|
|
continue;
|
|
}
|
|
|
|
/* Decode one character, multibyte or single byte. */
|
|
cp = fuzzy_decode_one(cp, textend, &ud);
|
|
|
|
/*
|
|
* Skip non-printable single bytes (control characters and raw
|
|
* bytes left over from a failed decode); keep printable ASCII
|
|
* and any decoded UTF-8.
|
|
*/
|
|
if (ud.size == 1 && (ud.data[0] <= 0x1f || ud.data[0] >= 0x7f))
|
|
continue;
|
|
fuzzy_add(&cs, ncs, &alloc, current, &ud, widths);
|
|
}
|
|
return (cs);
|
|
}
|
|
|
|
/*
|
|
* Work out the display column of a visible character given the trimmed widths
|
|
* and start columns of each alignment. Returns 0 and sets the column if the
|
|
* character is visible, otherwise returns -1.
|
|
*/
|
|
static int
|
|
fuzzy_column(const struct fuzzy_char *fc, const u_int *start, const u_int *src,
|
|
const u_int *vis, u_int *column)
|
|
{
|
|
enum style_align a = fc->align;
|
|
|
|
if (fc->offset < src[a] || fc->offset >= src[a] + vis[a])
|
|
return (-1);
|
|
*column = start[a] + (fc->offset - src[a]);
|
|
return (0);
|
|
}
|
|
|
|
/* Decode a UTF-8 term into an array of characters. */
|
|
static u_int
|
|
fuzzy_decode(const char *tok, size_t len, struct utf8_data *out)
|
|
{
|
|
const char *cp = tok, *end = tok + len;
|
|
u_int n = 0;
|
|
|
|
while (cp != end)
|
|
cp = fuzzy_decode_one(cp, end, &out[n++]);
|
|
return (n);
|
|
}
|
|
|
|
/* Add the score for a fuzzy token matched at the given positions. */
|
|
static int
|
|
fuzzy_score_positions(const u_int *pos, u_int npos, const struct fuzzy_char *cs)
|
|
{
|
|
u_int i, gap, span;
|
|
int score = 0;
|
|
|
|
if (npos == 0)
|
|
return (0);
|
|
if (pos[0] == 0)
|
|
score += FUZZY_BONUS_START;
|
|
else {
|
|
if (fuzzy_is_boundary(&cs[pos[0] - 1].ud))
|
|
score += FUZZY_BONUS_BOUNDARY;
|
|
if (pos[0] < FUZZY_PENALTY_LEADING_MAX)
|
|
score -= pos[0] * FUZZY_PENALTY_LEADING;
|
|
else {
|
|
score -= FUZZY_PENALTY_LEADING_MAX *
|
|
FUZZY_PENALTY_LEADING;
|
|
}
|
|
}
|
|
for (i = 1; i < npos; i++) {
|
|
if (pos[i] == pos[i - 1] + 1)
|
|
score += FUZZY_BONUS_CONSECUTIVE;
|
|
else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud))
|
|
score += FUZZY_BONUS_BOUNDARY;
|
|
}
|
|
span = pos[npos - 1] - pos[0] + 1;
|
|
gap = span - npos;
|
|
score -= gap * FUZZY_PENALTY_GAP;
|
|
return (score);
|
|
}
|
|
|
|
/*
|
|
* Match a token as a subsequence of the visible characters. Returns if the
|
|
* token matches.
|
|
*/
|
|
static int
|
|
fuzzy_match_fuzzy(const struct utf8_data *tok, u_int toklen,
|
|
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
|
|
{
|
|
u_int pi, ci, *pos;
|
|
int found, value;
|
|
|
|
if (toklen == 0 || ncs == 0)
|
|
return (0);
|
|
pos = xcalloc(toklen, sizeof *pos);
|
|
|
|
/* First find a subsequence from the start. */
|
|
ci = 0;
|
|
for (pi = 0; pi < toklen; pi++) {
|
|
while (ci != ncs &&
|
|
!fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold))
|
|
ci++;
|
|
if (ci == ncs) {
|
|
free(pos);
|
|
return (0);
|
|
}
|
|
pos[pi] = ci++;
|
|
}
|
|
|
|
/* Then compact it backwards to prefer a shorter span. */
|
|
ci = pos[toklen - 1];
|
|
for (pi = toklen; pi > 0; pi--) {
|
|
found = 0;
|
|
for (;;) {
|
|
if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) {
|
|
pos[pi - 1] = ci;
|
|
found = 1;
|
|
break;
|
|
}
|
|
if (ci == 0)
|
|
break;
|
|
ci--;
|
|
}
|
|
if (!found) {
|
|
free(pos);
|
|
return (0);
|
|
}
|
|
if (pi != 1)
|
|
ci--;
|
|
}
|
|
|
|
value = fuzzy_score_positions(pos, toklen, cs);
|
|
*score += value;
|
|
for (pi = 0; pi < toklen; pi++)
|
|
matched[pos[pi]] = 1;
|
|
free(pos);
|
|
return (1);
|
|
}
|
|
|
|
/* Score an exact, prefix or suffix match. */
|
|
static int
|
|
fuzzy_score_exact(u_int start, u_int toklen, u_int ncs,
|
|
const struct fuzzy_char *cs, int prefix, int suffix)
|
|
{
|
|
int score;
|
|
|
|
score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE;
|
|
if (prefix)
|
|
score += FUZZY_BONUS_PREFIX;
|
|
if (suffix)
|
|
score += FUZZY_BONUS_SUFFIX;
|
|
if (start == 0)
|
|
score += FUZZY_BONUS_START;
|
|
else if (fuzzy_is_boundary(&cs[start - 1].ud))
|
|
score += FUZZY_BONUS_BOUNDARY;
|
|
if (start < FUZZY_PENALTY_LEADING_MAX)
|
|
score -= start * FUZZY_PENALTY_LEADING;
|
|
else
|
|
score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING;
|
|
if (!prefix && !suffix)
|
|
score -= ncs - (start + toklen);
|
|
return (score);
|
|
}
|
|
|
|
/* Match an exact, prefix or suffix term against the visible characters. */
|
|
static int
|
|
fuzzy_match_exact(const struct utf8_data *tok, u_int toklen,
|
|
struct fuzzy_char *cs, u_int ncs, int fold, int prefix, int suffix,
|
|
int *score, char *matched)
|
|
{
|
|
u_int start, end, i, j, best = 0;
|
|
int ok, found = 0, value, bestscore = 0;
|
|
|
|
if (toklen == 0 || toklen > ncs)
|
|
return (0);
|
|
|
|
if (prefix && suffix) {
|
|
if (toklen != ncs)
|
|
return (0);
|
|
start = 0;
|
|
end = 1;
|
|
} else if (prefix) {
|
|
start = 0;
|
|
end = 1;
|
|
} else if (suffix) {
|
|
start = ncs - toklen;
|
|
end = start + 1;
|
|
} else {
|
|
start = 0;
|
|
end = ncs - toklen + 1;
|
|
}
|
|
|
|
for (i = start; i < end; i++) {
|
|
ok = 1;
|
|
for (j = 0; j < toklen; j++) {
|
|
if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (!ok)
|
|
continue;
|
|
value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix);
|
|
if (!found || value > bestscore) {
|
|
found = 1;
|
|
best = i;
|
|
bestscore = value;
|
|
}
|
|
}
|
|
if (!found)
|
|
return (0);
|
|
*score += bestscore;
|
|
if (matched != NULL) {
|
|
for (i = 0; i < toklen; i++)
|
|
matched[best + i] = 1;
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/* Parse one term. */
|
|
static int
|
|
fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term)
|
|
{
|
|
memset(term, 0, sizeof *term);
|
|
if (start == end)
|
|
return (0);
|
|
if (*start == '!') {
|
|
term->inverse = 1;
|
|
start++;
|
|
}
|
|
if (start == end)
|
|
return (0);
|
|
if (*start == '\'') {
|
|
term->exact = 1;
|
|
start++;
|
|
} else if (*start == '^') {
|
|
term->exact = 1;
|
|
term->prefix = 1;
|
|
start++;
|
|
}
|
|
if (start == end)
|
|
return (0);
|
|
if (end[-1] == '$') {
|
|
term->exact = 1;
|
|
term->suffix = 1;
|
|
end--;
|
|
}
|
|
if (start == end)
|
|
return (0);
|
|
|
|
if (term->inverse)
|
|
term->exact = 1;
|
|
term->text = start;
|
|
term->len = end - start;
|
|
return (1);
|
|
}
|
|
|
|
/* Match one parsed term. */
|
|
static int
|
|
fuzzy_match_term(const struct fuzzy_term *term, struct utf8_data *tok,
|
|
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
|
|
{
|
|
u_int toklen;
|
|
int value = 0, matched_term;
|
|
|
|
toklen = fuzzy_decode(term->text, term->len, tok);
|
|
if (term->exact) {
|
|
matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold,
|
|
term->prefix, term->suffix, &value,
|
|
term->inverse ? NULL : matched);
|
|
} else {
|
|
matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold,
|
|
&value, term->inverse ? NULL : matched);
|
|
}
|
|
|
|
if (term->inverse)
|
|
return (!matched_term);
|
|
if (!matched_term)
|
|
return (0);
|
|
*score += value;
|
|
return (1);
|
|
}
|
|
|
|
/* Match one AND group of terms. */
|
|
static int
|
|
fuzzy_match_group(const char *start, const char *end, struct utf8_data *tok,
|
|
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
|
|
{
|
|
const char *cp = start, *sp;
|
|
struct fuzzy_term term;
|
|
int any = 0;
|
|
|
|
*score = 0;
|
|
while (cp != end) {
|
|
while (cp != end && *cp == ' ')
|
|
cp++;
|
|
if (cp == end)
|
|
break;
|
|
sp = cp;
|
|
while (cp != end && *cp != ' ')
|
|
cp++;
|
|
if (!fuzzy_parse_term(sp, cp, &term))
|
|
return (0);
|
|
any = 1;
|
|
if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score,
|
|
matched))
|
|
return (0);
|
|
}
|
|
return (any);
|
|
}
|
|
|
|
/*
|
|
* Fuzzy match pattern against text, which is drawn into a region of the given
|
|
* display width. Returns a bitstr_t of width bits with a bit set for each
|
|
* column occupied by a matched character, or NULL if there is no match. A
|
|
* higher returned score is better.
|
|
*/
|
|
bitstr_t *
|
|
fuzzy_match(const char *pattern, const char *text, u_int width, u_int *score)
|
|
{
|
|
struct fuzzy_char *cs;
|
|
char *matched = NULL, *best = NULL, *groupmatched;
|
|
struct utf8_data *tok;
|
|
bitstr_t *mask;
|
|
u_int ncs, i, j, column;
|
|
u_int widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
|
u_int start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
|
u_int src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
|
u_int vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
|
u_int wl, wc, wr, wa;
|
|
const char *cp, *sp;
|
|
int bestscore = 0, groupscore, found = 0, fold;
|
|
|
|
if (width == 0)
|
|
return (NULL);
|
|
|
|
/* An empty query matches everything, with nothing highlighted. */
|
|
for (cp = pattern; *cp == ' ' || *cp == '|'; cp++)
|
|
/* nothing */;
|
|
if (*cp == '\0') {
|
|
if (score != NULL)
|
|
*score = 0;
|
|
return (bit_alloc(width));
|
|
}
|
|
|
|
/* Smart-case: fold unless the pattern has an uppercase character. */
|
|
fold = 1;
|
|
for (cp = pattern; *cp != '\0'; cp++) {
|
|
if (*cp >= 'A' && *cp <= 'Z') {
|
|
fold = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Scan the text into visible characters. */
|
|
cs = fuzzy_scan(text, &ncs, widths);
|
|
matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched);
|
|
best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best);
|
|
tok = xreallocarray(NULL, strlen(pattern) + 1, sizeof *tok);
|
|
|
|
/* Match each |-separated group and keep the best-scoring one. */
|
|
cp = pattern;
|
|
while (*cp != '\0') {
|
|
while (*cp == ' ' || *cp == '|')
|
|
cp++;
|
|
if (*cp == '\0')
|
|
break;
|
|
sp = cp;
|
|
while (*cp != '\0' && *cp != '|')
|
|
cp++;
|
|
memset(matched, 0, ncs == 0 ? 1 : ncs);
|
|
groupmatched = matched;
|
|
if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold,
|
|
&groupscore, groupmatched)) {
|
|
if (!found || groupscore > bestscore) {
|
|
found = 1;
|
|
bestscore = groupscore;
|
|
memcpy(best, matched, ncs == 0 ? 1 : ncs);
|
|
}
|
|
}
|
|
}
|
|
free(tok);
|
|
if (!found) {
|
|
free(best);
|
|
free(matched);
|
|
free(cs);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Work out the trimmed widths and start columns of each alignment,
|
|
* mirroring format_draw_none.
|
|
*/
|
|
wl = widths[STYLE_ALIGN_LEFT];
|
|
wc = widths[STYLE_ALIGN_CENTRE];
|
|
wr = widths[STYLE_ALIGN_RIGHT];
|
|
wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE];
|
|
while (wl + wc + wr > width) {
|
|
if (wc > 0)
|
|
wc--;
|
|
else if (wr > 0)
|
|
wr--;
|
|
else
|
|
wl--;
|
|
}
|
|
if (wa > width)
|
|
wa = width;
|
|
|
|
start[STYLE_ALIGN_LEFT] = 0;
|
|
src[STYLE_ALIGN_LEFT] = 0;
|
|
vis[STYLE_ALIGN_LEFT] = wl;
|
|
|
|
start[STYLE_ALIGN_RIGHT] = width - wr;
|
|
src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr;
|
|
vis[STYLE_ALIGN_RIGHT] = wr;
|
|
|
|
start[STYLE_ALIGN_CENTRE] =
|
|
wl + ((width - wr) - wl) / 2 - wc / 2;
|
|
src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2;
|
|
vis[STYLE_ALIGN_CENTRE] = wc;
|
|
|
|
start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2;
|
|
src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0;
|
|
vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa;
|
|
|
|
/* Set a bit for each column of each matched character. */
|
|
mask = bit_alloc(width);
|
|
for (i = 0; i < ncs; i++) {
|
|
if (!best[i])
|
|
continue;
|
|
if (fuzzy_column(&cs[i], start, src, vis, &column) != 0)
|
|
continue;
|
|
for (j = 0; j < cs[i].width && column + j < width; j++)
|
|
bit_set(mask, column + j);
|
|
}
|
|
|
|
free(best);
|
|
free(matched);
|
|
free(cs);
|
|
|
|
if (score != NULL)
|
|
*score = (bestscore < 0) ? 0 : (u_int)bestscore;
|
|
return (mask);
|
|
}
|