Path: blob/main/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Registry } from '../../../../../platform/registry/common/platform.js';6import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../../common/contributions.js';7import { IBulkEditService, ResourceEdit } from '../../../../../editor/browser/services/bulkEditService.js';8import { BulkEditPane } from './bulkEditPane.js';9import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from '../../../../common/views.js';10import { IViewsService } from '../../../../services/views/common/viewsService.js';11import { FocusedViewContext } from '../../../../common/contextkeys.js';12import { localize, localize2 } from '../../../../../nls.js';13import { ViewPaneContainer } from '../../../../browser/parts/views/viewPaneContainer.js';14import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';15import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';16import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';17import { KeyMod, KeyCode } from '../../../../../base/common/keyCodes.js';18import { WorkbenchListFocusContextKey } from '../../../../../platform/list/browser/listService.js';19import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js';20import { MenuId, registerAction2, Action2 } from '../../../../../platform/actions/common/actions.js';21import { EditorResourceAccessor, SideBySideEditor } from '../../../../common/editor.js';22import { EditorInput } from '../../../../common/editor/editorInput.js';23import type { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';24import { CancellationTokenSource } from '../../../../../base/common/cancellation.js';25import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';26import Severity from '../../../../../base/common/severity.js';27import { Codicon } from '../../../../../base/common/codicons.js';28import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js';29import { IPaneCompositePartService } from '../../../../services/panecomposite/browser/panecomposite.js';3031async function getBulkEditPane(viewsService: IViewsService): Promise<BulkEditPane | undefined> {32const view = await viewsService.openView(BulkEditPane.ID, true);33if (view instanceof BulkEditPane) {34return view;35}36return undefined;37}3839class UXState {4041private readonly _activePanel: string | undefined;4243constructor(44@IPaneCompositePartService private readonly _paneCompositeService: IPaneCompositePartService,45@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,46) {47this._activePanel = _paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel)?.getId();48}4950async restore(panels: boolean, editors: boolean): Promise<void> {5152// (1) restore previous panel53if (panels) {54if (typeof this._activePanel === 'string') {55await this._paneCompositeService.openPaneComposite(this._activePanel, ViewContainerLocation.Panel);56} else {57this._paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Panel);58}59}6061// (2) close preview editors62if (editors) {63for (const group of this._editorGroupsService.groups) {64const previewEditors: EditorInput[] = [];65for (const input of group.editors) {6667const resource = EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY });68if (resource?.scheme === BulkEditPane.Schema) {69previewEditors.push(input);70}71}7273if (previewEditors.length) {74group.closeEditors(previewEditors, { preserveFocus: true });75}76}77}78}79}8081class PreviewSession {82constructor(83readonly uxState: UXState,84readonly cts: CancellationTokenSource = new CancellationTokenSource(),85) { }86}8788class BulkEditPreviewContribution {8990static readonly ID = 'workbench.contrib.bulkEditPreview';9192static readonly ctxEnabled = new RawContextKey('refactorPreview.enabled', false);9394private readonly _ctxEnabled: IContextKey<boolean>;9596private _activeSession: PreviewSession | undefined;9798constructor(99@IPaneCompositePartService private readonly _paneCompositeService: IPaneCompositePartService,100@IViewsService private readonly _viewsService: IViewsService,101@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,102@IDialogService private readonly _dialogService: IDialogService,103@IBulkEditService bulkEditService: IBulkEditService,104@IContextKeyService contextKeyService: IContextKeyService,105) {106bulkEditService.setPreviewHandler(edits => this._previewEdit(edits));107this._ctxEnabled = BulkEditPreviewContribution.ctxEnabled.bindTo(contextKeyService);108}109110private async _previewEdit(edits: ResourceEdit[]): Promise<ResourceEdit[]> {111this._ctxEnabled.set(true);112113const uxState = this._activeSession?.uxState ?? new UXState(this._paneCompositeService, this._editorGroupsService);114const view = await getBulkEditPane(this._viewsService);115if (!view) {116this._ctxEnabled.set(false);117return edits;118}119120// check for active preview session and let the user decide121if (view.hasInput()) {122const { confirmed } = await this._dialogService.confirm({123type: Severity.Info,124message: localize('overlap', "Another refactoring is being previewed."),125detail: localize('detail', "Press 'Continue' to discard the previous refactoring and continue with the current refactoring."),126primaryButton: localize({ key: 'continue', comment: ['&& denotes a mnemonic'] }, "&&Continue")127});128129if (!confirmed) {130return [];131}132}133134// session135let session: PreviewSession;136if (this._activeSession) {137await this._activeSession.uxState.restore(false, true);138this._activeSession.cts.dispose(true);139session = new PreviewSession(uxState);140} else {141session = new PreviewSession(uxState);142}143this._activeSession = session;144145// the actual work...146try {147148return await view.setInput(edits, session.cts.token) ?? [];149150} finally {151// restore UX state152if (this._activeSession === session) {153await this._activeSession.uxState.restore(true, true);154this._activeSession.cts.dispose();155this._ctxEnabled.set(false);156this._activeSession = undefined;157}158}159}160}161162163// CMD: accept164registerAction2(class ApplyAction extends Action2 {165166constructor() {167super({168id: 'refactorPreview.apply',169title: localize2('apply', "Apply Refactoring"),170category: localize2('cat', "Refactor Preview"),171icon: Codicon.check,172precondition: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, BulkEditPane.ctxHasCheckedChanges),173menu: [{174id: MenuId.BulkEditContext,175order: 1176}],177keybinding: {178weight: KeybindingWeight.EditorContrib - 10,179when: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, FocusedViewContext.isEqualTo(BulkEditPane.ID)),180primary: KeyMod.CtrlCmd + KeyCode.Enter,181}182});183}184185async run(accessor: ServicesAccessor): Promise<void> {186const viewsService = accessor.get(IViewsService);187const view = await getBulkEditPane(viewsService);188view?.accept();189}190});191192// CMD: discard193registerAction2(class DiscardAction extends Action2 {194195constructor() {196super({197id: 'refactorPreview.discard',198title: localize2('Discard', "Discard Refactoring"),199category: localize2('cat', "Refactor Preview"),200icon: Codicon.clearAll,201precondition: BulkEditPreviewContribution.ctxEnabled,202menu: [{203id: MenuId.BulkEditContext,204order: 2205}]206});207}208209async run(accessor: ServicesAccessor): Promise<void> {210const viewsService = accessor.get(IViewsService);211const view = await getBulkEditPane(viewsService);212view?.discard();213}214});215216217// CMD: toggle change218registerAction2(class ToggleAction extends Action2 {219220constructor() {221super({222id: 'refactorPreview.toggleCheckedState',223title: localize2('toogleSelection', "Toggle Change"),224category: localize2('cat', "Refactor Preview"),225precondition: BulkEditPreviewContribution.ctxEnabled,226keybinding: {227weight: KeybindingWeight.WorkbenchContrib,228when: WorkbenchListFocusContextKey,229primary: KeyCode.Space,230},231menu: {232id: MenuId.BulkEditContext,233group: 'navigation'234}235});236}237238async run(accessor: ServicesAccessor): Promise<void> {239const viewsService = accessor.get(IViewsService);240const view = await getBulkEditPane(viewsService);241view?.toggleChecked();242}243});244245246// CMD: toggle category247registerAction2(class GroupByFile extends Action2 {248249constructor() {250super({251id: 'refactorPreview.groupByFile',252title: localize2('groupByFile', "Group Changes By File"),253category: localize2('cat', "Refactor Preview"),254icon: Codicon.ungroupByRefType,255precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile.negate(), BulkEditPreviewContribution.ctxEnabled),256menu: [{257id: MenuId.BulkEditTitle,258when: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile.negate()),259group: 'navigation',260order: 3,261}]262});263}264265async run(accessor: ServicesAccessor): Promise<void> {266const viewsService = accessor.get(IViewsService);267const view = await getBulkEditPane(viewsService);268view?.groupByFile();269}270});271272registerAction2(class GroupByType extends Action2 {273274constructor() {275super({276id: 'refactorPreview.groupByType',277title: localize2('groupByType', "Group Changes By Type"),278category: localize2('cat', "Refactor Preview"),279icon: Codicon.groupByRefType,280precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile, BulkEditPreviewContribution.ctxEnabled),281menu: [{282id: MenuId.BulkEditTitle,283when: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile),284group: 'navigation',285order: 3286}]287});288}289290async run(accessor: ServicesAccessor): Promise<void> {291const viewsService = accessor.get(IViewsService);292const view = await getBulkEditPane(viewsService);293view?.groupByType();294}295});296297registerAction2(class ToggleGrouping extends Action2 {298299constructor() {300super({301id: 'refactorPreview.toggleGrouping',302title: localize2('groupByType', "Group Changes By Type"),303category: localize2('cat', "Refactor Preview"),304icon: Codicon.listTree,305toggled: BulkEditPane.ctxGroupByFile.negate(),306precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPreviewContribution.ctxEnabled),307menu: [{308id: MenuId.BulkEditContext,309order: 3310}]311});312}313314async run(accessor: ServicesAccessor): Promise<void> {315const viewsService = accessor.get(IViewsService);316const view = await getBulkEditPane(viewsService);317view?.toggleGrouping();318}319});320321registerWorkbenchContribution2(322BulkEditPreviewContribution.ID, BulkEditPreviewContribution, WorkbenchPhase.BlockRestore323);324325const refactorPreviewViewIcon = registerIcon('refactor-preview-view-icon', Codicon.lightbulb, localize('refactorPreviewViewIcon', 'View icon of the refactor preview view.'));326327const container = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({328id: BulkEditPane.ID,329title: localize2('panel', "Refactor Preview"),330hideIfEmpty: true,331ctorDescriptor: new SyncDescriptor(332ViewPaneContainer,333[BulkEditPane.ID, { mergeViewWithContainerWhenSingleView: true }]334),335icon: refactorPreviewViewIcon,336storageId: BulkEditPane.ID337}, ViewContainerLocation.Panel);338339Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{340id: BulkEditPane.ID,341name: localize2('panel', "Refactor Preview"),342when: BulkEditPreviewContribution.ctxEnabled,343ctorDescriptor: new SyncDescriptor(BulkEditPane),344containerIcon: refactorPreviewViewIcon,345}], container);346347348