From f488a0590217c2fa5f08526146f113a9e92c29bf Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 3 Apr 2017 07:03:22 +0200 Subject: [PATCH] adopt menu service and commands for save conflict actions (for #23420) --- .../files/browser/fileActions.contribution.ts | 50 +++----- .../parts/files/browser/media/fileactions.css | 8 +- .../parts/files/browser/saveErrorHandler.ts | 119 ++++++++++-------- 3 files changed, 89 insertions(+), 88 deletions(-) diff --git a/src/vs/workbench/parts/files/browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/browser/fileActions.contribution.ts index 348ad26288894..ef60615a0292f 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.contribution.ts @@ -10,9 +10,8 @@ import { Action, IAction } from 'vs/base/common/actions'; import { isMacintosh } from 'vs/base/common/platform'; import { ActionItem, BaseActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actionBarRegistry'; -import { IEditorInputActionContext, IEditorInputAction, EditorInputActionContributor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { GlobalNewUntitledFileAction, SaveFileAsAction, OpenFileAction, ShowOpenedFileInNewWindow, CopyPathAction, GlobalCopyPathAction, RevealInOSAction, GlobalRevealInOSAction, pasteIntoFocusedFilesExplorerViewItem, FocusOpenEditorsView, FocusFilesExplorer, GlobalCompareResourcesAction, GlobalNewFileAction, GlobalNewFolderAction, RevertFileAction, SaveFilesAction, SaveAllAction, SaveFileAction, MoveFileToTrashAction, TriggerRenameFileAction, PasteFileAction, CopyFileAction, SelectResourceForCompareAction, CompareResourcesAction, NewFolderAction, NewFileAction, OpenToSideAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView } from 'vs/workbench/parts/files/browser/fileActions'; -import { RevertLocalChangesAction, AcceptLocalChangesAction, CONFLICT_RESOLUTION_SCHEME } from 'vs/workbench/parts/files/browser/saveErrorHandler'; +import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/parts/files/browser/saveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -20,8 +19,6 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { FileStat } from 'vs/workbench/parts/files/common/explorerViewModel'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/fileActions'; import { copyFocusedFilesExplorerViewItem, revealInOSFocusedFilesExplorerItem, openFocusedExplorerItemSideBySideCommand, copyPathOfFocusedExplorerItem, copyPathCommand, revealInExplorerCommand, revealInOSCommand, openFolderPickerCommand, openWindowCommand, openFileInNewWindowCommand, deleteFocusedFilesExplorerViewItemCommand, moveFocusedFilesExplorerViewItemToTrashCommand, renameFocusedFilesExplorerViewItemCommand } from 'vs/workbench/parts/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; @@ -174,38 +171,11 @@ class ExplorerViewersActionContributor extends ActionBarContributor { } } -class ConflictResolutionActionContributor extends EditorInputActionContributor { - - constructor( @IInstantiationService private instantiationService: IInstantiationService) { - super(); - } - - public hasActionsForEditorInput(context: IEditorInputActionContext): boolean { - if (context.input instanceof DiffEditorInput && context.input.originalInput instanceof ResourceEditorInput) { - const resource = context.input.originalInput.getResource(); - - return resource && resource.scheme === CONFLICT_RESOLUTION_SCHEME; // scheme used for conflict resolution - } - - return false; - } - - public getActionsForEditorInput(context: IEditorInputActionContext): IEditorInputAction[] { - return [ - this.instantiationService.createInstance(AcceptLocalChangesAction), - this.instantiationService.createInstance(RevertLocalChangesAction) - ]; - } -} - // Contribute to Viewers that show Files const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, FilesViewerActionContributor); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewersActionContributor); -// Contribute to Conflict Editor Inputs -actionBarRegistry.registerActionBarContributor(Scope.EDITOR, ConflictResolutionActionContributor); - // Contribute Global Actions const category = nls.localize('filesCategory', "Files"); @@ -340,4 +310,22 @@ function appendEditorTitleContextMenuItem(id: string, title: string, command: IC when: ContextKeyExpr.equals('resourceScheme', 'file'), group: '2_files' }); +} + +// Editor Title Menu for Conflict Resolution +appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use local changes and overwrite disk contents"), 'save-conflict-action-accept-changes', -10, acceptLocalChangesCommand); +appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard local changes and revert to content on disk"), 'save-conflict-action-revert-changes', -9, revertLocalChangesCommand); + +function appendSaveConflictEditorTitleAction(id: string, title: string, iconClass: string, order: number, command: ICommandHandler): void { + + // Command + CommandsRegistry.registerCommand(id, command); + + // Action + MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { id, title, iconClass }, + when: ContextKeyExpr.equals(CONFLICT_RESOLUTION_CONTEXT, true), + group: 'navigation', + order + }); } \ No newline at end of file diff --git a/src/vs/workbench/parts/files/browser/media/fileactions.css b/src/vs/workbench/parts/files/browser/media/fileactions.css index 5f22d89f243ff..f064ef66e0c9f 100644 --- a/src/vs/workbench/parts/files/browser/media/fileactions.css +++ b/src/vs/workbench/parts/files/browser/media/fileactions.css @@ -75,19 +75,19 @@ background-image: url('split-editor-horizontal-inverse.svg'); } -.monaco-workbench .conflict-editor-action.accept-changes { +.monaco-workbench .save-conflict-action-accept-changes { background: url('check.svg') center center no-repeat; } -.vs-dark .monaco-workbench .conflict-editor-action.accept-changes { +.vs-dark .monaco-workbench .save-conflict-action-accept-changes { background: url('check-inverse.svg') center center no-repeat; } -.monaco-workbench .conflict-editor-action.revert-changes { +.monaco-workbench .save-conflict-action-revert-changes { background: url('undo.svg') center center no-repeat; } -.vs-dark .monaco-workbench .conflict-editor-action.revert-changes { +.vs-dark .monaco-workbench .save-conflict-action-revert-changes { background: url('undo-inverse.svg') center center no-repeat; } diff --git a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts index d4c893bda6dce..6eff064a5ecca 100644 --- a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts @@ -11,13 +11,12 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import paths = require('vs/base/common/paths'); import { Action } from 'vs/base/common/actions'; import URI from 'vs/base/common/uri'; -import { EditorInputAction } from 'vs/workbench/browser/parts/editor/baseEditor'; import { SaveFileAsAction, RevertFileAction, SaveFileAction } from 'vs/workbench/parts/files/browser/fileActions'; import { IFileOperationResult, FileOperationResult } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, IMessageWithAction, Severity, CancelAction } from 'vs/platform/message/common/message'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -26,15 +25,20 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IModel } from 'vs/editor/common/editorCommon'; -import { toResource } from 'vs/workbench/common/editor'; import { ResourceMap } from 'vs/base/common/map'; +import { IEditorGroupService } from "vs/workbench/services/group/common/groupService"; +import { DiffEditorInput } from "vs/workbench/common/editor/diffEditorInput"; +import { ResourceEditorInput } from "vs/workbench/common/editor/resourceEditorInput"; +import { IContextKeyService, IContextKey, RawContextKey } from "vs/platform/contextkey/common/contextkey"; +export const CONFLICT_RESOLUTION_CONTEXT = 'saveConflictResolutionContext'; export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; // A handler for save error happening with conflict resolution actions export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContribution, ITextModelContentProvider { private messages: ResourceMap<() => void>; private toUnbind: IDisposable[]; + private conflictResolutionContext: IContextKey; constructor( @IMessageService private messageService: IMessageService, @@ -42,9 +46,13 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi @ITextModelResolverService private textModelResolverService: ITextModelResolverService, @IModelService private modelService: IModelService, @IModeService private modeService: IModeService, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService ) { this.messages = new ResourceMap<() => void>(); + this.conflictResolutionContext = new RawContextKey(CONFLICT_RESOLUTION_CONTEXT, false).bindTo(contextKeyService); this.toUnbind = []; // Register as text model content provider that supports to load a resource as it actually @@ -80,6 +88,19 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi private registerListeners(): void { this.toUnbind.push(this.textFileService.models.onModelSaved(e => this.onFileSavedOrReverted(e.resource))); this.toUnbind.push(this.textFileService.models.onModelReverted(e => this.onFileSavedOrReverted(e.resource))); + this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + } + + private onEditorsChanged(): void { + let isActiveEditorSaveConflictResolution = false; + const activeEditor = this.editorService.getActiveEditor(); + + if (activeEditor && activeEditor.input instanceof DiffEditorInput && activeEditor.input.originalInput instanceof ResourceEditorInput) { + const resource = activeEditor.input.originalInput.getResource(); + isActiveEditorSaveConflictResolution = resource && resource.scheme === CONFLICT_RESOLUTION_SCHEME; + } + + this.conflictResolutionContext.set(isActiveEditorSaveConflictResolution); } private onFileSavedOrReverted(resource: URI): void { @@ -217,72 +238,64 @@ class ResolveSaveConflictMessage implements IMessageWithAction { } } -// Accept changes to resolve a conflicting edit -export class AcceptLocalChangesAction extends EditorInputAction { +export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { + const editorService = accessor.get(IWorkbenchEditorService); + const resolverService = accessor.get(ITextModelResolverService); - constructor( - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @ITextModelResolverService private resolverService: ITextModelResolverService - ) { - super('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use local changes and overwrite disk contents"), 'conflict-editor-action accept-changes'); - } + const editor = editorService.getActiveEditor(); + const input = editor.input; + const position = editor.position; - public run(): TPromise { - return this.resolverService.createModelReference(toResource(this.input, { supportSideBySide: true })).then(reference => { - const model = reference.object as ITextFileEditorModel; - const localModelValue = model.getValue(); + resolverService.createModelReference(resource).then(reference => { + const model = reference.object as ITextFileEditorModel; + const localModelValue = model.getValue(); - clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions + clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions - // revert to be able to save - return model.revert().then(() => { + // revert to be able to save + return model.revert().then(() => { - // Restore user value - model.textEditorModel.setValue(localModelValue); + // Restore user value + model.textEditorModel.setValue(localModelValue); - // Trigger save - return model.save().then(() => { + // Trigger save + return model.save().then(() => { - // Reopen file input - return this.editorService.openEditor({ resource: model.getResource() }, this.position).then(() => { + // Reopen file input + return editorService.openEditor({ resource: model.getResource() }, position).then(() => { - // Clean up - this.input.dispose(); - reference.dispose(); - }); + // Clean up + input.dispose(); + reference.dispose(); }); }); }); - } -} + }); +}; -// Revert changes to resolve a conflicting edit -export class RevertLocalChangesAction extends EditorInputAction { +export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: URI) => { + const editorService = accessor.get(IWorkbenchEditorService); + const resolverService = accessor.get(ITextModelResolverService); - constructor( - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @ITextModelResolverService private resolverService: ITextModelResolverService - ) { - super('workbench.action.files.revert', nls.localize('revertLocalChanges', "Discard local changes and revert to content on disk"), 'conflict-editor-action revert-changes'); - } + const editor = editorService.getActiveEditor(); + const input = editor.input; + const position = editor.position; - public run(): TPromise { - return this.resolverService.createModelReference(toResource(this.input, { supportSideBySide: true })).then(reference => { - const model = reference.object as ITextFileEditorModel; + resolverService.createModelReference(resource).then(reference => { + const model = reference.object as ITextFileEditorModel; - clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions + clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions - // Revert on model - return model.revert().then(() => { + // Revert on model + return model.revert().then(() => { - // Reopen file input - return this.editorService.openEditor({ resource: model.getResource() }, this.position).then(() => { + // Reopen file input + return editorService.openEditor({ resource: model.getResource() }, position).then(() => { - // Clean up - this.input.dispose(); - reference.dispose(); - }); + // Clean up + input.dispose(); + reference.dispose(); }); }); - } -} \ No newline at end of file + }); +}; \ No newline at end of file