full PC config setup

shifting towards manual symlinks for more complex setup than laptop, allows for GUI settings changes to actually apply, and is much easier than nix-ifying every config
This commit is contained in:
greyweather committed 2026-07-12 04:01:28 +01:00
1 parent 19e8f0a6d5
commit 0f018cd2e3
149 files changed
+9539 -478

No files matched your search

@@ -0,0 +1,64 @@
{
"patternStore.savedPatterns": [
{
"label": "Import quotes to angles",
"find": "\"([^\"]+)\"",
"replace": "<$1>",
"flags": {
"isRegex": true,
"isCaseSensitive": false,
"matchWholeWord": false,
"isMultiline": false
},
"scope": "global"
},
{
"label": "TODO",
"find": "TODO",
"flags": {
"isRegex": false,
"isCaseSensitive": true,
"matchWholeWord": true,
"isMultiline": false
},
"scope": "global"
},
{
"label": "Dynamic module import",
"find": "${prompt:module}([^\"]*)",
"replace": "<${prompt:module}$1>",
"flags": {
"isRegex": true,
"isCaseSensitive": true,
"matchWholeWord": false,
"isMultiline": false
},
"scope": "global"
},
{
"label": "Find all TODOs (no replace)",
"find": "TODO",
"flags": {
"isRegex": false,
"isCaseSensitive": true,
"matchWholeWord": true,
"isMultiline": false
},
"scope": "global"
}
],
"patternStore.workspacePatterns": [
{
"label": "Workspace specific pattern",
"find": "oldClass",
"replace": "newClass",
"flags": {
"isRegex": false,
"isCaseSensitive": true,
"matchWholeWord": true,
"isMultiline": false
},
"scope": "workspace"
}
]
}
@@ -0,0 +1,19 @@
import uuid from "./index.js";
export import v1 = uuid.v1;
export import v1ToV6 = uuid.v1ToV6;
export import v3 = uuid.v3;
export import v4 = uuid.v4;
export import v5 = uuid.v5;
export import v6 = uuid.v6;
export import v6ToV1 = uuid.v6ToV1;
export import v7 = uuid.v7;
export import NIL = uuid.NIL;
export import MAX = uuid.MAX;
export import version = uuid.version;
export import validate = uuid.validate;
export import stringify = uuid.stringify;
export import parse = uuid.parse;
export import V1Options = uuid.V1Options;
export import V4Options = uuid.V4Options;
export import V6Options = uuid.V6Options;
export import V7Options = uuid.V7Options;
@@ -0,0 +1,54 @@
{
"name": "@types/uuid",
"version": "10.0.0",
"description": "TypeScript definitions for uuid",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/uuid",
"license": "MIT",
"contributors": [
{
"name": "Oliver Hoffmann",
"githubUsername": "iamolivinius",
"url": "https://github.com/iamolivinius"
},
{
"name": "Felipe Ochoa",
"githubUsername": "felipeochoa",
"url": "https://github.com/felipeochoa"
},
{
"name": "Chris Barth",
"githubUsername": "cjbarth",
"url": "https://github.com/cjbarth"
},
{
"name": "Linus Unnebäck",
"githubUsername": "LinusU",
"url": "https://github.com/LinusU"
},
{
"name": "Christoph Tavan",
"githubUsername": "ctavan",
"url": "https://github.com/ctavan"
}
],
"main": "",
"types": "index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"types": {
"import": "./index.d.mts",
"default": "./index.d.ts"
}
}
},
"repository": {
"type": "git",
"url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
"directory": "types/uuid"
},
"scripts": {},
"dependencies": {},
"typesPublisherContentHash": "08fbc5ff7d23aaac1e81b5acf98181d2544ce6ffd5578e9879e2a75f0c087d54",
"typeScriptVersion": "4.7"
}
@@ -0,0 +1,2 @@
#!/usr/bin/env node
import '../uuid-bin.js';
@@ -0,0 +1,14 @@
export { default as MAX } from './max.js';
export { default as NIL } from './nil.js';
export { default as parse } from './parse.js';
export { default as stringify } from './stringify.js';
export { default as v1 } from './v1.js';
export { default as v1ToV6 } from './v1ToV6.js';
export { default as v3 } from './v3.js';
export { default as v4 } from './v4.js';
export { default as v5 } from './v5.js';
export { default as v6 } from './v6.js';
export { default as v6ToV1 } from './v6ToV1.js';
export { default as v7 } from './v7.js';
export { default as validate } from './validate.js';
export { default as version } from './version.js';
@@ -0,0 +1 @@
export default 'ffffffff-ffff-ffff-ffff-ffffffffffff';
@@ -0,0 +1,11 @@
import { createHash } from 'node:crypto';
function md5(bytes) {
if (Array.isArray(bytes)) {
bytes = Buffer.from(bytes);
}
else if (typeof bytes === 'string') {
bytes = Buffer.from(bytes, 'utf8');
}
return createHash('md5').update(bytes).digest();
}
export default md5;
@@ -0,0 +1,2 @@
import { randomUUID } from 'node:crypto';
export default { randomUUID };
@@ -0,0 +1 @@
export default '00000000-0000-0000-0000-000000000000';
@@ -0,0 +1,9 @@
import validate from './validate.js';
function parse(uuid) {
if (!validate(uuid)) {
throw TypeError('Invalid UUID');
}
let v;
return Uint8Array.of((v = parseInt(uuid.slice(0, 8), 16)) >>> 24, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff, (v = parseInt(uuid.slice(9, 13), 16)) >>> 8, v & 0xff, (v = parseInt(uuid.slice(14, 18), 16)) >>> 8, v & 0xff, (v = parseInt(uuid.slice(19, 23), 16)) >>> 8, v & 0xff, ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff, (v / 0x100000000) & 0xff, (v >>> 24) & 0xff, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff);
}
export default parse;
@@ -0,0 +1 @@
export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
@@ -0,0 +1,10 @@
import { randomFillSync } from 'node:crypto';
const rnds8Pool = new Uint8Array(256);
let poolPtr = rnds8Pool.length;
export default function rng() {
if (poolPtr > rnds8Pool.length - 16) {
randomFillSync(rnds8Pool);
poolPtr = 0;
}
return rnds8Pool.slice(poolPtr, (poolPtr += 16));
}
@@ -0,0 +1,11 @@
import { createHash } from 'node:crypto';
function sha1(bytes) {
if (Array.isArray(bytes)) {
bytes = Buffer.from(bytes);
}
else if (typeof bytes === 'string') {
bytes = Buffer.from(bytes, 'utf8');
}
return createHash('sha1').update(bytes).digest();
}
export default sha1;
@@ -0,0 +1,35 @@
import validate from './validate.js';
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1));
}
export function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] +
byteToHex[arr[offset + 1]] +
byteToHex[arr[offset + 2]] +
byteToHex[arr[offset + 3]] +
'-' +
byteToHex[arr[offset + 4]] +
byteToHex[arr[offset + 5]] +
'-' +
byteToHex[arr[offset + 6]] +
byteToHex[arr[offset + 7]] +
'-' +
byteToHex[arr[offset + 8]] +
byteToHex[arr[offset + 9]] +
'-' +
byteToHex[arr[offset + 10]] +
byteToHex[arr[offset + 11]] +
byteToHex[arr[offset + 12]] +
byteToHex[arr[offset + 13]] +
byteToHex[arr[offset + 14]] +
byteToHex[arr[offset + 15]]).toLowerCase();
}
function stringify(arr, offset = 0) {
const uuid = unsafeStringify(arr, offset);
if (!validate(uuid)) {
throw TypeError('Stringified UUID is invalid');
}
return uuid;
}
export default stringify;
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,70 @@
import * as assert from 'node:assert/strict';
import v1 from './v1.js';
import v3 from './v3.js';
import v4 from './v4.js';
import v5 from './v5.js';
import v6 from './v6.js';
import v7 from './v7.js';
function usage() {
console.log('Usage:');
console.log(' uuid');
console.log(' uuid v1');
console.log(' uuid v3 <name> <namespace uuid>');
console.log(' uuid v4');
console.log(' uuid v5 <name> <namespace uuid>');
console.log(' uuid v6');
console.log(' uuid v7');
console.log(' uuid --help');
console.log('\nNote: <namespace uuid> may be "URL" or "DNS" to use the corresponding UUIDs defined by RFC9562');
}
const args = process.argv.slice(2);
if (args.indexOf('--help') >= 0) {
usage();
process.exit(0);
}
const version = args.shift() || 'v4';
switch (version) {
case 'v1':
console.log(v1());
break;
case 'v3': {
const name = args.shift();
let namespace = args.shift();
assert.ok(name != null, 'v3 name not specified');
assert.ok(namespace != null, 'v3 namespace not specified');
if (namespace === 'URL') {
namespace = v3.URL;
}
if (namespace === 'DNS') {
namespace = v3.DNS;
}
console.log(v3(name, namespace));
break;
}
case 'v4':
console.log(v4());
break;
case 'v5': {
const name = args.shift();
let namespace = args.shift();
assert.ok(name != null, 'v5 name not specified');
assert.ok(namespace != null, 'v5 namespace not specified');
if (namespace === 'URL') {
namespace = v5.URL;
}
if (namespace === 'DNS') {
namespace = v5.DNS;
}
console.log(v5(name, namespace));
break;
}
case 'v6':
console.log(v6());
break;
case 'v7':
console.log(v7());
break;
default:
usage();
process.exit(1);
}
@@ -0,0 +1,83 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
const _state = {};
function v1(options, buf, offset) {
let bytes;
const isV6 = options?._v6 ?? false;
if (options) {
const optionsKeys = Object.keys(options);
if (optionsKeys.length === 1 && optionsKeys[0] === '_v6') {
options = undefined;
}
}
if (options) {
bytes = v1Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.nsecs, options.clockseq, options.node, buf, offset);
}
else {
const now = Date.now();
const rnds = rng();
updateV1State(_state, now, rnds);
bytes = v1Bytes(rnds, _state.msecs, _state.nsecs, isV6 ? undefined : _state.clockseq, isV6 ? undefined : _state.node, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
export function updateV1State(state, now, rnds) {
state.msecs ??= -Infinity;
state.nsecs ??= 0;
if (now === state.msecs) {
state.nsecs++;
if (state.nsecs >= 10000) {
state.node = undefined;
state.nsecs = 0;
}
}
else if (now > state.msecs) {
state.nsecs = 0;
}
else if (now < state.msecs) {
state.node = undefined;
}
if (!state.node) {
state.node = rnds.slice(10, 16);
state.node[0] |= 0x01;
state.clockseq = ((rnds[8] << 8) | rnds[9]) & 0x3fff;
}
state.msecs = now;
return state;
}
function v1Bytes(rnds, msecs, nsecs, clockseq, node, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
nsecs ??= 0;
clockseq ??= ((rnds[8] << 8) | rnds[9]) & 0x3fff;
node ??= rnds.slice(10, 16);
msecs += 12219292800000;
const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
buf[offset++] = (tl >>> 24) & 0xff;
buf[offset++] = (tl >>> 16) & 0xff;
buf[offset++] = (tl >>> 8) & 0xff;
buf[offset++] = tl & 0xff;
const tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff;
buf[offset++] = (tmh >>> 8) & 0xff;
buf[offset++] = tmh & 0xff;
buf[offset++] = ((tmh >>> 24) & 0xf) | 0x10;
buf[offset++] = (tmh >>> 16) & 0xff;
buf[offset++] = (clockseq >>> 8) | 0x80;
buf[offset++] = clockseq & 0xff;
for (let n = 0; n < 6; ++n) {
buf[offset++] = node[n];
}
return buf;
}
export default v1;
@@ -0,0 +1,10 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export default function v1ToV6(uuid) {
const v1Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;
const v6Bytes = _v1ToV6(v1Bytes);
return typeof uuid === 'string' ? unsafeStringify(v6Bytes) : v6Bytes;
}
function _v1ToV6(v1Bytes) {
return Uint8Array.of(((v1Bytes[6] & 0x0f) << 4) | ((v1Bytes[7] >> 4) & 0x0f), ((v1Bytes[7] & 0x0f) << 4) | ((v1Bytes[4] & 0xf0) >> 4), ((v1Bytes[4] & 0x0f) << 4) | ((v1Bytes[5] & 0xf0) >> 4), ((v1Bytes[5] & 0x0f) << 4) | ((v1Bytes[0] & 0xf0) >> 4), ((v1Bytes[0] & 0x0f) << 4) | ((v1Bytes[1] & 0xf0) >> 4), ((v1Bytes[1] & 0x0f) << 4) | ((v1Bytes[2] & 0xf0) >> 4), 0x60 | (v1Bytes[2] & 0x0f), v1Bytes[3], v1Bytes[8], v1Bytes[9], v1Bytes[10], v1Bytes[11], v1Bytes[12], v1Bytes[13], v1Bytes[14], v1Bytes[15]);
}
@@ -0,0 +1,9 @@
import md5 from './md5.js';
import v35, { DNS, URL } from './v35.js';
export { DNS, URL } from './v35.js';
function v3(value, namespace, buf, offset) {
return v35(0x30, md5, value, namespace, buf, offset);
}
v3.DNS = DNS;
v3.URL = URL;
export default v3;
@@ -0,0 +1,36 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export function stringToBytes(str) {
str = unescape(encodeURIComponent(str));
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; ++i) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
export const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
export const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
export default function v35(version, hash, value, namespace, buf, offset) {
const valueBytes = typeof value === 'string' ? stringToBytes(value) : value;
const namespaceBytes = typeof namespace === 'string' ? parse(namespace) : namespace;
if (typeof namespace === 'string') {
namespace = parse(namespace);
}
if (namespace?.length !== 16) {
throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)');
}
let bytes = new Uint8Array(16 + valueBytes.length);
bytes.set(namespaceBytes);
bytes.set(valueBytes, namespaceBytes.length);
bytes = hash(bytes);
bytes[6] = (bytes[6] & 0x0f) | version;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
if (buf) {
offset = offset || 0;
for (let i = 0; i < 16; ++i) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
@@ -0,0 +1,30 @@
import native from './native.js';
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
function _v4(options, buf, offset) {
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
if (buf) {
offset = offset || 0;
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
function v4(options, buf, offset) {
if (native.randomUUID && !buf && !options) {
return native.randomUUID();
}
return _v4(options, buf, offset);
}
export default v4;
@@ -0,0 +1,9 @@
import sha1 from './sha1.js';
import v35, { DNS, URL } from './v35.js';
export { DNS, URL } from './v35.js';
function v5(value, namespace, buf, offset) {
return v35(0x50, sha1, value, namespace, buf, offset);
}
v5.DNS = DNS;
v5.URL = URL;
export default v5;
@@ -0,0 +1,17 @@
import { unsafeStringify } from './stringify.js';
import v1 from './v1.js';
import v1ToV6 from './v1ToV6.js';
function v6(options, buf, offset) {
options ??= {};
offset ??= 0;
let bytes = v1({ ...options, _v6: true }, new Uint8Array(16));
bytes = v1ToV6(bytes);
if (buf) {
for (let i = 0; i < 16; i++) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
export default v6;
@@ -0,0 +1,10 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export default function v6ToV1(uuid) {
const v6Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;
const v1Bytes = _v6ToV1(v6Bytes);
return typeof uuid === 'string' ? unsafeStringify(v1Bytes) : v1Bytes;
}
function _v6ToV1(v6Bytes) {
return Uint8Array.of(((v6Bytes[3] & 0x0f) << 4) | ((v6Bytes[4] >> 4) & 0x0f), ((v6Bytes[4] & 0x0f) << 4) | ((v6Bytes[5] & 0xf0) >> 4), ((v6Bytes[5] & 0x0f) << 4) | (v6Bytes[6] & 0x0f), v6Bytes[7], ((v6Bytes[1] & 0x0f) << 4) | ((v6Bytes[2] & 0xf0) >> 4), ((v6Bytes[2] & 0x0f) << 4) | ((v6Bytes[3] & 0xf0) >> 4), 0x10 | ((v6Bytes[0] & 0xf0) >> 4), ((v6Bytes[0] & 0x0f) << 4) | ((v6Bytes[1] & 0xf0) >> 4), v6Bytes[8], v6Bytes[9], v6Bytes[10], v6Bytes[11], v6Bytes[12], v6Bytes[13], v6Bytes[14], v6Bytes[15]);
}
@@ -0,0 +1,65 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
const _state = {};
function v7(options, buf, offset) {
let bytes;
if (options) {
bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
}
else {
const now = Date.now();
const rnds = rng();
updateV7State(_state, now, rnds);
bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
export function updateV7State(state, now, rnds) {
state.msecs ??= -Infinity;
state.seq ??= 0;
if (now > state.msecs) {
state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
state.msecs = now;
}
else {
state.seq = (state.seq + 1) | 0;
if (state.seq === 0) {
state.msecs++;
}
}
return state;
}
function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
seq ??= ((rnds[6] * 0x7f) << 24) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
buf[offset++] = (msecs / 0x10000000000) & 0xff;
buf[offset++] = (msecs / 0x100000000) & 0xff;
buf[offset++] = (msecs / 0x1000000) & 0xff;
buf[offset++] = (msecs / 0x10000) & 0xff;
buf[offset++] = (msecs / 0x100) & 0xff;
buf[offset++] = msecs & 0xff;
buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f);
buf[offset++] = (seq >>> 20) & 0xff;
buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f);
buf[offset++] = (seq >>> 6) & 0xff;
buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03);
buf[offset++] = rnds[11];
buf[offset++] = rnds[12];
buf[offset++] = rnds[13];
buf[offset++] = rnds[14];
buf[offset++] = rnds[15];
return buf;
}
export default v7;
@@ -0,0 +1,5 @@
import REGEX from './regex.js';
function validate(uuid) {
return typeof uuid === 'string' && REGEX.test(uuid);
}
export default validate;
@@ -0,0 +1,8 @@
import validate from './validate.js';
function version(uuid) {
if (!validate(uuid)) {
throw TypeError('Invalid UUID');
}
return parseInt(uuid.slice(14, 15), 16);
}
export default version;
@@ -0,0 +1,14 @@
export { default as MAX } from './max.js';
export { default as NIL } from './nil.js';
export { default as parse } from './parse.js';
export { default as stringify } from './stringify.js';
export { default as v1 } from './v1.js';
export { default as v1ToV6 } from './v1ToV6.js';
export { default as v3 } from './v3.js';
export { default as v4 } from './v4.js';
export { default as v5 } from './v5.js';
export { default as v6 } from './v6.js';
export { default as v6ToV1 } from './v6ToV1.js';
export { default as v7 } from './v7.js';
export { default as validate } from './validate.js';
export { default as version } from './version.js';
@@ -0,0 +1 @@
export default 'ffffffff-ffff-ffff-ffff-ffffffffffff';
@@ -0,0 +1,135 @@
function md5(bytes) {
const words = uint8ToUint32(bytes);
const md5Bytes = wordsToMd5(words, bytes.length * 8);
return uint32ToUint8(md5Bytes);
}
function uint32ToUint8(input) {
const bytes = new Uint8Array(input.length * 4);
for (let i = 0; i < input.length * 4; i++) {
bytes[i] = (input[i >> 2] >>> ((i % 4) * 8)) & 0xff;
}
return bytes;
}
function getOutputLength(inputLength8) {
return (((inputLength8 + 64) >>> 9) << 4) + 14 + 1;
}
function wordsToMd5(x, len) {
const xpad = new Uint32Array(getOutputLength(len)).fill(0);
xpad.set(x);
xpad[len >> 5] |= 0x80 << len % 32;
xpad[xpad.length - 1] = len;
x = xpad;
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
for (let i = 0; i < x.length; i += 16) {
const olda = a;
const oldb = b;
const oldc = c;
const oldd = d;
a = md5ff(a, b, c, d, x[i], 7, -680876936);
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5gg(b, c, d, a, x[i], 20, -373897302);
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5hh(d, a, b, c, x[i], 11, -358537222);
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5ii(a, b, c, d, x[i], 6, -198630844);
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safeAdd(a, olda);
b = safeAdd(b, oldb);
c = safeAdd(c, oldc);
d = safeAdd(d, oldd);
}
return Uint32Array.of(a, b, c, d);
}
function uint8ToUint32(input) {
if (input.length === 0) {
return new Uint32Array();
}
const output = new Uint32Array(getOutputLength(input.length * 8)).fill(0);
for (let i = 0; i < input.length; i++) {
output[i >> 2] |= (input[i] & 0xff) << ((i % 4) * 8);
}
return output;
}
function safeAdd(x, y) {
const lsw = (x & 0xffff) + (y & 0xffff);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
function bitRotateLeft(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
function md5cmn(q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
}
function md5ff(a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t);
}
function md5gg(a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t);
}
function md5hh(a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5ii(a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t);
}
export default md5;
@@ -0,0 +1,2 @@
const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto);
export default { randomUUID };
@@ -0,0 +1 @@
export default '00000000-0000-0000-0000-000000000000';
@@ -0,0 +1,9 @@
import validate from './validate.js';
function parse(uuid) {
if (!validate(uuid)) {
throw TypeError('Invalid UUID');
}
let v;
return Uint8Array.of((v = parseInt(uuid.slice(0, 8), 16)) >>> 24, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff, (v = parseInt(uuid.slice(9, 13), 16)) >>> 8, v & 0xff, (v = parseInt(uuid.slice(14, 18), 16)) >>> 8, v & 0xff, (v = parseInt(uuid.slice(19, 23), 16)) >>> 8, v & 0xff, ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff, (v / 0x100000000) & 0xff, (v >>> 24) & 0xff, (v >>> 16) & 0xff, (v >>> 8) & 0xff, v & 0xff);
}
export default parse;
@@ -0,0 +1 @@
export default /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
@@ -0,0 +1,11 @@
let getRandomValues;
const rnds8 = new Uint8Array(16);
export default function rng() {
if (!getRandomValues) {
if (typeof crypto === 'undefined' || !crypto.getRandomValues) {
throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported');
}
getRandomValues = crypto.getRandomValues.bind(crypto);
}
return getRandomValues(rnds8);
}
@@ -0,0 +1,70 @@
function f(s, x, y, z) {
switch (s) {
case 0:
return (x & y) ^ (~x & z);
case 1:
return x ^ y ^ z;
case 2:
return (x & y) ^ (x & z) ^ (y & z);
case 3:
return x ^ y ^ z;
}
}
function ROTL(x, n) {
return (x << n) | (x >>> (32 - n));
}
function sha1(bytes) {
const K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
const H = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0];
const newBytes = new Uint8Array(bytes.length + 1);
newBytes.set(bytes);
newBytes[bytes.length] = 0x80;
bytes = newBytes;
const l = bytes.length / 4 + 2;
const N = Math.ceil(l / 16);
const M = new Array(N);
for (let i = 0; i < N; ++i) {
const arr = new Uint32Array(16);
for (let j = 0; j < 16; ++j) {
arr[j] =
(bytes[i * 64 + j * 4] << 24) |
(bytes[i * 64 + j * 4 + 1] << 16) |
(bytes[i * 64 + j * 4 + 2] << 8) |
bytes[i * 64 + j * 4 + 3];
}
M[i] = arr;
}
M[N - 1][14] = ((bytes.length - 1) * 8) / Math.pow(2, 32);
M[N - 1][14] = Math.floor(M[N - 1][14]);
M[N - 1][15] = ((bytes.length - 1) * 8) & 0xffffffff;
for (let i = 0; i < N; ++i) {
const W = new Uint32Array(80);
for (let t = 0; t < 16; ++t) {
W[t] = M[i][t];
}
for (let t = 16; t < 80; ++t) {
W[t] = ROTL(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1);
}
let a = H[0];
let b = H[1];
let c = H[2];
let d = H[3];
let e = H[4];
for (let t = 0; t < 80; ++t) {
const s = Math.floor(t / 20);
const T = (ROTL(a, 5) + f(s, b, c, d) + e + K[s] + W[t]) >>> 0;
e = d;
d = c;
c = ROTL(b, 30) >>> 0;
b = a;
a = T;
}
H[0] = (H[0] + a) >>> 0;
H[1] = (H[1] + b) >>> 0;
H[2] = (H[2] + c) >>> 0;
H[3] = (H[3] + d) >>> 0;
H[4] = (H[4] + e) >>> 0;
}
return Uint8Array.of(H[0] >> 24, H[0] >> 16, H[0] >> 8, H[0], H[1] >> 24, H[1] >> 16, H[1] >> 8, H[1], H[2] >> 24, H[2] >> 16, H[2] >> 8, H[2], H[3] >> 24, H[3] >> 16, H[3] >> 8, H[3], H[4] >> 24, H[4] >> 16, H[4] >> 8, H[4]);
}
export default sha1;
@@ -0,0 +1,35 @@
import validate from './validate.js';
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1));
}
export function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] +
byteToHex[arr[offset + 1]] +
byteToHex[arr[offset + 2]] +
byteToHex[arr[offset + 3]] +
'-' +
byteToHex[arr[offset + 4]] +
byteToHex[arr[offset + 5]] +
'-' +
byteToHex[arr[offset + 6]] +
byteToHex[arr[offset + 7]] +
'-' +
byteToHex[arr[offset + 8]] +
byteToHex[arr[offset + 9]] +
'-' +
byteToHex[arr[offset + 10]] +
byteToHex[arr[offset + 11]] +
byteToHex[arr[offset + 12]] +
byteToHex[arr[offset + 13]] +
byteToHex[arr[offset + 14]] +
byteToHex[arr[offset + 15]]).toLowerCase();
}
function stringify(arr, offset = 0) {
const uuid = unsafeStringify(arr, offset);
if (!validate(uuid)) {
throw TypeError('Stringified UUID is invalid');
}
return uuid;
}
export default stringify;
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,70 @@
import * as assert from 'node:assert/strict';
import v1 from './v1.js';
import v3 from './v3.js';
import v4 from './v4.js';
import v5 from './v5.js';
import v6 from './v6.js';
import v7 from './v7.js';
function usage() {
console.log('Usage:');
console.log(' uuid');
console.log(' uuid v1');
console.log(' uuid v3 <name> <namespace uuid>');
console.log(' uuid v4');
console.log(' uuid v5 <name> <namespace uuid>');
console.log(' uuid v6');
console.log(' uuid v7');
console.log(' uuid --help');
console.log('\nNote: <namespace uuid> may be "URL" or "DNS" to use the corresponding UUIDs defined by RFC9562');
}
const args = process.argv.slice(2);
if (args.indexOf('--help') >= 0) {
usage();
process.exit(0);
}
const version = args.shift() || 'v4';
switch (version) {
case 'v1':
console.log(v1());
break;
case 'v3': {
const name = args.shift();
let namespace = args.shift();
assert.ok(name != null, 'v3 name not specified');
assert.ok(namespace != null, 'v3 namespace not specified');
if (namespace === 'URL') {
namespace = v3.URL;
}
if (namespace === 'DNS') {
namespace = v3.DNS;
}
console.log(v3(name, namespace));
break;
}
case 'v4':
console.log(v4());
break;
case 'v5': {
const name = args.shift();
let namespace = args.shift();
assert.ok(name != null, 'v5 name not specified');
assert.ok(namespace != null, 'v5 namespace not specified');
if (namespace === 'URL') {
namespace = v5.URL;
}
if (namespace === 'DNS') {
namespace = v5.DNS;
}
console.log(v5(name, namespace));
break;
}
case 'v6':
console.log(v6());
break;
case 'v7':
console.log(v7());
break;
default:
usage();
process.exit(1);
}
@@ -0,0 +1,83 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
const _state = {};
function v1(options, buf, offset) {
let bytes;
const isV6 = options?._v6 ?? false;
if (options) {
const optionsKeys = Object.keys(options);
if (optionsKeys.length === 1 && optionsKeys[0] === '_v6') {
options = undefined;
}
}
if (options) {
bytes = v1Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.nsecs, options.clockseq, options.node, buf, offset);
}
else {
const now = Date.now();
const rnds = rng();
updateV1State(_state, now, rnds);
bytes = v1Bytes(rnds, _state.msecs, _state.nsecs, isV6 ? undefined : _state.clockseq, isV6 ? undefined : _state.node, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
export function updateV1State(state, now, rnds) {
state.msecs ??= -Infinity;
state.nsecs ??= 0;
if (now === state.msecs) {
state.nsecs++;
if (state.nsecs >= 10000) {
state.node = undefined;
state.nsecs = 0;
}
}
else if (now > state.msecs) {
state.nsecs = 0;
}
else if (now < state.msecs) {
state.node = undefined;
}
if (!state.node) {
state.node = rnds.slice(10, 16);
state.node[0] |= 0x01;
state.clockseq = ((rnds[8] << 8) | rnds[9]) & 0x3fff;
}
state.msecs = now;
return state;
}
function v1Bytes(rnds, msecs, nsecs, clockseq, node, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
nsecs ??= 0;
clockseq ??= ((rnds[8] << 8) | rnds[9]) & 0x3fff;
node ??= rnds.slice(10, 16);
msecs += 12219292800000;
const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
buf[offset++] = (tl >>> 24) & 0xff;
buf[offset++] = (tl >>> 16) & 0xff;
buf[offset++] = (tl >>> 8) & 0xff;
buf[offset++] = tl & 0xff;
const tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff;
buf[offset++] = (tmh >>> 8) & 0xff;
buf[offset++] = tmh & 0xff;
buf[offset++] = ((tmh >>> 24) & 0xf) | 0x10;
buf[offset++] = (tmh >>> 16) & 0xff;
buf[offset++] = (clockseq >>> 8) | 0x80;
buf[offset++] = clockseq & 0xff;
for (let n = 0; n < 6; ++n) {
buf[offset++] = node[n];
}
return buf;
}
export default v1;
@@ -0,0 +1,10 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export default function v1ToV6(uuid) {
const v1Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;
const v6Bytes = _v1ToV6(v1Bytes);
return typeof uuid === 'string' ? unsafeStringify(v6Bytes) : v6Bytes;
}
function _v1ToV6(v1Bytes) {
return Uint8Array.of(((v1Bytes[6] & 0x0f) << 4) | ((v1Bytes[7] >> 4) & 0x0f), ((v1Bytes[7] & 0x0f) << 4) | ((v1Bytes[4] & 0xf0) >> 4), ((v1Bytes[4] & 0x0f) << 4) | ((v1Bytes[5] & 0xf0) >> 4), ((v1Bytes[5] & 0x0f) << 4) | ((v1Bytes[0] & 0xf0) >> 4), ((v1Bytes[0] & 0x0f) << 4) | ((v1Bytes[1] & 0xf0) >> 4), ((v1Bytes[1] & 0x0f) << 4) | ((v1Bytes[2] & 0xf0) >> 4), 0x60 | (v1Bytes[2] & 0x0f), v1Bytes[3], v1Bytes[8], v1Bytes[9], v1Bytes[10], v1Bytes[11], v1Bytes[12], v1Bytes[13], v1Bytes[14], v1Bytes[15]);
}
@@ -0,0 +1,9 @@
import md5 from './md5.js';
import v35, { DNS, URL } from './v35.js';
export { DNS, URL } from './v35.js';
function v3(value, namespace, buf, offset) {
return v35(0x30, md5, value, namespace, buf, offset);
}
v3.DNS = DNS;
v3.URL = URL;
export default v3;
@@ -0,0 +1,36 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export function stringToBytes(str) {
str = unescape(encodeURIComponent(str));
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; ++i) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
export const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
export const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
export default function v35(version, hash, value, namespace, buf, offset) {
const valueBytes = typeof value === 'string' ? stringToBytes(value) : value;
const namespaceBytes = typeof namespace === 'string' ? parse(namespace) : namespace;
if (typeof namespace === 'string') {
namespace = parse(namespace);
}
if (namespace?.length !== 16) {
throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)');
}
let bytes = new Uint8Array(16 + valueBytes.length);
bytes.set(namespaceBytes);
bytes.set(valueBytes, namespaceBytes.length);
bytes = hash(bytes);
bytes[6] = (bytes[6] & 0x0f) | version;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
if (buf) {
offset = offset || 0;
for (let i = 0; i < 16; ++i) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
@@ -0,0 +1,30 @@
import native from './native.js';
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
function _v4(options, buf, offset) {
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;
if (buf) {
offset = offset || 0;
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
function v4(options, buf, offset) {
if (native.randomUUID && !buf && !options) {
return native.randomUUID();
}
return _v4(options, buf, offset);
}
export default v4;
@@ -0,0 +1,9 @@
import sha1 from './sha1.js';
import v35, { DNS, URL } from './v35.js';
export { DNS, URL } from './v35.js';
function v5(value, namespace, buf, offset) {
return v35(0x50, sha1, value, namespace, buf, offset);
}
v5.DNS = DNS;
v5.URL = URL;
export default v5;
@@ -0,0 +1,17 @@
import { unsafeStringify } from './stringify.js';
import v1 from './v1.js';
import v1ToV6 from './v1ToV6.js';
function v6(options, buf, offset) {
options ??= {};
offset ??= 0;
let bytes = v1({ ...options, _v6: true }, new Uint8Array(16));
bytes = v1ToV6(bytes);
if (buf) {
for (let i = 0; i < 16; i++) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
export default v6;
@@ -0,0 +1,10 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export default function v6ToV1(uuid) {
const v6Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;
const v1Bytes = _v6ToV1(v6Bytes);
return typeof uuid === 'string' ? unsafeStringify(v1Bytes) : v1Bytes;
}
function _v6ToV1(v6Bytes) {
return Uint8Array.of(((v6Bytes[3] & 0x0f) << 4) | ((v6Bytes[4] >> 4) & 0x0f), ((v6Bytes[4] & 0x0f) << 4) | ((v6Bytes[5] & 0xf0) >> 4), ((v6Bytes[5] & 0x0f) << 4) | (v6Bytes[6] & 0x0f), v6Bytes[7], ((v6Bytes[1] & 0x0f) << 4) | ((v6Bytes[2] & 0xf0) >> 4), ((v6Bytes[2] & 0x0f) << 4) | ((v6Bytes[3] & 0xf0) >> 4), 0x10 | ((v6Bytes[0] & 0xf0) >> 4), ((v6Bytes[0] & 0x0f) << 4) | ((v6Bytes[1] & 0xf0) >> 4), v6Bytes[8], v6Bytes[9], v6Bytes[10], v6Bytes[11], v6Bytes[12], v6Bytes[13], v6Bytes[14], v6Bytes[15]);
}
@@ -0,0 +1,65 @@
import rng from './rng.js';
import { unsafeStringify } from './stringify.js';
const _state = {};
function v7(options, buf, offset) {
let bytes;
if (options) {
bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
}
else {
const now = Date.now();
const rnds = rng();
updateV7State(_state, now, rnds);
bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
export function updateV7State(state, now, rnds) {
state.msecs ??= -Infinity;
state.seq ??= 0;
if (now > state.msecs) {
state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
state.msecs = now;
}
else {
state.seq = (state.seq + 1) | 0;
if (state.seq === 0) {
state.msecs++;
}
}
return state;
}
function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error('Random bytes length must be >= 16');
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
}
else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
seq ??= ((rnds[6] * 0x7f) << 24) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];
buf[offset++] = (msecs / 0x10000000000) & 0xff;
buf[offset++] = (msecs / 0x100000000) & 0xff;
buf[offset++] = (msecs / 0x1000000) & 0xff;
buf[offset++] = (msecs / 0x10000) & 0xff;
buf[offset++] = (msecs / 0x100) & 0xff;
buf[offset++] = msecs & 0xff;
buf[offset++] = 0x70 | ((seq >>> 28) & 0x0f);
buf[offset++] = (seq >>> 20) & 0xff;
buf[offset++] = 0x80 | ((seq >>> 14) & 0x3f);
buf[offset++] = (seq >>> 6) & 0xff;
buf[offset++] = ((seq << 2) & 0xff) | (rnds[10] & 0x03);
buf[offset++] = rnds[11];
buf[offset++] = rnds[12];
buf[offset++] = rnds[13];
buf[offset++] = rnds[14];
buf[offset++] = rnds[15];
return buf;
}
export default v7;
@@ -0,0 +1,5 @@
import REGEX from './regex.js';
function validate(uuid) {
return typeof uuid === 'string' && REGEX.test(uuid);
}
export default validate;
@@ -0,0 +1,8 @@
import validate from './validate.js';
function version(uuid) {
if (!validate(uuid)) {
throw TypeError('Invalid UUID');
}
return parseInt(uuid.slice(14, 15), 16);
}
export default version;
@@ -0,0 +1,119 @@
{
"name": "uuid",
"version": "13.0.0",
"description": "RFC9562 UUIDs",
"type": "module",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"keywords": [
"uuid",
"guid",
"rfc4122",
"rfc9562"
],
"license": "MIT",
"bin": {
"uuid": "./dist-node/bin/uuid"
},
"sideEffects": false,
"types": "./dist/index.d.ts",
"exports": {
".": {
"node": "./dist-node/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"files": [
"dist",
"dist-node",
"!**/test"
],
"devDependencies": {
"@babel/eslint-parser": "7.27.1",
"@commitlint/cli": "19.8.0",
"@commitlint/config-conventional": "19.8.0",
"@eslint/js": "9.26.0",
"bundlewatch": "0.4.1",
"commander": "13.1.0",
"eslint": "9.26.0",
"eslint-config-prettier": "10.1.2",
"eslint-plugin-prettier": "5.4.0",
"globals": "16.0.0",
"husky": "9.1.7",
"jest": "29.7.0",
"lint-staged": "15.5.2",
"neostandard": "0.12.1",
"npm-run-all": "4.1.5",
"prettier": "3.5.3",
"release-please": "17.0.0",
"runmd": "1.4.1",
"standard-version": "9.5.0",
"typescript": "5.2.2",
"typescript-eslint": "8.32.0"
},
"optionalDevDependencies": {
"@wdio/browserstack-service": "9.2.1",
"@wdio/cli": "9.2.1",
"@wdio/jasmine-framework": "9.2.1",
"@wdio/local-runner": "9.2.1",
"@wdio/spec-reporter": "9.1.3",
"@wdio/static-server-service": "9.1.3"
},
"scripts": {
"build": "./scripts/build.sh",
"build:watch": "tsc --watch -p tsconfig.json",
"bundlewatch": "npm run pretest:browser && bundlewatch --config bundlewatch.config.json",
"docs:diff": "npm run docs && git diff --quiet -I \"[0-9a-f-]{36}\" README.md",
"docs": "npm run build && npx runmd --output=README.md README_js.md",
"eslint:check": "eslint src/ test/ examples/ *.[jt]s",
"eslint:fix": "eslint --fix src/ test/ examples/ *.[jt]s",
"examples:browser:rollup:build": "cd examples/browser-rollup && npm run build",
"examples:browser:webpack:build": "cd examples/browser-webpack && npm run build",
"examples:node:esmodules:test": "cd examples/node-esmodules && npm test",
"examples:node:jest:test": "cd examples/node-jest && npm test",
"examples:node:typescript:test": "cd examples/typescript && npm test",
"lint": "npm run eslint:check && npm run prettier:check",
"md": "runmd --watch --output=README.md README_js.md",
"prepack": "npm run build -- --no-pack",
"prepare": "husky",
"prepublishOnly": "npm run build",
"pretest:benchmark": "npm run build",
"pretest:browser": "./scripts/iodd && npm run build && npm-run-all --parallel examples:browser:**",
"pretest:node": "npm run build",
"pretest": "npm run build",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
"release": "standard-version --no-verify",
"test:benchmark": "cd examples/benchmark && npm test",
"test:browser": "wdio run ./wdio.conf.js",
"test:node": "npm-run-all --parallel examples:node:**",
"test:watch": "node --test --enable-source-maps --watch dist-node/test/*.js",
"test": "node --test --enable-source-maps dist-node/test/*.js"
},
"repository": {
"type": "git",
"url": "https://github.com/uuidjs/uuid.git"
},
"lint-staged": {
"*": [
"prettier --no-error-on-unmatched-pattern --write"
],
"*.{js,jsx}": [
"eslint --no-error-on-unmatched-pattern --fix"
]
},
"standard-version": {
"scripts": {
"postchangelog": "prettier --write CHANGELOG.md"
}
},
"packageManager": "npm@11.3.0"
}
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CHOICE_WORKSPACE_DESC = exports.CHOICE_WORKSPACE_LABEL = exports.CHOICE_GLOBAL_DESC = exports.CHOICE_GLOBAL_LABEL = exports.PROMPT_CONFIRM_OVERWRITE = exports.PROMPT_CONFIRM_DELETE = exports.PROMPT_SELECT_PATTERN = exports.PROMPT_SELECT_SCOPE = exports.PROMPT_PATTERN_NAME_PLACEHOLDER = exports.PROMPT_PATTERN_NAME = exports.INFO_PATTERN_DELETED = exports.INFO_PATTERN_SAVED = exports.INFO_PATTERN_LOADED = exports.INFO_NO_PATTERNS = exports.ERR_MANAGING_PATTERNS = exports.ERR_LOADING_PATTERN = exports.ERR_SAVING_PATTERN = exports.ERR_PATTERN_NAME_EMPTY = exports.ERR_NO_SEARCH_STATE = exports.UI_MANAGE_TITLE = exports.UI_SECTION_USER = exports.UI_SECTION_WORKSPACE = exports.UI_NO_PATTERNS_FOUND = exports.UI_NO_PATTERNS = exports.MSG_TYPE_SAVE = exports.MSG_TYPE_LOAD = exports.MSG_TYPE_DELETE = exports.MSG_TYPE_PATTERNS = exports.MSG_TYPE_READY = exports.COMMAND_MANAGE = exports.SCOPE_WORKSPACE = exports.SCOPE_GLOBAL = exports.CONFIG_KEY_WORKSPACE_PATTERNS = exports.CONFIG_KEY_USER_PATTERNS = exports.CONFIG_NAMESPACE = void 0;
exports.CONFIG_NAMESPACE = 'patternStore';
exports.CONFIG_KEY_USER_PATTERNS = 'savedPatterns';
exports.CONFIG_KEY_WORKSPACE_PATTERNS = 'workspacePatterns';
exports.SCOPE_GLOBAL = 'global';
exports.SCOPE_WORKSPACE = 'workspace';
exports.COMMAND_MANAGE = 'patternStore.manage';
exports.MSG_TYPE_READY = 'ready';
exports.MSG_TYPE_PATTERNS = 'patterns';
exports.MSG_TYPE_DELETE = 'delete';
exports.MSG_TYPE_LOAD = 'load';
exports.MSG_TYPE_SAVE = 'save';
exports.UI_NO_PATTERNS = 'No patterns yet';
exports.UI_NO_PATTERNS_FOUND = 'No patterns found';
exports.UI_SECTION_WORKSPACE = 'Workspace';
exports.UI_SECTION_USER = 'User';
exports.UI_MANAGE_TITLE = 'Manage Patterns';
exports.ERR_NO_SEARCH_STATE = 'Could not read current search state';
exports.ERR_PATTERN_NAME_EMPTY = 'Pattern name cannot be empty';
exports.ERR_SAVING_PATTERN = 'Error saving pattern';
exports.ERR_LOADING_PATTERN = 'Error loading pattern';
exports.ERR_MANAGING_PATTERNS = 'Error managing patterns';
exports.INFO_NO_PATTERNS = 'No saved patterns found. Use "PatternStore: Save Pattern" to create one.';
exports.INFO_PATTERN_LOADED = 'Loaded pattern';
exports.INFO_PATTERN_SAVED = 'Pattern saved';
exports.INFO_PATTERN_DELETED = 'Pattern deleted';
exports.PROMPT_PATTERN_NAME = 'Enter a name for this pattern';
exports.PROMPT_PATTERN_NAME_PLACEHOLDER = 'e.g., "Replace quotes with angles"';
exports.PROMPT_SELECT_SCOPE = 'Choose storage scope';
exports.PROMPT_SELECT_PATTERN = 'Select a pattern to load';
exports.PROMPT_CONFIRM_DELETE = 'Delete pattern';
exports.PROMPT_CONFIRM_OVERWRITE = 'already exists. Overwrite?';
exports.CHOICE_GLOBAL_LABEL = 'Global';
exports.CHOICE_GLOBAL_DESC = 'Available in all workspaces';
exports.CHOICE_WORKSPACE_LABEL = 'Workspace';
exports.CHOICE_WORKSPACE_DESC = 'Only for this workspace';
//# sourceMappingURL=constants.js.map
@@ -0,0 +1,127 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.activate = activate;
exports.deactivate = deactivate;
const vscode = __importStar(require("vscode"));
const storage = __importStar(require("./storage"));
const searchCtx = __importStar(require("./searchCtx"));
const WebviewManager_1 = require("./webview/WebviewManager");
/**
* Called when the extension is activated
*/
function activate(context) {
console.log('PatternStore extension is now active!');
// Register command: Manage Patterns
const manageCommand = vscode.commands.registerCommand('patternStore.manage', async () => {
await managePatternsCommand(context.extensionUri);
});
context.subscriptions.push(manageCommand);
}
/**
* Called when the extension is deactivated
*/
function deactivate() {
console.log('PatternStore extension is now deactivated');
}
/**
* Command: Save the current search state as a pattern
*/
async function savePatternCommand() {
try {
vscode.window.showInformationMessage('PatternStore: Save Pattern command executed!');
// Get current search state
const searchState = await searchCtx.getCurrentSearchState();
if (!searchState) {
vscode.window.showErrorMessage('Could not read current search state');
return;
}
// Prompt for pattern name
const label = await vscode.window.showInputBox({
prompt: 'Enter a name for this pattern',
placeHolder: 'e.g., "Replace quotes with angles"',
validateInput: (value) => {
if (!value || value.trim().length === 0) {
return 'Pattern name cannot be empty';
}
return null;
}
});
if (!label) {
return; // User cancelled
}
// Ask for scope
const scopeChoice = await vscode.window.showQuickPick([
{ label: 'Global', description: 'Available in all workspaces', value: 'global' },
{ label: 'Workspace', description: 'Only for this workspace', value: 'workspace' }
], { placeHolder: 'Choose storage scope' });
if (!scopeChoice) {
return; // User cancelled
}
// Create pattern object
const pattern = {
id: 'new', // Temporary ID for new patterns
label: label.trim(),
find: searchState.find || '',
replace: searchState.replace || '',
flags: searchState.flags || {
isRegex: false,
isCaseSensitive: false,
matchWholeWord: false,
isMultiline: false
},
scope: scopeChoice.value
};
// Check if pattern exists (will check when we implement storage)
const exists = storage.patternExists(pattern.label, pattern.scope);
if (exists) {
const overwrite = await vscode.window.showWarningMessage(`A pattern named "${pattern.label}" already exists. Overwrite?`, 'Yes', 'No');
if (overwrite !== 'Yes') {
return;
}
}
// Save pattern
await storage.savePattern(pattern);
}
catch (error) {
vscode.window.showErrorMessage(`Error saving pattern: ${error}`);
}
}
async function managePatternsCommand(extensionUri) {
console.log('Manage Patterns command pressed - opening webview');
// For now, just open global patterns. We'll add separate commands later
WebviewManager_1.WebviewManager.show(extensionUri, 'global');
}
//# sourceMappingURL=extension.js.map
@@ -0,0 +1,129 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCurrentSearchState = getCurrentSearchState;
exports.loadPatternIntoSearch = loadPatternIntoSearch;
const vscode = __importStar(require("vscode"));
/**
* Get the current search state from the Search panel
* Returns null if unable to get the state
*
* NOTE: VS Code doesn't expose search panel state via API.
* This function tries to use editor selection as fallback.
*/
async function getCurrentSearchState() {
// Try to get selected text from active editor
const editor = vscode.window.activeTextEditor;
const selection = editor?.document.getText(editor.selection);
if (selection && selection.length > 0) {
// Use selection as find text
return {
find: selection,
replace: '',
flags: {
isRegex: false,
isCaseSensitive: false,
matchWholeWord: false,
isMultiline: false
}
};
}
// No selection available - return null
// User will need to enter manually
return null;
}
/**
* Resolve placeholders like ${prompt:varname} in multiple strings
* Prompts once per unique placeholder name and reuses the value
*/
async function resolvePlaceholdersMultiple(texts) {
const RE = /\$\{prompt:([^}]+)\}/g;
// Collect all unique placeholder names across all texts
const allNames = new Set();
for (const text of texts) {
Array.from(text.matchAll(RE), m => m[1]).forEach(name => allNames.add(name));
}
const vals = {};
// Prompt once for each unique placeholder
for (const name of allNames) {
const value = await vscode.window.showInputBox({
prompt: `Value for ${name}`,
placeHolder: name
});
if (value === undefined) {
return undefined; // User cancelled
}
vals[name] = value;
}
// Replace placeholders in all texts using the collected values
return texts.map(text => text.replace(RE, (_, name) => vals[name] ?? ""));
}
/**
* Resolve placeholders like ${prompt:varname} in a string (single text version)
*/
async function resolvePlaceholders(text) {
const result = await resolvePlaceholdersMultiple([text]);
return result ? result[0] : undefined;
}
async function loadPatternIntoSearch(pattern) {
try {
const hasReplace = pattern.replace && pattern.replace.trim().length > 0;
const textsToResolve = hasReplace ? [pattern.find, pattern.replace] : [pattern.find];
const resolved = await resolvePlaceholdersMultiple(textsToResolve);
if (!resolved) {
return; // User cancelled
}
const find = resolved[0];
const replace = hasReplace ? resolved[1] : '';
const commandArgs = {
query: find,
replace: replace,
triggerSearch: false, // Don't auto-trigger search
isRegex: pattern.flags.isRegex,
isCaseSensitive: pattern.flags.isCaseSensitive,
matchWholeWord: pattern.flags.matchWholeWord,
preserveCase: false,
filesToInclude: pattern.filesToInclude || '',
filesToExclude: pattern.filesToExclude || ''
};
await vscode.commands.executeCommand("workbench.action.findInFiles", commandArgs);
vscode.window.showInformationMessage(`✅ Loaded pattern: "${pattern.label}"`);
}
catch (error) {
vscode.window.showErrorMessage(`Failed to load pattern: ${error}`);
throw error;
}
}
//# sourceMappingURL=searchCtx.js.map
@@ -0,0 +1,158 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAllPatterns = getAllPatterns;
exports.savePattern = savePattern;
exports.deletePattern = deletePattern;
exports.renamePattern = renamePattern;
exports.patternExists = patternExists;
const vscode = __importStar(require("vscode"));
const C = __importStar(require("./constants"));
/**
* Get patterns from settings and add runtime IDs
*/
function getPatternsByScope(scope) {
const config = vscode.workspace.getConfiguration(C.CONFIG_NAMESPACE);
const configKey = scope === 'global' ? C.CONFIG_KEY_USER_PATTERNS : C.CONFIG_KEY_WORKSPACE_PATTERNS;
let storedPatterns;
if (scope === 'global') {
storedPatterns = config.inspect(C.CONFIG_KEY_USER_PATTERNS)?.globalValue || [];
}
else {
storedPatterns = config.inspect(C.CONFIG_KEY_WORKSPACE_PATTERNS)?.workspaceValue || [];
}
// Add runtime IDs and ensure scope is set
return storedPatterns.map((p, index) => ({
...p,
id: `${scope}-${index}`, // Index-based runtime ID
scope: scope
}));
}
function getAllPatterns() {
const workspace = getPatternsByScope('workspace');
const global = getPatternsByScope('global');
// Return all patterns - duplicates across scopes are allowed
return [...workspace, ...global];
}
async function savePattern(pattern) {
const config = vscode.workspace.getConfiguration(C.CONFIG_NAMESPACE);
const configKey = pattern.scope === 'global' ? C.CONFIG_KEY_USER_PATTERNS : C.CONFIG_KEY_WORKSPACE_PATTERNS;
const target = pattern.scope === 'global'
? vscode.ConfigurationTarget.Global
: vscode.ConfigurationTarget.Workspace;
// Read fresh patterns with runtime IDs
const patternsWithIds = getPatternsByScope(pattern.scope);
// Find existing pattern by ID (runtime ID tells us the index it came from)
const existingIndex = patternsWithIds.findIndex(p => p.id === pattern.id);
const isUpdate = existingIndex !== -1;
// Strip runtime ID before saving
const { id, ...storedPattern } = pattern;
// Create array of stored patterns (without IDs)
let storedPatterns = patternsWithIds.map(p => {
const { id, ...stored } = p;
return stored;
});
if (isUpdate) {
// Update existing pattern IN PLACE (maintains order)
storedPatterns[existingIndex] = storedPattern;
}
else {
// New pattern - add at beginning
storedPatterns.unshift(storedPattern);
}
// Save to settings (without IDs)
await config.update(configKey, storedPatterns, target);
// Only show message if it's a new pattern (not an update)
if (!isUpdate) {
vscode.window.showInformationMessage(`✅ Pattern "${pattern.label}" saved to ${pattern.scope} settings`);
}
}
async function deletePattern(id, scope) {
const config = vscode.workspace.getConfiguration(C.CONFIG_NAMESPACE);
const configKey = scope === 'global' ? C.CONFIG_KEY_USER_PATTERNS : C.CONFIG_KEY_WORKSPACE_PATTERNS;
const target = scope === 'global'
? vscode.ConfigurationTarget.Global
: vscode.ConfigurationTarget.Workspace;
// Read fresh patterns with runtime IDs
const patternsWithIds = getPatternsByScope(scope);
// Find pattern by runtime ID
const patternToDelete = patternsWithIds.find(p => p.id === id);
if (!patternToDelete) {
vscode.window.showWarningMessage(`Pattern not found in ${scope} settings`);
return;
}
// Filter out the pattern and strip IDs
const storedPatterns = patternsWithIds
.filter(p => p.id !== id)
.map(p => {
const { id, ...stored } = p;
return stored;
});
// Save updated list (without IDs)
await config.update(configKey, storedPatterns, target);
vscode.window.showInformationMessage(`🗑️ Pattern "${patternToDelete.label}" deleted from ${scope} settings`);
}
async function renamePattern(id, newLabel, scope) {
const config = vscode.workspace.getConfiguration(C.CONFIG_NAMESPACE);
const configKey = scope === 'global' ? C.CONFIG_KEY_USER_PATTERNS : C.CONFIG_KEY_WORKSPACE_PATTERNS;
const target = scope === 'global'
? vscode.ConfigurationTarget.Global
: vscode.ConfigurationTarget.Workspace;
// Read fresh patterns with runtime IDs
const patternsWithIds = getPatternsByScope(scope);
// Find pattern by runtime ID
const pattern = patternsWithIds.find(p => p.id === id);
if (!pattern) {
vscode.window.showWarningMessage(`Pattern not found in ${scope} settings`);
return;
}
const oldLabel = pattern.label;
// Update label and strip IDs for saving
const storedPatterns = patternsWithIds.map(p => {
const { id: _id, ...stored } = p;
if (p.id === id) {
return { ...stored, label: newLabel };
}
return stored;
});
// Save updated list (without IDs)
await config.update(configKey, storedPatterns, target);
vscode.window.showInformationMessage(`✏️ Pattern renamed from "${oldLabel}" to "${newLabel}" in ${scope} settings`);
}
function patternExists(label, scope) {
const patterns = getPatternsByScope(scope);
return patterns.some(p => p.label === label);
}
//# sourceMappingURL=storage.js.map
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map
@@ -0,0 +1,266 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebviewManager = void 0;
const vscode = __importStar(require("vscode"));
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const storage = __importStar(require("../storage"));
const searchCtx = __importStar(require("../searchCtx"));
/**
* Manages the webview panel for pattern management
*/
class WebviewManager {
/**
* Create or show the webview panel
*/
static show(extensionUri, scope) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
// If we already have a panel, show it
if (WebviewManager.currentPanel) {
WebviewManager.currentPanel.panel.reveal(column);
return;
}
const panel = vscode.window.createWebviewPanel('patternStoreManage', 'PatternStore', column || vscode.ViewColumn.One, {
enableScripts: true,
retainContextWhenHidden: true,
});
WebviewManager.currentPanel = new WebviewManager(panel, extensionUri, scope);
}
constructor(panel, extensionUri, scope) {
this.disposables = [];
this.isInternalSave = false;
this.panel = panel;
this.extensionUri = extensionUri;
this.scope = scope;
// Set initial HTML content
this.panel.webview.html = this.getHtmlContent();
// Listen for when the panel is disposed
this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
// Handle messages from the webview
this.panel.webview.onDidReceiveMessage(message => {
this.handleMessage(message);
}, null, this.disposables);
// Watch for external config changes
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('patternStore')) {
this.handleExternalConfigChange();
}
}, null, this.disposables);
this.sendPatterns();
}
sendPatterns(selectId, selectScope, clearSelection) {
const allPatterns = storage.getAllPatterns();
const workspacePatterns = allPatterns.filter(p => p.scope === 'workspace');
const userPatterns = allPatterns.filter(p => p.scope === 'global');
const message = {
type: 'patterns',
workspace: workspacePatterns,
user: userPatterns
};
if (selectId && selectScope) {
message.selectPattern = {
id: selectId,
scope: selectScope
};
}
if (clearSelection) {
message.clearSelection = true;
}
this.panel.webview.postMessage(message);
}
/**
* Handle external configuration changes (e.g., manual edits to settings.json)
*/
handleExternalConfigChange() {
// Don't show notification if this was an internal save
if (this.isInternalSave) {
this.isInternalSave = false;
return;
}
// Show warning to user for actual external changes
vscode.window.showWarningMessage('Pattern settings changed externally. Webview reloaded.');
// Force full reload - this clears any dirty state and rebuilds runtime IDs
// Pass clearSelection flag to force the webview to clear the details view
this.sendPatterns(undefined, undefined, true);
}
async handleMessage(message) {
console.log('Received message from webview:', message);
switch (message.type) {
case 'ready':
// Webview is ready, send patterns
this.sendPatterns();
break;
case 'delete':
await this.handleDelete(message.id, message.scope);
break;
case 'load':
await this.handleLoad(message.id, message.scope, message.pattern);
break;
case 'create':
await this.handleCreate(message.scope);
break;
case 'save':
await this.handleSave(message.pattern);
break;
}
}
async handleCreate(scope) {
// Generate unique label for new pattern
const existingPatterns = storage.getAllPatterns().filter(p => p.scope === scope);
const baseName = 'New Pattern';
let label = baseName;
let counter = 1;
while (existingPatterns.some(p => p.label === label)) {
counter++;
label = `${baseName} ${counter}`;
}
const newPattern = {
id: 'new', // Temporary ID for new patterns
label: label,
find: '',
replace: '',
flags: {
isRegex: false,
isCaseSensitive: false,
matchWholeWord: false,
isMultiline: false
},
scope: scope
};
// Mark as internal save to suppress external change notification
this.isInternalSave = true;
await storage.savePattern(newPattern);
// New pattern will be at index 0 (savePattern uses unshift)
this.sendPatterns(`${scope}-0`, scope);
}
async handleSave(patternData) {
const { id, label, find, replace, flags, filesToInclude, filesToExclude, scope } = patternData;
const pattern = {
id: id, // Preserve existing ID
label: label,
find: find,
replace: replace,
flags: flags,
filesToInclude: filesToInclude,
filesToExclude: filesToExclude,
scope: scope
};
// Mark as internal save to suppress external change notification
this.isInternalSave = true;
await storage.savePattern(pattern);
this.sendPatterns(id, scope);
}
async handleDelete(id, scope) {
// Find pattern to get label for confirmation
const allPatterns = storage.getAllPatterns();
const pattern = allPatterns.find(p => p.id === id && p.scope === scope);
if (!pattern) {
vscode.window.showErrorMessage(`Pattern not found`);
return;
}
// Confirm deletion
const confirm = await vscode.window.showWarningMessage(`Delete pattern "${pattern.label}"?`, { modal: true }, 'Delete');
if (confirm === 'Delete') {
// Mark as internal save to suppress external change notification
this.isInternalSave = true;
await storage.deletePattern(id, scope);
// Refresh pattern list
this.sendPatterns();
}
}
async handleLoad(id, scope, providedPattern) {
let pattern = providedPattern;
// If no pattern was provided, look it up
if (!pattern) {
const allPatterns = storage.getAllPatterns();
pattern = allPatterns.find(p => p.id === id && p.scope === scope);
if (!pattern) {
vscode.window.showErrorMessage(`Pattern "${id}" not found`);
return;
}
}
await searchCtx.loadPatternIntoSearch(pattern);
}
getHtmlContent() {
// Generate nonce for CSP
const nonce = this.getNonce();
// Get URIs for webview resources
const cspSource = this.panel.webview.cspSource;
// Load HTML, CSS, and JS from files
const htmlPath = path.join(this.extensionUri.fsPath, 'src', 'webview', 'html', 'patternManager.html');
const cssPath = path.join(this.extensionUri.fsPath, 'src', 'webview', 'css', 'patternManager.css');
const jsPath = path.join(this.extensionUri.fsPath, 'src', 'webview', 'js', 'patternManager.js');
let html = fs.readFileSync(htmlPath, 'utf8');
const css = fs.readFileSync(cssPath, 'utf8');
const js = fs.readFileSync(jsPath, 'utf8');
// Build style tag with nonce
const styleTag = `<style nonce="${nonce}">\n${css}\n </style>`;
// Replace placeholders
html = html.replace(/{{nonce}}/g, nonce);
html = html.replace(/{{cspSource}}/g, cspSource);
html = html.replace(/{{styleTag}}/g, styleTag);
html = html.replace(/{{script}}/g, js);
return html;
}
/**
* Generate a nonce for CSP
*/
getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
/**
* Clean up resources
*/
dispose() {
WebviewManager.currentPanel = undefined;
this.panel.dispose();
while (this.disposables.length) {
const disposable = this.disposables.pop();
if (disposable) {
disposable.dispose();
}
}
}
}
exports.WebviewManager = WebviewManager;
//# sourceMappingURL=WebviewManager.js.map
@@ -0,0 +1,148 @@
{
"name": "patternstore",
"displayName": "PatternStore",
"description": "Save and recall reusable regex find/replace pairs in VS Code",
"version": "0.1.12",
"publisher": "jdunkel",
"engines": {
"vscode": "^1.85.0"
},
"icon": "assets/pattern-store-icon.png",
"repository": {
"type": "git",
"url": "https://github.com/JoachimDunkel/PatternStore"
},
"keywords": [
"search",
"replace",
"regex",
"find",
"pattern",
"snippet"
],
"categories": [
"Other"
],
"galleryBanner": {
"color": "#1e1e1e",
"theme": "dark"
},
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "patternStore.manage",
"title": "PatternStore: Manage Patterns",
"icon": "$(book)"
}
],
"configuration": {
"title": "PatternStore",
"properties": {
"patternStore.savedPatterns": {
"type": "array",
"default": [],
"description": "Global patterns available in all workspaces",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"find": {
"type": "string"
},
"replace": {
"type": "string"
},
"flags": {
"type": "object"
},
"filesToInclude": {
"type": "string",
"description": "Files to include (glob pattern, e.g. '*.ts,*.js' or 'src/**')"
},
"filesToExclude": {
"type": "string",
"description": "Files to exclude (glob pattern, e.g. 'node_modules/**,dist/**')"
},
"scope": {
"type": "string"
}
}
}
},
"patternStore.workspacePatterns": {
"type": "array",
"default": [],
"description": "Workspace-specific patterns",
"scope": "resource",
"items": {
"type": "object",
"properties": {
"label": {
"type": "string"
},
"find": {
"type": "string"
},
"replace": {
"type": "string"
},
"flags": {
"type": "object"
},
"filesToInclude": {
"type": "string",
"description": "Files to include (glob pattern, e.g. '*.ts,*.js' or 'src/**')"
},
"filesToExclude": {
"type": "string",
"description": "Files to exclude (glob pattern, e.g. 'node_modules/**,dist/**')"
},
"scope": {
"type": "string"
}
}
}
}
}
},
"keybindings": [
{
"command": "patternStore.manage",
"key": "ctrl+alt+r",
"mac": "cmd+alt+r"
}
],
"menus": {
"view/title": [
{
"command": "patternStore.manage",
"when": "view == workbench.view.search",
"group": "navigation"
}
]
}
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src --ext ts"
},
"devDependencies": {
"@types/node": "^20.x",
"@types/vscode": "^1.85.0",
"@typescript-eslint/eslint-plugin": "^6.x",
"@typescript-eslint/parser": "^6.x",
"eslint": "^8.x",
"typescript": "^5.3.0"
},
"__metadata": {
"installedTimestamp": 1783822352061,
"targetPlatform": "undefined",
"size": 1218391
}
}
@@ -0,0 +1,561 @@
body {
padding: 0;
margin: 0;
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
}
.container {
display: flex;
height: 100vh;
gap: 0;
background: var(--vscode-panel-border);
position: relative;
}
/* Left panel - Pattern list */
.pattern-list-panel {
flex: 0 0 300px;
min-width: 200px;
max-width: 600px;
background: var(--vscode-sideBar-background);
display: flex;
flex-direction: column;
}
/* Resize Handle */
.resize-handle {
flex: 0 0 4px;
background: var(--vscode-panel-border);
cursor: ew-resize;
position: relative;
user-select: none;
}
.resize-handle:hover {
background: var(--vscode-focusBorder);
}
.resize-handle.resizing {
background: var(--vscode-focusBorder);
}
.search-box {
padding: 8px;
background: var(--vscode-sideBar-background);
border-bottom: 1px solid var(--vscode-panel-border);
position: relative;
}
.search-wrapper {
position: relative;
display: flex;
align-items: center;
}
.search-icon {
position: absolute;
left: 8px;
pointer-events: none;
color: var(--vscode-input-placeholderForeground);
font-size: 14px;
}
.search-input {
width: 100%;
padding: 6px 30px 6px 30px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 2px;
font-size: 13px;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
.search-clear {
position: absolute;
right: 6px;
padding: 4px;
background: transparent;
border: none;
cursor: pointer;
color: var(--vscode-input-placeholderForeground);
border-radius: 3px;
display: none;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
transition: all 0.15s ease;
}
.search-clear:hover {
background: var(--vscode-toolbar-hoverBackground);
color: var(--vscode-foreground);
}
.search-clear.visible {
display: flex;
}
.search-clear .codicon {
font-size: 12px;
}
.pattern-list {
flex: 1;
overflow-y: auto;
padding: 4px;
}
.section-header {
padding: 8px 12px;
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
color: var(--vscode-foreground);
opacity: 0.6;
letter-spacing: 0.5px;
margin-top: 8px;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.section-header-left {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
flex: 1;
}
.add-pattern-btn {
padding: 4px;
background: transparent;
border: none;
cursor: pointer;
color: var(--vscode-foreground);
opacity: 0.6;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
transition: all 0.15s ease;
}
.add-pattern-btn:hover {
background: var(--vscode-toolbar-hoverBackground);
opacity: 1;
}
.add-pattern-btn .codicon {
font-size: 14px;
}
.section-header:hover {
opacity: 0.8;
}
.section-header:first-child {
margin-top: 0;
}
.section-header .codicon {
font-size: 14px;
transition: transform 0.1s ease;
}
.section-header.collapsed .codicon {
transform: rotate(-90deg);
}
.section-content {
max-height: 10000px;
overflow: hidden;
transition: max-height 0.15s ease;
}
.section-content.collapsed {
max-height: 0;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
color: var(--vscode-descriptionForeground);
text-align: center;
font-size: 14px;
}
.empty-state-section {
padding: 16px;
text-align: center;
color: var(--vscode-descriptionForeground);
font-size: 13px;
opacity: 0.8;
}
.pattern-item {
padding: 8px 12px;
cursor: pointer;
border-radius: 3px;
margin-bottom: 2px;
display: flex;
align-items: center;
gap: 8px;
position: relative;
}
.pattern-item:hover {
background: var(--vscode-list-hoverBackground);
}
.pattern-item:hover .pattern-actions {
opacity: 1;
}
.pattern-item.selected {
background: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground);
}
.pattern-info {
flex: 1;
min-width: 0;
}
.pattern-name {
font-weight: 500;
margin-bottom: 2px;
}
.pattern-preview {
font-size: 12px;
opacity: 0.8;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pattern-item-invalid {
border-left: 2px solid var(--vscode-errorForeground);
opacity: 0.8;
}
.pattern-item-invalid .pattern-name .codicon-warning {
color: var(--vscode-errorForeground);
margin-left: 4px;
font-size: 13px;
vertical-align: middle;
}
.pattern-item-invalid .pattern-preview {
color: var(--vscode-errorForeground);
}
.pattern-item-invalid .pattern-preview em {
font-style: italic;
opacity: 0.7;
}
/* Action buttons */
.pattern-actions {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.15s ease;
}
.action-btn {
padding: 4px;
background: transparent;
border: none;
cursor: pointer;
color: var(--vscode-foreground);
opacity: 0.7;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
}
.action-btn:hover {
background: var(--vscode-toolbar-hoverBackground);
opacity: 1;
}
.action-btn .codicon {
font-size: 16px;
}
.action-btn.delete:hover {
color: var(--vscode-errorForeground);
}
.action-btn.load:hover {
color: var(--vscode-textLink-foreground);
}
/* Right panel - Edit form */
.edit-panel {
flex: 1;
background: var(--vscode-editor-background);
overflow-y: auto;
padding: 16px;
}
.edit-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
opacity: 0.5;
text-align: center;
padding: 40px 20px;
}
.edit-empty-state .codicon {
font-size: 64px;
margin-bottom: 16px;
opacity: 0.6;
}
.edit-empty-state p {
margin: 4px 0;
font-size: 14px;
}
.edit-empty-state .hint {
font-size: 12px;
opacity: 0.7;
}
.edit-form {
display: none;
}
.edit-form.active {
display: block;
}
.edit-form-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.edit-form h2 {
font-size: 13px;
font-weight: 600;
margin: 0;
text-transform: uppercase;
opacity: 0.8;
white-space: nowrap;
}
.form-input-inline {
flex: 1;
padding: 4px 6px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 2px;
font-size: 13px;
font-family: var(--vscode-editor-font-family);
box-sizing: border-box;
}
.form-input-inline:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
.form-group {
margin-bottom: 12px;
}
.form-label {
display: block;
margin-bottom: 3px;
font-weight: 500;
font-size: 11px;
opacity: 0.8;
text-transform: uppercase;
}
.form-label-with-options {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 3px;
}
.form-label-with-options .form-label {
margin-bottom: 0;
}
.form-input,
.form-textarea {
width: 100%;
padding: 4px 6px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 2px;
font-size: 13px;
font-family: var(--vscode-editor-font-family);
box-sizing: border-box;
}
.form-input:focus,
.form-textarea:focus {
outline: none;
border-color: var(--vscode-focusBorder);
}
.form-input.error,
.form-textarea.error {
border-color: var(--vscode-errorForeground);
}
.form-input.dirty,
.form-input-inline.dirty,
.form-textarea.dirty {
box-shadow: 0 0 0 1px rgba(106, 153, 85, 0.6);
}
.form-textarea {
min-height: 60px;
resize: vertical;
font-family: var(--vscode-editor-font-family);
}
.validation-error {
display: none;
color: var(--vscode-errorForeground);
font-size: 11px;
margin-top: 3px;
}
.validation-error.visible {
display: block;
}
.checkbox-group-icons {
display: flex;
gap: 4px;
}
.toggle-btn {
padding: 4px 8px;
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: 1px solid transparent;
border-radius: 2px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease;
min-width: 28px;
height: 24px;
}
.toggle-btn:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
.toggle-btn.active {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border-color: var(--vscode-focusBorder);
}
.toggle-btn.dirty {
border-color: #6a9955;
}
.toggle-btn.active.dirty {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border-color: #6a9955;
}
.toggle-btn .codicon {
font-size: 14px;
}
.button-group {
display: flex;
gap: 6px;
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--vscode-panel-border);
}
button {
padding: 5px 12px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 2px;
cursor: pointer;
font-size: 12px;
}
button:hover {
background: var(--vscode-button-hoverBackground);
}
button.secondary {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
}
button.secondary:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
.dirty-indicator {
margin-left: 6px;
color: var(--vscode-editorWarning-foreground);
font-size: 8px;
animation: pulse 2s ease-in-out infinite;
width: 8px;
display: inline-block;
visibility: hidden;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@keyframes pulseBorder {
0%, 100% {
border-color: #4ec9b0;
}
50% {
border-color: #dcdcaa;
}
}
@@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; style-src {{cspSource}} https://unpkg.com 'nonce-{{nonce}}'; font-src https://unpkg.com; script-src 'nonce-{{nonce}}';">
<title>Manage Patterns</title>
<link href="https://unpkg.com/@vscode/codicons/dist/codicon.css" rel="stylesheet" />
{{styleTag}}
</head>
<body>
<div class="container">
<!-- Left: Pattern List -->
<div class="pattern-list-panel">
<div class="search-box">
<div class="search-wrapper">
<i class="codicon codicon-search search-icon"></i>
<input type="text" class="search-input" placeholder="Search patterns..." id="searchInput" />
<button class="search-clear" id="searchClear" title="Clear search">
<i class="codicon codicon-close"></i>
</button>
</div>
</div>
<div class="pattern-list" id="patternList">
<!-- Patterns will be inserted here -->
<div class="empty-state">No patterns yet</div>
</div>
</div>
<!-- Resize Handle -->
<div class="resize-handle" id="resizeHandle"></div>
<div class="edit-panel" id="editPanel">
<div class="edit-empty-state" id="editEmptyState">
<i class="codicon codicon-list-selection"></i>
<p>Select a pattern to edit</p>
<p class="hint">or create a new one with the + button</p>
</div>
<div class="edit-form" id="editForm">
<div class="edit-form-header">
<h2>Pattern</h2>
<input type="text" class="form-input-inline" id="labelInput" placeholder="Pattern name" />
</div>
<div class="form-group">
<div class="form-label-with-options">
<label class="form-label" for="findInput">Find</label>
<div class="checkbox-group-icons">
<button type="button" class="toggle-btn" id="isCaseSensitiveBtn" title="Match Case">
<i class="codicon codicon-case-sensitive"></i>
</button>
<button type="button" class="toggle-btn" id="matchWholeWordBtn" title="Match Whole Word">
<i class="codicon codicon-whole-word"></i>
</button>
<button type="button" class="toggle-btn" id="isRegexBtn" title="Use Regular Expression">
<i class="codicon codicon-regex"></i>
</button>
</div>
</div>
<textarea class="form-textarea" id="findInput" placeholder="Search pattern or regex"></textarea>
<div class="validation-error" id="findValidationError"></div>
<!-- Hidden checkboxes to maintain state -->
<input type="checkbox" id="isCaseSensitive" hidden />
<input type="checkbox" id="matchWholeWord" hidden />
<input type="checkbox" id="isRegex" hidden />
</div>
<div class="form-group">
<label class="form-label" for="replaceInput">Replace (optional)</label>
<textarea class="form-textarea" id="replaceInput" placeholder="Replacement text"></textarea>
</div>
<div class="form-group">
<label class="form-label" for="filesToInclude">Files to Include</label>
<input type="text" class="form-input" id="filesToInclude" placeholder="*.ts,*.js or src/**" />
</div>
<div class="form-group">
<label class="form-label" for="filesToExclude">Files to Exclude</label>
<input type="text" class="form-input" id="filesToExclude" placeholder="node_modules/**,dist/**" />
</div>
<div class="button-group">
<button id="saveBtn">
Save
<span id="dirtyIndicator" class="dirty-indicator">
<i class="codicon codicon-circle-filled"></i>
</span>
</button>
<button id="loadBtn" class="secondary">
<i class="codicon codicon-arrow-right"></i>
</button>
</div>
</div>
</div>
</div>
<script nonce="{{nonce}}">{{script}}</script>
</body>
</html>
@@ -0,0 +1,857 @@
const vscode = acquireVsCodeApi();
// Store patterns data
let workspacePatterns = [];
let userPatterns = [];
let workspacePatternsMap = new Map(); // id -> pattern
let userPatternsMap = new Map(); // id -> pattern
let searchQuery = '';
// Dirty state tracking
let savedPatternState = null;
// Cache DOM elements for performance
const domCache = {
labelInput: null,
findInput: null,
replaceInput: null,
filesToInclude: null,
filesToExclude: null,
isRegex: null,
isCaseSensitive: null,
matchWholeWord: null,
isRegexBtn: null,
isCaseSensitiveBtn: null,
matchWholeWordBtn: null,
dirtyIndicator: null,
patternList: null,
editForm: null,
editEmptyState: null,
findValidationError: null
};
function initDomCache() {
domCache.labelInput = document.getElementById('labelInput');
domCache.findInput = document.getElementById('findInput');
domCache.replaceInput = document.getElementById('replaceInput');
domCache.filesToInclude = document.getElementById('filesToInclude');
domCache.filesToExclude = document.getElementById('filesToExclude');
domCache.isRegex = document.getElementById('isRegex');
domCache.isCaseSensitive = document.getElementById('isCaseSensitive');
domCache.matchWholeWord = document.getElementById('matchWholeWord');
domCache.isRegexBtn = document.getElementById('isRegexBtn');
domCache.isCaseSensitiveBtn = document.getElementById('isCaseSensitiveBtn');
domCache.matchWholeWordBtn = document.getElementById('matchWholeWordBtn');
domCache.dirtyIndicator = document.getElementById('dirtyIndicator');
domCache.patternList = document.getElementById('patternList');
domCache.editForm = document.getElementById('editForm');
domCache.editEmptyState = document.getElementById('editEmptyState');
domCache.findValidationError = document.getElementById('findValidationError');
}
function checkIfDirty() {
const state = vscode.getState() || {};
const currentPattern = state.currentPattern;
if (!currentPattern || !savedPatternState) {
updateDirtyIndicator(false);
clearAllDirtyClasses();
return;
}
// Compare current form values with saved state
const isDirty =
domCache.labelInput.value.trim() !== savedPatternState.label ||
domCache.findInput.value !== savedPatternState.find ||
domCache.replaceInput.value !== savedPatternState.replace ||
domCache.isRegex.checked !== savedPatternState.flags.isRegex ||
domCache.isCaseSensitive.checked !== savedPatternState.flags.isCaseSensitive ||
domCache.matchWholeWord.checked !== savedPatternState.flags.matchWholeWord ||
(domCache.filesToInclude.value.trim() || '') !== (savedPatternState.filesToInclude || '') ||
(domCache.filesToExclude.value.trim() || '') !== (savedPatternState.filesToExclude || '');
updateDirtyIndicator(isDirty);
updateFieldDirtyStates();
}
function updateDirtyIndicator(isDirty) {
if (domCache.dirtyIndicator) {
domCache.dirtyIndicator.style.visibility = isDirty ? 'visible' : 'hidden';
}
}
function updateFieldDirtyStates() {
if (!savedPatternState) {
clearAllDirtyClasses();
return;
}
// Check text inputs using cached DOM references
if (domCache.labelInput) {
toggleDirtyClass(domCache.labelInput, domCache.labelInput.value.trim() !== savedPatternState.label);
}
if (domCache.findInput) {
toggleDirtyClass(domCache.findInput, domCache.findInput.value !== savedPatternState.find);
}
if (domCache.replaceInput) {
toggleDirtyClass(domCache.replaceInput, domCache.replaceInput.value !== savedPatternState.replace);
}
if (domCache.filesToInclude) {
toggleDirtyClass(domCache.filesToInclude, (domCache.filesToInclude.value.trim() || '') !== (savedPatternState.filesToInclude || ''));
}
if (domCache.filesToExclude) {
toggleDirtyClass(domCache.filesToExclude, (domCache.filesToExclude.value.trim() || '') !== (savedPatternState.filesToExclude || ''));
}
// Check toggle buttons using cached DOM references
if (domCache.isRegexBtn && domCache.isRegex) {
toggleDirtyClass(domCache.isRegexBtn, domCache.isRegex.checked !== savedPatternState.flags.isRegex);
}
if (domCache.isCaseSensitiveBtn && domCache.isCaseSensitive) {
toggleDirtyClass(domCache.isCaseSensitiveBtn, domCache.isCaseSensitive.checked !== savedPatternState.flags.isCaseSensitive);
}
if (domCache.matchWholeWordBtn && domCache.matchWholeWord) {
toggleDirtyClass(domCache.matchWholeWordBtn, domCache.matchWholeWord.checked !== savedPatternState.flags.matchWholeWord);
}
}
function toggleDirtyClass(element, isDirty) {
if (isDirty) {
element.classList.add('dirty');
} else {
element.classList.remove('dirty');
}
}
function clearAllDirtyClasses() {
const elements = [
domCache.labelInput, domCache.findInput, domCache.replaceInput,
domCache.filesToInclude, domCache.filesToExclude,
domCache.isRegexBtn, domCache.isCaseSensitiveBtn, domCache.matchWholeWordBtn
];
elements.forEach(element => {
if (element) {
element.classList.remove('dirty');
}
});
}
function saveDirtyState(pattern) {
savedPatternState = {
label: pattern.label,
find: pattern.find || '',
replace: pattern.replace || '',
flags: { ...pattern.flags },
filesToInclude: pattern.filesToInclude || '',
filesToExclude: pattern.filesToExclude || ''
};
updateDirtyIndicator(false);
clearAllDirtyClasses();
}
function findPatternById(id, scope) {
const map = scope === 'workspace' ? workspacePatternsMap : userPatternsMap;
return map.get(id);
}
function rebuildPatternMaps() {
workspacePatternsMap.clear();
userPatternsMap.clear();
workspacePatterns.forEach(p => workspacePatternsMap.set(p.id, p));
userPatterns.forEach(p => userPatternsMap.set(p.id, p));
}
// Get collapse state from localStorage
const getCollapseState = (section) => {
const state = vscode.getState() || {};
return state[section + '_collapsed'] === true;
};
// Get collapse state with a default value if not previously set
const getCollapseStateWithDefault = (section, defaultValue) => {
const state = vscode.getState() || {};
const key = section + '_collapsed';
// If user has explicitly set a preference, use it; otherwise use default
return state[key] !== undefined ? state[key] : defaultValue;
};
// Save collapse state to localStorage
const setCollapseState = (section, collapsed) => {
const state = vscode.getState() || {};
state[section + '_collapsed'] = collapsed;
vscode.setState(state);
};
window.addEventListener('message', event => {
const message = event.data;
if (message.type === 'patterns') {
disablePatternList(false);
document.body.style.cursor = '';
workspacePatterns = message.workspace;
userPatterns = message.user;
rebuildPatternMaps(); // Rebuild Maps for O(1) lookups
renderPatternList();
setupSearchInput();
// Force clear selection if requested (e.g., on external config changes)
if (message.clearSelection) {
clearDetailsView();
} else if (message.selectPattern) {
selectPattern(message.selectPattern.id, message.selectPattern.scope);
} else {
const state = vscode.getState() || {};
if (state.currentPattern) {
const allPatterns = state.currentPattern.scope === 'workspace' ? workspacePatterns : userPatterns;
const stillExists = allPatterns.some(p => p.id === state.currentPattern.id);
if (!stillExists) {
clearDetailsView();
}
}
}
// Focus search input when webview opens
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.focus();
}
}
});
function setupSearchInput() {
const searchInput = document.getElementById('searchInput');
const searchClear = document.getElementById('searchClear');
if (!searchInput || !searchClear) return;
// if (searchInput.dataset.initialized === 'true') {
// return;
// }
// searchInput.dataset.initialized = 'true';
searchInput.addEventListener('input', (e) => {
searchQuery = e.target.value.toLowerCase();
if (searchQuery) {
searchClear.classList.add('visible');
} else {
searchClear.classList.remove('visible');
}
renderPatternList();
});
searchClear.addEventListener('click', () => {
searchInput.value = '';
searchQuery = '';
searchClear.classList.remove('visible');
renderPatternList();
searchInput.focus();
});
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && searchQuery) {
searchInput.value = '';
searchQuery = '';
searchClear.classList.remove('visible');
renderPatternList();
}
});
}
function filterPatterns(patterns) {
if (!searchQuery) return patterns;
return patterns.filter(pattern => {
const nameMatch = pattern.label.toLowerCase().includes(searchQuery);
const findMatch = pattern.find.toLowerCase().includes(searchQuery);
const replaceMatch = pattern.replace && pattern.replace.toLowerCase().includes(searchQuery);
return nameMatch || findMatch || replaceMatch;
});
}
function renderPatternList() {
const listContainer = document.getElementById('patternList');
const filteredWorkspace = filterPatterns(workspacePatterns);
const filteredUser = filterPatterns(userPatterns);
let html = '';
if (searchQuery && filteredWorkspace.length === 0 && filteredUser.length === 0) {
listContainer.innerHTML = '<div class="empty-state">No patterns found</div>';
return;
}
// Collapse empty sections by default, but respect user's saved preference
const workspaceCollapsed = searchQuery ? false : getCollapseStateWithDefault('workspace', filteredWorkspace.length === 0);
html += renderSection('workspace', 'Workspace', filteredWorkspace, workspaceCollapsed);
const userCollapsed = searchQuery ? false : getCollapseStateWithDefault('user', filteredUser.length === 0);
html += renderSection('user', 'User', filteredUser, userCollapsed);
listContainer.innerHTML = html;
document.querySelectorAll('.section-header').forEach(header => {
header.addEventListener('click', toggleSection);
});
// Event delegation handles all pattern list interactions, no need to re-attach
}
function renderSection(id, title, patterns, collapsed) {
const chevron = collapsed ? 'codicon-chevron-right' : 'codicon-chevron-down';
const contentClass = collapsed ? 'section-content collapsed' : 'section-content';
let html = `
<div class="section-header ${collapsed ? 'collapsed' : ''}" data-section="${id}">
<div class="section-header-left">
<i class="codicon ${chevron}"></i>
<span>${title} (${patterns.length})</span>
</div>
<button class="add-pattern-btn" data-scope="${id}" title="Add new pattern">
<i class="codicon codicon-add"></i>
</button>
</div>
<div class="${contentClass}" data-section-content="${id}">
`;
if (patterns.length === 0) {
html += '<div class="empty-state-section">No patterns in this scope</div>';
} else {
patterns.forEach(pattern => {
html += renderPatternItem(pattern);
});
}
html += '</div>';
return html;
}
function toggleSection(event) {
if (event.target.closest('.add-pattern-btn')) {
return;
}
const header = event.currentTarget;
const sectionId = header.dataset.section;
const content = document.querySelector('[data-section-content="' + sectionId + '"]');
const chevron = header.querySelector('.codicon');
const isCollapsed = header.classList.contains('collapsed');
if (isCollapsed) {
header.classList.remove('collapsed');
content.classList.remove('collapsed');
chevron.classList.remove('codicon-chevron-right');
chevron.classList.add('codicon-chevron-down');
setCollapseState(sectionId, false);
} else {
header.classList.add('collapsed');
content.classList.add('collapsed');
chevron.classList.remove('codicon-chevron-down');
chevron.classList.add('codicon-chevron-right');
setCollapseState(sectionId, true);
}
}
function renderPatternItem(pattern) {
const preview = pattern.find.substring(0, 40);
const truncated = pattern.find.length > 40 ? '...' : '';
const isInvalid = !pattern.find || pattern.find.trim() === '';
const invalidClass = isInvalid ? 'pattern-item-invalid' : '';
return `
<div class="pattern-item ${invalidClass}" data-id="${pattern.id}" data-label="${pattern.label}" data-scope="${pattern.scope}"> <div class="pattern-info">
<div class="pattern-name">
${pattern.label}
${isInvalid ? '<i class="codicon codicon-warning" title="Find field is empty"></i>' : ''}
</div>
<div class="pattern-preview">${isInvalid ? '<em>Empty find pattern</em>' : preview + truncated}</div>
</div>
<div class="pattern-actions">
${renderActionButton('trash', 'delete', 'Delete pattern')}
${renderActionButton('arrow-right', 'load', 'Load to search')}
</div>
</div>
`;
}
function renderActionButton(icon, action, title) {
return `
<button class="action-btn ${action}"
data-action="${action}"
title="${title}"
onclick="event.stopPropagation()">
<i class="codicon codicon-${icon}"></i>
</button>
`;
}
// Event delegation for pattern list - set up once, works for all items
function setupEventDelegation() {
if (!domCache.patternList) return;
// Delegate all pattern list events to the parent container
domCache.patternList.addEventListener('click', (event) => {
// Handle pattern item click
const patternItem = event.target.closest('.pattern-item');
if (patternItem && !event.target.closest('.action-btn') && !event.target.closest('.add-pattern-btn')) {
handlePatternClick(patternItem);
return;
}
// Handle action button clicks (load, delete)
const actionBtn = event.target.closest('.action-btn');
if (actionBtn) {
event.stopPropagation();
handleActionClick(actionBtn);
return;
}
// Handle add pattern button clicks
const addBtn = event.target.closest('.add-pattern-btn');
if (addBtn) {
event.stopPropagation();
handleAddPattern(addBtn);
return;
}
});
}
function handlePatternClick(item) {
const id = item.dataset.id;
const scope = item.dataset.scope;
document.querySelectorAll('.pattern-item').forEach(i => {
i.classList.remove('selected');
});
item.classList.add('selected');
const pattern = findPatternById(id, scope);
if (pattern) {
populateDetailsView(pattern);
}
}
function populateDetailsView(pattern) {
domCache.editEmptyState.style.display = 'none';
domCache.editForm.classList.add('active');
domCache.labelInput.value = pattern.label;
domCache.findInput.value = pattern.find || '';
domCache.replaceInput.value = pattern.replace || '';
domCache.isRegex.checked = pattern.flags.isRegex;
domCache.isCaseSensitive.checked = pattern.flags.isCaseSensitive;
domCache.matchWholeWord.checked = pattern.flags.matchWholeWord;
updateToggleButtonState(domCache.isRegexBtn, pattern.flags.isRegex);
updateToggleButtonState(domCache.isCaseSensitiveBtn, pattern.flags.isCaseSensitive);
updateToggleButtonState(domCache.matchWholeWordBtn, pattern.flags.matchWholeWord);
domCache.findInput.classList.remove('error');
domCache.findValidationError.classList.remove('visible');
validateRegexPattern();
domCache.filesToInclude.value = pattern.filesToInclude || '';
domCache.filesToExclude.value = pattern.filesToExclude || '';
const state = vscode.getState() || {};
state.currentPattern = {
id: pattern.id,
label: pattern.label,
scope: pattern.scope
};
vscode.setState(state);
// Save the clean state for dirty tracking
saveDirtyState(pattern);
}
function selectPattern(id, scope) {
const item = document.querySelector(
`.pattern-item[data-id="${id}"][data-scope="${scope}"]`
);
if (item) {
item.click();
}
}
function handleSavePattern() {
const state = vscode.getState() || {};
const currentPattern = state.currentPattern;
if (!currentPattern) {
console.error('No pattern selected to save');
return;
}
const label = domCache.labelInput.value.trim();
const find = domCache.findInput.value;
const replace = domCache.replaceInput.value;
if (!label) {
alert('Pattern name cannot be empty');
return;
}
disablePatternList(true);
document.body.style.cursor = 'wait';
const pattern = {
id: currentPattern.id,
label: label,
find: find,
replace: replace,
flags: {
isRegex: domCache.isRegex.checked,
isCaseSensitive: domCache.isCaseSensitive.checked,
matchWholeWord: domCache.matchWholeWord.checked,
isMultiline: false
},
filesToInclude: domCache.filesToInclude.value.trim() || undefined,
filesToExclude: domCache.filesToExclude.value.trim() || undefined,
scope: currentPattern.scope
};
vscode.postMessage({
type: 'save',
pattern: pattern
});
}
function disablePatternList(disabled) {
const listPanel = document.querySelector('.pattern-list-panel');
if (disabled) {
listPanel.style.pointerEvents = 'none';
listPanel.style.opacity = '0.6';
} else {
listPanel.style.pointerEvents = '';
listPanel.style.opacity = '';
}
}
function handleDeletePattern() {
const state = vscode.getState() || {};
const currentPattern = state.currentPattern;
if (!currentPattern) {
console.error('No pattern selected to delete');
return;
}
vscode.postMessage({
type: 'delete',
id: currentPattern.id,
scope: currentPattern.scope
});
}
function handleLoadPatternFromDetails() {
const state = vscode.getState() || {};
const currentPattern = state.currentPattern;
if (!currentPattern) {
console.error('No pattern selected to load');
return;
}
// Build pattern from current form values (may be dirty)
const pattern = {
id: currentPattern.id,
label: domCache.labelInput.value.trim(),
find: domCache.findInput.value,
replace: domCache.replaceInput.value,
flags: {
isRegex: domCache.isRegex.checked,
isCaseSensitive: domCache.isCaseSensitive.checked,
matchWholeWord: domCache.matchWholeWord.checked,
isMultiline: false
},
filesToInclude: domCache.filesToInclude.value.trim() || undefined,
filesToExclude: domCache.filesToExclude.value.trim() || undefined,
scope: currentPattern.scope
};
vscode.postMessage({
type: 'load',
id: currentPattern.id,
scope: currentPattern.scope,
pattern: pattern
});
}
function clearDetailsView() {
domCache.editForm.classList.remove('active');
domCache.editEmptyState.style.display = 'flex';
domCache.labelInput.value = '';
domCache.findInput.value = '';
domCache.replaceInput.value = '';
domCache.isRegex.checked = false;
domCache.isCaseSensitive.checked = false;
domCache.matchWholeWord.checked = false;
domCache.filesToInclude.value = '';
domCache.filesToExclude.value = '';
const state = vscode.getState() || {};
delete state.currentPattern;
vscode.setState(state);
document.querySelectorAll('.pattern-item').forEach(i => {
i.classList.remove('selected');
});
// Clear dirty state
savedPatternState = null;
updateDirtyIndicator(false);
clearAllDirtyClasses();
}
/**
* Setup toggle buttons for flags
*/
function setupToggleButtons() {
const buttons = [
{ btnId: 'isRegexBtn', checkboxId: 'isRegex' },
{ btnId: 'isCaseSensitiveBtn', checkboxId: 'isCaseSensitive' },
{ btnId: 'matchWholeWordBtn', checkboxId: 'matchWholeWord' }
];
buttons.forEach(({ btnId, checkboxId }) => {
const btn = document.getElementById(btnId);
const checkbox = document.getElementById(checkboxId);
if (!btn || !checkbox) return;
btn.addEventListener('click', () => {
checkbox.checked = !checkbox.checked;
updateToggleButtonState(btn, checkbox.checked);
// Validate regex when regex flag changes
if (checkboxId === 'isRegex') {
validateRegexPattern();
}
// Check if field is dirty
checkIfDirty();
});
// Initialize button state
updateToggleButtonState(btn, checkbox.checked);
});
}
/**
* Update visual state of toggle button
*/
function updateToggleButtonState(button, isActive) {
if (isActive) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
}
/**
* Validate regex pattern
*/
function validateRegexPattern() {
const isRegex = domCache.isRegex.checked;
if (!isRegex) {
// Not using regex, clear any errors
domCache.findInput.classList.remove('error');
domCache.findValidationError.classList.remove('visible');
domCache.findValidationError.textContent = '';
return true;
}
const pattern = domCache.findInput.value;
if (!pattern) {
// Empty pattern is okay
domCache.findInput.classList.remove('error');
domCache.findValidationError.classList.remove('visible');
domCache.findValidationError.textContent = '';
return true;
}
try {
new RegExp(pattern);
// Valid regex
domCache.findInput.classList.remove('error');
domCache.findValidationError.classList.remove('visible');
domCache.findValidationError.textContent = '';
return true;
} catch (e) {
// Invalid regex
domCache.findInput.classList.add('error');
domCache.findValidationError.classList.add('visible');
domCache.findValidationError.textContent = `Invalid regular expression: ${e.message}`;
return false;
}
}
function handleAddPattern(button) {
const scope = button.dataset.scope;
// Visual feedback: disable button temporarily
button.disabled = true;
button.style.opacity = '0.5';
button.style.cursor = 'wait';
vscode.postMessage({
type: 'create',
scope: scope === 'workspace' ? 'workspace' : 'global'
});
// Re-enable after 2 seconds (fallback in case of error)
setTimeout(() => {
button.disabled = false;
button.style.opacity = '';
button.style.cursor = '';
}, 2000);
}
function handleActionClick(button) {
const action = button.dataset.action;
const item = button.closest('.pattern-item');
const id = item.dataset.id;
const scope = item.dataset.scope;
if (action === 'delete') {
handleDelete(id, scope);
} else if (action === 'load') {
handleLoad(id, scope);
}
}
function handleDelete(id, scope) {
vscode.postMessage({
type: 'delete',
id: id,
scope: scope
});
}
function handleLoad(id, scope) {
if (!id || !scope) {
console.error('Pattern not found');
return;
}
vscode.postMessage({
type: 'load',
id: id,
scope: scope
});
}
document.getElementById('saveBtn').addEventListener('click', handleSavePattern);
document.getElementById('loadBtn').addEventListener('click', handleLoadPatternFromDetails);
setupToggleButtons();
// Add event listeners to all form inputs for dirty tracking
const formInputs = [
'labelInput', 'findInput', 'replaceInput',
'isRegex', 'isCaseSensitive', 'matchWholeWord',
'filesToInclude', 'filesToExclude'
];
formInputs.forEach(id => {
const element = document.getElementById(id);
if (element) {
element.addEventListener('input', checkIfDirty);
element.addEventListener('change', checkIfDirty);
}
});
// Initialize DOM cache for performance
initDomCache();
// Set up event delegation for pattern list (once, not per render)
setupEventDelegation();
// Resizable panels functionality
function setupResizablePanels() {
const resizeHandle = document.getElementById('resizeHandle');
const patternListPanel = document.querySelector('.pattern-list-panel');
if (!resizeHandle || !patternListPanel) return;
let isResizing = false;
let startX = 0;
let startWidth = 0;
// Load saved width from state
const state = vscode.getState() || {};
if (state.panelWidth) {
patternListPanel.style.flexBasis = `${state.panelWidth}px`;
}
resizeHandle.addEventListener('mousedown', (e) => {
isResizing = true;
startX = e.clientX;
startWidth = patternListPanel.offsetWidth;
resizeHandle.classList.add('resizing');
document.body.style.cursor = 'ew-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
const deltaX = e.clientX - startX;
const newWidth = startWidth + deltaX;
// Respect min/max constraints
const minWidth = 200;
const maxWidth = 600;
const clampedWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
patternListPanel.style.flexBasis = `${clampedWidth}px`;
});
document.addEventListener('mouseup', () => {
if (isResizing) {
isResizing = false;
resizeHandle.classList.remove('resizing');
document.body.style.cursor = '';
document.body.style.userSelect = '';
// Save width to state
const currentWidth = patternListPanel.offsetWidth;
const state = vscode.getState() || {};
state.panelWidth = currentWidth;
vscode.setState(state);
}
});
}
// Set up resizable panels
setupResizablePanels();
// Add validation listener to find input
if (domCache.findInput) {
domCache.findInput.addEventListener('input', () => {
validateRegexPattern();
});
}
vscode.postMessage({ type: 'ready' });