37 lines
1.2 KiB
JavaScript
37 lines
1.2 KiB
JavaScript
// Pure, dependency-free ranking of a vocabulary against a query string.
|
|
// Used by both the CodeMirror editor (via autocomplete.js) and the
|
|
// runtime console (via the vocab-rank bundle exposed on window).
|
|
//
|
|
// Score (lower = better):
|
|
// exact match → 0
|
|
// prefix match → 1 + label.length (shorter prefix matches win)
|
|
// substring match → 10000 + indexOf (earlier substring beats later)
|
|
// no match → -1 (excluded)
|
|
|
|
function score(query, label) {
|
|
if (label === query) return 0;
|
|
if (label.startsWith(query)) return 1 + label.length;
|
|
const i = label.indexOf(query);
|
|
if (i !== -1) return 10000 + i;
|
|
return -1;
|
|
}
|
|
|
|
export function rankVocab(query, vocab, { limit = 50 } = {}) {
|
|
if (!query) return [];
|
|
const q = query.toLowerCase();
|
|
|
|
const entries = [
|
|
...vocab.cvars.map(e => ({ ...e, kind: "cvar" })),
|
|
...vocab.commands.map(e => ({ ...e, kind: "command" })),
|
|
];
|
|
|
|
const scored = [];
|
|
for (const e of entries) {
|
|
const s = score(q, e.name.toLowerCase());
|
|
if (s === -1) continue;
|
|
scored.push([s, e]);
|
|
if (scored.length > limit * 4) break;
|
|
}
|
|
scored.sort((a, b) => a[0] - b[0]);
|
|
return scored.slice(0, limit).map(([, e]) => e);
|
|
}
|