From 52529059d863aee75b741cc40385cf931e04ca84 Mon Sep 17 00:00:00 2001 From: ozgen Date: Thu, 26 Mar 2026 16:34:16 +0100 Subject: [PATCH] fix: stabilize file decorations and eliminate explorer flicker Use snapshot-based decoration updates instead of loading state during provideFileDecoration calls. This removes redundant refreshes, prevents transient color changes in the Explorer, and keeps staged counts accurate. --- CHANGELOG.md | 36 ++ package.json | 2 +- src/app/deps.ts | 17 +- src/registration/registerRefresh.ts | 26 +- .../views/worklistDecorationProvider.test.ts | 415 +++++++++++++----- src/views/worklistDecorationProvider.ts | 100 +++-- 6 files changed, 430 insertions(+), 166 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fec045a..e77b28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,42 @@ No changes yet. --- +## [1.2.0] - 2026-03-26 + +### Added + +- **Improved Changelist Indicators** + - Clear staged and partially staged status shown in changelist tree view + - Consistent badge display for files across all changelists + - Enhanced tooltips including staging information + +### Changed + +- **Stable File Decorations** + - Explorer file decorations are now stable and no longer change color during updates + - Changelist membership is now visually separated from Git staging state + - Simplified decoration logic to avoid conflicts with built-in Git visuals + +- **Refresh & State Handling** + - Introduced snapshot-based updates for decoration provider + - Reduced redundant refresh cycles for smoother UI behavior + - Improved synchronization between Git state and changelist state + +### Fixed + +- **Explorer Flickering** + - Fixed blinking file colors (e.g. red/blue/green transitions) when staging files + - Eliminated inconsistent decoration updates during rapid state changes + +- **Decoration Consistency** + - Fixed stale or incorrect file colors after staging and untracking operations + - Fixed race conditions caused by async state loading in decoration provider + +- **Commit View Accuracy** + - Fixed incorrect staged file count calculation + +--- + ## [1.1.0] - 2026-03-25 ### Added diff --git a/package.json b/package.json index 83e55ad..99144c0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "git-worklists", "displayName": "Git Worklists", - "description": "IntelliJ-style changelists for VS Code. Organize changes into work units, stage selectively, and commit with full control.", + "description": "IntelliJ-style changelists, diff, bookmarks, and stash for VS Code. Organize changes, review diffs, and stage changes with precision using partial staging.", "publisher": "ozgen", "version": "1.1.0", "icon": "media/icon.png", diff --git a/src/app/deps.ts b/src/app/deps.ts index b0c31cd..15a1490 100644 --- a/src/app/deps.ts +++ b/src/app/deps.ts @@ -28,12 +28,12 @@ import { ReconcileWithGitStatus } from "../usecases/reconcileWithGitStatus"; import { RenameChangelist } from "../usecases/renameChangelist"; import { RestageAlreadyStaged } from "../usecases/restageAlreadyStaged"; import { RestoreFilesToChangelist } from "../usecases/stash/restoreFilesToChangelist"; +import { BookmarkDecorationProvider } from "../views/bookmark/bookmarkDecorationProvider"; import { ChangelistDragDrop } from "../views/changelistDragDrop"; import { ChangelistTreeProvider } from "../views/changelistTreeProvider"; import { StashesTreeProvider } from "../views/stash/stashesTreeProvider"; import { WorklistDecorationProvider } from "../views/worklistDecorationProvider"; import { Deps } from "./types"; -import { BookmarkDecorationProvider } from "../views/bookmark/bookmarkDecorationProvider"; function sortRepoRoots(repoRoots: string[]): string[] { return [...new Set(repoRoots)].sort((a, b) => a.localeCompare(b)); @@ -116,21 +116,28 @@ export async function createDeps( await loadOrInit.run(deps.repoRoot); await reconcile.run(deps.repoRoot); + const state = await store.load(deps.repoRoot); const fileStageStates = await git.getFileStageStates(deps.repoRoot); + treeProvider.setFileStageStates(fileStageStates); - deco.setFileStageStates(fileStageStates); + deco.updateSnapshot({ + state, + fileStageStates, + }); treeProvider.refresh(); - deco.refreshAll(); if (deps.commitView) { + const stagedCount = [...fileStageStates.values()].filter( + (s) => s === "all" || s === "partial", + ).length; + deps.commitView.updateState({ - stagedCount: fileStageStates.size, + stagedCount, lastError: undefined, }); } - const state = await store.load(deps.repoRoot); const totalFiles = state?.version === 1 ? state.lists.reduce((sum, l) => sum + l.files.length, 0) diff --git a/src/registration/registerRefresh.ts b/src/registration/registerRefresh.ts index 5df1e6e..17f13a8 100644 --- a/src/registration/registerRefresh.ts +++ b/src/registration/registerRefresh.ts @@ -25,24 +25,32 @@ export function registerRefresh(deps: Deps) { title: "Git Worklists: syncing with Git…", }, async () => { + await deps.loadOrInit.run(deps.repoRoot); await deps.reconcile.run(deps.repoRoot); - const fileStageStates = await deps.git.getFileStageStates(deps.repoRoot); + const state = (await deps.store.load(deps.repoRoot)) as + | PersistedState + | undefined; + const fileStageStates = await deps.git.getFileStageStates( + deps.repoRoot, + ); + deps.treeProvider.setFileStageStates(fileStageStates); - deps.deco.setFileStageStates(fileStageStates); + deps.deco.updateSnapshot({ + state, + fileStageStates, + }); deps.treeProvider.refresh(); - deps.deco.refreshAll(); - deps.commitView.updateState({ - stagedCount: fileStageStates.size, + deps.commitView?.updateState({ + stagedCount: [...fileStageStates.values()].filter( + (s) => s === "all" || s === "partial", + ).length, lastError: undefined, }); - const state = await deps.store.load(deps.repoRoot); - const count = computeTotalWorklistCount( - state as PersistedState | undefined, - ); + const count = computeTotalWorklistCount(state); deps.treeView.badge = count > 0 diff --git a/src/test/unit/views/worklistDecorationProvider.test.ts b/src/test/unit/views/worklistDecorationProvider.test.ts index c44b0ba..79ecdea 100644 --- a/src/test/unit/views/worklistDecorationProvider.test.ts +++ b/src/test/unit/views/worklistDecorationProvider.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; const vscodeMock = vi.hoisted(() => { class ThemeColor { @@ -32,18 +32,20 @@ const vscodeMock = vi.hoisted(() => { vi.mock("vscode", () => vscodeMock); import * as vscode from "vscode"; -import { WorklistDecorationProvider } from "../../../views/worklistDecorationProvider"; import { WorkspaceStateStore, type PersistedState, } from "../../../adapters/storage/workspaceStateStore"; import { SystemChangelist } from "../../../core/changelist/systemChangelist"; +import { WorklistDecorationProvider } from "../../../views/worklistDecorationProvider"; class MemMemento { private data = new Map(); + get(key: string): T | undefined { return this.data.get(key); } + async update(key: string, value: any): Promise { this.data.set(key, value); } @@ -59,7 +61,6 @@ describe("WorklistDecorationProvider", () => { let provider: WorklistDecorationProvider; const repoRoot = "/repo"; - const keyRepo = repoRoot; beforeEach(() => { vi.clearAllMocks(); @@ -74,23 +75,54 @@ describe("WorklistDecorationProvider", () => { expect(ee.fire).toHaveBeenCalledWith(undefined); }); - it("refreshAll fires change", () => { + it("fires change when updateSnapshot is called", () => { const ee = (provider as any)._onDidChange; ee.fire.mockClear(); - provider.refreshAll(); + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + ]), + fileStageStates: new Map(), + }); + expect(ee.fire).toHaveBeenCalledWith(undefined); }); it("returns undefined if repoRoot not set", async () => { const p = new WorklistDecorationProvider(store); + p.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: ["a.txt"], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + ]), + fileStageStates: new Map(), + }); + const dec = await p.provideFileDecoration( vscode.Uri.file("/repo/a.txt") as any, ); expect(dec).toBeUndefined(); }); - it("returns undefined if state missing", async () => { + it("returns undefined if snapshot state is missing", async () => { const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/a.txt") as any, ); @@ -98,17 +130,21 @@ describe("WorklistDecorationProvider", () => { }); it("returns undefined if uri is outside repo", async () => { - await store.save( - keyRepo, - state([ + provider.updateSnapshot({ + state: state([ { id: SystemChangelist.Unversioned, name: "Unversioned", files: ["a.txt"], }, - { id: SystemChangelist.Default, name: "Changes", files: ["b.txt"] }, + { + id: SystemChangelist.Default, + name: "Changes", + files: ["b.txt"], + }, ]), - ); + fileStageStates: new Map(), + }); const dec = await provider.provideFileDecoration( vscode.Uri.file("/other/a.txt") as any, @@ -117,18 +153,26 @@ describe("WorklistDecorationProvider", () => { }); it("unversioned has priority and returns U decoration", async () => { - await store.save( - keyRepo, - state([ + provider.updateSnapshot({ + state: state([ { id: SystemChangelist.Unversioned, name: "Unversioned", files: ["a.txt"], }, - { id: SystemChangelist.Default, name: "Changes", files: ["a.txt"] }, - { id: "cl_1", name: "Hotfix", files: ["a.txt"] }, + { + id: SystemChangelist.Default, + name: "Changes", + files: ["a.txt"], + }, + { + id: "cl_1", + name: "Hotfix", + files: ["a.txt"], + }, ]), - ); + fileStageStates: new Map(), + }); const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/a.txt") as any, @@ -137,82 +181,160 @@ describe("WorklistDecorationProvider", () => { expect(dec).toBeDefined(); expect(dec?.badge).toBe("U"); expect(dec?.tooltip).toBe("Unversioned"); - expect(dec?.color).toMatchObject({ - id: "gitDecoration.untrackedResourceForeground", + expect(dec?.color).toBeUndefined(); + }); + + it("unversioned includes staged suffix when stageState is all", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: ["a.txt"], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + ]), + fileStageStates: new Map([["a.txt", "all"]]), }); + + const dec = await provider.provideFileDecoration( + vscode.Uri.file("/repo/a.txt") as any, + ); + + expect(dec?.badge).toBe("U"); + expect(dec?.tooltip).toBe("Unversioned • Staged"); + expect(dec?.color).toBeUndefined(); }); - it("default returns D decoration with orange color when stageState is none", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: ["b.txt"] }, + it("unversioned includes partially staged suffix when stageState is partial", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: ["a.txt"], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, ]), + fileStageStates: new Map([["a.txt", "partial"]]), + }); + + const dec = await provider.provideFileDecoration( + vscode.Uri.file("/repo/a.txt") as any, ); + expect(dec?.badge).toBe("U"); + expect(dec?.tooltip).toBe("Unversioned • Partially staged"); + expect(dec?.color).toBeUndefined(); + }); + + it("default returns D decoration when stageState is none", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: ["b.txt"], + }, + ]), + fileStageStates: new Map(), + }); + const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/b.txt") as any, ); expect(dec?.badge).toBe("D"); expect(dec?.tooltip).toBe("In Changes"); - expect(dec?.color).toMatchObject({ - id: "gitDecoration.modifiedResourceForeground", - }); + expect(dec?.color).toBeUndefined(); }); - it("default changelist file shows green and 'Partially Staged' when stageState is partial", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: ["b.txt"] }, + it("default changelist file shows staged suffix when stageState is partial", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: ["b.txt"], + }, ]), - ); - provider.setFileStageStates(new Map([["b.txt", "partial"]])); + fileStageStates: new Map([["b.txt", "partial"]]), + }); const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/b.txt") as any, ); expect(dec?.badge).toBe("D"); - expect(dec?.tooltip).toBe("Partially Staged"); - expect(dec?.color).toMatchObject({ - id: "gitDecoration.stagedModifiedResourceForeground", - }); + expect(dec?.tooltip).toBe("In Changes • Partially staged"); + expect(dec?.color).toBeUndefined(); }); - it("default changelist file shows green and 'Staged' when stageState is all", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: ["b.txt"] }, + it("default changelist file shows staged suffix when stageState is all", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: ["b.txt"], + }, ]), - ); - provider.setFileStageStates(new Map([["b.txt", "all"]])); + fileStageStates: new Map([["b.txt", "all"]]), + }); const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/b.txt") as any, ); expect(dec?.badge).toBe("D"); - expect(dec?.tooltip).toBe("Staged"); - expect(dec?.color).toMatchObject({ - id: "gitDecoration.stagedModifiedResourceForeground", - }); + expect(dec?.tooltip).toBe("In Changes • Staged"); + expect(dec?.color).toBeUndefined(); }); - it("custom returns first-letter badge and tooltip with modified color when stageState is none", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: [] }, - { id: "cl_x", name: "refactor", files: ["c.txt"] }, + it("custom returns first-letter badge and tooltip when stageState is none", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + { + id: "cl_x", + name: "refactor", + files: ["c.txt"], + }, ]), - ); + fileStageStates: new Map(), + }); const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/c.txt") as any, @@ -220,84 +342,165 @@ describe("WorklistDecorationProvider", () => { expect(dec?.badge).toBe("R"); expect(dec?.tooltip).toBe("In refactor"); - expect(dec?.color).toMatchObject({ - id: "gitDecoration.modifiedResourceForeground", - }); + expect(dec?.color).toBeUndefined(); }); - it("custom badge falls back to L for empty/invalid names", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: [] }, - { id: "cl_x", name: " ", files: ["d.txt"] }, + it("custom badge falls back to L for empty names", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + { + id: "cl_x", + name: " ", + files: ["d.txt"], + }, ]), - ); + fileStageStates: new Map(), + }); const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/d.txt") as any, ); + expect(dec?.badge).toBe("L"); expect(dec?.tooltip).toBe("In "); + expect(dec?.color).toBeUndefined(); }); - it("returns undefined if file not in any list", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: [] }, - { id: "cl_x", name: "X", files: ["x.txt"] }, + it("custom changelist file shows staged suffix when stageState is all", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + { + id: "cl_x", + name: "refactor", + files: ["c.txt"], + }, ]), - ); + fileStageStates: new Map([["c.txt", "all"]]), + }); const dec = await provider.provideFileDecoration( - vscode.Uri.file("/repo/nope.txt") as any, + vscode.Uri.file("/repo/c.txt") as any, ); - expect(dec).toBeUndefined(); + + expect(dec?.badge).toBe("R"); + expect(dec?.tooltip).toBe("In refactor • Staged"); + expect(dec?.color).toBeUndefined(); }); - it("custom returns first-letter badge and tooltip with modified color when stageState is none", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: [] }, - { id: "cl_x", name: "refactor", files: ["c.txt"] }, + it("custom changelist file shows partially staged suffix when stageState is partial", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + { + id: "cl_x", + name: "refactor", + files: ["c.txt"], + }, ]), - ); + fileStageStates: new Map([["c.txt", "partial"]]), + }); const dec = await provider.provideFileDecoration( vscode.Uri.file("/repo/c.txt") as any, ); expect(dec?.badge).toBe("R"); - expect(dec?.tooltip).toBe("In refactor"); - expect(dec?.color).toMatchObject({ - id: "gitDecoration.modifiedResourceForeground", - }); + expect(dec?.tooltip).toBe("In refactor • Partially staged"); + expect(dec?.color).toBeUndefined(); }); - it("custom changelist file shows green and 'Staged in ' when stageState is all", async () => { - await store.save( - keyRepo, - state([ - { id: SystemChangelist.Unversioned, name: "Unversioned", files: [] }, - { id: SystemChangelist.Default, name: "Changes", files: [] }, - { id: "cl_x", name: "refactor", files: ["c.txt"] }, + it("returns undefined if file not in any list", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: [], + }, + { + id: "cl_x", + name: "X", + files: ["x.txt"], + }, ]), + fileStageStates: new Map(), + }); + + const dec = await provider.provideFileDecoration( + vscode.Uri.file("/repo/nope.txt") as any, ); - provider.setFileStageStates(new Map([["c.txt", "all"]])); + expect(dec).toBeUndefined(); + }); + + it("ignores invalid snapshot state", async () => { + provider.updateSnapshot({ + state: { version: 999 }, + fileStageStates: new Map(), + }); const dec = await provider.provideFileDecoration( - vscode.Uri.file("/repo/c.txt") as any, + vscode.Uri.file("/repo/a.txt") as any, ); - expect(dec?.badge).toBe("R"); - expect(dec?.tooltip).toBe("Staged in refactor"); - expect(dec?.color).toMatchObject({ - id: "gitDecoration.stagedModifiedResourceForeground", + expect(dec).toBeUndefined(); + }); + + it("normalizes stage-state paths from snapshot", async () => { + provider.updateSnapshot({ + state: state([ + { + id: SystemChangelist.Unversioned, + name: "Unversioned", + files: [], + }, + { + id: SystemChangelist.Default, + name: "Changes", + files: ["dir/file.txt"], + }, + ]), + fileStageStates: new Map([["dir\\file.txt", "all"]]), }); + + const dec = await provider.provideFileDecoration( + vscode.Uri.file("/repo/dir/file.txt") as any, + ); + + expect(dec?.badge).toBe("D"); + expect(dec?.tooltip).toBe("In Changes • Staged"); }); }); diff --git a/src/views/worklistDecorationProvider.ts b/src/views/worklistDecorationProvider.ts index e083dd9..a90510a 100644 --- a/src/views/worklistDecorationProvider.ts +++ b/src/views/worklistDecorationProvider.ts @@ -4,6 +4,17 @@ import { WorkspaceStateStore } from "../adapters/storage/workspaceStateStore"; import { SystemChangelist } from "../core/changelist/systemChangelist"; import { normalizeRepoRelPath } from "../utils/paths"; +type PersistedChangelist = { + id: string; + name: string; + files: string[]; +}; + +type PersistedStateV1 = { + version: 1; + lists: PersistedChangelist[]; +}; + export class WorklistDecorationProvider implements vscode.FileDecorationProvider { @@ -14,32 +25,39 @@ export class WorklistDecorationProvider private repoRootFsPath?: string; private fileStageStates = new Map(); + private state?: PersistedStateV1; constructor(private readonly store: WorkspaceStateStore) {} - setFileStageStates(states: Map) { - this.fileStageStates = states; - this._onDidChange.fire(undefined); - } - setRepoRoot(repoRootFsPath: string) { this.repoRootFsPath = repoRootFsPath; this._onDidChange.fire(undefined); } - refreshAll() { + updateSnapshot(args: { + state: unknown; + fileStageStates: Map; + }) { + const normalized = new Map(); + for (const [p, s] of args.fileStageStates) { + normalized.set(normalizeRepoRelPath(p), s); + } + + this.fileStageStates = normalized; + this.state = + args.state && + typeof args.state === "object" && + (args.state as PersistedStateV1).version === 1 + ? (args.state as PersistedStateV1) + : undefined; + this._onDidChange.fire(undefined); } - async provideFileDecoration( + provideFileDecoration( uri: vscode.Uri, - ): Promise { - if (!this.repoRootFsPath) { - return; - } - - const state = await this.store.load(this.repoRootFsPath); - if (!state || state.version !== 1) { + ): vscode.ProviderResult { + if (!this.repoRootFsPath || !this.state) { return; } @@ -50,27 +68,29 @@ export class WorklistDecorationProvider const normalizedRel = normalizeRepoRelPath(rel); const stageState = this.fileStageStates.get(normalizedRel) ?? "none"; + const lists = this.state.lists; - // Priority: Unversioned > Default > Custom - const unversioned = state.lists.find( + const unversioned = lists.find( (l) => l.id === SystemChangelist.Unversioned, ); if (unversioned?.files.includes(normalizedRel)) { return new vscode.FileDecoration( "U", - "Unversioned", - new vscode.ThemeColor("gitDecoration.untrackedResourceForeground"), + stageState === "all" + ? "Unversioned • Staged" + : stageState === "partial" + ? "Unversioned • Partially staged" + : "Unversioned", + undefined, ); } - const defaultList = state.lists.find( - (l) => l.id === SystemChangelist.Default, - ); + const defaultList = lists.find((l) => l.id === SystemChangelist.Default); if (defaultList?.files.includes(normalizedRel)) { return decorationForList("D", "Default", stageState); } - const customList = state.lists.find( + const customList = lists.find( (l) => l.id !== SystemChangelist.Unversioned && l.id !== SystemChangelist.Default && @@ -78,8 +98,11 @@ export class WorklistDecorationProvider ); if (customList) { - const badge = badgeFromName(customList.name); - return decorationForList(badge, customList.name, stageState); + return decorationForList( + badgeFromName(customList.name), + customList.name, + stageState, + ); } return; @@ -91,29 +114,16 @@ function decorationForList( listName: string, stageState: FileStageState, ): vscode.FileDecoration { - if (stageState === "all") { - return new vscode.FileDecoration( - badge, - listName === "Default" ? "Staged" : `Staged in ${listName}`, - new vscode.ThemeColor("gitDecoration.stagedModifiedResourceForeground"), - ); - } + const base = listName === "Default" ? "In Changes" : `In ${listName}`; - if (stageState === "partial") { - return new vscode.FileDecoration( - badge, - listName === "Default" - ? "Partially Staged" - : `Partially Staged in ${listName}`, - new vscode.ThemeColor("gitDecoration.stagedModifiedResourceForeground"), - ); - } + const suffix = + stageState === "all" + ? " • Staged" + : stageState === "partial" + ? " • Partially staged" + : ""; - return new vscode.FileDecoration( - badge, - listName === "Default" ? "In Changes" : `In ${listName}`, - new vscode.ThemeColor("gitDecoration.modifiedResourceForeground"), - ); + return new vscode.FileDecoration(badge, `${base}${suffix}`, undefined); } function toRepoRelPath(repoRootFsPath: string, uri: vscode.Uri): string {