diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 83479e8..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/class-name-casing": "warn", - "@typescript-eslint/semi": "warn", - "curly": "warn", - "eqeqeq": "warn", - "no-throw-literal": "warn", - "semi": "off" - } -} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ccc8051 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Enforce LF for all text files — prevents the CRLF spurious-diff problem +* text=auto eol=lf + +# Binary assets +*.png binary +*.gif binary +*.vsix binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e4fdda9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,55 @@ +name: Bug report +description: Something inserted wrong, errored, or didn't behave as documented +labels: [ "bug" ] +body: + - type: textarea + id: what-happened + attributes: + label: What happened + description: What did you see, and what did you expect instead? + placeholder: 'e.g. "Insert Random: Record…" with a CSV shape inserted an unescaped comma inside a quoted field…' + validations: + required: true + - type: input + id: command + attributes: + label: Command used + placeholder: 'e.g. Insert Random: UUID · Pick… · Record… · Generate Dataset… · Randomize Selection' + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to reproduce + placeholder: | + 1. Open a .sql file + 2. Add 3 cursors + 3. Run "Insert Random: Person" + validations: + required: true + - type: textarea + id: settings + attributes: + label: Relevant settings + description: Any non-default settings — insertType, withQuote, bulkCount, outputFormat, seed, locale, strictUnique, dateFormat, recordFormat… + placeholder: | + insertType: Cursor + insertRandomText.bulkCount: 10 + insertRandomText.seed: 42 + - type: dropdown + id: environment + attributes: + label: Where does it happen? + options: + - VS Code desktop + - vscode.dev / github.dev (browser) + - VSCodium / Cursor / Windsurf (Open VSX build) + validations: + required: true + - type: input + id: versions + attributes: + label: VS Code version · extension version + placeholder: e.g. 1.102.0 · 1.0.0 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..95cd7bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,22 @@ +name: Feature request +description: A new data type, format option, or capability you're missing +labels: [ "enhancement" ] +body: + - type: textarea + id: what + attributes: + label: What do you want to generate or do? + placeholder: e.g. IPv6 CIDR ranges · a "no weekends" option on Date (Between…) · Markdown table output format + validations: + required: true + - type: textarea + id: example + attributes: + label: Example of the output you'd expect + placeholder: | + 2001:db8:3c4d::/48 + - type: textarea + id: workaround + attributes: + label: How do you handle it today? + description: A template/pattern workaround, another tool, by hand… (helps us judge urgency, and sometimes there's already a way — happy to point it out) diff --git a/.gitignore b/.gitignore index 5fe00fe..5d63fe8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,26 @@ +# Build output out +dist +*.tsbuildinfo + +# Dependencies node_modules + +# VS Code extension testing & packaging .vscode-test/ *.vsix + +# Local-only working files — not tracked, not shipped, excluded from release & audits +/docs/ +/qa/ + +# Logs +*.log + +# OS +.DS_Store +Thumbs.db + +# Test coverage output +coverage/ +.nyc_output/ diff --git a/.vscode-test.mjs b/.vscode-test.mjs new file mode 100644 index 0000000..f6b6399 --- /dev/null +++ b/.vscode-test.mjs @@ -0,0 +1,25 @@ +import { defineConfig } from '@vscode/test-cli'; +import os from 'node:os'; +import path from 'node:path'; + +// VS Code's default user-data-dir lives inside the project (.vscode-test/user-data). On deep project +// paths that pushes the Extension Host IPC socket past macOS's ~103-char unix-socket limit +// (listen EINVAL: ...-main.sock). Relocate it to a short temp path so `npm test` runs anywhere. +const userDataDir = path.join(os.tmpdir(), 'irt-vscode-test'); + +export default defineConfig({ + tests: [ + { + files: 'out/test/**/*.test.js', + mocha: { + ui: 'bdd', + }, + launchArgs: [ '--user-data-dir', userDataDir ], + }, + ], + coverage: { + includeAll: true, + exclude: [ '**/test/**', '**/*.test.*' ], + reporter: [ 'text', 'html' ], + }, +}); diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3ac9aeb..d7a3ca1 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,5 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"] } diff --git a/.vscode/launch.json b/.vscode/launch.json index b1fbaf5..b99c706 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,26 +9,37 @@ "name": "Run Extension", "type": "extensionHost", "request": "launch", - "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}" ], "outFiles": [ - "${workspaceFolder}/out/**/*.js" + "${workspaceFolder}/dist/**/*.js" ], "preLaunchTask": "${defaultBuildTask}" }, { - "name": "Extension Tests", + "name": "Run Extension (QA Workspace)", "type": "extensionHost", "request": "launch", - "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + "${workspaceFolder}/qa/workspace" ], "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" + "${workspaceFolder}/dist/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Run Extension (Demo Workspace)", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "${workspaceFolder}/qa/demo-workspace" + ], + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" ], "preLaunchTask": "${defaultBuildTask}" } diff --git a/.vscode/settings.json b/.vscode/settings.json index 30bf8c2..16a5c02 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,13 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" -} \ No newline at end of file + "files.exclude": { + "out": false, // set this to true to hide the "out" folder with the compiled JS files + "dist": false // set this to true to hide the "dist" folder with the compiled JS files + }, + "search.exclude": { + "out": true, // set this to false to include "out" folder in search results + "dist": true // set this to false to include "dist" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b17e53..db5b3ad 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,10 +4,11 @@ "version": "2.0.0", "tasks": [ { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, + "label": "watch", + "dependsOn": [ + "npm: watch:tsc", + "npm: watch:esbuild" + ], "presentation": { "reveal": "never" }, @@ -15,6 +16,49 @@ "kind": "build", "isDefault": true } + }, + { + "type": "npm", + "script": "watch:esbuild", + "group": "build", + "problemMatcher": "$esbuild-watch", + "isBackground": true, + "label": "npm: watch:esbuild", + "presentation": { + "group": "watch", + "reveal": "never" + } + }, + { + "type": "npm", + "script": "watch:tsc", + "group": "build", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "label": "npm: watch:tsc", + "presentation": { + "group": "watch", + "reveal": "never" + } + }, + { + "type": "npm", + "script": "watch-tests", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "watchers" + }, + "group": "build" + }, + { + "label": "tasks: watch-tests", + "dependsOn": [ + "npm: watch", + "npm: watch-tests" + ], + "problemMatcher": [] } ] } diff --git a/.vscodeignore b/.vscodeignore index c06f4a7..ee4b801 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,10 +1,24 @@ .vscode/** .vscode-test/** -out/test/** +.github/** +out/** +node_modules/** +coverage/** +.nyc_output/** src/** +qa/** +docs/** .gitignore -vsc-extension-quickstart.md +.gitattributes +esbuild.js **/tsconfig.json -**/.eslintrc.json +**/eslint.config.mjs **/*.map **/*.ts +**/.vscode-test.* +package-lock.json +CLAUDE.md +SPEC.md +images/*.gif +.claude/** +**/.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da34a4..6383242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,77 @@ # Changelog -## v0.1.3 (2020-7-25) +## v1.0.0 (2026-07-10) -### Changes +The first stable release — a ground-up relaunch as **Random, Fake & Mock Data Generator**. + +### Features + +- Switched the random engine to [`@faker-js/faker`](https://fakerjs.dev) for realistic, coherent data. +- Expanded the catalog to **~130 data types** — on top of the originals (names, dates, numbers, strings, lorem, hashes, …), added email, UUID, company, commerce, finance (credit card, IBAN, currency, crypto), git, system/files, vehicle, food, music, travel, color variants, and richer identity / location / date / number / internet types, plus IDs like ULID & nanoid. Every type is a searchable command and a grouped Quick Pick entry. +- **Multi-cursor fill** — insert a different value at every cursor in one step. +- New **Insert Random: Pick…** command — choose any type from a searchable, grouped menu. +- **Clipboard insert type** — set `insertType` to `Clipboard` to copy a generated value to the clipboard instead of inserting it (no editor needed; resolves #4). +- **Automatic quoting** — inserted values wrap in the correct quote for the file's language with zero configuration, so they're always valid syntax: SQL dialects use single quotes and escape an embedded quote by doubling it (`'O''Brien'`); every other language (JSON, Go, Java, Rust, JS/TS, Python, and the rest) uses double quotes. No quote settings to configure. +- **Multi-field records** — new **Insert Random: Record…** command: multi-select any fields and insert them together as one record — a JSON object, a SQL `INSERT` row, or a CSV line — at every cursor. Set the shape with `insertRandomText.recordFormat` (`json` / `sql` / `csv`) and the SQL table name with `insertRandomText.recordSqlTable`. Respects bulk count (a JSON array / repeated rows), multi-cursor, seed, and the insert type (cursors / top of file / clipboard). +- **Generate whole datasets** — new **Insert Random: Generate Dataset…** command: pick fields (custom lists included), a format, and a row count, and up to **100,000 rows** open as a new untitled file — a JSON array (one record per line), SQL `INSERT` statements, or CSV **with a header row**. The format pick starts on your `recordFormat`, the row count on your bulk count (large counts confirm first); locale, seed, and date format apply, so a seeded run regenerates the identical dataset. Instant and offline, works with no editor open. +- New settings: `insertRandomText.uniquePerCursor`, `insertRandomText.strictUnique`, `insertRandomText.seed` (reproducible output), `insertRandomText.locale`, `insertRandomText.bulkCount`, `insertRandomText.outputFormat` (`plain` / `jsonArray` / `quotedList`), `insertRandomText.dateFormat`, `insertRandomText.recordFormat`, `insertRandomText.recordSqlTable`, and an opt-in editor context-menu submenu (`insertRandomText.contextMenu.enabled`). +- **Settings commands** — change any setting from the Command Palette: *Insert Random: Set Insert Type / Output Format / Date Format / Record Format / Record SQL Table / Bulk Count / Seed / Locale*, *Toggle* commands for each boolean setting (Wrap With Quotes, Trailing New Line, Unique Value Per Cursor, Strict Unique, Editor Context Menu), and *Reset Settings to Defaults*. +- New **Image URL** and **Avatar URL** types (new **Media** category) — UI placeholders and Storybook props — plus **MongoDB ObjectId** under IDs. +- **Parameterized types** — new *Insert Random: Number (Range…)*, *Float (Range…)*, *String (Length…)*, *Date (Between…)*, *Words (Count…)*, *Sentences (Count…)*, and *Paragraphs (Count…)* commands ask for a min & max, a length up to 1000, a from/to date (`YYYY-MM-DD` or full ISO 8601), or a lorem count up to 100 in validated input boxes, then insert through the normal pipeline (multi-cursor, bulk, quoting, seed all apply). Last-used inputs are remembered and prefilled; Esc cancels cleanly. +- **Format variants** — new *Insert Random: UUID (Format…)*, *Password (Options…)*, and *Phone (Format…)* commands: pick a UUID rendering (lowercase / UPPERCASE / braced / no dashes / UPPERCASE without dashes), a password length (8–128) with or without symbols, or a phone style (human / national / international) from a Quick Pick. The last pick is remembered and floats to the top next time; same pipeline and clean Esc-cancel as the other parameterized types. +- **Templates & patterns** — new *Insert Random: From Template…* and *From Pattern…* commands expose faker's entire surface through one input box: a mustache template (`{{person.firstName}} <{{internet.email}}>`) or a regex-like pattern (`[A-Z]{3}-[0-9]{4}`, faker's limited regex subset) is re-rendered with fresh values at every cursor and bulk item. Input is proven by test-rendering as you type — a typo shows faker's error plus a working example inline, and nothing inserts until it renders; the last template and pattern are remembered and prefilled. +- **Your own data** — two new settings put your data in the picker: `insertRandomText.templates` (named faker templates) and `insertRandomText.customLists` (named value lists) appear as **Templates** and **Custom Lists** groups at the *top* of *Insert Random: Pick…*, and custom lists double as *Record…* fields (the name becomes the field key). Everything rides the normal pipeline — multi-cursor, bulk, quoting, seed. New *Insert Random: Manage Templates* / *Manage Custom Lists* commands jump straight to the settings; malformed entries are skipped safely (logged to the console), and both settings survive *Reset Settings to Defaults*. +- **Sequences** — new *Insert Random: Sequence (Start/Step…)* command fills cursors with incrementing values: enter a start and a step (negative counts down) and 1, 2, 3… lands down your column — one counter runs through the cursors and bulk items of an insert, and the next insert restarts at your start. Deliberately not random — the multi-cursor machinery makes numbered columns free. +- **Runs in the browser** — the extension now ships a web build alongside the desktop one, so it works on [vscode.dev](https://vscode.dev) and github.dev; the web bundle carries no Node dependencies and generates everything in the browser, still fully offline. +- **Restricted Mode & virtual workspaces** — the extension now runs in untrusted (Restricted Mode) and virtual workspaces: every value is generated in-process, with no project-file reads and no network calls. +- **Six locales** — new `insertRandomText.locale` setting (`en` / `de` / `fr` / `es` / `pt_BR` / `ja`): names, addresses, words and more come out in the chosen language, across everything — single inserts, records, templates and patterns. Switching applies to the next insert with no reload, seeded runs stay reproducible per locale, and a type without localized data falls back to English. Change it from the palette with *Insert Random: Set Locale*. +- **Anonymize in place, type-aware** — new *Insert Random: Randomize Selection* command replaces each selection where it stands: a selection that *is* an email, UUID, or ISO date/timestamp becomes a **fresh realistic fake of the same type** (a UUID stays a *valid* UUID — case and braces preserved; a date stays a real calendar date at its own precision), and everything else gets a same-shape randomization — digits become digits, letters keep their case, punctuation and layout don't move (`3.14` → `8.77`). Works across a multi-selection in one step, honors the seed for reproducible scrubs, and joins the editor right-click submenu. +- **Date format control** — new `insertRandomText.dateFormat` setting (`iso` / `isoDate` / `isoTime` / `unixSeconds` / `unixMillis`) renders every timestamp Time type (Date, Past/Future/Recent/Soon Date, Birthdate, Date (Between…)) as a full ISO 8601 timestamp, date only, time only, or Unix seconds/milliseconds — in single inserts and record fields alike (Weekday/Month are unaffected). Change it from the palette with *Insert Random: Set Date Format*. +- **Strict unique (opt-in)** — new `insertRandomText.strictUnique` setting: duplicates are re-drawn so values meant to differ within one insert really do — bulk values at a cursor, and values across cursors when `uniquePerCursor` is on. Honestly bounded: after 25 re-draws a small pool (booleans, weekdays) keeps its duplicate rather than hanging, and seeded runs stay reproducible. Flip it from the palette with *Insert Random: Toggle Strict Unique*. -- updated readme +### Fixes -## v0.1.2 (2020-3-21) +- Inserted text no longer stays selected: after a Cursor-mode insert over a selection, the cursor now sits right after the inserted block — same as typing. +- Lorem is now genuinely randomized (previously a fixed substring of one hardcoded string). +- Random string is alphanumeric, so it no longer breaks quote-wrapping. +- Quote-wrapped values are now escaped, so a value containing the active quote character (e.g. `O'Brien`) stays a valid string literal. +- Removed the global notification-clearing side effect and a duplicate-draw bug in the animal command. ### Changes -- updated readme +- Removed the `loremSize`, `hashSize`, and `disableNotifs` settings — the base Lorem and Hash commands no longer take a configurable length (the Small / Medium / Large variants still cover the sizes), and inserts no longer show notifications, so there is nothing left to disable. +- Removed the `quoteStyle` setting — quoting is now automatic and language-aware (see **Automatic quoting**): SQL keeps single quotes, every other language now gets double quotes where the old default wrapped in single, and a saved `quoteStyle` value is no longer read. +- Existing `extension.insertRandom*` commands continue to work unchanged. +- Modernized the build (esbuild bundle; now requires VS Code 1.97+). +- Refreshed the marketplace listing: new icon, new description, added the **Testing** category, finalized the keyword set, a **Sponsor** button linking to the README's Support section, and a gallery banner tuned to the icon. +- New demo GIFs — the multi-cursor hero, records → whole datasets, and the smart trio (language-aware quotes, locales, anonymize in place) — replacing the 2020-era demo. -## v0.1.1 (2020-3-16) +## v0.1.3 (2020-07-25) + +### Changes -### Feature +- Expanded the README — clearer step-by-step installation instructions, contributing notes, and a new **Support** section with donation options. -- toggle new line +## v0.1.2 (2020-03-21) ### Changes -- added new configuration `withNewLine` +- Polished the README — tidied the installation steps and links. + +## v0.1.1 (2020-03-16) + +### Features + +- **Trailing new line** — new `withNewLine` setting appends a newline after the inserted value, so back-to-back inserts land on their own lines. + +## v0.1.0 (2020-03-15) + +The first release, as **Insert Random Text** — insert random text on the fly, powered by [Chance](https://chancejs.com). -## v0.1.0 (2020-3-15) +### Features -- initial release +- **14 insert commands** — *Insert Random: Animal / Person / Date / Country / Number / String*, plus *Lorem* and *Hash* in four sizes each (base, Small, Medium, Large). +- **Insert type** — the `insertType` setting places the value at the cursor (`Cursor`) or on line 1 of the file (`Top`). +- **Quote wrapping** — `withQuote` wraps inserted values in quotes; `quoteStyle` chooses single or double. +- **Length controls** — `loremSize` and `hashSize` set the generated length for the base Lorem and Hash commands. +- **Notifications toggle** — `disableNotifs` silences the insert confirmation notifications. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3e0480d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,82 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +VS Code extension (display name **"Random, Fake & Mock Data Generator"**, id `insert-random-text`, publisher `ElecTreeFrying`) that inserts random / fake / mock data — names, emails, addresses, numbers, dates, UUIDs, lorem ipsum, and ~130 types in all — at **every cursor**. Randomness comes from **`@faker-js/faker`** (six locales — `en` default, plus `de`/`fr`/`es`/`pt_BR`/`ja` via `insertRandomText.locale`). + +### Project goals (north star) +1. Grow active installs in the random/mock-data category (near-term: pass the category mid-tier; long-term: challenge the leader). Active installs *subtract* on uninstall, so **retention is the real metric** — every defect fix is rank-defending. +2. Value is the engine: each generator is simultaneously a real feature, a searchable command title, and a Quick Pick entry. Breadth = discoverability + utility. + +## Commands + +- `npm run compile` — type-check + lint + **esbuild** bundles (`src/extension.ts` → `dist/extension.js` node target **and** `dist/web/extension.js` browser target, one run) +- `npm run watch` — incremental dev build; `watch:esbuild` + `watch:tsc` in parallel (`npm-run-all`). Default build task (auto-runs on F5 via `preLaunchTask`). +- `npm run check-types` — `tsc --noEmit` (esbuild does the actual transpile) +- `npm run lint` — `eslint src` (flat config, `eslint.config.mjs`) +- `npm run package` — production build: `check-types && lint && esbuild --production`. `vscode:prepublish` runs this. +- `npx @vscode/vsce package` — build the `.vsix` (runs prepublish first). Keep the packaged `.vsix` ≤ ~1.5 MB. + +`main` is `./dist/extension.js` and `browser` is `./dist/web/extension.js` (same source, two esbuild platforms; **faker inlined** in both — the browser bundle is what runs on vscode.dev/github.dev). `dist/` and `out/` are gitignored — build before running. **esbuild's entry is `src/extension.ts`, so that file must keep its name/location** (both targets are configured in `esbuild.js`, which is out of the usual edit scope). + +### Verification +Tests live in **`src/test//*.test.ts`** (mocha `describe`/`it`; `compile-tests` transpiles them to `out/`). Two tiers: +- **Pure areas, headless** — `npm run compile-tests && npx mocha "out/test/{quote,config,catalog,engine,format,record,prompted,custom,randomize}/*.test.js"`. Don't widen the glob to `**`: `extension.test.ts` and `commands/` import `vscode` and fail under plain node. +- **Full suite (Extension Host)** — `npm test` (`pretest` runs compile-tests + compile first; compile itself lints). Required for `commands/` and `extension.test.ts`. + +Gotcha: `out/` keeps orphaned compiled tests after a `.test.ts` is deleted/renamed — `rm -rf out` before a headless run when results look stale. Release gate on top of tests: `check-types` + `lint` + `node esbuild.js --production` + `npx @vscode/vsce package`. + +### Running / debugging +Press F5 → **"Run Extension"** opens an Extension Development Host with the extension loaded (the `preLaunchTask` builds first). + +## Architecture + +Eleven single-responsibility modules. Generation is `vscode`-free and lives apart from the editor glue, so the logic that matters is decoupled from the API surface: + +- **`src/engine.ts`** — faker lifecycle: `load(locale?)` (lazy, idempotent; default `en`) imports and caches one instance per shipped locale (`LOCALES`/`LocaleId`, one **literal** dynamic import each, cached as promises so concurrent loads share the import) and makes it **active**; the `faker()` accessor returns the active instance; `seed()` seeds it. Encapsulates the ESM/dynamic-import detail (see Gotchas). No `vscode` import. +- **`src/catalog.ts`** — the **generator registry**: the `Generator` interface and the readonly `generators` array (each `{ id, label, group, hidden?, generate(opts?) }`), plus `getGenerator(id)`. `generate` takes an optional `GenerateOptions` (`{ dateFormat }`) — only the six timestamp Time generators read it, rendering their `Date` via the exported `formatTimestamp(date, format)` (UTC-derived slices; `unixSeconds` floors); every other generator ignores it, and zero-arg `generate: () => …` entries still satisfy the contract. Single source of truth — drives generation, the commands, and the Quick Pick. No `vscode` import. +- **`src/formatter.ts`** — **pure** rendering: `buildBlocks(cursorCount, generator, options)` returns one block per cursor; `formatBlock` applies `bulkCount` + `outputFormat` (`plain` / `jsonArray` / `quotedList`) + quote/newline wrapping (escaping per the quote policy), and hands `options.dateFormat` to every `generate()` call. `options.strictUnique` routes draws through a seen-set re-draw (`uniqueDraw`, 25-retry budget — part of the seeded-output contract; exhaustion keeps the duplicate, never hangs): one set per `buildBlocks` call, so it spans cursors when `uniquePerCursor` is on and covers just the shared block when off. Single-type inserts only — records never read it. No `vscode` import → trivially checkable. +- **`src/quotePolicy.ts`** — **pure** language-aware quoting: `resolveQuotePolicy(languageId, opts)` maps a file's `languageId` to a `{ quote, escape }` policy (SQL-family → single quotes + `''`-doubling; everything else → double quotes); `formatter.wrap()` consumes the `escape`. No `vscode` import. +- **`src/record.ts`** — **pure** multi-field records: `buildRecords(fields, shape, opts)` composes picked generators into one JSON object / SQL `INSERT` row / CSV line (stacked by `bulkCount`); escaping is decided by the **shape**, not the file's language; `opts.dateFormat` is handed to each field draw. `opts.dataset` renders the same records as a standalone **file** (Generate Dataset…): CSV gains a header row of field keys (csv-escaped — a custom-list name may hold a comma), JSON is always an array with one record per line, and the text ends with a trailing newline; at-cursor records never pass it. No `vscode` import. +- **`src/randomize.ts`** — the **pure** halves of Randomize Selection (anonymize in place). Type-aware replace: `detect(text)` → `'email' | 'uuid' | 'isoDate' | 'isoTimestamp' | undefined` (anchored whole-string matches, calendar-checked dates, UTC-`Z`-only timestamps — conservative by design; numbers deliberately undetected, the scramble already serves them), plus the pure dressers `shapeUuid(fresh, original)` (case + braces follow the original) and `shapeTimestamp(freshIso, original)` (strip `.mmm` when the original had none). Fallback scramble: `randomize(text, rng)` maps each digit → random digit, `a–z` → random lowercase, `A–Z` → random uppercase, everything else untouched; iterates code points so surrogate pairs pass through whole. `rng(bound)` → `[0, bound)` is injected — the extension wraps the shared seeded faker (`number.int`), tests script it. No `vscode` import. +- **`src/prompted.ts`** — **pure** parameterized-command registry: `promptedCommands` (each `{ id, label, group, steps }` plus exactly one rendering surface — `render(params, opts?)` for stateless draws, or `createRender(params)` returning a closure built once per insert for stateful ones (Sequence's counter; a spec test enforces the exactly-one rule); a step is a discriminated union — an **input box** (`InputStep`: prompt/placeholder/fallback plus a `validateInput`-shaped `validate(input, prior)` — `prior` enables cross-field rules like min ≤ max) or a **Quick Pick** (`PickStep`: `kind: 'pick'`, `options: { value, label, detail }[]`, fallback-first order, no validation — closed set)), `getPromptedCommand(id)`, `toGenerator(command, params)` wrapping a finished flow as a one-off `Generator`, and the pure `formatUuid(uuid, format)` post-transform behind UUID (Format…). The free-form From Template…/From Pattern… boxes validate by **test-rendering** the input through faker (`helpers.fake` / `helpers.fromRegExp`) — faker's error plus a working example on failure, empty input rejected — so their `validate` needs the engine loaded. `render(params, opts?)` receives the same `GenerateOptions` as catalog generators (Date (Between…) renders per `dateFormat`). Deliberately **not** catalog entries — absent from Pick…/Record…. No `vscode` import. +- **`src/custom.ts`** — **pure** user-defined data pools: `templateGenerators(map)` / `customListGenerators(map)` wrap the validated `insertRandomText.templates` / `.customLists` settings as plain `Generator`s (Templates render via `helpers.fake`, lists draw via `helpers.arrayElement`). The **id is the bare user-chosen name** — it doubles as the Record… field key — and these never enter the catalog registry, so a name matching a catalog id shadows nothing. `TEMPLATES_GROUP` / `CUSTOM_LISTS_GROUP` are the picker headings. No `vscode` import. +- **`src/configuration.ts`** — `Configuration` reads workspace settings into a typed `Settings` via `read()`; `ConfigKey` holds the package.json key constants; the two enum settings are normalized to booleans here, `locale` is validated against `LOCALES` (junk → `en`), and the two **object settings** (`templates`, `customLists`) are shape-validated — junk entries dropped, each drop logged through an injectable `WarnSink` (constructor param defaulting to `console.warn`; inject it in tests — the extension host's patched console can't be monkey-swapped). Depends only on a narrow `WorkspaceLike` seam, not `vscode`. +- **`src/extension.ts`** — thin activation entry. `COMMAND_TO_GENERATOR` maps every command id → a generator id; `activate()` registers them all through one `insertGenerated(id)`, plus the `insertRandomText.pick` Quick Pick, the `insertRandomText.record` multi-field composer, the `insertRandomText.generateDataset` records-to-file builder, the `insertRandomText.randomizeSelection` in-place anonymizer, and every prompted command from `promptedCommands` via `runPrompted` (all but randomize and dataset honor `insertType`). The pipeline seam is **`insertWith(generator)`** — `load(settings.locale)` → `applySeed()` → read cached `settings` → `buildBlocks` over `editor.selections` (Cursor mode = multi-cursor fill via `fillSelections`, which collapses each cursor to sit after its block — inserted text never stays selected; Top mode = one block at line 1; Clipboard mode = a bare value copied to the clipboard, no editor needed); `insertGenerated(id)` is just registry lookup + `insertWith`, and `runPrompted` is `await load(settings.locale)` (validation may test-render through faker) + the step walk (input boxes prefill from `globalState`; pick steps float the remembered pick to the top marked "Last used"; Esc = clean cancel) + `insertWith`. `customPickItems(includeTemplates)` builds the user-group items that lead Pick… (Templates + Custom Lists) and the field picker (Custom Lists only; the picked lists join `fields` first, keyed by name); custom selections go through `insertCustom` — `insertWith` in a try/catch that surfaces a template render failure as a friendly error naming the Manage command. **`pickRecordFields(placeHolder)`** is the shared multi-select field picker behind Record… AND Generate Dataset… (returns picked generators in picker display order, or undefined on cancel/empty). `generateDataset()` = `load` → `pickRecordFields` → shape pick (the `recordFormat` setting floats to the top marked current; picking does NOT write the setting) → row-count box (default `bulkCount`, hard cap 100 000, modal confirm above 10 000) → `applySeed()` → `buildRecords(…, { dataset: true })` → `openTextDocument({ content, language })` + `showTextDocument` (json/sql/`plaintext` for csv) — a new untitled file, so `insertType` never applies and no editor is needed. `randomizeSelections()` bypasses the insert pipeline deliberately (a replacement, not an insertion — no quote/newline/bulk/insertType): `load` + `applySeed`, then per non-empty selection `typedReplacement(text) ?? randomize(text, rng)` through `fillSelections` — `typedReplacement` switches on `detect()` and draws the fresh typed value through `faker()` (email / re-dressed uuid / `date.anytime()` rendered per the original's format), so typed redraws are seeded like everything else (empty selections map to an identity `''` replace, so bare carets get nothing); no non-empty selection anywhere → one info message. +- **`src/settingsCommands.ts`** — palette commands that *write or open* settings: `SETTING_COMMANDS` maps command id → handler (Quick Pick for the enum settings, toggle for the booleans, input box for bulk count / seed, `manage*` commands that open the Settings UI at a key, plus a confirm-gated reset). **The reset deliberately skips `templates`/`customLists`** (`RESET_KEEPS`) — user-authored content, not tuning; the modal says so. Writes to the open workspace when one is present, else global user settings; `extension.ts` registers them. The read side stays in `configuration.ts`. + +### Config flow (the key cross-file mechanism) +`extension.ts` holds a single module-level `settings: Settings`. On activate, `watchConfiguration()` snapshots it via `Configuration.read()`, then re-snapshots on any relevant `onDidChangeConfiguration` (a `locale` change additionally fires `load(locale)` so the active faker swaps eagerly). Commands read this **cached** `settings`, never re-reading at invocation. Anything bypassing `watchConfiguration` sees stale config. + +### Commands & settings model +- **Two command namespaces.** The original 14 `extension.insertRandom*` ids are kept for **back-compat** (existing keybindings); every new type uses a namespaced `insertRandomText.` id. Both register via `COMMAND_TO_GENERATOR`. +- **Settings are split.** The 3 remaining legacy keys stay **flat & non-namespaced** (`insertType`, `withQuote`, `withNewLine`) for back-compat; **all new keys are namespaced** under `insertRandomText.*` (`uniquePerCursor`, `seed`, `locale`, `bulkCount`, `outputFormat`, `templates`, `customLists`, `contextMenu.enabled`, …). Do **not** migrate the legacy keys. +- **Hidden generators.** The Lorem/Hash Small/Medium/Large back-compat generators carry `hidden: true` so they serve their legacy commands without cluttering the Quick Pick. +- **Settings commands.** `settingsCommands.ts` contributes `insertRandomText.set*` / `toggle*` / `resetSettings` so every setting is changeable from the Command Palette (mirrors the sibling `auto-import-relative-path`). Each new one is a `package.json` command + an entry in `SETTING_COMMANDS` (registered in `extension.ts`). Writes are workspace-scoped when a folder is open, else global. +- **Prompted commands.** The parameterized types (`numberRange` / `floatRange` / `stringLength` / `dateBetween` / `wordsCount` / `sentencesCount` / `paragraphsCount` / `uuidFormat` / `passwordOptions` / `phoneFormat` / `fromTemplate` / `fromPattern` / `sequence`, all `insertRandomText.*`) ask for parameters in input boxes and Quick Picks, then insert via `insertWith`. `sequence` is the stateful one — not random at all: `createRender` builds one counter per insert (start/step boxes), advancing per cursor/bulk item and restarting on the next insert. Adding one: entry in `promptedCommands` (`src/prompted.ts`) + a `package.json` command `insertRandomText.` — registration is automatic; they are intentionally **not** in `COMMAND_TO_GENERATOR` (the parity test reads `promptedCommands` and holds contributed ↔ registered in sync, and guards prompted ids against catalog-id collisions). + +### Adding a generator (the common case) +1. `src/catalog.ts` — add a `{ id, label, group, generate }` entry. It appears in the Quick Pick automatically. +2. `package.json` → `contributes.commands` — add `{ "command": "insertRandomText.", "title": "Insert Random: