- Tag-builder: port ~1,477 v1 characters into the v2 chip format so the character picker shows 1,486 characters (was 9); 1,390 carry rich per-chip descriptions restructured from each character's own bio. Adds scripts/port_v1_characters_to_v2.py. - Ideogram: rewrite caption to the real Ideogram-4 schema + broaden refusal detection; sampler updates. - Pose-studio, regional-detailer, attention-couple: assorted fixes. - Add validate_templates / test_regions / test_crop_windows scripts.
46 lines
1.9 KiB
JavaScript
46 lines
1.9 KiB
JavaScript
// $region highlight — colors `$name { ... }` markers (regional-conditioning blocks)
|
|
// so it's visually obvious they pin a section to a mannequin. Self-contained CM6
|
|
// extension, no node/app imports. The close brace is matched by DEPTH so a
|
|
// wildcard/alternation brace ({a|b}) inside a body isn't mistaken for the region
|
|
// close, and the opener allows any \w+ name to match the binding/compiler rule.
|
|
|
|
export function regionHighlightExtension(CM) {
|
|
const nameDeco = CM.Decoration.mark({ class: "pcr-region-name" });
|
|
const braceDeco = CM.Decoration.mark({ class: "pcr-region-brace" });
|
|
const OPEN = /\$\w+\s*\{/g;
|
|
|
|
const build = (view) => {
|
|
const ranges = [];
|
|
const text = view.state.doc.toString();
|
|
let m;
|
|
OPEN.lastIndex = 0;
|
|
while ((m = OPEN.exec(text)) !== null) {
|
|
const nameLen = m[0].match(/^\$\w+/)[0].length;
|
|
ranges.push(nameDeco.range(m.index, m.index + nameLen)); // $name
|
|
const open = m.index + m[0].length - 1;
|
|
ranges.push(braceDeco.range(open, open + 1)); // {
|
|
// Depth-track to the matching close (mirrors the binding parser) so nested
|
|
// wildcard braces in the body don't steal the region-close decoration.
|
|
let depth = 1, i = open + 1;
|
|
for (; i < text.length && depth > 0; i++) {
|
|
const c = text[i];
|
|
if (c === "{") depth++;
|
|
else if (c === "}") depth--;
|
|
}
|
|
if (depth === 0) {
|
|
ranges.push(braceDeco.range(i - 1, i)); // }
|
|
OPEN.lastIndex = i; // resume past this region; its body never holds another opener
|
|
}
|
|
}
|
|
ranges.sort((a, b) => a.from - b.from);
|
|
return CM.Decoration.set(ranges, true);
|
|
};
|
|
|
|
const plugin = CM.ViewPlugin.fromClass(class {
|
|
constructor(view) { this.decorations = build(view); }
|
|
update(u) { if (u.docChanged || u.viewportChanged) this.decorations = build(u.view); }
|
|
}, { decorations: (v) => v.decorations });
|
|
|
|
return [plugin];
|
|
}
|