Save conflict: left hand diff editor does not update on changes to file (fixes #33792)

This commit is contained in:
Benjamin Pasero
2017-09-04 14:38:53 +02:00
parent 3d81fe63b3
commit de80c24728
3 changed files with 104 additions and 110 deletions
@@ -22,10 +22,10 @@ import { Action, IAction } from 'vs/base/common/actions';
import { MessageType, IInputValidator } from 'vs/base/browser/ui/inputbox/inputBox';
import { ITree, IHighlightEvent, IActionProvider } from 'vs/base/parts/tree/browser/tree';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { VIEWLET_ID } from 'vs/workbench/parts/files/common/files';
import { VIEWLET_ID, FileOnDiskContentProvider } from 'vs/workbench/parts/files/common/files';
import labels = require('vs/base/common/labels');
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService, IFileStat, FileChangeType } from 'vs/platform/files/common/files';
import { IFileService, IFileStat } from 'vs/platform/files/common/files';
import { toResource, IEditorIdentifier, EditorInput } from 'vs/workbench/common/editor';
import { FileStat, Model, NewStatPlaceholder } from 'vs/workbench/parts/files/common/explorerModel';
import { ExplorerView } from 'vs/workbench/parts/files/browser/views/explorerView';
@@ -42,16 +42,13 @@ import { IInstantiationService, IConstructorSignature2, ServicesAccessor } from
import { IMessageService, IMessageWithAction, IConfirmation, Severity, CancelAction } from 'vs/platform/message/common/message';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
import { IEditorViewState, IModel } from 'vs/editor/common/editorCommon';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { withFocusedFilesExplorer, revealInOSCommand, revealInExplorerCommand, copyPathCommand } from 'vs/workbench/parts/files/browser/fileCommands';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IMode } from 'vs/editor/common/modes';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
export interface IEditableData {
action: IAction;
@@ -2013,7 +2010,7 @@ export function getWellFormedFileName(filename: string): string {
return filename;
}
export class CompareWithSavedAction extends Action implements ITextModelContentProvider {
export class CompareWithSavedAction extends Action {
public static ID = 'workbench.files.action.compareWithSaved';
public static LABEL = nls.localize('compareWithSaved', "Compare Active File with Saved");
@@ -2021,24 +2018,25 @@ export class CompareWithSavedAction extends Action implements ITextModelContentP
private static SCHEME = 'showModifications';
private resource: URI;
private fileWatcher: IDisposable;
private toDispose: IDisposable[];
constructor(
id: string,
label: string,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@ITextFileService private textFileService: ITextFileService,
@IFileService private fileService: IFileService,
@IMessageService messageService: IMessageService,
@IModeService private modeService: IModeService,
@IModelService private modelService: IModelService,
@IInstantiationService instantiationService: IInstantiationService,
@ITextModelService textModelService: ITextModelService
) {
super(id, label);
textModelService.registerTextModelContentProvider(CompareWithSavedAction.SCHEME, this);
this.enabled = true;
this.toDispose = [];
const provider = instantiationService.createInstance(FileOnDiskContentProvider);
this.toDispose.push(provider);
const registrationDisposal = textModelService.registerTextModelContentProvider(CompareWithSavedAction.SCHEME, provider);
this.toDispose.push(registrationDisposal);
}
public setResource(resource: URI) {
@@ -2063,61 +2061,10 @@ export class CompareWithSavedAction extends Action implements ITextModelContentP
return TPromise.as(true);
}
public provideTextContent(resource: URI): TPromise<IModel> {
// Make sure our file from disk is resolved up to date
return this.resolveEditorModel(resource).then(codeEditorModel => {
// Make sure to keep contents on disk up to date when it changes
if (!this.fileWatcher) {
this.fileWatcher = this.fileService.onFileChanges(changes => {
if (changes.contains(resource, FileChangeType.UPDATED)) {
this.resolveEditorModel(resource, false /* do not create if missing */).done(null, errors.onUnexpectedError); // update model when resource changes
}
});
const disposeListener = codeEditorModel.onWillDispose(() => {
disposeListener.dispose();
this.fileWatcher.dispose();
this.fileWatcher = void 0;
});
}
return codeEditorModel;
});
}
private resolveEditorModel(resource: URI, createAsNeeded = true): TPromise<IModel> {
const fileOnDiskResource = URI.file(resource.fsPath);
return this.textFileService.resolveTextContent(fileOnDiskResource).then(content => {
let codeEditorModel = this.modelService.getModel(resource);
if (codeEditorModel) {
this.modelService.updateModel(codeEditorModel, content.value);
} else if (createAsNeeded) {
const fileOnDiskModel = this.modelService.getModel(fileOnDiskResource);
let mode: TPromise<IMode>;
if (fileOnDiskModel) {
mode = this.modeService.getOrCreateMode(fileOnDiskModel.getModeId());
} else {
mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(fileOnDiskResource.fsPath);
}
codeEditorModel = this.modelService.createModel(content.value, mode, resource);
}
return codeEditorModel;
});
}
public dispose(): void {
super.dispose();
if (this.fileWatcher) {
this.fileWatcher.dispose();
this.fileWatcher = void 0;
}
this.toDispose = dispose(this.toDispose);
}
}
@@ -18,25 +18,22 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
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';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { IModel } from 'vs/editor/common/editorCommon';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
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';
import { IMode } from 'vs/editor/common/modes';
import { FileOnDiskContentProvider } from 'vs/workbench/parts/files/common/files';
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 {
export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContribution {
private messages: ResourceMap<() => void>;
private toUnbind: IDisposable[];
private conflictResolutionContext: IContextKey<boolean>;
@@ -44,22 +41,21 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi
constructor(
@IMessageService private messageService: IMessageService,
@ITextFileService private textFileService: ITextFileService,
@ITextModelService private textModelResolverService: ITextModelService,
@IModelService private modelService: IModelService,
@IModeService private modeService: IModeService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IContextKeyService contextKeyService: IContextKeyService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@ITextModelService textModelService: ITextModelService
) {
this.toUnbind = [];
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
// is stored on disk as opposed to using the file:// scheme that will return a dirty buffer
// if there is one.
this.textModelResolverService.registerTextModelContentProvider(CONFLICT_RESOLUTION_SCHEME, this);
const provider = instantiationService.createInstance(FileOnDiskContentProvider);
this.toUnbind.push(provider);
const registrationDisposal = textModelService.registerTextModelContentProvider(CONFLICT_RESOLUTION_SCHEME, provider);
this.toUnbind.push(registrationDisposal);
// Hook into model
TextFileEditorModel.setSaveErrorHandler(this);
@@ -67,31 +63,6 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi
this.registerListeners();
}
public provideTextContent(resource: URI): TPromise<IModel> {
const fileOnDiskResource = URI.file(resource.fsPath);
// Make sure our file from disk is resolved up to date
return this.textFileService.resolveTextContent(fileOnDiskResource).then(content => {
let codeEditorModel = this.modelService.getModel(resource);
if (codeEditorModel) {
this.modelService.updateModel(codeEditorModel, content.value);
} else {
const fileOnDiskModel = this.modelService.getModel(fileOnDiskResource);
let mode: TPromise<IMode>;
if (fileOnDiskModel) {
mode = this.modeService.getOrCreateMode(fileOnDiskModel.getModeId());
} else {
mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(fileOnDiskResource.fsPath);
}
codeEditorModel = this.modelService.createModel(content.value, mode, resource);
}
return codeEditorModel;
});
}
public getId(): string {
return 'vs.files.saveerrorhandler';
}
+78 -2
View File
@@ -7,9 +7,18 @@
import URI from 'vs/base/common/uri';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { IFilesConfiguration, FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { FileStat, OpenEditor } from 'vs/workbench/parts/files/common/explorerModel';
import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IModel } from 'vs/editor/common/editorCommon';
import { IMode } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
/**
* Explorer viewlet id.
@@ -104,4 +113,71 @@ export const SortOrderConfiguration = {
MODIFIED: 'modified'
};
export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified';
export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified';
export class FileOnDiskContentProvider implements ITextModelContentProvider {
private fileWatcher: IDisposable;
constructor(
@ITextFileService private textFileService: ITextFileService,
@IFileService private fileService: IFileService,
@IModeService private modeService: IModeService,
@IModelService private modelService: IModelService
) {
}
public provideTextContent(resource: URI): TPromise<IModel> {
// Make sure our file from disk is resolved up to date
return this.resolveEditorModel(resource).then(codeEditorModel => {
// Make sure to keep contents on disk up to date when it changes
if (!this.fileWatcher) {
this.fileWatcher = this.fileService.onFileChanges(changes => {
if (changes.contains(resource, FileChangeType.UPDATED)) {
this.resolveEditorModel(resource, false /* do not create if missing */).done(null, onUnexpectedError); // update model when resource changes
}
});
const disposeListener = codeEditorModel.onWillDispose(() => {
disposeListener.dispose();
this.fileWatcher.dispose();
this.fileWatcher = void 0;
});
}
return codeEditorModel;
});
}
private resolveEditorModel(resource: URI, createAsNeeded = true): TPromise<IModel> {
const fileOnDiskResource = URI.file(resource.fsPath);
return this.textFileService.resolveTextContent(fileOnDiskResource).then(content => {
let codeEditorModel = this.modelService.getModel(resource);
if (codeEditorModel) {
this.modelService.updateModel(codeEditorModel, content.value);
} else if (createAsNeeded) {
const fileOnDiskModel = this.modelService.getModel(fileOnDiskResource);
let mode: TPromise<IMode>;
if (fileOnDiskModel) {
mode = this.modeService.getOrCreateMode(fileOnDiskModel.getModeId());
} else {
mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(fileOnDiskResource.fsPath);
}
codeEditorModel = this.modelService.createModel(content.value, mode, resource);
}
return codeEditorModel;
});
}
public dispose(): void {
if (this.fileWatcher) {
this.fileWatcher.dispose();
this.fileWatcher = void 0;
}
}
}