diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts index 6cb51c86933..d5f5b11d91e 100644 --- a/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -6,6 +6,7 @@ import { localize } from '../../../nls.js'; import { Registry } from '../../registry/common/platform.js'; import { DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; +import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IKeybindingService } from '../../keybinding/common/keybinding.js'; import { Extensions, IQuickAccessProvider, IQuickAccessProviderDescriptor, IQuickAccessRegistry } from '../common/quickAccess.js'; import { IQuickInputService, IQuickPick, IQuickPickItem } from '../common/quickInput.js'; @@ -22,7 +23,8 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { constructor( @IQuickInputService private readonly quickInputService: IQuickInputService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { } provide(picker: IQuickPick): IDisposable { @@ -39,8 +41,8 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { // Also open a picker when we detect the user typed the exact // name of a provider (e.g. `?term` for terminals) disposables.add(picker.onDidChangeValue(value => { - const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length)); - if (providerDescriptor && providerDescriptor.prefix && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { + const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length), this.contextKeyService); + if (providerDescriptor?.prefix && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { this.quickInputService.quickAccess.show(providerDescriptor.prefix, { preserveValue: true }); } })); @@ -53,7 +55,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { getQuickAccessProviders(): IHelpQuickAccessPickItem[] { const providers: IHelpQuickAccessPickItem[] = this.registry - .getQuickAccessProviders() + .getQuickAccessProviders(this.contextKeyService) .sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix)) .flatMap(provider => this.createPicks(provider)); diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts index 151a3c1dc24..82033448f80 100644 --- a/src/vs/platform/quickinput/browser/quickAccess.ts +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -7,6 +7,7 @@ import { DeferredPromise } from '../../../base/common/async.js'; import { CancellationTokenSource } from '../../../base/common/cancellation.js'; import { Event } from '../../../base/common/event.js'; import { Disposable, DisposableStore, IDisposable, isDisposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IInstantiationService } from '../../instantiation/common/instantiation.js'; import { DefaultQuickAccessFilterValue, Extensions, IQuickAccessController, IQuickAccessOptions, IQuickAccessProvider, IQuickAccessProviderDescriptor, IQuickAccessRegistry } from '../common/quickAccess.js'; import { IQuickInputService, IQuickPick, IQuickPickItem, ItemActivation } from '../common/quickInput.js'; @@ -27,7 +28,8 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon constructor( @IQuickInputService private readonly quickInputService: IQuickInputService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this._register(toDisposable(() => { @@ -233,7 +235,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon } private getOrInstantiateProvider(value: string, enabledProviderPrefixes?: string[]): [IQuickAccessProvider | undefined, IQuickAccessProviderDescriptor | undefined] { - const providerDescriptor = this.registry.getQuickAccessProvider(value); + const providerDescriptor = this.registry.getQuickAccessProvider(value, this.contextKeyService); if (!providerDescriptor || enabledProviderPrefixes && !enabledProviderPrefixes?.includes(providerDescriptor.prefix)) { return [undefined, undefined]; } diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index 3645acd7131..142a0b7d640 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -6,6 +6,7 @@ import { coalesce } from '../../../base/common/arrays.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; +import { ContextKeyExpression, IContextKeyService } from '../../contextkey/common/contextkey.js'; import { ItemActivation, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, QuickPickItem, IQuickPickSeparator } from './quickInput.js'; import { Registry } from '../../registry/common/platform.js'; @@ -194,6 +195,12 @@ export interface IQuickAccessProviderDescriptor { * picker for the provider is showing. */ readonly contextKey?: string; + + /** + * A context key expression that must evaluate to true for the + * provider to be considered in the registry. + */ + readonly when?: ContextKeyExpression; } export const Extensions = { @@ -210,12 +217,12 @@ export interface IQuickAccessRegistry { /** * Get all registered quick access providers. */ - getQuickAccessProviders(): IQuickAccessProviderDescriptor[]; + getQuickAccessProviders(contextKeyService: IContextKeyService): IQuickAccessProviderDescriptor[]; /** * Get a specific quick access provider for a given prefix. */ - getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined; + getQuickAccessProvider(prefix: string, contextKeyService: IContextKeyService): IQuickAccessProviderDescriptor | undefined; } export class QuickAccessRegistry implements IQuickAccessRegistry { @@ -245,12 +252,15 @@ export class QuickAccessRegistry implements IQuickAccessRegistry { }); } - getQuickAccessProviders(): IQuickAccessProviderDescriptor[] { - return coalesce([this.defaultProvider, ...this.providers]); + getQuickAccessProviders(contextKeyService: IContextKeyService): IQuickAccessProviderDescriptor[] { + return coalesce([this.defaultProvider, ...this.providers]) + .filter(provider => !provider.when || contextKeyService.contextMatchesRules(provider.when)); } - getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined { - const result = prefix ? (this.providers.find(provider => prefix.startsWith(provider.prefix)) || undefined) : undefined; + getQuickAccessProvider(prefix: string, contextKeyService: IContextKeyService): IQuickAccessProviderDescriptor | undefined { + const result = prefix + ? this.providers.find(provider => prefix.startsWith(provider.prefix) && (!provider.when || contextKeyService.contextMatchesRules(provider.when))) + : undefined; return result || this.defaultProvider; } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts index 5baf0428100..df8c7cff778 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts @@ -154,6 +154,7 @@ Registry.as(QuickAccessExtensions.Quickaccess).registerQui ctor: AgentSessionsQuickAccessProvider, prefix: AGENT_SESSIONS_QUICK_ACCESS_PREFIX, contextKey: 'inAgentSessionsPicker', + when: ChatContextKeys.enabled, placeholder: localize('agentSessionsQuickAccessPlaceholder', "Search agent sessions by name"), helpEntries: [{ description: localize('agentSessionsQuickAccessHelp', "Show All Agent Sessions"), diff --git a/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts b/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts index 433046be5f9..3d990135a38 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcp.contribution.ts @@ -16,6 +16,7 @@ import { IConfigurationMigrationRegistry, Extensions as ConfigurationMigrationEx import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { EditorExtensions } from '../../../common/editor.js'; import { mcpSchemaId } from '../../../services/configuration/common/configuration.js'; +import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js'; import { ExtensionMcpDiscovery } from '../common/discovery/extensionMcpDiscovery.js'; import { InstalledMcpServersDiscovery } from '../common/discovery/installedMcpServersDiscovery.js'; import { mcpDiscoveryRegistry } from '../common/discovery/mcpDiscovery.js'; @@ -108,6 +109,7 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ ctor: McpResourceQuickAccess, prefix: McpResourceQuickAccess.PREFIX, + when: ChatContextKeys.enabled, placeholder: localize('mcp.quickaccess.placeholder', "Filter to an MCP resource"), helpEntries: [{ description: localize('mcp.quickaccess.add', "MCP Server Resources"), diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index 7d451a4cf68..8d449a07b3e 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -52,6 +52,7 @@ import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uri import { stripIcons } from '../../../../base/common/iconLabels.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { ASK_QUICK_QUESTION_ACTION_ID } from '../../chat/browser/actions/chatQuickInputActions.js'; import { IQuickChatService } from '../../chat/browser/chat.js'; @@ -136,6 +137,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider p.helpEntries.some(h => h.commandCenterOrder !== undefined)) .flatMap(provider => provider.helpEntries .filter(h => h.commandCenterOrder !== undefined) diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts index 7b0be39ac64..d24e26899f7 100644 --- a/src/vs/workbench/test/browser/quickAccess.test.ts +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -21,6 +21,9 @@ import { EditorsOrder } from '../../common/editor.js'; import { Range } from '../../../editor/common/core/range.js'; import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js'; +import { IContextKeyService, ContextKeyExpr } from '../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyService } from '../../../platform/contextkey/browser/contextKeyService.js'; +import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js'; suite('QuickAccess', () => { @@ -114,26 +117,81 @@ suite('QuickAccess', () => { test('registry', () => { const registry = (Registry.as(Extensions.Quickaccess)); const restore = (registry as QuickAccessRegistry).clear(); + const contextKeyService = instantiationService.get(IContextKeyService); - assert.ok(!registry.getQuickAccessProvider('test')); + assert.ok(!registry.getQuickAccessProvider('test', contextKeyService)); const disposables = new DisposableStore(); disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); - assert(registry.getQuickAccessProvider('') === providerDescriptorDefault); - assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + assert(registry.getQuickAccessProvider('', contextKeyService) === providerDescriptorDefault); + assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptorDefault); const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); - assert(registry.getQuickAccessProvider('test') === providerDescriptor1); + assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptor1); - const providers = registry.getQuickAccessProviders(); + const providers = registry.getQuickAccessProviders(contextKeyService); assert(providers.some(provider => provider.prefix === 'test')); disposable.dispose(); - assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + assert(registry.getQuickAccessProvider('test', contextKeyService) === providerDescriptorDefault); disposables.dispose(); - assert.ok(!registry.getQuickAccessProvider('test')); + assert.ok(!registry.getQuickAccessProvider('test', contextKeyService)); + + restore(); + }); + + test('registry - when condition', () => { + const registry = (Registry.as(Extensions.Quickaccess)); + const restore = (registry as QuickAccessRegistry).clear(); + + // Use real ContextKeyService that properly evaluates rules + const contextKeyService = disposables.add(new ContextKeyService(new TestConfigurationService())); + const localDisposables = new DisposableStore(); + + // Create a context key that starts as undefined (falsy) + const contextKey = contextKeyService.createKey('testQuickAccessContextKey', undefined); + + // Register a provider with a when condition that requires testQuickAccessContextKey to be truthy + const providerWithWhen = { + ctor: TestProvider1, + prefix: 'whentest', + helpEntries: [], + when: ContextKeyExpr.has('testQuickAccessContextKey') + }; + localDisposables.add(registry.registerQuickAccessProvider(providerWithWhen)); + + // Verify the expression works with the context key service + assert.strictEqual(contextKeyService.contextMatchesRules(providerWithWhen.when), false); + + // Provider with false when condition should not be found + assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), undefined); + + // Should not appear in the list of providers + let providers = registry.getQuickAccessProviders(contextKeyService); + assert.ok(!providers.some(p => p.prefix === 'whentest')); + + // Set the context key to true + contextKey.set(true); + + // Verify the expression now matches + assert.strictEqual(contextKeyService.contextMatchesRules(providerWithWhen.when), true); + + // Now the provider should be found + assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), providerWithWhen); + + // Should appear in the list of providers + providers = registry.getQuickAccessProviders(contextKeyService); + assert.ok(providers.some(p => p.prefix === 'whentest')); + + // Set context key back to undefined (falsy) + contextKey.set(undefined); + + // Provider should not be found again + assert.strictEqual(registry.getQuickAccessProvider('whentest', contextKeyService), undefined); + + localDisposables.dispose(); restore(); });