UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

217 lines (216 loc) 8.95 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; import { DeferredPromise } from '@sussudio/base/common/async.mjs'; import { CancellationTokenSource } from '@sussudio/base/common/cancellation.mjs'; import { once } from '@sussudio/base/common/functional.mjs'; import { Disposable, DisposableStore, toDisposable } from '@sussudio/base/common/lifecycle.mjs'; import { IInstantiationService } from '../../instantiation/common/instantiation.mjs'; import { DefaultQuickAccessFilterValue, Extensions } from '../common/quickAccess.mjs'; import { IQuickInputService, ItemActivation } from '../common/quickInput.mjs'; import { Registry } from '../../registry/common/platform.mjs'; let QuickAccessController = class QuickAccessController extends Disposable { quickInputService; instantiationService; registry = Registry.as(Extensions.Quickaccess); mapProviderToDescriptor = new Map(); lastAcceptedPickerValues = new Map(); visibleQuickAccess = undefined; constructor(quickInputService, instantiationService) { super(); this.quickInputService = quickInputService; this.instantiationService = instantiationService; } pick(value = '', options) { return this.doShowOrPick(value, true, options); } show(value = '', options) { this.doShowOrPick(value, false, options); } doShowOrPick(value, pick, options) { // Find provider for the value to show const [provider, descriptor] = this.getOrInstantiateProvider(value); // Return early if quick access is already showing on that same prefix const visibleQuickAccess = this.visibleQuickAccess; const visibleDescriptor = visibleQuickAccess?.descriptor; if (visibleQuickAccess && descriptor && visibleDescriptor === descriptor) { // Apply value only if it is more specific than the prefix // from the provider and we are not instructed to preserve if (value !== descriptor.prefix && !options?.preserveValue) { visibleQuickAccess.picker.value = value; } // Always adjust selection this.adjustValueSelection(visibleQuickAccess.picker, descriptor, options); return; } // Rewrite the filter value based on certain rules unless disabled if (descriptor && !options?.preserveValue) { let newValue = undefined; // If we have a visible provider with a value, take it's filter value but // rewrite to new provider prefix in case they differ if (visibleQuickAccess && visibleDescriptor && visibleDescriptor !== descriptor) { const newValueCandidateWithoutPrefix = visibleQuickAccess.value.substr(visibleDescriptor.prefix.length); if (newValueCandidateWithoutPrefix) { newValue = `${descriptor.prefix}${newValueCandidateWithoutPrefix}`; } } // Otherwise, take a default value as instructed if (!newValue) { const defaultFilterValue = provider?.defaultFilterValue; if (defaultFilterValue === DefaultQuickAccessFilterValue.LAST) { newValue = this.lastAcceptedPickerValues.get(descriptor); } else if (typeof defaultFilterValue === 'string') { newValue = `${descriptor.prefix}${defaultFilterValue}`; } } if (typeof newValue === 'string') { value = newValue; } } // Create a picker for the provider to use with the initial value // and adjust the filtering to exclude the prefix from filtering const disposables = new DisposableStore(); const picker = disposables.add(this.quickInputService.createQuickPick()); picker.value = value; this.adjustValueSelection(picker, descriptor, options); picker.placeholder = descriptor?.placeholder; picker.quickNavigate = options?.quickNavigateConfiguration; picker.hideInput = !!picker.quickNavigate && !visibleQuickAccess; // only hide input if there was no picker opened already if (typeof options?.itemActivation === 'number' || options?.quickNavigateConfiguration) { picker.itemActivation = options?.itemActivation ?? ItemActivation.SECOND /* quick nav is always second */; } picker.contextKey = descriptor?.contextKey; picker.filterValue = (value) => value.substring(descriptor ? descriptor.prefix.length : 0); if (descriptor?.placeholder) { picker.ariaLabel = descriptor?.placeholder; } // Pick mode: setup a promise that can be resolved // with the selected items and prevent execution let pickPromise = undefined; if (pick) { pickPromise = new DeferredPromise(); disposables.add( once(picker.onWillAccept)((e) => { e.veto(); picker.hide(); }), ); } // Register listeners disposables.add(this.registerPickerListeners(picker, provider, descriptor, value, options?.providerOptions)); // Ask provider to fill the picker as needed if we have one // and pass over a cancellation token that will indicate when // the picker is hiding without a pick being made. const cts = disposables.add(new CancellationTokenSource()); if (provider) { disposables.add(provider.provide(picker, cts.token, options?.providerOptions)); } // Finally, trigger disposal and cancellation when the picker // hides depending on items selected or not. once(picker.onDidHide)(() => { if (picker.selectedItems.length === 0) { cts.cancel(); } // Start to dispose once picker hides disposables.dispose(); // Resolve pick promise with selected items pickPromise?.complete(picker.selectedItems.slice(0)); }); // Finally, show the picker. This is important because a provider // may not call this and then our disposables would leak that rely // on the onDidHide event. picker.show(); // Pick mode: return with promise if (pick) { return pickPromise?.p; } } adjustValueSelection(picker, descriptor, options) { let valueSelection; // Preserve: just always put the cursor at the end if (options?.preserveValue) { valueSelection = [picker.value.length, picker.value.length]; } // Otherwise: select the value up until the prefix else { valueSelection = [descriptor?.prefix.length ?? 0, picker.value.length]; } picker.valueSelection = valueSelection; } registerPickerListeners(picker, provider, descriptor, value, providerOptions) { const disposables = new DisposableStore(); // Remember as last visible picker and clean up once picker get's disposed const visibleQuickAccess = (this.visibleQuickAccess = { picker, descriptor, value }); disposables.add( toDisposable(() => { if (visibleQuickAccess === this.visibleQuickAccess) { this.visibleQuickAccess = undefined; } }), ); // Whenever the value changes, check if the provider has // changed and if so - re-create the picker from the beginning disposables.add( picker.onDidChangeValue((value) => { const [providerForValue] = this.getOrInstantiateProvider(value); if (providerForValue !== provider) { this.show(value, { // do not rewrite value from user typing! preserveValue: true, // persist the value of the providerOptions from the original showing providerOptions, }); } else { visibleQuickAccess.value = value; // remember the value in our visible one } }), ); // Remember picker input for future use when accepting if (descriptor) { disposables.add( picker.onDidAccept(() => { this.lastAcceptedPickerValues.set(descriptor, picker.value); }), ); } return disposables; } getOrInstantiateProvider(value) { const providerDescriptor = this.registry.getQuickAccessProvider(value); if (!providerDescriptor) { return [undefined, undefined]; } let provider = this.mapProviderToDescriptor.get(providerDescriptor); if (!provider) { provider = this.instantiationService.createInstance(providerDescriptor.ctor); this.mapProviderToDescriptor.set(providerDescriptor, provider); } return [provider, providerDescriptor]; } }; QuickAccessController = __decorate( [__param(0, IQuickInputService), __param(1, IInstantiationService)], QuickAccessController, ); export { QuickAccessController };