From cb2b1cdb29184cb6a63231596018923e8f9e02ee Mon Sep 17 00:00:00 2001 From: Phill Johntony Date: Wed, 15 Apr 2026 00:44:18 +0000 Subject: [PATCH 1/5] Implement persistence for tree filters --- src/commands/filterTree.ts | 65 +++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/src/commands/filterTree.ts b/src/commands/filterTree.ts index fb5f0aa6..1cb70fc3 100644 --- a/src/commands/filterTree.ts +++ b/src/commands/filterTree.ts @@ -14,6 +14,7 @@ interface TreeFilterState { } const treeFilters = new Map(); +const filterMementoKeyPrefix = "vscode-containers.filters"; // Only support filtering for containers and images const contextKeys: Partial> = { @@ -25,16 +26,48 @@ export function getTreeFilter(treePrefix: TreePrefix): TreeFilterState { return treeFilters.get(treePrefix) || { filterText: "", isActive: false }; } -function setTreeFilter(treePrefix: TreePrefix, filterText: string): void { +function getFilterMementoKey(treePrefix: TreePrefix): string { + return `${filterMementoKeyPrefix}.${treePrefix}`; +} + +function loadPersistedTreeFilter(treePrefix: TreePrefix): void { + const persistedFilter = ext.context.workspaceState.get( + getFilterMementoKey(treePrefix) + ); + + if (persistedFilter) { + treeFilters.set(treePrefix, { + filterText: persistedFilter.toLowerCase(), + isActive: true, + }); + } else { + treeFilters.delete(treePrefix); + } +} + +async function setTreeFilter( + treePrefix: TreePrefix, + filterText: string +): Promise { + const normalizedFilterText = filterText.toLowerCase(); + treeFilters.set(treePrefix, { - filterText: filterText.toLowerCase(), - isActive: filterText.length > 0, + filterText: normalizedFilterText, + isActive: normalizedFilterText.length > 0, }); - setFilterContextValue(treePrefix, filterText.length > 0); + await ext.context.workspaceState.update( + getFilterMementoKey(treePrefix), + normalizedFilterText || undefined + ); + setFilterContextValue(treePrefix, normalizedFilterText.length > 0); } -function clearTreeFilter(treePrefix: TreePrefix): void { - treeFilters.set(treePrefix, { filterText: "", isActive: false }); +async function clearTreeFilter(treePrefix: TreePrefix): Promise { + treeFilters.delete(treePrefix); + await ext.context.workspaceState.update( + getFilterMementoKey(treePrefix), + undefined + ); setFilterContextValue(treePrefix, false); } @@ -47,8 +80,10 @@ function setFilterContextValue(treePrefix: TreePrefix, value: boolean): void { export function setInitialFilterContextValues(): void { for (const treePrefix of Object.keys(contextKeys) as TreePrefix[]) { + loadPersistedTreeFilter(treePrefix); const filter = getTreeFilter(treePrefix); setFilterContextValue(treePrefix, filter.isActive); + updateTreeViewTitle(treePrefix); } } @@ -121,25 +156,25 @@ async function filterTreeView( ]; } - quickPick.onDidAccept(() => { + quickPick.onDidAccept(async () => { const value = quickPick.value.trim(); const selectedItem = quickPick.selectedItems[0]; // Check if "Clear Filter" was selected if (selectedItem?.label === clearFilterLabel) { - clearTreeFilter(treePrefix); + await clearTreeFilter(treePrefix); context.telemetry.properties.action = "clearFilter"; } else if (value) { - setTreeFilter(treePrefix, value); + await setTreeFilter(treePrefix, value); context.telemetry.properties.action = "applyFilter"; context.telemetry.properties.filterLength = value.length.toString(); } else { - clearTreeFilter(treePrefix); + await clearTreeFilter(treePrefix); context.telemetry.properties.action = "clearFilter"; } quickPick.hide(); - void refreshTreeView(treePrefix); + await refreshTreeView(treePrefix); }); quickPick.onDidHide(() => { @@ -223,15 +258,15 @@ export async function filterImagesTree(context: IActionContext): Promise { export async function clearContainersFilter( context: IActionContext ): Promise { - clearTreeFilter("containers"); + await clearTreeFilter("containers"); context.telemetry.properties.action = "clearFilter"; - void refreshTreeView("containers"); + await refreshTreeView("containers"); } export async function clearImagesFilter( context: IActionContext ): Promise { - clearTreeFilter("images"); + await clearTreeFilter("images"); context.telemetry.properties.action = "clearFilter"; - void refreshTreeView("images"); + await refreshTreeView("images"); } From c703eb6693ddf4e335c6221588c5a2b9d16d7976 Mon Sep 17 00:00:00 2001 From: Phill Johntony Date: Wed, 15 Apr 2026 00:51:54 +0000 Subject: [PATCH 2/5] Refactor TestMemento storage type and improve update logic --- src/test/TestMemento.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/test/TestMemento.ts b/src/test/TestMemento.ts index f508bbf1..0094eb8c 100644 --- a/src/test/TestMemento.ts +++ b/src/test/TestMemento.ts @@ -6,18 +6,23 @@ import * as vscode from 'vscode'; export class TestMemento implements vscode.Memento { - private readonly values: { [key: string]: never } = {}; + private readonly values: Record = {}; keys(): readonly string[] { return Object.keys(this.values); } get(key: string, defaultValue?: T): T | undefined { - return this.values[key] ?? defaultValue; + return (this.values[key] as T | undefined) ?? defaultValue; } - update(key: string, value: never): Thenable { - this.values[key] = value; + update(key: string, value: unknown): Thenable { + if (value === undefined) { + delete this.values[key]; + } else { + this.values[key] = value; + } + return Promise.resolve(); } } From 6729c9a00b6a0cd812dbd08a8d9f6a3876749c5c Mon Sep 17 00:00:00 2001 From: Phill Johntony Date: Wed, 15 Apr 2026 01:07:03 +0000 Subject: [PATCH 3/5] fix(tree): init display settings before filtering --- src/tree/LocalRootTreeItemBase.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/tree/LocalRootTreeItemBase.ts b/src/tree/LocalRootTreeItemBase.ts index 1c93155d..87eb4b7b 100644 --- a/src/tree/LocalRootTreeItemBase.ts +++ b/src/tree/LocalRootTreeItemBase.ts @@ -97,6 +97,14 @@ export abstract class LocalRootTreeItemBase Date: Wed, 15 Apr 2026 10:07:02 -0400 Subject: [PATCH 4/5] Handle empty persisted tree filters --- src/commands/filterTree.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/filterTree.ts b/src/commands/filterTree.ts index 1cb70fc3..eb21b79d 100644 --- a/src/commands/filterTree.ts +++ b/src/commands/filterTree.ts @@ -35,10 +35,11 @@ function loadPersistedTreeFilter(treePrefix: TreePrefix): void { getFilterMementoKey(treePrefix) ); - if (persistedFilter) { + if (persistedFilter !== undefined) { + const normalizedFilter = persistedFilter.toLowerCase(); treeFilters.set(treePrefix, { - filterText: persistedFilter.toLowerCase(), - isActive: true, + filterText: normalizedFilter, + isActive: normalizedFilter.length > 0, }); } else { treeFilters.delete(treePrefix); From a36049074f0004a4fdb231cd02c41bcba2eaccd3 Mon Sep 17 00:00:00 2001 From: Phill Johntony Date: Wed, 15 Apr 2026 10:09:59 -0400 Subject: [PATCH 5/5] Catch filter QuickPick accept errors --- src/commands/filterTree.ts | 45 ++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/commands/filterTree.ts b/src/commands/filterTree.ts index eb21b79d..19d5d862 100644 --- a/src/commands/filterTree.ts +++ b/src/commands/filterTree.ts @@ -157,25 +157,32 @@ async function filterTreeView( ]; } - quickPick.onDidAccept(async () => { - const value = quickPick.value.trim(); - const selectedItem = quickPick.selectedItems[0]; - - // Check if "Clear Filter" was selected - if (selectedItem?.label === clearFilterLabel) { - await clearTreeFilter(treePrefix); - context.telemetry.properties.action = "clearFilter"; - } else if (value) { - await setTreeFilter(treePrefix, value); - context.telemetry.properties.action = "applyFilter"; - context.telemetry.properties.filterLength = value.length.toString(); - } else { - await clearTreeFilter(treePrefix); - context.telemetry.properties.action = "clearFilter"; - } - - quickPick.hide(); - await refreshTreeView(treePrefix); + quickPick.onDidAccept(() => { + void (async () => { + const value = quickPick.value.trim(); + const selectedItem = quickPick.selectedItems[0]; + + // Check if "Clear Filter" was selected + if (selectedItem?.label === clearFilterLabel) { + await clearTreeFilter(treePrefix); + context.telemetry.properties.action = "clearFilter"; + } else if (value) { + await setTreeFilter(treePrefix, value); + context.telemetry.properties.action = "applyFilter"; + context.telemetry.properties.filterLength = + value.length.toString(); + } else { + await clearTreeFilter(treePrefix); + context.telemetry.properties.action = "clearFilter"; + } + + quickPick.hide(); + await refreshTreeView(treePrefix); + })().catch((error) => { + void vscode.window.showErrorMessage( + vscode.l10n.t("Failed to apply filter: {0}", String(error)) + ); + }); }); quickPick.onDidHide(() => {