diff --git a/.claude/skills/code-dedup/SKILL.md b/.claude/skills/code-dedup/SKILL.md index 6ce2930..df54f9b 100644 --- a/.claude/skills/code-dedup/SKILL.md +++ b/.claude/skills/code-dedup/SKILL.md @@ -6,7 +6,7 @@ description: Searches for duplicate code, duplicate tests, and dead code, then s # Code Dedup -Carefully search for duplicate code, duplicate tests, and dead code across the Diffly repo. Merge duplicates and delete dead code — but only when test coverage proves the change is safe. +Carefully search for duplicate code, duplicate tests, and dead code across the Diffr repo. Merge duplicates and delete dead code — but only when test coverage proves the change is safe. ## Prerequisites — hard gate @@ -14,7 +14,7 @@ Before touching ANY code, verify these conditions. If any fail, stop and report 1. Run `make test` — all tests must pass. If tests fail, stop. Do not dedup a broken codebase. 2. Run `make test` — tests are fail-fast AND enforce the coverage threshold from `coverage-thresholds.json`. If anything fails, stop and fix it before deduping. -3. Verify the project uses **static typing**. TypeScript with `tsconfig.json` `"strict": true` is required (which Diffly enforces — see CLAUDE.md). If `strict` is off, STOP and refuse. +3. Verify the project uses **static typing**. TypeScript with `tsconfig.json` `"strict": true` is required (which Diffr enforces — see CLAUDE.md). If `strict` is off, STOP and refuse. ## Steps @@ -43,7 +43,7 @@ Before deciding what to touch, understand what is tested. Search for code that is never called, never imported, never referenced. 1. Look for unused exports, unused functions, unused classes, unused variables. -2. Check TypeScript: `noUnusedLocals`/`noUnusedParameters` are already enabled in Diffly's `tsconfig.json`. Look for unexported helpers with zero references. +2. Check TypeScript: `noUnusedLocals`/`noUnusedParameters` are already enabled in Diffr's `tsconfig.json`. Look for unexported helpers with zero references. 3. For each candidate: **grep the entire codebase** (including `src/test/`, `package.json` `contributes`, `scripts/`, configs) for references. Only mark as dead if truly zero references. 4. List all dead code found with file paths and line numbers. Do NOT delete yet. @@ -54,7 +54,7 @@ Search for code blocks that do the same thing in multiple places. 1. Look for functions/methods with identical or near-identical logic. 2. Look for copy-pasted blocks (same structure, maybe different variable names). 3. Look for multiple implementations of the same algorithm or pattern. -4. Check across module boundaries — Diffly's layered architecture (`src/git/`, `src/ui/`, `src/providers/`, `src/commands/`) is a natural place for accidental duplication. +4. Check across module boundaries — Diffr's layered architecture (`src/git/`, `src/ui/`, `src/providers/`, `src/commands/`) is a natural place for accidental duplication. 5. For each duplicate pair: note both locations, what they do, and how they differ (if at all). 6. List all duplicates found. Do NOT merge yet. @@ -101,7 +101,7 @@ For each change, follow this cycle: **change → test → verify coverage → co - **No test coverage = do not touch.** If a file has no tests covering it, leave it alone entirely. You cannot safely dedup what you cannot verify. - **Coverage must not drop.** If removing or merging code causes coverage to decrease, revert and investigate. The coverage floor from Step 1 is sacred. -- **Strict TypeScript only.** Diffly enforces `strict: true`. If that ever changes, refuse to dedup until it's re-enabled. +- **Strict TypeScript only.** Diffr enforces `strict: true`. If that ever changes, refuse to dedup until it's re-enabled. - **One change at a time.** Make one dedup change, run tests, verify coverage. Never batch multiple dedup changes before testing. - **When in doubt, leave it.** If two code blocks look similar but you're not 100% sure they're functionally identical, leave both. False dedup is worse than duplication. - **Preserve public API surface.** Do not change exported function signatures, command IDs, URI scheme, or `package.json` `contributes` keys that the extension exposes. diff --git a/.claude/skills/fix-bug/SKILL.md b/.claude/skills/fix-bug/SKILL.md index 10e3763..5b190d0 100644 --- a/.claude/skills/fix-bug/SKILL.md +++ b/.claude/skills/fix-bug/SKILL.md @@ -22,7 +22,7 @@ You MUST follow this exact workflow. Do NOT skip steps. Do NOT fix the bug befor - Write a test that **directly exercises the buggy behavior** - The test must assert the **correct/expected** behavior — so it FAILS against the current broken code - The test name should clearly describe the bug (e.g., `test_orange_color_not_applied_to_head`) -- Use the project's existing test framework and conventions (Diffly uses mocha for unit and `@vscode/test-electron` for E2E — pick the right tier per CLAUDE.md) +- Use the project's existing test framework and conventions (Diffr uses mocha for unit and `@vscode/test-electron` for E2E — pick the right tier per CLAUDE.md) ## Step 3: Run the Test — Confirm It FAILS diff --git a/.claude/skills/spec-check/SKILL.md b/.claude/skills/spec-check/SKILL.md index e183f50..08e15eb 100644 --- a/.claude/skills/spec-check/SKILL.md +++ b/.claude/skills/spec-check/SKILL.md @@ -11,7 +11,7 @@ argument-hint: "[optional spec ID or filename filter]" Audit spec/plan documents against the codebase. Ensures every spec section has implementing code, tests, and that the code logic matches the spec. -In Diffly, the primary spec lives at [docs/specs/spec.md](../../../docs/specs/spec.md) and the live plan/TODO list at [docs/plans/plan.md](../../../docs/plans/plan.md). +In Diffr, the primary spec lives at [docs/specs/spec.md](../../../docs/specs/spec.md) and the live plan/TODO list at [docs/plans/plan.md](../../../docs/plans/plan.md). ## Arguments diff --git a/.claude/skills/upgrade-packages/SKILL.md b/.claude/skills/upgrade-packages/SKILL.md index 37a4871..4adcc16 100644 --- a/.claude/skills/upgrade-packages/SKILL.md +++ b/.claude/skills/upgrade-packages/SKILL.md @@ -1,13 +1,13 @@ --- name: upgrade-packages -description: Upgrade all dependencies/packages to their latest versions. Diffly is TypeScript/Node — use when the user says "upgrade packages", "update dependencies", "bump versions", "update packages", or "upgrade deps". +description: Upgrade all dependencies/packages to their latest versions. Diffr is TypeScript/Node — use when the user says "upgrade packages", "update dependencies", "bump versions", "update packages", or "upgrade deps". argument-hint: "[--check-only] [--major] [package-name]" --- # Upgrade Packages -Upgrade Diffly's npm dependencies to their latest compatible (or latest major, if `--major`) versions. +Upgrade Diffr's npm dependencies to their latest compatible (or latest major, if `--major`) versions. ## Arguments @@ -17,7 +17,7 @@ Upgrade Diffly's npm dependencies to their latest compatible (or latest major, i ## Step 1 — Detect package manager -Diffly uses **npm** (package manifest: `package.json`, lockfile: `package-lock.json`). If for some reason the repo has migrated to yarn or pnpm, adapt accordingly (`yarn outdated`/`yarn up` or `pnpm outdated`/`pnpm update`). +Diffr uses **npm** (package manifest: `package.json`, lockfile: `package-lock.json`). If for some reason the repo has migrated to yarn or pnpm, adapt accordingly (`yarn outdated`/`yarn up` or `pnpm outdated`/`pnpm update`). If `package.json` is missing, stop and tell the user. @@ -47,7 +47,7 @@ npm update # semver-compatible (within package.json r npx npm-check-updates -u && npm install # bump package.json to latest majors ``` -### Diffly-specific cautions +### Diffr-specific cautions - `@types/vscode` and `@types/node` must remain compatible with the `engines.vscode` minimum declared in `package.json`. A major bump that requires a newer VSCode runtime is a breaking change to consumers. - `@vscode/test-electron`, `mocha`, `c8`, and `vsce` (`@vscode/vsce`) are tightly coupled to the extension test/release pipeline — review their changelogs before bumping. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ec230e5..71dbde2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "_agent_pmo": "74cf183", - "name": "Diffly (Node.js)", + "name": "Diffr (Node.js)", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20", "remoteUser": "vscode", "postCreateCommand": "make setup", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76d7c7b..5d45522 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ # agent-pmo:74cf183 -# Release Diffly on a v* tag: build → package one universal VSIX → publish to the +# Release Diffr on a v* tag: build → package one universal VSIX → publish to the # VS Code Marketplace → attach it to the GitHub release. One sequential job. # Pure-TS extension (no native binaries) → a single platform-agnostic VSIX. @@ -32,25 +32,29 @@ jobs: - name: Set version from tag run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" + - name: Stamp version into every carrier + run: | + node scripts/stamp-release-version.mjs "$VERSION" + node scripts/verify-versions.mjs "$VERSION" + - name: Build and package universal VSIX run: | - npm version "$VERSION" --no-git-tag-version --allow-same-version npm run build - npx vsce package --out "diffly-$VERSION.vsix" + npx vsce package --out "diffr-$VERSION.vsix" - name: Publish to VS Code Marketplace env: VSCE_PAT: ${{ secrets.VSCODE_MARKETPLACE_PAT }} run: | if [ -z "$VSCE_PAT" ]; then - echo "::error::VSCODE_MARKETPLACE_PAT not accessible. Add Diffly to the secret's allowed repositories under Org Settings → Secrets and variables → Actions." + echo "::error::VSCODE_MARKETPLACE_PAT not accessible. Add Diffr to the secret's allowed repositories under Org Settings → Secrets and variables → Actions." exit 1 fi - npx vsce publish --packagePath "diffly-$VERSION.vsix" --allow-proposed-apis contribSourceControlHistoryItemMenu + npx vsce publish --packagePath "diffr-$VERSION.vsix" --allow-proposed-apis contribSourceControlHistoryItemMenu - name: Attach VSIX to GitHub release uses: softprops/action-gh-release@v2 with: - files: diffly-*.vsix + files: diffr-*.vsix generate_release_notes: true prerelease: ${{ contains(github.ref_name, '-') }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5df8a..73c270d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,4 @@ Initial release — pick two things and diff them, entirely from VS Code's existing context menus. -[0.1.0]: https://github.com/Nimblesite/Diffly/releases/tag/v0.1.0 +[0.1.0]: https://github.com/Nimblesite/Diffr/releases/tag/v0.1.0 diff --git a/CLAUDE.md b/CLAUDE.md index e1da1c4..c42ac8d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ -# Diffly — Agent Instructions +# Diffr — Agent Instructions @@ -11,13 +11,13 @@ Call out irrelevant context before proceeding. Bloat degrades reasoning. ⚠️ ⚠️ **CRITICAL: THIS CODEBASE RECEIVES A GRADE OF A+.** WE DON'T ALLOW BAD CODE. NOT EVEN FOR ONE LINE. CODE MUST PASS REVIEW AT Google / Meta / Microsoft. ANYTHING LESS IS ⛔️ ILLEGAL AND MUST BE FIXED IMMEDIATELY.⚠️ ⚠️ **NEW VIEWS, ACTIVITY-BAR ICONS, SIDEBARS, TREE PROVIDERS, OR WEBVIEWS ARE ⛔️ ILLEGAL.** -Diffly is **context-menu only**. Every feature hangs off VSCode's existing SCM history, SCM resource state, editor title, and explorer menus — plus a small set of palette commands. If a feature needs a new panel to exist, the feature is wrong.⚠️ +Diffr is **context-menu only**. Every feature hangs off VSCode's existing SCM history, SCM resource state, editor title, and explorer menus — plus a small set of palette commands. If a feature needs a new panel to exist, the feature is wrong.⚠️ Full design + execution plan: [spec.md](spec.md). ## Project Overview -**Diffly** is a VSCode extension that does exactly one thing: **pick two things and diff them** against a git repository. Side A is a commit; Side B is another commit, the working copy, the index, or a branch/tag (resolved to a commit). It shells out to `git`, hands two URIs to VSCode's built-in `vscode.diff`, and uses a multi-step QuickPick for browsing many changed files. No custom renderer, no custom view. +**Diffr** is a VSCode extension that does exactly one thing: **pick two things and diff them** against a git repository. Side A is a commit; Side B is another commit, the working copy, the index, or a branch/tag (resolved to a commit). It shells out to `git`, hands two URIs to VSCode's built-in `vscode.diff`, and uses a multi-step QuickPick for browsing many changed files. No custom renderer, no custom view. **Primary language:** TypeScript (pure — Rust LSP was considered and rejected; LSP is for _language_ semantics, not diffing) **Build command:** `make ci` @@ -32,12 +32,12 @@ There are 7 standard make targets: `build`, `test`, `lint`, `fmt`, `clean`, `ci` context-menu / palette command → ui/* QuickPick (CommitPicker | SideBPicker | RefPicker | FilePicker) → git/GitRepo → git/GitRunner (subprocess) - → providers/DifflyContentProvider (TextDocumentContentProvider for diffly://) + → providers/DiffrContentProvider (TextDocumentContentProvider for diffr://) → vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title) ``` - **`src/git/`** — pure logic. No `vscode` imports. Subprocess wrapper + NUL-delimited porcelain parsers. Trivially portable to IntelliJ/Kotlin if anyone ever wants that. -- **`src/providers/DifflyContentProvider.ts`** — `TextDocumentContentProvider` for scheme `diffly`. URI parse + GitRepo call. Pure dispatch. +- **`src/providers/DiffrContentProvider.ts`** — `TextDocumentContentProvider` for scheme `diffr`. URI parse + GitRepo call. Pure dispatch. - **`src/ui/`** — `vscode.window.createQuickPick()` wrappers. Each returns `Result`. FilePicker stays open after selection (`ignoreFocusOut: true`) so a single comparison drives many diffs. - **`src/commands/`** — one file per command. Glue only; logic lives in `git/` and `ui/`. - **`src/extension.ts`** — activate/deactivate; command + provider registration ONLY. No business logic. @@ -45,14 +45,14 @@ context-menu / palette command ## Hard Rules (no exceptions, NON-NEGOTIABLE) -- **NO git commands from the agent.** No `git add`, `commit`, `push`, `checkout`, `merge`, `rebase`. CI and GitHub Actions handle git. (Diffly itself shells out to `git` at runtime — that's the product. The _agent_ doesn't drive git in the dev loop.) +- **NO git commands from the agent.** No `git add`, `commit`, `push`, `checkout`, `merge`, `rebase`. CI and GitHub Actions handle git. (Diffr itself shells out to `git` at runtime — that's the product. The _agent_ doesn't drive git in the dev loop.) - **NO new views, sidebars, activity-bar icons, tree providers, or webviews.** Context menus + palette commands only. Browsing many files is a QuickPick, not a panel. - **NO THROWING EXCEPTIONS for control flow.** Return `Result` via a discriminated union. Panics are bugs. - **NO REGEX on structured data.** Git porcelain output is parsed via NUL-delimited splits (`-z` flag everywhere). Never regex over JSON, YAML, source code, or git output. - **NO PLACEHOLDERS.** If something isn't implemented, leave a loud compilation error with TODO. Silent no-ops = ⛔️ ILLEGAL. - **Functions < 20 lines.** Refactor aggressively. - **Files < 450 lines.** Extract modules when over. -- **ZERO DUPLICATION.** Search before writing. Move code, don't copy. Diffly detects diffs — its own codebase must be exemplary. +- **ZERO DUPLICATION.** Search before writing. Move code, don't copy. Diffr detects diffs — its own codebase must be exemplary. - **TypeScript strict mode.** `tsconfig.json` has `"strict": true`. No `any`. No `!` (non-null assertion) — use optional chaining or explicit guards. No `// @ts-ignore` / `@ts-nocheck`. No `as Type` casts without a comment explaining safety. All function params and return types annotated. - **No suppressing linter warnings.** Fix the code, not the linter. - **Decouple providers from the VSCode SDK.** `src/git/` and `src/ui/uri.ts` have ZERO `vscode` imports. SDK-bound modules are thin shells around pure logic. @@ -104,7 +104,7 @@ Do not write assertions that guard against AI / taxonomy strings. Assert on **po ⛔️ BAD ```typescript -assert.doesNotMatch(label, /\[diffly-internal-tag\]/); +assert.doesNotMatch(label, /\[diffr-internal-tag\]/); ``` ✅ GOOD @@ -127,7 +127,7 @@ Two audiences. Write for the right one. ## Repo Structure ``` -Diffly/ +Diffr/ ├── .github/workflows/{ci.yml, release.yml} ├── .vscode-test.mjs ├── Makefile # 7 standard targets + `package` @@ -154,7 +154,7 @@ Diffly/ │ │ ├── parsers.ts # NUL-delimited porcelain parsers │ │ └── types.ts │ ├── providers/ -│ │ └── DifflyContentProvider.ts +│ │ └── DiffrContentProvider.ts │ ├── ui/ │ │ ├── CommitPicker.ts │ │ ├── RefPicker.ts diff --git a/Makefile b/Makefile index 848727f..2f677ef 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # agent-pmo:74cf183 # ============================================================================= -# Standard Makefile — Diffly (VSCode extension, TypeScript) +# Standard Makefile — Diffr (VSCode extension, TypeScript) # Cross-platform: Linux, macOS, Windows (via GNU Make) # ============================================================================= diff --git a/README.md b/README.md index 2146c2e..8fc715a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Diffly +# Diffr **Pick two things and diff them.** Context-menu git diffing for VS Code — no panels, no sidebars, no activity-bar icons. Just the menus that are already there. @@ -8,19 +8,19 @@ ## Install -- **VS Code**: search _Diffly_ in the Extensions view, or -- **Command line**: `code --install-extension nimblesite.diffly` -- **Marketplace**: https://marketplace.visualstudio.com/items?itemName=nimblesite.diffly +- **VS Code**: search _Diffr_ in the Extensions view, or +- **Command line**: `code --install-extension nimblesite.diffr` +- **Marketplace**: https://marketplace.visualstudio.com/items?itemName=nimblesite.diffr -Diffly depends on the built-in **Git** extension and needs `git` on `PATH`. +Diffr depends on the built-in **Git** extension and needs `git` on `PATH`. --- -## Why Diffly? +## Why Diffr? VS Code's built-in "Open Changes" only diffs a file against its parent commit. To compare _anything else_ — a commit against a branch tip, your working copy against three commits back, a single file across two arbitrary refs — you end up in `git diff` on the terminal or installing a heavy "git client" extension. -Diffly fills exactly that gap with the smallest possible surface area: +Diffr fills exactly that gap with the smallest possible surface area: - **No new UI.** Every entry point hangs off menus VS Code already shows (SCM history, SCM resource state, editor tab, file explorer) plus a few palette commands. - **No custom renderer.** Diffs open in `vscode.diff` — the same native diff editor as everything else. @@ -35,22 +35,22 @@ Diffly fills exactly that gap with the smallest possible surface area: Source Control (Ctrl/Cmd+Shift+G) └── History └── - ├── Diffly: Compare with… ← pick anything for Side B - ├── Diffly: Compare with Working Copy ← Side B = on-disk - └── Diffly: Compare with Previous ← Side B = parent commit + ├── Diffr: Compare with… ← pick anything for Side B + ├── Diffr: Compare with Working Copy ← Side B = on-disk + └── Diffr: Compare with Previous ← Side B = parent commit Source Control → Changes / Staged Changes / Merge Changes └── - └── Diffly: Compare with Commit… ← pick commit; diff vs working copy + └── Diffr: Compare with Commit… ← pick commit; diff vs working copy Editor tab (right-click) ──┐ -File Explorer (right-click file) ──┼──► Diffly: Compare with Commit… +File Explorer (right-click file) ──┼──► Diffr: Compare with Commit… └── (uses that file as the target) Command Palette (Ctrl/Cmd+Shift+P) -├── Diffly: Compare Two Commits -├── Diffly: Compare with Commit… ← uses focused editor's file -└── Diffly: Reopen Last Comparison +├── Diffr: Compare Two Commits +├── Diffr: Compare with Commit… ← uses focused editor's file +└── Diffr: Reopen Last Comparison ``` Diff tabs are titled human-first, e.g. `a1b2c3d ↔ Working Copy — src/foo.ts` or `a1b2c3d ↔ 9f8e7d6 — src/foo.ts`. No internal labels, no debug strings. @@ -65,11 +65,11 @@ There are **seven** commands. Three are reachable from the Command Palette; four Open **Source Control** (Ctrl/Cmd+Shift+G), expand a repository's **History** section, and right-click any commit: -| Menu label | Command ID | What it does | -| ------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Diffly: Compare with…** | `diffly.compareWith` | The right-clicked commit is Side A. A QuickPick asks what Side B is: **Working Copy**, **Index**, **Pick a commit…**, or **Pick a branch or tag…**. Then a file QuickPick lists every changed file; selecting one opens `vscode.diff`. The picker stays open so you can open many files in a row. | -| **Diffly: Compare with Working Copy** | `diffly.compareWithWorkingCopy` | Same as above, but Side B is hardcoded to your on-disk working copy. Skips the Side B prompt. | -| **Diffly: Compare with Previous** | `diffly.compareWithPrevious` | Side A is the right-clicked commit, Side B is its first parent (`^1`). Classic "what changed in this commit?" view. | +| Menu label | Command ID | What it does | +| ------------------------------------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Diffr: Compare with…** | `diffr.compareWith` | The right-clicked commit is Side A. A QuickPick asks what Side B is: **Working Copy**, **Index**, **Pick a commit…**, or **Pick a branch or tag…**. Then a file QuickPick lists every changed file; selecting one opens `vscode.diff`. The picker stays open so you can open many files in a row. | +| **Diffr: Compare with Working Copy** | `diffr.compareWithWorkingCopy` | Same as above, but Side B is hardcoded to your on-disk working copy. Skips the Side B prompt. | +| **Diffr: Compare with Previous** | `diffr.compareWithPrevious` | Side A is the right-clicked commit, Side B is its first parent (`^1`). Classic "what changed in this commit?" view. | > These three are intentionally **hidden from the Command Palette** — they only make sense when invoked against a specific commit, which the right-click target provides. @@ -77,39 +77,39 @@ Open **Source Control** (Ctrl/Cmd+Shift+G), expand a repository's **History** se Right-click any file under **Changes**, **Staged Changes**, or **Merge Changes**: -| Menu label | Command ID | What it does | -| -------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------- | -| **Diffly: Compare with Commit…** | `diffly.compareFileWithCommit` | Pick a commit from a log QuickPick. Opens a single-file diff: that file at the chosen commit ↔ your working copy. | +| Menu label | Command ID | What it does | +| ------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| **Diffr: Compare with Commit…** | `diffr.compareFileWithCommit` | Pick a commit from a log QuickPick. Opens a single-file diff: that file at the chosen commit ↔ your working copy. | ### From the **editor tab title** (right-click the file's tab) -| Menu label | Command ID | What it does | -| -------------------------------- | ------------------------------ | ------------------------------------------------------------------ | -| **Diffly: Compare with Commit…** | `diffly.compareFileWithCommit` | Same as above; the target is the file whose tab you right-clicked. | +| Menu label | Command ID | What it does | +| ------------------------------- | ----------------------------- | ------------------------------------------------------------------ | +| **Diffr: Compare with Commit…** | `diffr.compareFileWithCommit` | Same as above; the target is the file whose tab you right-clicked. | ### From the **File Explorer** (right-click a file) -| Menu label | Command ID | What it does | -| -------------------------------- | ------------------------------ | ------------------------------------------------------------ | -| **Diffly: Compare with Commit…** | `diffly.compareFileWithCommit` | Same again — the target is the file you clicked in the tree. | +| Menu label | Command ID | What it does | +| ------------------------------- | ----------------------------- | ------------------------------------------------------------ | +| **Diffr: Compare with Commit…** | `diffr.compareFileWithCommit` | Same again — the target is the file you clicked in the tree. | ### From the **Command Palette** (Ctrl/Cmd+Shift+P) -Type `Diffly:` to filter. Three entries appear: +Type `Diffr:` to filter. Three entries appear: -| Palette label | Command ID | What it does | -| ---------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Diffly: Compare Two Commits** | `diffly.compareTwoCommits` | No target needed. QuickPick chain: pick repo → pick Side A commit → pick Side B (working copy / index / commit / ref) → pick files. | -| **Diffly: Compare with Commit…** | `diffly.compareFileWithCommit` | Uses the **currently focused editor's file** as the target. Same flow as the right-click version. If no editor is focused, Diffly will tell you to open a file first. | -| **Diffly: Reopen Last Comparison** | `diffly.reopenLast` | Reopens the file picker for the last A↔B comparison you made (stored per-workspace). Handy after closing the picker mid-review. | +| Palette label | Command ID | What it does | +| --------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Diffr: Compare Two Commits** | `diffr.compareTwoCommits` | No target needed. QuickPick chain: pick repo → pick Side A commit → pick Side B (working copy / index / commit / ref) → pick files. | +| **Diffr: Compare with Commit…** | `diffr.compareFileWithCommit` | Uses the **currently focused editor's file** as the target. Same flow as the right-click version. If no editor is focused, Diffr will tell you to open a file first. | +| **Diffr: Reopen Last Comparison** | `diffr.reopenLast` | Reopens the file picker for the last A↔B comparison you made (stored per-workspace). Handy after closing the picker mid-review. | -> **`Diffly: Show Logs`** (`diffly.showLogs`) exists but is hidden from the palette — it's reserved for the extension to surface its OutputChannel programmatically. Open it manually via **View → Output → "Diffly"**. +> **`Diffr: Show Logs`** (`diffr.showLogs`) exists but is hidden from the palette — it's reserved for the extension to surface its OutputChannel programmatically. Open it manually via **View → Output → "Diffr"**. --- ## What Side B can be -Whenever Diffly asks you to pick Side B, you get four choices: +Whenever Diffr asks you to pick Side B, you get four choices: - **Working Copy** — the on-disk files in the repo (uncommitted changes included). - **Index** — the staging area (what `git diff --cached` would compare against). @@ -122,17 +122,17 @@ Whenever Diffly asks you to pick Side B, you get four choices: - VS Code **^1.85.0** - **Git** on `PATH` -- The built-in **Git** extension (Diffly depends on it via `extensionDependencies`) +- The built-in **Git** extension (Diffr depends on it via `extensionDependencies`) ## Troubleshooting -- **"No git repository found"** — Diffly uses the built-in Git extension's repo list. If VS Code doesn't see your repo, neither will Diffly. Open the Source Control view and confirm the repo appears there. -- **Diffly menus don't appear in the SCM History view** — make sure the workspace has commits and that you're right-clicking inside the **History** section (not Changes). -- **Anything else** — `View → Output → "Diffly"` shows structured logs for every operation. Or run **Diffly: Show Logs** to focus the channel. +- **"No git repository found"** — Diffr uses the built-in Git extension's repo list. If VS Code doesn't see your repo, neither will Diffr. Open the Source Control view and confirm the repo appears there. +- **Diffr menus don't appear in the SCM History view** — make sure the workspace has commits and that you're right-clicking inside the **History** section (not Changes). +- **Anything else** — `View → Output → "Diffr"` shows structured logs for every operation. Or run **Diffr: Show Logs** to focus the channel. ## Issues & contributions -Bugs, feature requests, and PRs welcome at https://github.com/Nimblesite/Diffly/issues. +Bugs, feature requests, and PRs welcome at https://github.com/Nimblesite/Diffr/issues. ## License diff --git a/docs/assets/diffly-icon-primary-1024.png b/docs/assets/diffr-icon-primary-1024.png similarity index 100% rename from docs/assets/diffly-icon-primary-1024.png rename to docs/assets/diffr-icon-primary-1024.png diff --git a/docs/assets/diffly-icon-primary-128.png b/docs/assets/diffr-icon-primary-128.png similarity index 100% rename from docs/assets/diffly-icon-primary-128.png rename to docs/assets/diffr-icon-primary-128.png diff --git a/docs/assets/diffly-icon-primary-256.png b/docs/assets/diffr-icon-primary-256.png similarity index 100% rename from docs/assets/diffly-icon-primary-256.png rename to docs/assets/diffr-icon-primary-256.png diff --git a/docs/assets/diffly-icon-primary-512.png b/docs/assets/diffr-icon-primary-512.png similarity index 100% rename from docs/assets/diffly-icon-primary-512.png rename to docs/assets/diffr-icon-primary-512.png diff --git a/docs/assets/diffly-icon-primary.webp b/docs/assets/diffr-icon-primary.webp similarity index 100% rename from docs/assets/diffly-icon-primary.webp rename to docs/assets/diffr-icon-primary.webp diff --git a/docs/design-system.md b/docs/design-system.md index f4d57ee..192b1e9 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -1,12 +1,12 @@ -# Diffly Design System +# Diffr Design System -This design system defines the visual and content standards for the future Diffly website, documentation pages, marketplace graphics, release notes, and adjacent product collateral. +This design system defines the visual and content standards for the future Diffr website, documentation pages, marketplace graphics, release notes, and adjacent product collateral. -It does not define in-extension UI. Diffly remains a native VS Code extension that uses context menus, Command Palette entries, QuickPick, OutputChannel, and `vscode.diff`. +It does not define in-extension UI. Diffr remains a native VS Code extension that uses context menus, Command Palette entries, QuickPick, OutputChannel, and `vscode.diff`. ## Product Position -Diffly is a developer tool for comparing two git states without leaving the workflow the developer already uses. +Diffr is a developer tool for comparing two git states without leaving the workflow the developer already uses. Core promise: @@ -14,7 +14,7 @@ Core promise: Design principles: -- **Native first:** Present Diffly as a small, precise addition to VS Code, not a replacement shell. +- **Native first:** Present Diffr as a small, precise addition to VS Code, not a replacement shell. - **Fast orientation:** Help developers understand entry points, command scope, and resulting diffs quickly. - **Quiet confidence:** Use restrained visual emphasis, clear hierarchy, and practical examples. - **Diff literacy:** Visual language should make "left vs right", changed files, additions, removals, and revisions immediately legible. @@ -28,7 +28,7 @@ Audience: ## Brand Voice -Diffly's voice is direct, concise, and work-focused. +Diffr's voice is direct, concise, and work-focused. Use: @@ -52,11 +52,11 @@ Example headlines: Example body copy: -> Diffly adds focused compare commands to the VS Code surfaces you already use: SCM history, SCM changes, editor tabs, Explorer, and the Command Palette. +> Diffr adds focused compare commands to the VS Code surfaces you already use: SCM history, SCM changes, editor tabs, Explorer, and the Command Palette. ## Logo Direction -The Diffly mark should communicate comparison, pairing, and code review without looking like a separate IDE. +The Diffr mark should communicate comparison, pairing, and code review without looking like a separate IDE. The current logo set is raster-only. There is no SVG source for these assets. Each icon is built from flat geometric shapes, hard color boundaries, and no text, so a future vector conversion can trace the shapes cleanly. @@ -66,11 +66,11 @@ Current assets: | Asset | Format | Use | | -------------------------------------------------- | ------ | ---------------------------- | -| [Primary 128](assets/diffly-icon-primary-128.png) | PNG | Root extension icon source | -| [Primary 256](assets/diffly-icon-primary-256.png) | PNG | Marketplace and docs | -| [Primary 512](assets/diffly-icon-primary-512.png) | PNG | High-resolution export | -| [Primary 1024](assets/diffly-icon-primary-1024.png) | PNG | Print, hero, and zoom assets | -| [Primary WebP](assets/diffly-icon-primary.webp) | WebP | Compressed website asset | +| [Primary 128](assets/diffr-icon-primary-128.png) | PNG | Root extension icon source | +| [Primary 256](assets/diffr-icon-primary-256.png) | PNG | Marketplace and docs | +| [Primary 512](assets/diffr-icon-primary-512.png) | PNG | High-resolution export | +| [Primary 1024](assets/diffr-icon-primary-1024.png) | PNG | Print, hero, and zoom assets | +| [Primary WebP](assets/diffr-icon-primary.webp) | WebP | Compressed website asset | | Root extension icon: `icon.png` | PNG | VS Code package icon | Preferred concepts: @@ -227,11 +227,11 @@ Website imagery should show the actual developer workflow. Preferred assets: -- Clean screenshots of VS Code SCM history context menus with Diffly commands. +- Clean screenshots of VS Code SCM history context menus with Diffr commands. - File QuickPick screenshots showing changed files and stats. - `vscode.diff` screenshots with readable left/right labels. - Simple diagrams showing Side A, Side B, and file selection flow. -- Marketplace graphics that pair the Diffly mark with a real diff or command surface. +- Marketplace graphics that pair the Diffr mark with a real diff or command surface. Avoid: @@ -284,7 +284,7 @@ Purpose: Structure: -- Left: Diffly mark and wordmark. +- Left: Diffr mark and wordmark. - Center or right: Docs, GitHub, Releases. - Right: primary install action. @@ -297,11 +297,11 @@ Behavior: Purpose: -- State what Diffly does and show it in context. +- State what Diffr does and show it in context. Required content: -- H1: `Diffly`. +- H1: `Diffr`. - Supporting copy that includes "Pick two things and diff them." - Primary action: install or marketplace link. - Secondary action: view docs or GitHub. @@ -331,8 +331,8 @@ Example: | Surface | Command | Side A | Side B | Result | | ----------- | ------------------------------- | ------------- | ------------------------------------------- | ---------------------- | -| SCM history | `Diffly: Compare with...` | Picked commit | Commit, branch, tag, index, or working copy | File picker, then diff | -| Editor tab | `Diffly: Compare with Commit...` | Picked commit | Current file | Single-file diff | +| SCM history | `Diffr: Compare with...` | Picked commit | Commit, branch, tag, index, or working copy | File picker, then diff | +| Editor tab | `Diffr: Compare with Commit...` | Picked commit | Current file | Single-file diff | ### Workflow Diagram @@ -432,7 +432,7 @@ Types: Constraint callout example: -> Diffly does not add a sidebar, activity-bar icon, tree view, or webview. It uses existing VS Code surfaces. +> Diffr does not add a sidebar, activity-bar icon, tree view, or webview. It uses existing VS Code surfaces. ## Motion @@ -495,18 +495,18 @@ Use this where space is limited: Use this for marketplace and social metadata: -> Diffly lets you pick two git states and open file diffs through VS Code's native compare experience. +> Diffr lets you pick two git states and open file diffs through VS Code's native compare experience. ### Longer Description Use this for the website intro: -> Diffly adds focused compare commands to VS Code's existing SCM history, SCM changes, editor tab, Explorer, and Command Palette surfaces. Pick a commit, branch, tag, index, or working copy target, then open changed files in VS Code's built-in diff editor. +> Diffr adds focused compare commands to VS Code's existing SCM history, SCM changes, editor tab, Explorer, and Command Palette surfaces. Pick a commit, branch, tag, index, or working copy target, then open changed files in VS Code's built-in diff editor. ### SEO Title Pattern ```text -Diffly - Context-menu git diffing for VS Code +Diffr - Context-menu git diffing for VS Code ``` ### Social Description Pattern @@ -550,54 +550,54 @@ Use these as the first pass for a future web implementation. ```css :root { - --diffly-ink-950: #14161a; - --diffly-ink-800: #272c33; - --diffly-ink-600: #5b6470; - --diffly-ink-300: #b8c0ca; - --diffly-ink-100: #e6e9ee; - - --diffly-paper-000: #ffffff; - --diffly-paper-050: #f7f8fa; - --diffly-paper-100: #eef1f5; - - --diffly-blue-600: #2563eb; - --diffly-blue-700: #1d4ed8; - --diffly-cyan-500: #0891b2; - --diffly-green-600: #16a34a; - --diffly-red-600: #dc2626; - --diffly-amber-500: #d97706; - --diffly-violet-600: #7c3aed; - - --diffly-text-primary: var(--diffly-ink-950); - --diffly-text-secondary: var(--diffly-ink-600); - --diffly-surface-page: var(--diffly-paper-000); - --diffly-surface-subtle: var(--diffly-paper-050); - --diffly-surface-code: var(--diffly-paper-100); - --diffly-border-default: var(--diffly-ink-100); - --diffly-action-primary: var(--diffly-blue-600); - --diffly-action-primary-hover: var(--diffly-blue-700); - --diffly-diff-addition: var(--diffly-green-600); - --diffly-diff-deletion: var(--diffly-red-600); - --diffly-diff-modified: var(--diffly-amber-500); - --diffly-diff-rename: var(--diffly-cyan-500); - - --diffly-radius-sm: 4px; - --diffly-radius-md: 8px; - --diffly-shadow-soft: 0 12px 30px rgb(20 22 26 / 10%); - - --diffly-container-reading: 760px; - --diffly-container-content: 1120px; - --diffly-container-wide: 1280px; - - --diffly-space-1: 4px; - --diffly-space-2: 8px; - --diffly-space-3: 12px; - --diffly-space-4: 16px; - --diffly-space-5: 24px; - --diffly-space-6: 32px; - --diffly-space-7: 48px; - --diffly-space-8: 64px; - --diffly-space-9: 96px; + --diffr-ink-950: #14161a; + --diffr-ink-800: #272c33; + --diffr-ink-600: #5b6470; + --diffr-ink-300: #b8c0ca; + --diffr-ink-100: #e6e9ee; + + --diffr-paper-000: #ffffff; + --diffr-paper-050: #f7f8fa; + --diffr-paper-100: #eef1f5; + + --diffr-blue-600: #2563eb; + --diffr-blue-700: #1d4ed8; + --diffr-cyan-500: #0891b2; + --diffr-green-600: #16a34a; + --diffr-red-600: #dc2626; + --diffr-amber-500: #d97706; + --diffr-violet-600: #7c3aed; + + --diffr-text-primary: var(--diffr-ink-950); + --diffr-text-secondary: var(--diffr-ink-600); + --diffr-surface-page: var(--diffr-paper-000); + --diffr-surface-subtle: var(--diffr-paper-050); + --diffr-surface-code: var(--diffr-paper-100); + --diffr-border-default: var(--diffr-ink-100); + --diffr-action-primary: var(--diffr-blue-600); + --diffr-action-primary-hover: var(--diffr-blue-700); + --diffr-diff-addition: var(--diffr-green-600); + --diffr-diff-deletion: var(--diffr-red-600); + --diffr-diff-modified: var(--diffr-amber-500); + --diffr-diff-rename: var(--diffr-cyan-500); + + --diffr-radius-sm: 4px; + --diffr-radius-md: 8px; + --diffr-shadow-soft: 0 12px 30px rgb(20 22 26 / 10%); + + --diffr-container-reading: 760px; + --diffr-container-content: 1120px; + --diffr-container-wide: 1280px; + + --diffr-space-1: 4px; + --diffr-space-2: 8px; + --diffr-space-3: 12px; + --diffr-space-4: 16px; + --diffr-space-5: 24px; + --diffr-space-6: 32px; + --diffr-space-7: 48px; + --diffr-space-8: 64px; + --diffr-space-9: 96px; } ``` diff --git a/docs/plans/plan.md b/docs/plans/plan.md index 0d21eb1..6daafb4 100644 --- a/docs/plans/plan.md +++ b/docs/plans/plan.md @@ -1,10 +1,10 @@ -# Diffly — Execution Plan +# Diffr — Execution Plan Full design spec: [../specs/spec.md](../specs/spec.md). This file is the **how + when** — phased milestones and a live TODO checklist at the bottom. The checklist is the source of truth for "what's left." Tick boxes as work lands; never delete unchecked items without justification in the PR description. ## Goal -Ship Diffly v1.0.0 as a VSCode extension that lets a developer pick two git references (commit/working-copy/index/branch/tag) and diff them through VSCode's built-in `vscode.diff`, surfaced exclusively through existing SCM/editor/explorer context menus and a handful of palette commands. No new views, sidebars, activity-bar icons, or webviews. KISS, A+ codebase, 100% coverage goal with ratchet-only thresholds. +Ship Diffr v1.0.0 as a VSCode extension that lets a developer pick two git references (commit/working-copy/index/branch/tag) and diff them through VSCode's built-in `vscode.diff`, surfaced exclusively through existing SCM/editor/explorer context menus and a handful of palette commands. No new views, sidebars, activity-bar icons, or webviews. KISS, A+ codebase, 100% coverage goal with ratchet-only thresholds. ## Phases @@ -24,19 +24,19 @@ Build everything that has zero `vscode` imports: `Result`, `constants`, all ### Phase 2 — VSCode integration -Wire the pure logic into VSCode: `extension.ts`, `DifflyContentProvider`, the four picker shells (`CommitPicker`, `RefPicker`, `SideBPicker`, `FilePicker`), the six command handlers, the full `package.json` `contributes` block, and the `logger`/`state` modules. Implement the `vscode.git` API consumer (repo resolution). +Wire the pure logic into VSCode: `extension.ts`, `DiffrContentProvider`, the four picker shells (`CommitPicker`, `RefPicker`, `SideBPicker`, `FilePicker`), the six command handlers, the full `package.json` `contributes` block, and the `logger`/`state` modules. Implement the `vscode.git` API consumer (repo resolution). -**Checkpoint:** Extension activates in an Extension Development Host; all six commands appear in the palette; `activation.test.ts` (E2E) asserts every command ID is registered. The `diffly://` scheme resolves through `DifflyContentProvider` against the seed repo. +**Checkpoint:** Extension activates in an Extension Development Host; all six commands appear in the palette; `activation.test.ts` (E2E) asserts every command ID is registered. The `diffr://` scheme resolves through `DiffrContentProvider` against the seed repo. ### Phase 3 — Full E2E coverage Drive each command end-to-end through black-box tests using `@vscode/test-electron` against `test-fixtures/repo-seed/`. Replace any internal-call shortcuts with real command invocations. -**Checkpoint:** E2E suite covers `compareTwoCommits`, `compareFileWithCommit`, `compareWith`, `compareWithWorkingCopy`, `compareWithPrevious`, `reopenLast`, and the `diffly://` content provider. Polling on `vscode.window.tabGroups.all` confirms `TabInputTextDiff` tabs open with the expected left/right URIs and human-readable titles. Coverage threshold ratchets to ≥80%. +**Checkpoint:** E2E suite covers `compareTwoCommits`, `compareFileWithCommit`, `compareWith`, `compareWithWorkingCopy`, `compareWithPrevious`, `reopenLast`, and the `diffr://` content provider. Polling on `vscode.window.tabGroups.all` confirms `TabInputTextDiff` tabs open with the expected left/right URIs and human-readable titles. Coverage threshold ratchets to ≥80%. ### Phase 4 — UX polish + observability -Wire pino → file + OutputChannel; finalize diff titles (`${shortShaA} ↔ ${shortShaB} — ${basename}`); add helpful error UX for the failure modes (not in a repo, no commits, deleted file on Side A, etc.); add the `Diffly: Reopen Last Comparison` memento round-trip; flesh out README, CHANGELOG (0.1.0 entry), LICENSE. +Wire pino → file + OutputChannel; finalize diff titles (`${shortShaA} ↔ ${shortShaB} — ${basename}`); add helpful error UX for the failure modes (not in a repo, no commits, deleted file on Side A, etc.); add the `Diffr: Reopen Last Comparison` memento round-trip; flesh out README, CHANGELOG (0.1.0 entry), LICENSE. **Checkpoint:** Manual smoke (all 8 scenarios from the spec's Verification section) passes in a real repo. OutputChannel surfaces errors in plain English with a "Show Logs" button or hint. No PII, no secrets, no commit messages in logs. @@ -49,7 +49,7 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re ## Open questions / decisions deferred to implementation - Default commit list limit for `CommitPicker` — start at 200, surface a "Load more" item if needed (v1.1 if heavy). -- `Index` URI: prefer built-in `toGitUri(fileUri, '~')` when the git extension exposes it; fall back to `diffly://index/`. Decide at implementation time which path is reachable from the public `vscode.git` API surface. +- `Index` URI: prefer built-in `toGitUri(fileUri, '~')` when the git extension exposes it; fall back to `diffr://index/`. Decide at implementation time which path is reachable from the public `vscode.git` API surface. - Multi-repo workspaces: if more than one repo is open, prompt for repo via QuickPick before showing the commit list. No global preference for v1. - Telemetry: **none** for v1. Revisit only with explicit user opt-in. @@ -57,7 +57,7 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re - **Built-in `vscode.git` API surface drift.** Pin against the `git.d.ts` shape at the version of VSCode in `engines.vscode`. E2E catches breakage. - **SCM history view contributions stability.** `scm/history/item/context` is relatively new; if it disappears or moves, palette + editor/explorer entries still cover the core flows. -- **Subprocess `git` not on PATH.** Detect at activation; show a single warning in the OutputChannel and disable commands via a context key (`diffly.gitAvailable`). No popups. +- **Subprocess `git` not on PATH.** Detect at activation; show a single warning in the OutputChannel and disable commands via a context key (`diffr.gitAvailable`). No popups. - **Large repos / slow `git log`.** Cap log fetch at `--max-count=200` by default; surface latency in `debug` logs. --- @@ -74,7 +74,7 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re - [ ] Create `tsconfig.json` with `strict: true`, `noImplicitAny: true`, `noUncheckedIndexedAccess: true`, `target: ES2022`, `module: commonjs`, `outDir: out`, `rootDir: src` - [ ] Create `eslint.config.mjs` (flat config) wiring `@typescript-eslint`; ban `any`, `!`, `@ts-ignore`, `@ts-nocheck`, untyped casts; max-lines 450; max-lines-per-function 20 - [ ] Create `.prettierrc.json` (match CommandTree's settings) -- [ ] Create `cspell.json` with project-specific dictionary entries (`Diffly`, `numstat`, `revparse`, `pino`, etc.) +- [ ] Create `cspell.json` with project-specific dictionary entries (`Diffr`, `numstat`, `revparse`, `pino`, etc.) - [ ] Create `coverage-thresholds.json` with `{ "default_threshold": 0.0 }` — ratchet from first real test - [ ] Create `Makefile` with 7 targets (`build`, `test`, `lint`, `fmt`, `clean`, `ci`, `setup`) + repo-specific `package` below a horizontal marker - [ ] Create `.vscode-test.mjs` pointing at `out/test/suite/**/*.test.js` @@ -89,7 +89,7 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re ### Phase 1 — Pure logic + unit tests -- [ ] `src/constants.ts` — command IDs, scheme name (`diffly`), context keys, default log limit (200), built-in command IDs (`vscode.diff`) +- [ ] `src/constants.ts` — command IDs, scheme name (`diffr`), context keys, default log limit (200), built-in command IDs (`vscode.diff`) - [ ] `src/result.ts` — `Result` discriminated union; `ok`, `err`, `isOk`, `isErr`, `map`, `andThen`, `unwrapOr` helpers - [ ] `src/test/unit/result.test.ts` — round-trip + every combinator - [ ] `src/git/types.ts` — `Sha`, `Commit`, `ChangedFileStatus` (`A|M|D|R|C`), `ChangedFile`, `DiffStat`, `RevSpec`, `Ref`, `GitError` @@ -97,16 +97,16 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re - [ ] `src/test/unit/parsers.test.ts` — every status code (`A`, `M`, `D`, `R`, `C`), binary numstat (`-\t-`), empty input, malformed → `Err`, paths with spaces / unicode / quotes - [ ] `src/git/GitRunner.ts` — `run({ args, cwd })` spawning `git`, capturing stdout/stderr, never throws, returns `Result`; entry/exit `debug` logs - [ ] `src/git/GitRepo.ts` — `log`, `nameStatus`, `numstat`, `show`, `refs`, `revParse`; named-param objects; each method <20 lines -- [ ] `src/ui/uri.ts` — pure `buildDifflyUri(rev, path)` / `parseDifflyUri(uri)`; ZERO `vscode` imports (use Node `URL` or a tiny custom encoder) +- [ ] `src/ui/uri.ts` — pure `buildDiffrUri(rev, path)` / `parseDiffrUri(uri)`; ZERO `vscode` imports (use Node `URL` or a tiny custom encoder) - [ ] `src/test/unit/uri.test.ts` — round-trip every RevSpec kind; paths with spaces, unicode, `#`, `?`; reject malformed → `Err` - [ ] Ratchet `coverage-thresholds.json` to current measured value minus 1% rounding - [ ] Verify `make test` and `make lint` pass ### Phase 2 — VSCode integration -- [ ] `src/logger.ts` — pino base logger + custom transport writing to extension `globalStorageUri` log file AND mirroring `info+` to a `vscode.OutputChannel("Diffly")` +- [ ] `src/logger.ts` — pino base logger + custom transport writing to extension `globalStorageUri` log file AND mirroring `info+` to a `vscode.OutputChannel("Diffr")` - [ ] `src/state.ts` — `MementoStore` wrapper with typed `getLastComparison()` / `setLastComparison()`; single global-state surface -- [ ] `src/providers/DifflyContentProvider.ts` — implements `TextDocumentContentProvider`; pure dispatch `parseDifflyUri` → `GitRepo.show`; empty string for deleted files +- [ ] `src/providers/DiffrContentProvider.ts` — implements `TextDocumentContentProvider`; pure dispatch `parseDiffrUri` → `GitRepo.show`; empty string for deleted files - [ ] `src/ui/CommitPicker.ts` — `pickCommit({ repo, limit })` returning `Result`; label = `${shortSha}`, description = `${subject}`, detail = `${author} • ${relativeTime}` - [ ] `src/ui/RefPicker.ts` — `pickRef({ repo })` listing branches + tags; resolves to `{ kind:'commit', sha }` via `revParse` - [ ] `src/ui/SideBPicker.ts` — `pickSideB({ repo })`; prepends synthetic `WORKING_COPY`, `INDEX`, `BRANCH_OR_TAG…` items; last item chains into `RefPicker` @@ -115,9 +115,9 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re - [ ] `src/commands/compareWithWorkingCopy.ts` — same, but `revB = { kind:'workingCopy' }` - [ ] `src/commands/compareWithPrevious.ts` — same, but `revB = { kind:'commit', sha: ^1 }` via `revParse` - [ ] `src/commands/compareTwoCommits.ts` — palette entry; resolve repo (auto if single, else QuickPick); CommitPicker → SideBPicker → FilePicker -- [ ] `src/commands/compareFileWithCommit.ts` — accepts `Uri` arg (or falls back to `activeTextEditor`); CommitPicker → `vscode.diff(difflyUri, fileUri, title)` +- [ ] `src/commands/compareFileWithCommit.ts` — accepts `Uri` arg (or falls back to `activeTextEditor`); CommitPicker → `vscode.diff(diffrUri, fileUri, title)` - [ ] `src/commands/reopenLast.ts` — read from `MementoStore`; re-derive nameStatus + numstat; re-open FilePicker; no-op with toast if no last comparison -- [ ] `src/extension.ts` — `activate()` registers content provider, OutputChannel, all six commands; sets `diffly.gitAvailable` context key based on git binary detection; `deactivate()` disposes everything +- [ ] `src/extension.ts` — `activate()` registers content provider, OutputChannel, all six commands; sets `diffr.gitAvailable` context key based on git binary detection; `deactivate()` disposes everything - [ ] `package.json` — finalize `contributes.commands` (6 entries) and `contributes.menus` (5 menu IDs + `commandPalette` hides) per spec - [ ] Activation E2E (`src/test/suite/activation.test.ts`) — `vscode.commands.getCommands(true)` includes all 6 IDs; `vscode.extensions.getExtension('').isActive === true` - [ ] Verify in Extension Development Host: all commands visible in palette; right-clicking SCM history shows three entries; right-clicking explorer file shows "Compare with Commit…" @@ -126,20 +126,20 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re - [ ] `test-fixtures/repo-seed/seed.sh` — script that builds a deterministic git repo with 3 commits modifying `a.txt`, `b.txt`, `dir/c.txt`, renaming `b.txt → b2.txt`, deleting `d.txt`; fixed author + timestamps - [ ] Wire `seed.sh` into the test bootstrap (`@vscode/test-electron` `extensionTestsEnv` opens the seeded folder as the workspace) -- [ ] `src/test/suite/contentProvider.test.ts` — `openTextDocument(Uri.parse('diffly://commit//a.txt'))` returns expected content for each of the 3 commits; deleted file returns empty string -- [ ] `src/test/suite/compareTwoCommits.test.ts` — black-box invoke `diffly.compareTwoCommits`; stub QuickPick via test harness; assert FilePicker lists exactly the expected files with correct `+N -M`; pick one file; poll `vscode.window.tabGroups.all` for a `TabInputTextDiff` with the expected left/right URIs; assert title is human-readable -- [ ] `src/test/suite/compareFileWithCommit.test.ts` — open a fixture file; invoke command; assert diff tab opens; left URI scheme is `diffly:`; right URI scheme is `file:`; title matches `${shortSha} ↔ Working Copy — ${basename}` +- [ ] `src/test/suite/contentProvider.test.ts` — `openTextDocument(Uri.parse('diffr://commit//a.txt'))` returns expected content for each of the 3 commits; deleted file returns empty string +- [ ] `src/test/suite/compareTwoCommits.test.ts` — black-box invoke `diffr.compareTwoCommits`; stub QuickPick via test harness; assert FilePicker lists exactly the expected files with correct `+N -M`; pick one file; poll `vscode.window.tabGroups.all` for a `TabInputTextDiff` with the expected left/right URIs; assert title is human-readable +- [ ] `src/test/suite/compareFileWithCommit.test.ts` — open a fixture file; invoke command; assert diff tab opens; left URI scheme is `diffr:`; right URI scheme is `file:`; title matches `${shortSha} ↔ Working Copy — ${basename}` - [ ] `src/test/suite/compareWith.test.ts` — invoke with a synthetic `historyItem` arg pointing at commit 2; SideB = Working Copy; assert FilePicker populates; pick a file; assert diff opens - [ ] `src/test/suite/compareWithPrevious.test.ts` — invoke with commit 2; assert diff opens between commit 1 (`^1`) and commit 2 -- [ ] `src/test/suite/reopenLast.test.ts` — run `compareTwoCommits`, dismiss; run `diffly.reopenLast`; assert FilePicker re-populates with same files +- [ ] `src/test/suite/reopenLast.test.ts` — run `compareTwoCommits`, dismiss; run `diffr.reopenLast`; assert FilePicker re-populates with same files - [ ] Add assertions to existing E2E tests rather than creating new ones where feasible (per CLAUDE.md "loads of assertions per test") - [ ] Ratchet `coverage-thresholds.json` to ≥80% ### Phase 4 — UX polish + observability - [ ] Confirm diff titles everywhere match `${shortShaA} ↔ ${shortShaB} — ${basename}` / `${shortSha} ↔ Working Copy — ${basename}` / `${shortSha} ↔ Index — ${basename}` -- [ ] Error UX: not in a git repo → toast + OutputChannel hint; no commits in log → toast; deleted file on Side A → diff opens with empty left pane (no error); subprocess git missing → activation-time warning + commands disabled via `diffly.gitAvailable` context key -- [ ] OutputChannel: "Diffly" channel shows all `info+` entries; one-line "Show Logs" hint in error toasts via `command:diffly.showLogs` (internal-only command, not contributed to palette) +- [ ] Error UX: not in a git repo → toast + OutputChannel hint; no commits in log → toast; deleted file on Side A → diff opens with empty left pane (no error); subprocess git missing → activation-time warning + commands disabled via `diffr.gitAvailable` context key +- [ ] OutputChannel: "Diffr" channel shows all `info+` entries; one-line "Show Logs" hint in error toasts via `command:diffr.showLogs` (internal-only command, not contributed to palette) - [ ] Confirm logger redaction: assert in unit tests that file contents, commit messages, and full paths are never present in serialized log records - [ ] Flesh out `README.md`: install instructions, animated GIF placeholder, every command with a screenshot placeholder, "How does this differ from built-in Git?" section, link to spec/plan - [ ] `CHANGELOG.md` — `## [0.1.0]` entry summarizing every shipping command @@ -150,7 +150,7 @@ Add icon, finalize publisher metadata, build VSIX, install in a clean VSCode, re - [ ] Replace placeholder `icon.png` with the final 128x128 PNG (and a 256x256 marketplace asset) - [ ] Set `publisher` to `nimblesite` (or chosen ID) in `package.json`; verify `engines.vscode` matches the lowest API surface actually used -- [ ] `make package` produces `diffly-0.1.0.vsix` +- [ ] `make package` produces `diffr-0.1.0.vsix` - [ ] Install the VSIX in a clean VSCode profile; rerun the 8 manual smoke scenarios - [ ] Tag `v0.1.0`; verify `release.yml` builds and attaches the VSIX to the GitHub Release - [ ] (Hold for explicit user approval before `vsce publish` to the marketplace) diff --git a/docs/specs/spec.md b/docs/specs/spec.md index aa7ec61..416bffc 100644 --- a/docs/specs/spec.md +++ b/docs/specs/spec.md @@ -1,16 +1,16 @@ -# Diffly — VSCode Extension Spec +# Diffr — VSCode Extension Spec Execution plan and live TODO checklist: [../plans/plan.md](../plans/plan.md). ## Context -Diffly is a new VSCode extension whose only job is **"pick two things and diff them"** against a git repository and allow editing of the working state side. Side A is a commit; Side B can be another commit, the working copy, the index, or a branch/tag (resolved to a commit). +Diffr is a new VSCode extension whose only job is **"pick two things and diff them"** against a git repository and allow editing of the working state side. Side A is a commit; Side B can be another commit, the working copy, the index, or a branch/tag (resolved to a commit). -**KISS principle, enforced**: Diffly adds **no new views, no activity-bar icon, no tree provider, no webviews**. Everything is wired as **context-menu entries on VSCode's existing Source Control surfaces** (SCM history, SCM resource state, editor title, explorer) plus a small number of palette commands. Browsing many changed files between two commits is handled by a **multi-step QuickPick**, not a persistent panel. The user already has a Rust LSP framework (`lsp_toolkit`) and Rust+TS extension precedent (`GithubIssues`) — both explicitly rejected for Diffly. +**KISS principle, enforced**: Diffr adds **no new views, no activity-bar icon, no tree provider, no webviews**. Everything is wired as **context-menu entries on VSCode's existing Source Control surfaces** (SCM history, SCM resource state, editor title, explorer) plus a small number of palette commands. Browsing many changed files between two commits is handled by a **multi-step QuickPick**, not a persistent panel. The user already has a Rust LSP framework (`lsp_toolkit`) and Rust+TS extension precedent (`GithubIssues`) — both explicitly rejected for Diffr. ## Stack decision: pure TypeScript -LSP exposes **language** semantics (completion, hover, diagnostics). Diffly does none of that — it shells out to `git` and hands two URIs to `vscode.diff`. A Rust LSP adds per-platform binaries, IPC plumbing, and deployment complexity for zero capability or perf gain. Match the existing [CommandTree](../../../CommandTree) TS-only structure. +LSP exposes **language** semantics (completion, hover, diagnostics). Diffr does none of that — it shells out to `git` and hands two URIs to `vscode.diff`. A Rust LSP adds per-platform binaries, IPC plumbing, and deployment complexity for zero capability or perf gain. Match the existing [CommandTree](../../../CommandTree) TS-only structure. ## Non-negotiables (inherited from CommandTree CLAUDE.md) @@ -29,20 +29,20 @@ LSP exposes **language** semantics (completion, hover, diagnostics). Diffly does ``` SCM history view, right-click a commit: - ├── Diffly: Compare with… → SideBPicker → diff (single file) or multi-file QuickPick - ├── Diffly: Compare with Working Copy → multi-file QuickPick → diff - └── Diffly: Compare with Previous → diff against commit^1 + ├── Diffr: Compare with… → SideBPicker → diff (single file) or multi-file QuickPick + ├── Diffr: Compare with Working Copy → multi-file QuickPick → diff + └── Diffr: Compare with Previous → diff against commit^1 SCM Changes view, right-click a changed file: - └── Diffly: Compare with Commit… → CommitPicker → diff this file at picked commit vs working copy + └── Diffr: Compare with Commit… → CommitPicker → diff this file at picked commit vs working copy Editor title bar / Explorer, right-click a file: - └── Diffly: Compare with Commit… → CommitPicker → diff this file at picked commit vs working copy + └── Diffr: Compare with Commit… → CommitPicker → diff this file at picked commit vs working copy Command palette: - ├── Diffly: Compare Two Commits → CommitPicker → SideBPicker → file QuickPick → diff - ├── Diffly: Compare Current File with Commit - └── Diffly: Reopen Last Comparison + ├── Diffr: Compare Two Commits → CommitPicker → SideBPicker → file QuickPick → diff + ├── Diffr: Compare Current File with Commit + └── Diffr: Reopen Last Comparison ``` **Browsing many changed files between two commits**: after Side A and Side B are picked, show a `QuickPick` listing all changed files with `+N -M` in the description and `A/M/D/R/C` status as the detail. Picking a file opens `vscode.diff`; the QuickPick stays open (`ignoreFocusOut: true`) so the user can dismiss the diff and pick another file. **This replaces the tree view entirely.** @@ -50,7 +50,7 @@ Command palette: ## Project layout ``` -/Users/christianfindlay/Documents/Code/Diffly/ +/Users/christianfindlay/Documents/Code/Diffr/ ├── .github/workflows/{ci.yml,release.yml} ├── .vscode-test.mjs ├── Makefile @@ -80,13 +80,13 @@ Command palette: │ │ ├── parsers.ts # NUL-delimited porcelain parsers (no regex) │ │ └── types.ts # Commit, ChangedFile, DiffStat, RevSpec, Ref │ ├── providers/ -│ │ └── DifflyContentProvider.ts # TextDocumentContentProvider for diffly:// +│ │ └── DiffrContentProvider.ts # TextDocumentContentProvider for diffr:// │ ├── ui/ │ │ ├── CommitPicker.ts # QuickPick over git log │ │ ├── RefPicker.ts # branches + tags, resolves to RevSpec │ │ ├── SideBPicker.ts # Commit | Working Copy | Index | Branch/Tag │ │ ├── FilePicker.ts # QuickPick over changed files with stats -│ │ └── uri.ts # buildDifflyUri / parseDifflyUri (pure) +│ │ └── uri.ts # buildDiffrUri / parseDiffrUri (pure) │ ├── commands/ │ │ ├── compareWith.ts # SCM-history "Compare with…" entry │ │ ├── compareWithWorkingCopy.ts @@ -118,18 +118,18 @@ Command palette: - `show({ rev, path }): Promise>` → `git show :` - `refs(): Promise>` → `git for-each-ref --format='%(refname:short)%00%(objectname)%00%(objecttype)'` - `revParse(name): Promise>` -- **`providers/DifflyContentProvider.ts`** — `TextDocumentContentProvider` for scheme `diffly`. URI shapes: - - `diffly://commit//` → `git show :` - - `diffly://index/` → `git show :` (fallback when built-in git ext's `toGitUri` is unavailable) +- **`providers/DiffrContentProvider.ts`** — `TextDocumentContentProvider` for scheme `diffr`. URI shapes: + - `diffr://commit//` → `git show :` + - `diffr://index/` → `git show :` (fallback when built-in git ext's `toGitUri` is unavailable) Returns empty string for deleted files. Pure dispatch — URI parse + GitRepo call. - **`ui/CommitPicker.ts`** / **`RefPicker.ts`** / **`SideBPicker.ts`** / **`FilePicker.ts`** — `vscode.window.createQuickPick()` wrappers returning `Result`. SideB prepends synthetic items: `WORKING_COPY`, `INDEX`, `BRANCH_OR_TAG…` (last opens RefPicker). FilePicker stays open after selection so multiple files can be diffed in sequence from one comparison. -- **`ui/uri.ts`** — `buildDifflyUri(rev, path): Uri`, `parseDifflyUri(uri): Result<{rev, path}, ParseError>`. Pure, unit-tested. Handles spaces/unicode/`#`/`?` via proper URI encoding. +- **`ui/uri.ts`** — `buildDiffrUri(rev, path): Uri`, `parseDiffrUri(uri): Result<{rev, path}, ParseError>`. Pure, unit-tested. Handles spaces/unicode/`#`/`?` via proper URI encoding. - **Types** - `RevSpec = { kind: 'commit', sha: string } | { kind: 'workingCopy' } | { kind: 'index' }` — refs resolve to `commit` before reaching anything downstream. - `uriFor(rev, absPath, relPath)`: - - `commit` → `diffly://commit//` + - `commit` → `diffr://commit//` - `workingCopy` → `Uri.file(absPath)` - - `index` → built-in git extension's `toGitUri(fileUri, '~')` if available, else `diffly://index/` + - `index` → built-in git extension's `toGitUri(fileUri, '~')` if available, else `diffr://index/` ## `package.json` contributions @@ -139,32 +139,32 @@ Command palette: "extensionDependencies": ["vscode.git"], "contributes": { "commands": [ - { "command": "diffly.compareWith", "title": "Diffly: Compare with…" }, - { "command": "diffly.compareWithWorkingCopy", "title": "Diffly: Compare with Working Copy" }, - { "command": "diffly.compareWithPrevious", "title": "Diffly: Compare with Previous" }, - { "command": "diffly.compareTwoCommits", "title": "Diffly: Compare Two Commits" }, - { "command": "diffly.compareFileWithCommit", "title": "Diffly: Compare with Commit…" }, - { "command": "diffly.reopenLast", "title": "Diffly: Reopen Last Comparison" } + { "command": "diffr.compareWith", "title": "Diffr: Compare with…" }, + { "command": "diffr.compareWithWorkingCopy", "title": "Diffr: Compare with Working Copy" }, + { "command": "diffr.compareWithPrevious", "title": "Diffr: Compare with Previous" }, + { "command": "diffr.compareTwoCommits", "title": "Diffr: Compare Two Commits" }, + { "command": "diffr.compareFileWithCommit", "title": "Diffr: Compare with Commit…" }, + { "command": "diffr.reopenLast", "title": "Diffr: Reopen Last Comparison" } ], "menus": { "scm/history/item/context": [ - { "command": "diffly.compareWith", "when": "scmProvider == git", "group": "diffly@1" }, - { "command": "diffly.compareWithWorkingCopy", "when": "scmProvider == git", "group": "diffly@2" }, - { "command": "diffly.compareWithPrevious", "when": "scmProvider == git", "group": "diffly@3" } + { "command": "diffr.compareWith", "when": "scmProvider == git", "group": "diffr@1" }, + { "command": "diffr.compareWithWorkingCopy", "when": "scmProvider == git", "group": "diffr@2" }, + { "command": "diffr.compareWithPrevious", "when": "scmProvider == git", "group": "diffr@3" } ], "scm/resourceState/context": [ - { "command": "diffly.compareFileWithCommit", "when": "scmProvider == git", "group": "diffly@1" } + { "command": "diffr.compareFileWithCommit", "when": "scmProvider == git", "group": "diffr@1" } ], "editor/title/context": [ - { "command": "diffly.compareFileWithCommit", "when": "resourceScheme == file", "group": "3_compare" } + { "command": "diffr.compareFileWithCommit", "when": "resourceScheme == file", "group": "3_compare" } ], "explorer/context": [ - { "command": "diffly.compareFileWithCommit", "when": "resourceScheme == file && !explorerResourceIsFolder", "group": "3_compare" } + { "command": "diffr.compareFileWithCommit", "when": "resourceScheme == file && !explorerResourceIsFolder", "group": "3_compare" } ], "commandPalette": [ - { "command": "diffly.compareWith", "when": "false" }, - { "command": "diffly.compareWithWorkingCopy", "when": "false" }, - { "command": "diffly.compareWithPrevious", "when": "false" } + { "command": "diffr.compareWith", "when": "false" }, + { "command": "diffr.compareWithWorkingCopy", "when": "false" }, + { "command": "diffr.compareWithPrevious", "when": "false" } ] } } @@ -177,7 +177,7 @@ The three SCM-history commands receive the commit object as an argument from VSC **SCM history: "Compare with…"** (entry point passes the commit) ``` -diffly.compareWith(historyItem) +diffr.compareWith(historyItem) → revA = { kind:'commit', sha: historyItem.id } → SideBPicker → revB → GitRepo.nameStatus({ from: revA, to: revB }) + numstat @@ -192,7 +192,7 @@ diffly.compareWith(historyItem) **Palette: "Compare Two Commits"** ``` -diffly.compareTwoCommits +diffr.compareTwoCommits → resolve repo via vscode.git API (auto if single) → GitRepo.log({ limit:200 }) → CommitPicker → revA → SideBPicker → revB @@ -201,9 +201,9 @@ diffly.compareTwoCommits **Editor/Explorer/SCM-resource: "Compare with Commit…"** ``` -diffly.compareFileWithCommit(uri) +diffr.compareFileWithCommit(uri) → GitRepo.log → CommitPicker → rev - → leftUri = buildDifflyUri({ kind:'commit', sha:rev }, relPath) + → leftUri = buildDiffrUri({ kind:'commit', sha:rev }, relPath) → rightUri = uri (file://) → vscode.diff(leftUri, rightUri, `${shortSha} ↔ Working Copy — ${basename}`) ``` @@ -218,8 +218,8 @@ diffly.compareFileWithCommit(uri) **E2E (`src/test/suite/`, `@vscode/test-electron`, black-box only):** - `activation.test.ts` — extension activates; all commands registered (`vscode.commands.getCommands(true)`) - `compareTwoCommits.test.ts` — invoke palette command against fixture repo; assert FilePicker opens with expected file list; selecting a file opens a `TabInputTextDiff` tab (poll `vscode.window.tabGroups.all`) -- `compareFileWithCommit.test.ts` — open a fixture file, invoke command, assert diff tab opens with expected `diffly://` left URI -- `contentProvider.test.ts` — `workspace.openTextDocument(Uri.parse('diffly://commit//file.txt'))` returns the historical content from the seed repo +- `compareFileWithCommit.test.ts` — open a fixture file, invoke command, assert diff tab opens with expected `diffr://` left URI +- `contentProvider.test.ts` — `workspace.openTextDocument(Uri.parse('diffr://commit//file.txt'))` returns the historical content from the seed repo Coverage starts at 80% in `coverage-thresholds.json`; ratchet-only. diff --git a/package-lock.json b/package-lock.json index 39445d0..44e7f2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "diffly", + "name": "diffr", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "diffly", + "name": "diffr", "version": "0.1.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1c75d79..a07fb48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "diffly", - "displayName": "Diffly", + "name": "diffr", + "displayName": "Diffr", "description": "Pick two things and diff them — context-menu git diffing in VSCode.", "version": "0.1.0", "publisher": "nimblesite", @@ -8,12 +8,12 @@ "icon": "icon.png", "repository": { "type": "git", - "url": "https://github.com/Nimblesite/Diffly.git" + "url": "https://github.com/Nimblesite/Diffr.git" }, "bugs": { - "url": "https://github.com/Nimblesite/Diffly/issues" + "url": "https://github.com/Nimblesite/Diffr/issues" }, - "homepage": "https://github.com/Nimblesite/Diffly#readme", + "homepage": "https://github.com/Nimblesite/Diffr#readme", "main": "./out/extension.js", "engines": { "vscode": "^1.85.0", @@ -50,152 +50,152 @@ "contributes": { "commands": [ { - "command": "diffly.compareWith", - "title": "Diffly: Compare with…" + "command": "diffr.compareWith", + "title": "Diffr: Compare with…" }, { - "command": "diffly.compareWithWorkingCopy", - "title": "Diffly: Compare with Working Copy" + "command": "diffr.compareWithWorkingCopy", + "title": "Diffr: Compare with Working Copy" }, { - "command": "diffly.compareWithPrevious", - "title": "Diffly: Compare with Previous" + "command": "diffr.compareWithPrevious", + "title": "Diffr: Compare with Previous" }, { - "command": "diffly.compareWithBranch", - "title": "Diffly: Compare with Branch…" + "command": "diffr.compareWithBranch", + "title": "Diffr: Compare with Branch…" }, { - "command": "diffly.compareWithTag", - "title": "Diffly: Compare with Tag…" + "command": "diffr.compareWithTag", + "title": "Diffr: Compare with Tag…" }, { - "command": "diffly.compareTwoCommits", - "title": "Diffly: Compare Two Commits" + "command": "diffr.compareTwoCommits", + "title": "Diffr: Compare Two Commits" }, { - "command": "diffly.compareFileWithCommit", - "title": "Diffly: Compare with Commit…" + "command": "diffr.compareFileWithCommit", + "title": "Diffr: Compare with Commit…" }, { - "command": "diffly.compareFileWithBranch", - "title": "Diffly: Compare with Branch…" + "command": "diffr.compareFileWithBranch", + "title": "Diffr: Compare with Branch…" }, { - "command": "diffly.compareFileWithTag", - "title": "Diffly: Compare with Tag…" + "command": "diffr.compareFileWithTag", + "title": "Diffr: Compare with Tag…" }, { - "command": "diffly.reopenLast", - "title": "Diffly: Reopen Last Comparison" + "command": "diffr.reopenLast", + "title": "Diffr: Reopen Last Comparison" }, { - "command": "diffly.showLogs", - "title": "Diffly: Show Logs" + "command": "diffr.showLogs", + "title": "Diffr: Show Logs" } ], "menus": { "scm/historyItem/context": [ { - "command": "diffly.compareWith", + "command": "diffr.compareWith", "when": "scmProvider == git", - "group": "diffly@1" + "group": "diffr@1" }, { - "command": "diffly.compareWithWorkingCopy", + "command": "diffr.compareWithWorkingCopy", "when": "scmProvider == git", - "group": "diffly@2" + "group": "diffr@2" }, { - "command": "diffly.compareWithPrevious", + "command": "diffr.compareWithPrevious", "when": "scmProvider == git", - "group": "diffly@3" + "group": "diffr@3" }, { - "command": "diffly.compareWithBranch", + "command": "diffr.compareWithBranch", "when": "scmProvider == git", - "group": "diffly@4" + "group": "diffr@4" }, { - "command": "diffly.compareWithTag", + "command": "diffr.compareWithTag", "when": "scmProvider == git", - "group": "diffly@5" + "group": "diffr@5" } ], "scm/resourceState/context": [ { - "command": "diffly.compareFileWithCommit", + "command": "diffr.compareFileWithCommit", "when": "scmProvider == git", - "group": "diffly@1" + "group": "diffr@1" }, { - "command": "diffly.compareFileWithBranch", + "command": "diffr.compareFileWithBranch", "when": "scmProvider == git", - "group": "diffly@2" + "group": "diffr@2" }, { - "command": "diffly.compareFileWithTag", + "command": "diffr.compareFileWithTag", "when": "scmProvider == git", - "group": "diffly@3" + "group": "diffr@3" } ], "editor/title/context": [ { - "command": "diffly.compareFileWithCommit", + "command": "diffr.compareFileWithCommit", "when": "resourceScheme == file", - "group": "diffly@1" + "group": "diffr@1" }, { - "command": "diffly.compareFileWithBranch", + "command": "diffr.compareFileWithBranch", "when": "resourceScheme == file", - "group": "diffly@2" + "group": "diffr@2" }, { - "command": "diffly.compareFileWithTag", + "command": "diffr.compareFileWithTag", "when": "resourceScheme == file", - "group": "diffly@3" + "group": "diffr@3" } ], "explorer/context": [ { - "command": "diffly.compareFileWithCommit", + "command": "diffr.compareFileWithCommit", "when": "resourceScheme == file && !explorerResourceIsFolder", - "group": "diffly@1" + "group": "diffr@1" }, { - "command": "diffly.compareFileWithBranch", + "command": "diffr.compareFileWithBranch", "when": "resourceScheme == file && !explorerResourceIsFolder", - "group": "diffly@2" + "group": "diffr@2" }, { - "command": "diffly.compareFileWithTag", + "command": "diffr.compareFileWithTag", "when": "resourceScheme == file && !explorerResourceIsFolder", - "group": "diffly@3" + "group": "diffr@3" } ], "commandPalette": [ { - "command": "diffly.compareWith", + "command": "diffr.compareWith", "when": "false" }, { - "command": "diffly.compareWithWorkingCopy", + "command": "diffr.compareWithWorkingCopy", "when": "false" }, { - "command": "diffly.compareWithPrevious", + "command": "diffr.compareWithPrevious", "when": "false" }, { - "command": "diffly.compareWithBranch", + "command": "diffr.compareWithBranch", "when": "false" }, { - "command": "diffly.compareWithTag", + "command": "diffr.compareWithTag", "when": "false" }, { - "command": "diffly.showLogs", + "command": "diffr.showLogs", "when": "false" } ] diff --git a/scripts/stamp-release-version.mjs b/scripts/stamp-release-version.mjs index 2087837..dd6e515 100644 --- a/scripts/stamp-release-version.mjs +++ b/scripts/stamp-release-version.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// Shipwright SWR-VERSION-BUILD-STAMPING for Diffly. +// Shipwright SWR-VERSION-BUILD-STAMPING for Diffr. // // Stamps the release version into every deployed carrier in the runner working // tree, then writes build-info.json. Source-controlled values are not committed diff --git a/scripts/verify-versions.mjs b/scripts/verify-versions.mjs index 8d23461..8479641 100644 --- a/scripts/verify-versions.mjs +++ b/scripts/verify-versions.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// Shipwright SWR-REL-VERSION-VERIFY for Diffly. +// Shipwright SWR-REL-VERSION-VERIFY for Diffr. // // After scripts/stamp-release-version.mjs runs, assert that every deployed // version carrier in the runner working tree agrees with the expected version. diff --git a/shipwright.json b/shipwright.json index 502c1ad..fc4afd3 100644 --- a/shipwright.json +++ b/shipwright.json @@ -1,15 +1,15 @@ { "manifestVersion": 1, "product": { - "id": "diffly", - "displayName": "Diffly", + "id": "diffr", + "displayName": "Diffr", "version": "0.1.0", - "repository": "https://github.com/Nimblesite/Diffly", - "homepage": "https://github.com/Nimblesite/Diffly" + "repository": "https://github.com/Nimblesite/Diffr", + "homepage": "https://github.com/Nimblesite/Diffr" }, "components": [ { - "id": "diffly-vscode", + "id": "diffr-vscode", "kind": "extension-vscode", "language": "typescript", "expectedVersion": "${PRODUCT_VERSION}", diff --git a/src/commands/flow.ts b/src/commands/flow.ts index d9b715f..077208e 100644 --- a/src/commands/flow.ts +++ b/src/commands/flow.ts @@ -29,7 +29,7 @@ export const reportGitError = ({ output, op, e }: { output: vscode.OutputChannel if (e.stderr !== undefined && e.stderr !== "") { output.appendLine(e.stderr); } - void vscode.window.showErrorMessage(`${TITLE_PREFIX} ${op} failed (see Output → Diffly).`); + void vscode.window.showErrorMessage(`${TITLE_PREFIX} ${op} failed (see Output → Diffr).`); }; export const resolveSideB = async ({ diff --git a/src/commands/shared.ts b/src/commands/shared.ts index c397b46..3bda810 100644 --- a/src/commands/shared.ts +++ b/src/commands/shared.ts @@ -5,11 +5,11 @@ import type { GitRepo } from "../git/GitRepo"; import type { GitRunner } from "../git/GitRunner"; import { createGitRepo } from "../git/GitRepo"; import { type GitApi, type GitVsRepository, findRepoForUri } from "../vscodeGitApi"; -import type { CommitRev, DifflyAddressableRev, RevSpec, Sha } from "../git/types"; +import type { CommitRev, DiffrAddressableRev, RevSpec, Sha } from "../git/types"; import { logger } from "../logger"; import { type Result, err, ok } from "../result"; import { CANCELLED, type Cancelled } from "../ui/cancelled"; -import { buildDifflyUri } from "../ui/uri"; +import { buildDiffrUri } from "../ui/uri"; export interface CommandDeps { readonly runner: GitRunner; @@ -51,9 +51,9 @@ export const uriForRev = ({ if (rev.kind === REV_KINDS.workingCopy) { return vscode.Uri.file(path.join(repoRoot, relPath)); } - const addressable: DifflyAddressableRev = + const addressable: DiffrAddressableRev = rev.kind === REV_KINDS.commit ? { kind: REV_KINDS.commit, sha: rev.sha } : { kind: REV_KINDS.index }; - return vscode.Uri.parse(buildDifflyUri(addressable, relPath)); + return vscode.Uri.parse(buildDiffrUri(addressable, relPath)); }; export const openDiff = async ({ diff --git a/src/constants.ts b/src/constants.ts index 76cbc16..a42cd15 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,19 +1,34 @@ -export const SCHEME = "diffly"; +// The product name lives here ONCE. Every command id, context key, memento key, +// menu group, title prefix, URI scheme, and channel label derives from it. +const PUBLISHER = "nimblesite"; +const BRAND_ID = "diffr"; +const BRAND_NAME = "Diffr"; -export const OUTPUT_CHANNEL_NAME = "Diffly"; +const ns = (suffix: string): string => `${BRAND_ID}.${suffix}`; + +export const EXTENSION_ID = `${PUBLISHER}.${BRAND_ID}`; + +export const ENV_VARS = { + logLevel: `${BRAND_ID.toUpperCase()}_LOG_LEVEL`, + e2e: `${BRAND_ID.toUpperCase()}_E2E`, +} as const; + +export const SCHEME = BRAND_ID; + +export const OUTPUT_CHANNEL_NAME = BRAND_NAME; export const COMMAND_IDS = { - compareWith: "diffly.compareWith", - compareWithWorkingCopy: "diffly.compareWithWorkingCopy", - compareWithPrevious: "diffly.compareWithPrevious", - compareWithBranch: "diffly.compareWithBranch", - compareWithTag: "diffly.compareWithTag", - compareTwoCommits: "diffly.compareTwoCommits", - compareFileWithCommit: "diffly.compareFileWithCommit", - compareFileWithBranch: "diffly.compareFileWithBranch", - compareFileWithTag: "diffly.compareFileWithTag", - reopenLast: "diffly.reopenLast", - showLogs: "diffly.showLogs", + compareWith: ns("compareWith"), + compareWithWorkingCopy: ns("compareWithWorkingCopy"), + compareWithPrevious: ns("compareWithPrevious"), + compareWithBranch: ns("compareWithBranch"), + compareWithTag: ns("compareWithTag"), + compareTwoCommits: ns("compareTwoCommits"), + compareFileWithCommit: ns("compareFileWithCommit"), + compareFileWithBranch: ns("compareFileWithBranch"), + compareFileWithTag: ns("compareFileWithTag"), + reopenLast: ns("reopenLast"), + showLogs: ns("showLogs"), } as const; export const BUILT_IN_COMMANDS = { @@ -22,11 +37,11 @@ export const BUILT_IN_COMMANDS = { } as const; export const CONTEXT_KEYS = { - gitAvailable: "diffly.gitAvailable", + gitAvailable: ns("gitAvailable"), } as const; export const MEMENTO_KEYS = { - lastComparison: "diffly.lastComparison", + lastComparison: ns("lastComparison"), } as const; export const URI_AUTHORITIES = { @@ -110,9 +125,9 @@ export const MENU_WHEN = { never: "false", } as const; -export const MENU_GROUP_PREFIX = "diffly"; +export const MENU_GROUP_PREFIX = BRAND_ID; -export const TITLE_PREFIX = "Diffly:"; +export const TITLE_PREFIX = `${BRAND_NAME}:`; export const UI_TEXT = { workingCopy: "Working Copy", @@ -134,7 +149,7 @@ export const UI_TEXT = { refLabel: "Ref", binaryStat: "binary", justNow: "just now", - noChanges: "Diffly: no changes between selected sides.", + noChanges: `${BRAND_NAME}: no changes between selected sides.`, pathArrow: "↔", pathDash: "—", bulletDot: "•", diff --git a/src/extension.ts b/src/extension.ts index cf279e7..ce04e8a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,7 +21,7 @@ import { makeCompareWithPrevious } from "./commands/compareWithPrevious"; import { makeCompareWithRef } from "./commands/compareWithRef"; import { makeCompareWithWorkingCopy } from "./commands/compareWithWorkingCopy"; import { makeReopenLast } from "./commands/reopenLast"; -import { registerDifflyContentProvider } from "./providers/DifflyContentProvider"; +import { registerDiffrContentProvider } from "./providers/DiffrContentProvider"; import type { CommandDeps } from "./commands/shared"; import { buildRepo } from "./commands/shared"; import { createMementoStore } from "./state"; @@ -110,7 +110,7 @@ export const activate = async (context: vscode.ExtensionContext): Promise const state = createMementoStore(context.globalState); const deps = { runner, gitApi: api, output, state } as const; - registerDifflyContentProvider(context, makeRepoResolver(api, runner)); + registerDiffrContentProvider(context, makeRepoResolver(api, runner)); registerAll(context, deps); logger.info({ repos: api.repositories.length }, LOG_EVENTS.extensionActivated); }; diff --git a/src/git/GitRepo.ts b/src/git/GitRepo.ts index fb466f8..ce17169 100644 --- a/src/git/GitRepo.ts +++ b/src/git/GitRepo.ts @@ -7,7 +7,7 @@ import type { Commit, CommitRev, DiffStat, - DifflyAddressableRev, + DiffrAddressableRev, GitError, Ref, RevSpec, @@ -20,7 +20,7 @@ export interface DiffSides { } export interface ShowArgs { - readonly rev: DifflyAddressableRev; + readonly rev: DiffrAddressableRev; readonly path: string; } diff --git a/src/git/types.ts b/src/git/types.ts index 099f592..6eee1ef 100644 --- a/src/git/types.ts +++ b/src/git/types.ts @@ -40,7 +40,7 @@ export interface IndexRev { export type RevSpec = CommitRev | WorkingCopyRev | IndexRev; -export type DifflyAddressableRev = CommitRev | IndexRev; +export type DiffrAddressableRev = CommitRev | IndexRev; export type RefType = (typeof REF_TYPES)[keyof typeof REF_TYPES]; diff --git a/src/logger.ts b/src/logger.ts index 1343022..c24d509 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,7 +1,7 @@ import pino, { type Logger as PinoLogger, type Level, multistream } from "pino"; import { LOG_LEVELS } from "./constants"; -const ENV_LOG_LEVEL_VAR = "DIFFLY_LOG_LEVEL"; +const ENV_LOG_LEVEL_VAR = "DIFFR_LOG_LEVEL"; export interface Logger { trace: (fields: object, msg?: string) => void; diff --git a/src/providers/DifflyContentProvider.ts b/src/providers/DiffrContentProvider.ts similarity index 80% rename from src/providers/DifflyContentProvider.ts rename to src/providers/DiffrContentProvider.ts index 45dbec5..d0806a5 100644 --- a/src/providers/DifflyContentProvider.ts +++ b/src/providers/DiffrContentProvider.ts @@ -2,9 +2,9 @@ import * as vscode from "vscode"; import { LOG_EVENTS, SCHEME } from "../constants"; import type { GitRepo } from "../git/GitRepo"; import { logger } from "../logger"; -import { parseDifflyUri } from "../ui/uri"; +import { parseDiffrUri } from "../ui/uri"; -export class DifflyContentProvider implements vscode.TextDocumentContentProvider { +export class DiffrContentProvider implements vscode.TextDocumentContentProvider { private readonly emitter = new vscode.EventEmitter(); readonly onDidChange: vscode.Event = this.emitter.event; @@ -12,7 +12,7 @@ export class DifflyContentProvider implements vscode.TextDocumentContentProvider constructor(private readonly resolveRepo: (uri: vscode.Uri) => GitRepo | undefined) {} async provideTextDocumentContent(uri: vscode.Uri): Promise { - const parsed = parseDifflyUri(uri.toString()); + const parsed = parseDiffrUri(uri.toString()); if (!parsed.ok) { logger.warn({ kind: parsed.error.kind }, LOG_EVENTS.providerParseFailed); return ""; @@ -35,11 +35,11 @@ export class DifflyContentProvider implements vscode.TextDocumentContentProvider } } -export const registerDifflyContentProvider = ( +export const registerDiffrContentProvider = ( context: vscode.ExtensionContext, resolver: (uri: vscode.Uri) => GitRepo | undefined -): DifflyContentProvider => { - const provider = new DifflyContentProvider(resolver); +): DiffrContentProvider => { + const provider = new DiffrContentProvider(resolver); context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(SCHEME, provider), provider); return provider; }; diff --git a/src/test/runTests.ts b/src/test/runTests.ts index 981a0cc..a665d3b 100644 --- a/src/test/runTests.ts +++ b/src/test/runTests.ts @@ -30,7 +30,7 @@ const main = async (): Promise => { } const coverageDir = process.env["NODE_V8_COVERAGE"]; const extensionTestsEnv: Record = { - DIFFLY_E2E: "1", + DIFFR_E2E: "1", }; if (coverageDir !== undefined && coverageDir !== "") { extensionTestsEnv["NODE_V8_COVERAGE"] = coverageDir; @@ -39,7 +39,7 @@ const main = async (): Promise => { extensionDevelopmentPath, extensionTestsPath, extensionTestsEnv, - launchArgs: [workspacePath, "--disable-telemetry", "--enable-proposed-api", "nimblesite.diffly"], + launchArgs: [workspacePath, "--disable-telemetry", "--enable-proposed-api", "nimblesite.diffr"], }); }; diff --git a/src/test/suite/activation.test.ts b/src/test/suite/activation.test.ts index 3af2359..895a4ba 100644 --- a/src/test/suite/activation.test.ts +++ b/src/test/suite/activation.test.ts @@ -3,7 +3,7 @@ import * as vscode from "vscode"; import { COMMAND_IDS, OUTPUT_CHANNEL_NAME } from "../../constants"; import { tick } from "./helpers"; -const EXTENSION_ID = "nimblesite.diffly"; +const EXTENSION_ID = "nimblesite.diffr"; const TICK_MS = 20; const ALL_COMMAND_IDS: readonly string[] = Object.values(COMMAND_IDS); @@ -21,7 +21,7 @@ describe("activation", () => { for (const id of ALL_COMMAND_IDS) { assert.ok( registered.includes(id), - `command ${id} should be registered (was: ${registered.filter((c) => c.startsWith("diffly.")).join(", ")})` + `command ${id} should be registered (was: ${registered.filter((c) => c.startsWith("diffr.")).join(", ")})` ); } }); @@ -35,7 +35,7 @@ describe("activation", () => { assert.match(folder.uri.fsPath.replace(/\\/g, "/"), /repo-seed\/workspace$/); }); - it("shows logs command opens the Diffly OutputChannel without errors", async () => { + it("shows logs command opens the Diffr OutputChannel without errors", async () => { await vscode.commands.executeCommand(COMMAND_IDS.showLogs); // The channel's "visible" state isn't observable from the test host, but // the command must have a registered handler and complete without throwing. diff --git a/src/test/suite/commands.test.ts b/src/test/suite/commands.test.ts index e1fc62e..2974520 100644 --- a/src/test/suite/commands.test.ts +++ b/src/test/suite/commands.test.ts @@ -16,7 +16,7 @@ import { workspaceRoot, } from "./helpers"; -const EXTENSION_ID = "nimblesite.diffly"; +const EXTENSION_ID = "nimblesite.diffr"; const ensureActivated = async (): Promise => { const ext = vscode.extensions.getExtension(EXTENSION_ID); @@ -36,7 +36,7 @@ const moveAndAccept = async (steps: number): Promise => { await accept(); }; -describe("Diffly commands — end-to-end through real QuickPick UI", () => { +describe("Diffr commands — end-to-end through real QuickPick UI", () => { before(async () => { await ensureActivated(); }); @@ -72,7 +72,7 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { }); it("compareFileWithCommit with a file outside any repo → warning, no diff", async () => { - const outside = vscode.Uri.file("/tmp/diffly-not-in-any-repo.txt"); + const outside = vscode.Uri.file("/tmp/diffr-not-in-any-repo.txt"); const before = allDiffTabs().length; await vscode.commands.executeCommand(COMMAND_IDS.compareFileWithCommit, outside); await tick(40); @@ -117,9 +117,9 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); + assert.equal(uris.left.scheme, "diffr"); assert.equal(uris.right.scheme, "file"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.third}/`)); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.third}/`)); assert.match(uris.right.fsPath, /a\.txt$/); assert.match(labelStrings(diffTab), /↔ Working Copy — a\.txt$/); assert.match(labelStrings(diffTab), new RegExp(`^${shas.third.slice(0, 7)}`)); @@ -141,8 +141,8 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.first}/`)); + assert.equal(uris.left.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.first}/`)); assert.match(labelStrings(diffTab), new RegExp(`^${shas.first.slice(0, 7)} ↔ Working Copy — `)); await dismissQuickPick(); @@ -162,8 +162,8 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.first}/`)); + assert.equal(uris.left.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.first}/`)); assert.equal(uris.right.scheme, "file"); assert.match(labelStrings(diffTab), new RegExp(`^${shas.first.slice(0, 7)} ↔ Working Copy — `)); @@ -171,7 +171,7 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { await flow; }); - it("compareWith(historyItem) → SideB=Index → diff has diffly://index/ on the right", async () => { + it("compareWith(historyItem) → SideB=Index → diff has diffr://index/ on the right", async () => { const shas = readSeedShas(); const flow = vscode.commands.executeCommand(COMMAND_IDS.compareWith, { historyItem: { id: shas.first }, @@ -185,10 +185,10 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.equal(uris.right.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.first}/`)); - assert.match(uris.right.toString(), /^diffly:\/\/index\//); + assert.equal(uris.left.scheme, "diffr"); + assert.equal(uris.right.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.first}/`)); + assert.match(uris.right.toString(), /^diffr:\/\/index\//); assert.match(labelStrings(diffTab), new RegExp(`^${shas.first.slice(0, 7)} ↔ Index — `)); await dismissQuickPick(); @@ -213,10 +213,10 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { await accept(); const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.equal(uris.right.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.third}/`)); - assert.match(uris.right.toString(), new RegExp(`diffly://commit/${shas.second}/`)); + assert.equal(uris.left.scheme, "diffr"); + assert.equal(uris.right.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.third}/`)); + assert.match(uris.right.toString(), new RegExp(`diffr://commit/${shas.second}/`)); assert.match(labelStrings(diffTab), new RegExp(`^${shas.third.slice(0, 7)} ↔ ${shas.second.slice(0, 7)} — `)); await dismissQuickPick(); @@ -240,8 +240,8 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { await accept(); const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.third}/`)); - assert.match(uris.right.toString(), new RegExp(`diffly://commit/${shas.first}/`)); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.third}/`)); + assert.match(uris.right.toString(), new RegExp(`diffr://commit/${shas.first}/`)); await dismissQuickPick(); await flow; @@ -258,9 +258,9 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); + assert.equal(uris.left.scheme, "diffr"); assert.equal(uris.right.scheme, "file"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.second}/`)); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.second}/`)); assert.match(labelStrings(diffTab), new RegExp(`^${shas.second.slice(0, 7)} ↔ Working Copy — `)); await dismissQuickPick(); @@ -277,10 +277,10 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { await accept(); const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.equal(uris.right.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.third}/`)); - assert.match(uris.right.toString(), new RegExp(`diffly://commit/${shas.second}/`)); + assert.equal(uris.left.scheme, "diffr"); + assert.equal(uris.right.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.third}/`)); + assert.match(uris.right.toString(), new RegExp(`diffr://commit/${shas.second}/`)); assert.match(labelStrings(diffTab), new RegExp(`^${shas.third.slice(0, 7)} ↔ ${shas.second.slice(0, 7)} — `)); await dismissQuickPick(); @@ -297,8 +297,8 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.third}/a\\.txt$`)); + assert.equal(uris.left.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.third}/a\\.txt$`)); assert.equal(uris.right.scheme, "file"); assert.match(uris.right.fsPath, /a\.txt$/); assert.match(labelStrings(diffTab), new RegExp(`^${shas.third.slice(0, 7)} ↔ Working Copy — a\\.txt$`)); @@ -317,7 +317,7 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.first}/dir/c\\.txt$`)); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.first}/dir/c\\.txt$`)); assert.match(uris.right.fsPath.replace(/\\/g, "/"), /dir\/c\.txt$/); await flow; @@ -332,7 +332,7 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { await accept(); // FilePicker const firstDiff = await waitForDiffTab(); const firstUris = tabInputUris(firstDiff); - assert.match(firstUris.left.toString(), new RegExp(`diffly://commit/${shas.first}/`)); + assert.match(firstUris.left.toString(), new RegExp(`diffr://commit/${shas.first}/`)); await dismissQuickPick(); await setup; await closeAllEditors(); @@ -342,7 +342,7 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { await accept(); // FilePicker again const reDiff = await waitForDiffTab(); const reUris = tabInputUris(reDiff); - assert.match(reUris.left.toString(), new RegExp(`diffly://commit/${shas.first}/`)); + assert.match(reUris.left.toString(), new RegExp(`diffr://commit/${shas.first}/`)); assert.equal(reUris.right.scheme, "file"); await dismissQuickPick(); @@ -365,8 +365,8 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { await accept(); // FilePicker const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.second}/`)); - assert.match(uris.right.toString(), new RegExp(`diffly://commit/${shas.first}/`)); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.second}/`)); + assert.match(uris.right.toString(), new RegExp(`diffr://commit/${shas.first}/`)); await dismissQuickPick(); await flow; }); @@ -385,10 +385,10 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.equal(uris.right.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.first}/`)); - assert.match(uris.right.toString(), new RegExp(`diffly://commit/${shas.second}/`)); + assert.equal(uris.left.scheme, "diffr"); + assert.equal(uris.right.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.first}/`)); + assert.match(uris.right.toString(), new RegExp(`diffr://commit/${shas.second}/`)); assert.match(labelStrings(diffTab), new RegExp(`^${shas.first.slice(0, 7)} ↔ ${shas.second.slice(0, 7)} — `)); await dismissQuickPick(); @@ -415,10 +415,10 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.equal(uris.right.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.first}/`)); - assert.match(uris.right.toString(), new RegExp(`diffly://commit/${shas.second}/`)); + assert.equal(uris.left.scheme, "diffr"); + assert.equal(uris.right.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.first}/`)); + assert.match(uris.right.toString(), new RegExp(`diffr://commit/${shas.second}/`)); assert.match(labelStrings(diffTab), new RegExp(`^${shas.first.slice(0, 7)} ↔ ${shas.second.slice(0, 7)} — `)); await dismissQuickPick(); @@ -443,8 +443,8 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.second}/a\\.txt$`)); + assert.equal(uris.left.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.second}/a\\.txt$`)); assert.equal(uris.right.scheme, "file"); assert.match(uris.right.fsPath, /a\.txt$/); assert.match(labelStrings(diffTab), new RegExp(`^${shas.second.slice(0, 7)} ↔ Working Copy — a\\.txt$`)); @@ -462,8 +462,8 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { const diffTab = await waitForDiffTab(); const uris = tabInputUris(diffTab); - assert.equal(uris.left.scheme, "diffly"); - assert.match(uris.left.toString(), new RegExp(`diffly://commit/${shas.second}/a\\.txt$`)); + assert.equal(uris.left.scheme, "diffr"); + assert.match(uris.left.toString(), new RegExp(`diffr://commit/${shas.second}/a\\.txt$`)); assert.equal(uris.right.scheme, "file"); assert.match(uris.right.fsPath, /a\.txt$/); assert.match(labelStrings(diffTab), new RegExp(`^${shas.second.slice(0, 7)} ↔ Working Copy — a\\.txt$`)); @@ -481,7 +481,7 @@ describe("Diffly commands — end-to-end through real QuickPick UI", () => { }); it("compareFileWithTag with a file outside any repo → warning, no diff", async () => { - const outside = vscode.Uri.file("/tmp/diffly-not-in-any-repo.txt"); + const outside = vscode.Uri.file("/tmp/diffr-not-in-any-repo.txt"); const before = allDiffTabs().length; await vscode.commands.executeCommand(COMMAND_IDS.compareFileWithTag, outside); await tick(40); diff --git a/src/test/suite/contentProvider.test.ts b/src/test/suite/contentProvider.test.ts index 1daa418..0a6bb21 100644 --- a/src/test/suite/contentProvider.test.ts +++ b/src/test/suite/contentProvider.test.ts @@ -2,14 +2,14 @@ import { strict as assert } from "node:assert"; import * as vscode from "vscode"; import { readSeedShas, waitForRepoReady } from "./helpers"; -const EXTENSION_ID = "nimblesite.diffly"; +const EXTENSION_ID = "nimblesite.diffr"; -const readDiffly = async (uriString: string): Promise => { +const readDiffr = async (uriString: string): Promise => { const doc = await vscode.workspace.openTextDocument(vscode.Uri.parse(uriString)); return doc.getText(); }; -describe("DifflyContentProvider (real diffly:// URIs against the seeded repo)", () => { +describe("DiffrContentProvider (real diffr:// URIs against the seeded repo)", () => { before(async () => { const ext = vscode.extensions.getExtension(EXTENSION_ID); if (ext !== undefined && !ext.isActive) { @@ -21,47 +21,47 @@ describe("DifflyContentProvider (real diffly:// URIs against the seeded repo)", it("resolves a.txt at every committed version with the expected content", async () => { const shas = readSeedShas(); - const a1 = await readDiffly(`diffly://commit/${shas.first}/a.txt`); + const a1 = await readDiffr(`diffr://commit/${shas.first}/a.txt`); assert.match(a1, /a\.txt v1/); assert.match(a1, /second line v1/); assert.doesNotMatch(a1, /third line/); - const a2 = await readDiffly(`diffly://commit/${shas.second}/a.txt`); + const a2 = await readDiffr(`diffr://commit/${shas.second}/a.txt`); assert.match(a2, /a\.txt v2 edited/); assert.match(a2, /third line added/); assert.doesNotMatch(a2, /working copy uncommitted/); - const a3 = await readDiffly(`diffly://commit/${shas.third}/a.txt`); + const a3 = await readDiffr(`diffr://commit/${shas.third}/a.txt`); assert.equal(a3, a2, "a.txt is unchanged from commit 2 to commit 3"); }); it("resolves dir/c.txt at v1 and v2 with the expected text", async () => { const shas = readSeedShas(); - const c1 = await readDiffly(`diffly://commit/${shas.first}/dir/c.txt`); + const c1 = await readDiffr(`diffr://commit/${shas.first}/dir/c.txt`); assert.match(c1, /c\.txt v1/); - const c2 = await readDiffly(`diffly://commit/${shas.second}/dir/c.txt`); + const c2 = await readDiffr(`diffr://commit/${shas.second}/dir/c.txt`); assert.match(c2, /c\.txt v2 edited/); assert.notEqual(c1, c2); }); it("returns the renamed file b2.txt at the third commit but not before", async () => { const shas = readSeedShas(); - const b2 = await readDiffly(`diffly://commit/${shas.third}/b2.txt`); + const b2 = await readDiffr(`diffr://commit/${shas.third}/b2.txt`); assert.match(b2, /renamed and extended/); - const missing = await readDiffly(`diffly://commit/${shas.first}/b2.txt`); + const missing = await readDiffr(`diffr://commit/${shas.first}/b2.txt`); assert.equal(missing, "", "b2.txt does not exist at commit 1 and should resolve to empty"); }); it("returns empty string for a deleted path (d.txt at commit 2)", async () => { const shas = readSeedShas(); - const deletedAtV2 = await readDiffly(`diffly://commit/${shas.second}/d.txt`); + const deletedAtV2 = await readDiffr(`diffr://commit/${shas.second}/d.txt`); assert.equal(deletedAtV2, ""); - const aliveAtV1 = await readDiffly(`diffly://commit/${shas.first}/d.txt`); + const aliveAtV1 = await readDiffr(`diffr://commit/${shas.first}/d.txt`); assert.match(aliveAtV1, /d\.txt v1/); }); - it("returns empty string for a malformed diffly URI", async () => { - const bad = await readDiffly("diffly://nonsense/whatever"); + it("returns empty string for a malformed diffr URI", async () => { + const bad = await readDiffr("diffr://nonsense/whatever"); assert.equal(bad, ""); }); }); diff --git a/src/test/unit/menus.test.ts b/src/test/unit/menus.test.ts index 0c148c0..1eb8e83 100644 --- a/src/test/unit/menus.test.ts +++ b/src/test/unit/menus.test.ts @@ -52,7 +52,7 @@ describe("menu manifest — single source of truth", () => { ); }); - it("SCM history item menu contains all five commit-level Diffly commands", () => { + it("SCM history item menu contains all five commit-level Diffr commands", () => { const entries = buildMenuManifest().menus[MENU_IDS.scmHistoryItem]; assert.ok(entries !== undefined); const cmds = entries.map((e) => e.command); @@ -65,11 +65,11 @@ describe("menu manifest — single source of truth", () => { ]); for (const e of entries) { assert.equal(e.when, MENU_WHEN.scmGit); - assert.match(e.group, /^diffly@\d+$/); + assert.match(e.group, /^diffr@\d+$/); } }); - it("editor/title/context and explorer/context and scm/resourceState/context all expose the three file-level Diffly commands", () => { + it("editor/title/context and explorer/context and scm/resourceState/context all expose the three file-level Diffr commands", () => { const m = buildMenuManifest().menus; const fileLevel = [ COMMAND_IDS.compareFileWithCommit, diff --git a/src/test/unit/uri.test.ts b/src/test/unit/uri.test.ts index def7453..f8799b6 100644 --- a/src/test/unit/uri.test.ts +++ b/src/test/unit/uri.test.ts @@ -1,129 +1,129 @@ import { strict as assert } from "node:assert"; import { REV_KINDS, URI_PARSE_ERROR_KINDS } from "../../constants"; -import { buildDifflyUri, parseDifflyUri } from "../../ui/uri"; +import { buildDiffrUri, parseDiffrUri } from "../../ui/uri"; import { expectErr, expectOk } from "../../result"; -import type { DifflyAddressableRev } from "../../git/types"; +import type { DiffrAddressableRev } from "../../git/types"; const SHA = "abc1234def567890fedcba0987654321aaaaaaaa"; -const commit = (sha: string): DifflyAddressableRev => ({ +const commit = (sha: string): DiffrAddressableRev => ({ kind: REV_KINDS.commit, sha, }); -const index = (): DifflyAddressableRev => ({ kind: REV_KINDS.index }); +const index = (): DiffrAddressableRev => ({ kind: REV_KINDS.index }); -const roundTrip = (rev: DifflyAddressableRev, path: string): void => { - const uri = buildDifflyUri(rev, path); - const parsed = parseDifflyUri(uri); +const roundTrip = (rev: DiffrAddressableRev, path: string): void => { + const uri = buildDiffrUri(rev, path); + const parsed = parseDiffrUri(uri); expectOk(parsed); assert.deepEqual(parsed.value.rev, rev); assert.equal(parsed.value.path, path); }; -describe("buildDifflyUri", () => { +describe("buildDiffrUri", () => { it("builds a commit URI with sha and path", () => { - const uri = buildDifflyUri(commit(SHA), "src/file.ts"); - assert.equal(uri, `diffly://commit/${SHA}/src/file.ts`); + const uri = buildDiffrUri(commit(SHA), "src/file.ts"); + assert.equal(uri, `diffr://commit/${SHA}/src/file.ts`); }); it("builds an index URI without sha", () => { - const uri = buildDifflyUri(index(), "src/file.ts"); - assert.equal(uri, "diffly://index/src/file.ts"); + const uri = buildDiffrUri(index(), "src/file.ts"); + assert.equal(uri, "diffr://index/src/file.ts"); }); it("percent-encodes spaces in path segments", () => { - const uri = buildDifflyUri(commit(SHA), "a folder/b file.ts"); - assert.equal(uri, `diffly://commit/${SHA}/a%20folder/b%20file.ts`); + const uri = buildDiffrUri(commit(SHA), "a folder/b file.ts"); + assert.equal(uri, `diffr://commit/${SHA}/a%20folder/b%20file.ts`); }); it("percent-encodes ? and # so they are not parsed as query/fragment", () => { - const uri = buildDifflyUri(commit(SHA), "weird?name#here.ts"); - assert.equal(uri, `diffly://commit/${SHA}/weird%3Fname%23here.ts`); + const uri = buildDiffrUri(commit(SHA), "weird?name#here.ts"); + assert.equal(uri, `diffr://commit/${SHA}/weird%3Fname%23here.ts`); }); it("preserves forward slashes as segment separators", () => { - const uri = buildDifflyUri(commit(SHA), "a/b/c/d.ts"); - assert.equal(uri, `diffly://commit/${SHA}/a/b/c/d.ts`); + const uri = buildDiffrUri(commit(SHA), "a/b/c/d.ts"); + assert.equal(uri, `diffr://commit/${SHA}/a/b/c/d.ts`); }); it("encodes unicode characters in path segments", () => { - const uri = buildDifflyUri(commit(SHA), "src/日本語.ts"); + const uri = buildDiffrUri(commit(SHA), "src/日本語.ts"); const encoded = encodeURIComponent("日本語"); - assert.equal(uri, `diffly://commit/${SHA}/src/${encoded}.ts`); + assert.equal(uri, `diffr://commit/${SHA}/src/${encoded}.ts`); }); }); -describe("parseDifflyUri", () => { +describe("parseDiffrUri", () => { it("parses a commit URI and decodes the path", () => { - const r = parseDifflyUri(`diffly://commit/${SHA}/src/file.ts`); + const r = parseDiffrUri(`diffr://commit/${SHA}/src/file.ts`); expectOk(r); assert.deepEqual(r.value.rev, { kind: REV_KINDS.commit, sha: SHA }); assert.equal(r.value.path, "src/file.ts"); }); it("parses an index URI and decodes the path", () => { - const r = parseDifflyUri("diffly://index/src/file.ts"); + const r = parseDiffrUri("diffr://index/src/file.ts"); expectOk(r); assert.deepEqual(r.value.rev, { kind: REV_KINDS.index }); assert.equal(r.value.path, "src/file.ts"); }); - it("rejects a non-diffly scheme", () => { - const r = parseDifflyUri("file:///some/path.ts"); + it("rejects a non-diffr scheme", () => { + const r = parseDiffrUri("file:///some/path.ts"); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.invalidScheme); }); it("rejects an unknown authority", () => { - const r = parseDifflyUri(`diffly://stash/${SHA}/x.ts`); + const r = parseDiffrUri(`diffr://stash/${SHA}/x.ts`); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.invalidAuthority); }); it("rejects a commit URI with no path after the sha", () => { - const r = parseDifflyUri(`diffly://commit/${SHA}/`); + const r = parseDiffrUri(`diffr://commit/${SHA}/`); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.emptyPath); }); it("rejects a commit URI with no sha", () => { - const r = parseDifflyUri("diffly://commit//file.ts"); + const r = parseDiffrUri("diffr://commit//file.ts"); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.missingSha); }); it("rejects an index URI with no path", () => { - const r = parseDifflyUri("diffly://index/"); + const r = parseDiffrUri("diffr://index/"); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.emptyPath); }); it("rejects a malformed URI with no scheme separator", () => { - const r = parseDifflyUri("not-a-uri"); + const r = parseDiffrUri("not-a-uri"); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.malformed); }); it("rejects a URI missing the authority/path slash", () => { - const r = parseDifflyUri("diffly://commit"); + const r = parseDiffrUri("diffr://commit"); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.malformed); }); it("rejects a URI with malformed percent encoding", () => { - const r = parseDifflyUri(`diffly://commit/${SHA}/bad%ZZpath.ts`); + const r = parseDiffrUri(`diffr://commit/${SHA}/bad%ZZpath.ts`); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.badEncoding); }); it("rejects an index URI with malformed percent encoding", () => { - const r = parseDifflyUri("diffly://index/bad%ZZpath.ts"); + const r = parseDiffrUri("diffr://index/bad%ZZpath.ts"); expectErr(r); assert.equal(r.error.kind, URI_PARSE_ERROR_KINDS.badEncoding); }); }); -describe("buildDifflyUri ↔ parseDifflyUri round-trip", () => { +describe("buildDiffrUri ↔ parseDiffrUri round-trip", () => { it("round-trips a simple commit URI", () => { roundTrip(commit(SHA), "src/file.ts"); }); diff --git a/src/ui/uri.ts b/src/ui/uri.ts index 51bb528..0f6d949 100644 --- a/src/ui/uri.ts +++ b/src/ui/uri.ts @@ -1,8 +1,8 @@ import { REV_KINDS, SCHEME, URI_AUTHORITIES, URI_PARSE_ERROR_KINDS } from "../constants"; import { type Result, ok, err } from "../result"; -import type { DifflyAddressableRev, Sha } from "../git/types"; +import type { DiffrAddressableRev, Sha } from "../git/types"; -export type DifflyUriParseError = +export type DiffrUriParseError = | { readonly kind: typeof URI_PARSE_ERROR_KINDS.invalidScheme; readonly got: string; @@ -22,8 +22,8 @@ export type DifflyUriParseError = readonly reason: string; }; -export interface DifflyUriComponents { - readonly rev: DifflyAddressableRev; +export interface DiffrUriComponents { + readonly rev: DiffrAddressableRev; readonly path: string; } @@ -32,7 +32,7 @@ const PATH_SEP = "/"; const encodePath = (path: string): string => path.split(PATH_SEP).map(encodeURIComponent).join(PATH_SEP); -const decodePath = (encoded: string): Result => { +const decodePath = (encoded: string): Result => { try { return ok(encoded.split(PATH_SEP).map(decodeURIComponent).join(PATH_SEP)); } catch { @@ -40,7 +40,7 @@ const decodePath = (encoded: string): Result => { } }; -export const buildDifflyUri = (rev: DifflyAddressableRev, path: string): string => { +export const buildDiffrUri = (rev: DiffrAddressableRev, path: string): string => { const encoded = encodePath(path); if (rev.kind === REV_KINDS.commit) { return `${SCHEME}${SCHEME_SEP}${URI_AUTHORITIES.commit}/${rev.sha}/${encoded}`; @@ -48,7 +48,7 @@ export const buildDifflyUri = (rev: DifflyAddressableRev, path: string): string return `${SCHEME}${SCHEME_SEP}${URI_AUTHORITIES.index}/${encoded}`; }; -const parseCommitUri = (rest: string): Result => { +const parseCommitUri = (rest: string): Result => { const slash = rest.indexOf(PATH_SEP); if (slash <= 0) { return err({ kind: URI_PARSE_ERROR_KINDS.missingSha }); @@ -65,7 +65,7 @@ const parseCommitUri = (rest: string): Result => { +const parseIndexUri = (rest: string): Result => { if (rest === "") { return err({ kind: URI_PARSE_ERROR_KINDS.emptyPath }); } @@ -82,7 +82,7 @@ interface UriSplit { readonly rest: string; } -const splitUri = (uri: string): Result => { +const splitUri = (uri: string): Result => { const sep = uri.indexOf(SCHEME_SEP); if (sep < 0) { return err({ @@ -106,7 +106,7 @@ const splitUri = (uri: string): Result => { }); }; -export const parseDifflyUri = (uri: string): Result => { +export const parseDiffrUri = (uri: string): Result => { const split = splitUri(uri); if (!split.ok) { return split;