Skip to content

Commit

Permalink
adopt menu service and commands for save conflict actions (for #23420)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Apr 3, 2017
1 parent 6f42a23 commit f488a05
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 88 deletions.
50 changes: 19 additions & 31 deletions src/vs/workbench/parts/files/browser/fileActions.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,15 @@ 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';
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';
Expand Down Expand Up @@ -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<IActionBarRegistry>(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");

Expand Down Expand Up @@ -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
});
}
8 changes: 4 additions & 4 deletions src/vs/workbench/parts/files/browser/media/fileactions.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
119 changes: 66 additions & 53 deletions src/vs/workbench/parts/files/browser/saveErrorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -26,25 +25,34 @@ 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<boolean>;

constructor(
@IMessageService private messageService: IMessageService,
@ITextFileService private textFileService: ITextFileService,
@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<boolean>(CONFLICT_RESOLUTION_CONTEXT, false).bindTo(contextKeyService);
this.toUnbind = [];

// Register as text model content provider that supports to load a resource as it actually
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<void> {
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<void> {
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();
});
});
}
}
});
};

0 comments on commit f488a05

Please sign in to comment.