From 92dd82088f0a18824d94c6928d52ba1b436041f1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Nov 2016 10:24:03 +0100 Subject: [PATCH 01/28] wip: dirtydiff as a workbench concept --- .../electron-browser/workbench.main.ts | 2 + .../workbench/electron-browser/workbench.ts | 5 + .../git/browser/gitWorkbenchContributions.ts | 342 ------------------ .../parts/git/common/gitDirtyDiff.ts | 80 ++++ .../git/electron-browser/git.contribution.ts | 11 +- .../parts/scm/browser/dirtydiffDecorator.ts | 218 +++++++++++ .../parts/scm/browser/scm.contribution.ts | 13 + .../services/scm/common/dirtydiff.ts | 25 ++ .../services/scm/common/dirtydiffService.ts | 43 +++ 9 files changed, 394 insertions(+), 345 deletions(-) create mode 100644 src/vs/workbench/parts/git/common/gitDirtyDiff.ts create mode 100644 src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts create mode 100644 src/vs/workbench/parts/scm/browser/scm.contribution.ts create mode 100644 src/vs/workbench/services/scm/common/dirtydiff.ts create mode 100644 src/vs/workbench/services/scm/common/dirtydiffService.ts diff --git a/src/vs/workbench/electron-browser/workbench.main.ts b/src/vs/workbench/electron-browser/workbench.main.ts index efc06c1ca57..07149fa38b8 100644 --- a/src/vs/workbench/electron-browser/workbench.main.ts +++ b/src/vs/workbench/electron-browser/workbench.main.ts @@ -40,6 +40,8 @@ import 'vs/workbench/parts/search/browser/search.contribution'; import 'vs/workbench/parts/search/browser/searchViewlet'; // can be packaged separately import 'vs/workbench/parts/search/browser/openAnythingHandler'; // can be packaged separately +import 'vs/workbench/parts/scm/browser/scm.contribution'; + import 'vs/workbench/parts/git/electron-browser/git.contribution'; import 'vs/workbench/parts/git/browser/gitQuickOpen'; import 'vs/workbench/parts/git/browser/gitActions.contribution'; diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index f9bbd8e4713..33a5f2ac864 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -75,6 +75,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TextFileService } from 'vs/workbench/services/textfile/electron-browser/textFileService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IDirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiff'; +import { DirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiffService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelResolverService } from 'vs/platform/textmodelResolver/common/resolver'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -456,6 +458,9 @@ export class Workbench implements IPartService { // Text File Service serviceCollection.set(ITextFileService, this.instantiationService.createInstance(TextFileService)); + // DirtyDiff Service + serviceCollection.set(IDirtyDiffService, this.instantiationService.createInstance(DirtyDiffService)); + // Backup Model Service serviceCollection.set(IBackupModelService, this.instantiationService.createInstance(BackupModelService)); diff --git a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts index 32d184db3d6..f0b0644c178 100644 --- a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts +++ b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts @@ -8,14 +8,9 @@ import 'vs/css!./media/git.contribution'; import nls = require('vs/nls'); import async = require('vs/base/common/async'); -import errors = require('vs/base/common/errors'); -import paths = require('vs/base/common/paths'); import lifecycle = require('vs/base/common/lifecycle'); -import winjs = require('vs/base/common/winjs.base'); import ext = require('vs/workbench/common/contributions'); import git = require('vs/workbench/parts/git/common/git'); -import common = require('vs/editor/common/editorCommon'); -import widget = require('vs/editor/browser/codeEditor'); import viewlet = require('vs/workbench/browser/viewlet'); import statusbar = require('vs/workbench/browser/parts/statusbar/statusbar'); import platform = require('vs/platform/platform'); @@ -30,18 +25,10 @@ import quickopen = require('vs/workbench/browser/quickopen'); import 'vs/workbench/parts/git/browser/gitEditorContributions'; import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activityService'; import { IEventService } from 'vs/platform/event/common/event'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService } from 'vs/platform/message/common/message'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { RawText } from 'vs/editor/common/model/textModel'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import URI from 'vs/base/common/uri'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { Schemas } from 'vs/base/common/network'; import IGitService = git.IGitService; @@ -120,330 +107,6 @@ export class StatusUpdater implements ext.IWorkbenchContribution { } } -class DirtyDiffModelDecorator { - static ID = 'vs.git.editor.dirtyDiffDecorator'; - static MODIFIED_DECORATION_OPTIONS: common.IModelDecorationOptions = { - linesDecorationsClassName: 'git-dirty-modified-diff-glyph', - isWholeLine: true, - overviewRuler: { - color: 'rgba(0, 122, 204, 0.6)', - darkColor: 'rgba(0, 122, 204, 0.6)', - position: common.OverviewRulerLane.Left - } - }; - static ADDED_DECORATION_OPTIONS: common.IModelDecorationOptions = { - linesDecorationsClassName: 'git-dirty-added-diff-glyph', - isWholeLine: true, - overviewRuler: { - color: 'rgba(0, 122, 204, 0.6)', - darkColor: 'rgba(0, 122, 204, 0.6)', - position: common.OverviewRulerLane.Left - } - }; - static DELETED_DECORATION_OPTIONS: common.IModelDecorationOptions = { - linesDecorationsClassName: 'git-dirty-deleted-diff-glyph', - isWholeLine: true, - overviewRuler: { - color: 'rgba(0, 122, 204, 0.6)', - darkColor: 'rgba(0, 122, 204, 0.6)', - position: common.OverviewRulerLane.Left - } - }; - - private modelService: IModelService; - private editorWorkerService: IEditorWorkerService; - private editorService: IWorkbenchEditorService; - private contextService: IWorkspaceContextService; - private gitService: IGitService; - - private model: common.IModel; - private _originalContentsURI: URI; - private path: string; - private decorations: string[]; - - private delayer: async.ThrottledDelayer; - private diffDelayer: async.ThrottledDelayer; - private toDispose: lifecycle.IDisposable[]; - - constructor(model: common.IModel, path: string, - @IModelService modelService: IModelService, - @IEditorWorkerService editorWorkerService: IEditorWorkerService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IGitService gitService: IGitService - ) { - this.modelService = modelService; - this.editorWorkerService = editorWorkerService; - this.editorService = editorService; - this.contextService = contextService; - this.gitService = gitService; - - this.model = model; - this._originalContentsURI = model.uri.with({ scheme: Schemas.internal }); - this.path = path; - this.decorations = []; - - this.delayer = new async.ThrottledDelayer(500); - this.diffDelayer = new async.ThrottledDelayer(200); - - this.toDispose = []; - this.toDispose.push(model.onDidChangeContent(() => this.triggerDiff())); - this.toDispose.push(this.gitService.addListener2(git.ServiceEvents.STATE_CHANGED, () => this.onChanges())); - this.toDispose.push(this.gitService.addListener2(git.ServiceEvents.OPERATION_END, e => { - if (e.operation.id !== git.ServiceOperations.BACKGROUND_FETCH) { - this.onChanges(); - } - })); - - this.onChanges(); - } - - private onChanges(): void { - if (!this.gitService) { - return; - } - - if (this.gitService.getState() !== git.ServiceState.OK) { - return; - } - - // go through all interesting models - this.trigger(); - } - - private trigger(): void { - this.delayer - .trigger(() => this.diffOriginalContents()) - .done(null, errors.onUnexpectedError); - } - - private diffOriginalContents(): winjs.TPromise { - return this.getOriginalContents() - .then(contents => { - if (!this.model || this.model.isDisposed()) { - return; // disposed - } - - if (!contents) { - // untracked file - this.modelService.destroyModel(this._originalContentsURI); - return this.triggerDiff(); - } - - let originalModel = this.modelService.getModel(this._originalContentsURI); - if (originalModel) { - let contentsRawText = RawText.fromStringWithModelOptions(contents, originalModel); - - // return early if nothing has changed - if (originalModel.equals(contentsRawText)) { - return winjs.TPromise.as(null); - } - - // we already have the original contents - originalModel.setValueFromRawText(contentsRawText); - } else { - // this is the first time we load the original contents - this.modelService.createModel(contents, null, this._originalContentsURI); - } - - return this.triggerDiff(); - }); - } - - private getOriginalContents(): winjs.TPromise { - var gitModel = this.gitService.getModel(); - var treeish = gitModel.getStatus().find(this.path, git.StatusType.INDEX) ? '~' : 'HEAD'; - - return this.gitService.buffer(this.path, treeish); - } - - private triggerDiff(): winjs.Promise { - if (!this.diffDelayer) { - return winjs.TPromise.as(null); - } - - return this.diffDelayer.trigger(() => { - if (!this.model || this.model.isDisposed()) { - return winjs.TPromise.as([]); // disposed - } - - return this.editorWorkerService.computeDirtyDiff(this._originalContentsURI, this.model.uri, true); - }).then((diff: common.IChange[]) => { - if (!this.model || this.model.isDisposed()) { - return; // disposed - } - - return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); - }); - } - - private static changesToDecorations(diff: common.IChange[]): common.IModelDeltaDecoration[] { - return diff.map((change) => { - var startLineNumber = change.modifiedStartLineNumber; - var endLineNumber = change.modifiedEndLineNumber || startLineNumber; - - // Added - if (change.originalEndLineNumber === 0) { - return { - range: { - startLineNumber: startLineNumber, startColumn: 1, - endLineNumber: endLineNumber, endColumn: 1 - }, - options: DirtyDiffModelDecorator.ADDED_DECORATION_OPTIONS - }; - } - - // Removed - if (change.modifiedEndLineNumber === 0) { - return { - range: { - startLineNumber: startLineNumber, startColumn: 1, - endLineNumber: startLineNumber, endColumn: 1 - }, - options: DirtyDiffModelDecorator.DELETED_DECORATION_OPTIONS - }; - } - - // Modified - return { - range: { - startLineNumber: startLineNumber, startColumn: 1, - endLineNumber: endLineNumber, endColumn: 1 - }, - options: DirtyDiffModelDecorator.MODIFIED_DECORATION_OPTIONS - }; - }); - } - - public dispose(): void { - this.modelService.destroyModel(this._originalContentsURI); - this.toDispose = lifecycle.dispose(this.toDispose); - if (this.model && !this.model.isDisposed()) { - this.model.deltaDecorations(this.decorations, []); - } - this.model = null; - this.decorations = null; - if (this.delayer) { - this.delayer.cancel(); - this.delayer = null; - } - if (this.diffDelayer) { - this.diffDelayer.cancel(); - this.diffDelayer = null; - } - } -} - -export class DirtyDiffDecorator implements ext.IWorkbenchContribution { - - private gitService: IGitService; - private messageService: IMessageService; - private editorService: IWorkbenchEditorService; - private eventService: IEventService; - private contextService: IWorkspaceContextService; - private instantiationService: IInstantiationService; - private models: common.IModel[]; - private decorators: { [modelId: string]: DirtyDiffModelDecorator }; - private toDispose: lifecycle.IDisposable[]; - - constructor( - @IGitService gitService: IGitService, - @IMessageService messageService: IMessageService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IEditorGroupService editorGroupService: IEditorGroupService, - @IEventService eventService: IEventService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IInstantiationService instantiationService: IInstantiationService - ) { - this.gitService = gitService; - this.messageService = messageService; - this.editorService = editorService; - this.eventService = eventService; - this.contextService = contextService; - this.instantiationService = instantiationService; - - this.models = []; - this.decorators = Object.create(null); - this.toDispose = []; - this.toDispose.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); - this.toDispose.push(gitService.addListener2(git.ServiceEvents.DISPOSE, () => this.dispose())); - } - - public getId(): string { - return 'git.DirtyDiffModelDecorator'; - } - - private onEditorsChanged(): void { - // HACK: This is the best current way of figuring out whether to draw these decorations - // or not. Needs context from the editor, to know whether it is a diff editor, in place editor - // etc. - - const repositoryRoot = this.gitService.getModel().getRepositoryRoot(); - - // If there is no repository root, just wait until that changes - if (typeof repositoryRoot !== 'string') { - this.gitService.addOneTimeDisposableListener(git.ServiceEvents.STATE_CHANGED, () => this.onEditorsChanged()); - - this.models.forEach(m => this.onModelInvisible(m)); - this.models = []; - return; - } - - const models = this.editorService.getVisibleEditors() - - // map to the editor controls - .map(e => e.getControl()) - - // only interested in code editor widgets - .filter(c => c instanceof widget.CodeEditor) - - // map to models - .map(e => (e).getModel()) - - // remove nulls and duplicates - .filter((m, i, a) => !!m && a.indexOf(m, i + 1) === -1) - - // get the associated resource - .map(m => ({ model: m, resource: m.uri })) - - // remove nulls - .filter(p => !!p.resource && - // and invalid resources - (p.resource.scheme === 'file' && paths.isEqualOrParent(p.resource.fsPath, repositoryRoot)) - ) - - // get paths - .map(p => ({ model: p.model, path: paths.normalize(paths.relative(repositoryRoot, p.resource.fsPath)) })) - - // remove nulls and inside .git files - .filter(p => !!p.path && p.path.indexOf('.git/') === -1); - - var newModels = models.filter(p => this.models.every(m => p.model !== m)); - var oldModels = this.models.filter(m => models.every(p => p.model !== m)); - - newModels.forEach(p => this.onModelVisible(p.model, p.path)); - oldModels.forEach(m => this.onModelInvisible(m)); - - this.models = models.map(p => p.model); - } - - private onModelVisible(model: common.IModel, path: string): void { - this.decorators[model.id] = this.instantiationService.createInstance(DirtyDiffModelDecorator, model, path); - } - - private onModelInvisible(model: common.IModel): void { - this.decorators[model.id].dispose(); - delete this.decorators[model.id]; - } - - public dispose(): void { - this.toDispose = lifecycle.dispose(this.toDispose); - this.models.forEach(m => this.decorators[m.id].dispose()); - this.models = null; - this.decorators = null; - } -} - export const VIEWLET_ID = 'workbench.view.git'; class OpenGitViewletAction extends viewlet.ToggleViewletAction { @@ -500,11 +163,6 @@ export function registerContributions(): void { StatusUpdater ); - // Register DirtyDiffDecorator - (platform.Registry.as(ext.Extensions.Workbench)).registerWorkbenchContribution( - DirtyDiffDecorator - ); - // Register Quick Open for git (platform.Registry.as(quickopen.Extensions.Quickopen)).registerQuickOpenHandler( new quickopen.QuickOpenHandlerDescriptor( diff --git a/src/vs/workbench/parts/git/common/gitDirtyDiff.ts b/src/vs/workbench/parts/git/common/gitDirtyDiff.ts new file mode 100644 index 00000000000..ecc113cbeea --- /dev/null +++ b/src/vs/workbench/parts/git/common/gitDirtyDiff.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { IModelService } from 'vs/editor/common/services/modelService'; +import URI from 'vs/base/common/uri'; +import { dispose } from 'vs/base/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IModel } from 'vs/editor/common/editorCommon'; +import { ITextModelResolverService, ITextModelContentProvider } from 'vs/platform/textmodelResolver/common/resolver'; +import { IDirtyDiffTextDocumentProvider, IDirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiff'; +import { IGitService, StatusType, ServiceEvents, ServiceOperations, ServiceState } from 'vs/workbench/parts/git/common/git'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; + +export class GitContentProvider implements IWorkbenchContribution, ITextModelContentProvider, IDirtyDiffTextDocumentProvider { + + constructor( + @ITextModelResolverService textModelResolverService: ITextModelResolverService, + @IModelService private modelService: IModelService, + @IDirtyDiffService private dirtyDiffService: IDirtyDiffService, + @IGitService private gitService: IGitService + ) { + this.dirtyDiffService.registerDirtyDiffTextDocumentProvider(this); + textModelResolverService.registerTextModelContentProvider('git-index', this); + } + + getDirtyDiffTextDocument(resource: URI): TPromise { + return TPromise.as(resource.with({ scheme: 'git-index' })); + } + + provideTextContent(uri: URI): TPromise { + if (uri.scheme !== 'git-index') { + return null; + } + + const gitModel = this.gitService.getModel(); + const path = uri.fsPath; + const treeish = gitModel.getStatus().find(path, StatusType.INDEX) ? '~' : 'HEAD'; + + return this.gitService.buffer(path, treeish) + .then(contents => this.modelService.createModel(contents, null, uri)) + .then(model => { + const trigger = () => { + this.gitService.buffer(path, treeish). + then(contents => model.setValue(contents)) + .done(null, onUnexpectedError); + }; + + const onChanges = () => { + if (this.gitService.getState() !== ServiceState.OK) { + return; + } + + trigger(); + }; + + const disposables = [ + this.gitService.addListener2(ServiceEvents.STATE_CHANGED, onChanges), + this.gitService.addListener2(ServiceEvents.OPERATION_END, e => { + if (e.operation.id !== ServiceOperations.BACKGROUND_FETCH) { + onChanges(); + } + }) + ]; + + model.onWillDispose(() => { + dispose(disposables); + }); + + return model; + }); + } + + getId(): string { + return 'git.contentprovider'; + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/git/electron-browser/git.contribution.ts b/src/vs/workbench/parts/git/electron-browser/git.contribution.ts index 0516fbf8f54..bd96031dca3 100644 --- a/src/vs/workbench/parts/git/electron-browser/git.contribution.ts +++ b/src/vs/workbench/parts/git/electron-browser/git.contribution.ts @@ -10,15 +10,20 @@ import { IGitService } from 'vs/workbench/parts/git/common/git'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/platform'; -import { Extensions as WorkbenchExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actionRegistry'; import { CloneAction } from './gitActions'; +import { GitContentProvider } from '../common/gitDirtyDiff'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actionRegistry'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; registerContributions(); // Register Service registerSingleton(IGitService, ElectronGitService); -const workbenchActionRegistry = Registry.as(WorkbenchExtensions.WorkbenchActions); const category = localize('git', "Git"); -workbenchActionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloneAction, CloneAction.ID, CloneAction.LABEL), 'Git: Clone', category); \ No newline at end of file +Registry.as(WorkbenchActionExtensions.WorkbenchActions) + .registerWorkbenchAction(new SyncActionDescriptor(CloneAction, CloneAction.ID, CloneAction.LABEL), 'Git: Clone', category); + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(GitContentProvider); \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts new file mode 100644 index 00000000000..1b0ceeb86e9 --- /dev/null +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -0,0 +1,218 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { ThrottledDelayer } from 'vs/base/common/async'; +import * as lifecycle from 'vs/base/common/lifecycle'; +import * as winjs from 'vs/base/common/winjs.base'; +import * as ext from 'vs/workbench/common/contributions'; +import * as common from 'vs/editor/common/editorCommon'; +import * as widget from 'vs/editor/browser/codeEditor'; +import { IEventService } from 'vs/platform/event/common/event'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IMessageService } from 'vs/platform/message/common/message'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import URI from 'vs/base/common/uri'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { IDirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiff'; + +class DirtyDiffModelDecorator { + + static MODIFIED_DECORATION_OPTIONS: common.IModelDecorationOptions = { + linesDecorationsClassName: 'git-dirty-modified-diff-glyph', + isWholeLine: true, + overviewRuler: { + color: 'rgba(0, 122, 204, 0.6)', + darkColor: 'rgba(0, 122, 204, 0.6)', + position: common.OverviewRulerLane.Left + } + }; + + static ADDED_DECORATION_OPTIONS: common.IModelDecorationOptions = { + linesDecorationsClassName: 'git-dirty-added-diff-glyph', + isWholeLine: true, + overviewRuler: { + color: 'rgba(0, 122, 204, 0.6)', + darkColor: 'rgba(0, 122, 204, 0.6)', + position: common.OverviewRulerLane.Left + } + }; + + static DELETED_DECORATION_OPTIONS: common.IModelDecorationOptions = { + linesDecorationsClassName: 'git-dirty-deleted-diff-glyph', + isWholeLine: true, + overviewRuler: { + color: 'rgba(0, 122, 204, 0.6)', + darkColor: 'rgba(0, 122, 204, 0.6)', + position: common.OverviewRulerLane.Left + } + }; + + private decorations: string[]; + private diffDelayer: ThrottledDelayer; + private toDispose: lifecycle.IDisposable[]; + + constructor( + private model: common.IModel, + private uri: URI, + @IDirtyDiffService private dirtyDiffService: IDirtyDiffService, + @IModelService private modelService: IModelService, + @IEditorWorkerService private editorWorkerService: IEditorWorkerService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IWorkspaceContextService private contextService: IWorkspaceContextService + ) { + this.decorations = []; + this.diffDelayer = new ThrottledDelayer(200); + this.toDispose = []; + this.triggerDiff(); + this.toDispose.push(model.onDidChangeContent(() => this.triggerDiff())); + } + + private triggerDiff(): winjs.Promise { + if (!this.diffDelayer) { + return winjs.TPromise.as(null); + } + + return this.dirtyDiffService.getDirtyDiffTextDocument(this.uri).then(originalUri => { + return this.diffDelayer.trigger(() => { + if (!this.model || this.model.isDisposed()) { + return winjs.TPromise.as([]); // disposed + } + + return this.editorWorkerService.computeDirtyDiff(originalUri, this.model.uri, true); + }).then((diff: common.IChange[]) => { + if (!this.model || this.model.isDisposed()) { + return; // disposed + } + + return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); + }); + }); + } + + private static changesToDecorations(diff: common.IChange[]): common.IModelDeltaDecoration[] { + return diff.map((change) => { + const startLineNumber = change.modifiedStartLineNumber; + const endLineNumber = change.modifiedEndLineNumber || startLineNumber; + + // Added + if (change.originalEndLineNumber === 0) { + return { + range: { + startLineNumber: startLineNumber, startColumn: 1, + endLineNumber: endLineNumber, endColumn: 1 + }, + options: DirtyDiffModelDecorator.ADDED_DECORATION_OPTIONS + }; + } + + // Removed + if (change.modifiedEndLineNumber === 0) { + return { + range: { + startLineNumber: startLineNumber, startColumn: 1, + endLineNumber: startLineNumber, endColumn: 1 + }, + options: DirtyDiffModelDecorator.DELETED_DECORATION_OPTIONS + }; + } + + // Modified + return { + range: { + startLineNumber: startLineNumber, startColumn: 1, + endLineNumber: endLineNumber, endColumn: 1 + }, + options: DirtyDiffModelDecorator.MODIFIED_DECORATION_OPTIONS + }; + }); + } + + dispose(): void { + this.toDispose = lifecycle.dispose(this.toDispose); + if (this.model && !this.model.isDisposed()) { + this.model.deltaDecorations(this.decorations, []); + } + this.model = null; + this.decorations = null; + if (this.diffDelayer) { + this.diffDelayer.cancel(); + this.diffDelayer = null; + } + } +} + +export class DirtyDiffDecorator implements ext.IWorkbenchContribution { + + private models: common.IModel[] = []; + private decorators: { [modelId: string]: DirtyDiffModelDecorator } = Object.create(null); + private toDispose: lifecycle.IDisposable[] = []; + + constructor( + @IMessageService private messageService: IMessageService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IEditorGroupService editorGroupService: IEditorGroupService, + @IEventService private eventService: IEventService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + this.toDispose.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + } + + getId(): string { + return 'git.DirtyDiffModelDecorator'; + } + + private onEditorsChanged(): void { + // HACK: This is the best current way of figuring out whether to draw these decorations + // or not. Needs context from the editor, to know whether it is a diff editor, in place editor + // etc. + + const models = this.editorService.getVisibleEditors() + + // map to the editor controls + .map(e => e.getControl()) + + // only interested in code editor widgets + .filter(c => c instanceof widget.CodeEditor) + + // map to models + .map(e => (e).getModel()) + + // remove nulls and duplicates + .filter((m, i, a) => !!m && !!m.uri && a.indexOf(m, i + 1) === -1) + + // get the associated resource + .map(m => ({ model: m, uri: m.uri })); + + const newModels = models.filter(p => this.models.every(m => p.model !== m)); + const oldModels = this.models.filter(m => models.every(p => p.model !== m)); + + newModels.forEach(({ model, uri }) => this.onModelVisible(model, uri)); + oldModels.forEach(m => this.onModelInvisible(m)); + + this.models = models.map(p => p.model); + } + + private onModelVisible(model: common.IModel, uri: URI): void { + this.decorators[model.id] = this.instantiationService.createInstance(DirtyDiffModelDecorator, model, uri); + } + + private onModelInvisible(model: common.IModel): void { + this.decorators[model.id].dispose(); + delete this.decorators[model.id]; + } + + dispose(): void { + this.toDispose = lifecycle.dispose(this.toDispose); + this.models.forEach(m => this.decorators[m.id].dispose()); + this.models = null; + this.decorators = null; + } +} diff --git a/src/vs/workbench/parts/scm/browser/scm.contribution.ts b/src/vs/workbench/parts/scm/browser/scm.contribution.ts new file mode 100644 index 00000000000..40d189f227c --- /dev/null +++ b/src/vs/workbench/parts/scm/browser/scm.contribution.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { Registry } from 'vs/platform/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { DirtyDiffDecorator } from './dirtydiffDecorator'; + +Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(DirtyDiffDecorator); \ No newline at end of file diff --git a/src/vs/workbench/services/scm/common/dirtydiff.ts b/src/vs/workbench/services/scm/common/dirtydiff.ts new file mode 100644 index 00000000000..bfe45f39c80 --- /dev/null +++ b/src/vs/workbench/services/scm/common/dirtydiff.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export interface IDirtyDiffTextDocumentProvider { + getDirtyDiffTextDocument(resource: URI): TPromise; +} + +export const IDirtyDiffService = createDecorator('dirtyDiff'); + +export interface IDirtyDiffService { + + _serviceBrand: any; + + getDirtyDiffTextDocument(resource: URI): TPromise; + registerDirtyDiffTextDocumentProvider(provider: IDirtyDiffTextDocumentProvider): IDisposable; +} \ No newline at end of file diff --git a/src/vs/workbench/services/scm/common/dirtydiffService.ts b/src/vs/workbench/services/scm/common/dirtydiffService.ts new file mode 100644 index 00000000000..18e25db657f --- /dev/null +++ b/src/vs/workbench/services/scm/common/dirtydiffService.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDirtyDiffService, IDirtyDiffTextDocumentProvider } from './dirtydiff'; + +export class DirtyDiffService implements IDirtyDiffService { + + _serviceBrand; + + private providers: IDirtyDiffTextDocumentProvider[] = []; + + getDirtyDiffTextDocument(resource: URI): TPromise { + // TODO@Joao: just take the first + const [provider] = this.providers; + + if (!provider) { + return TPromise.as(null); + } + + return provider.getDirtyDiffTextDocument(resource); + } + + registerDirtyDiffTextDocumentProvider(provider: IDirtyDiffTextDocumentProvider): IDisposable { + this.providers = [provider, ...this.providers]; + + return toDisposable(() => { + const index = this.providers.indexOf(provider); + + if (index < 0) { + return; + } + + this.providers.splice(index, 1); + }); + } +} \ No newline at end of file From 2272d37e2f8ab5f9101f642addd43834f8dabfa9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Nov 2016 15:44:52 +0100 Subject: [PATCH 02/28] dirty diff: contributable models --- .../workbench/parts/git/node/rawGitService.ts | 7 +++- .../parts/scm/browser/dirtydiffDecorator.ts | 42 +++++++++++++------ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/parts/git/node/rawGitService.ts b/src/vs/workbench/parts/git/node/rawGitService.ts index 85efc9c8c66..48361f0a4ed 100644 --- a/src/vs/workbench/parts/git/node/rawGitService.ts +++ b/src/vs/workbench/parts/git/node/rawGitService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { join } from 'path'; +import { join, isAbsolute, relative } from 'path'; import { TPromise, Promise } from 'vs/base/common/winjs.base'; import { detectMimesFromFile, detectMimesFromStream } from 'vs/base/node/mime'; import { realpath, exists } from 'vs/base/node/pfs'; @@ -187,6 +187,11 @@ export class RawGitService implements IRawGitService { // careful, this buffers the whole object into memory show(filePath: string, treeish?: string): TPromise { treeish = (!treeish || treeish === '~') ? '' : treeish; + + if (isAbsolute(filePath)) { + filePath = relative(this.repo.path, filePath); + } + return this.repo.buffer(treeish + ':' + filePath).then(null, e => { if (e instanceof GitError) { return ''; // mostly untracked files end up in a git error diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index 1b0ceeb86e9..a86b479aad2 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -8,6 +8,7 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import * as lifecycle from 'vs/base/common/lifecycle'; import * as winjs from 'vs/base/common/winjs.base'; +import { memoize } from 'vs/base/common/decorators'; import * as ext from 'vs/workbench/common/contributions'; import * as common from 'vs/editor/common/editorCommon'; import * as widget from 'vs/editor/browser/codeEditor'; @@ -15,6 +16,7 @@ import { IEventService } from 'vs/platform/event/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService } from 'vs/platform/message/common/message'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ITextModelResolverService } from 'vs/platform/textmodelResolver/common/resolver'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; @@ -65,7 +67,8 @@ class DirtyDiffModelDecorator { @IModelService private modelService: IModelService, @IEditorWorkerService private editorWorkerService: IEditorWorkerService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IWorkspaceContextService private contextService: IWorkspaceContextService + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @ITextModelResolverService private textModelResolverService: ITextModelResolverService ) { this.decorations = []; this.diffDelayer = new ThrottledDelayer(200); @@ -74,25 +77,38 @@ class DirtyDiffModelDecorator { this.toDispose.push(model.onDidChangeContent(() => this.triggerDiff())); } + @memoize + private get originalURIPromise(): winjs.TPromise { + return this.dirtyDiffService.getDirtyDiffTextDocument(this.uri) + .then(originalUri => this.textModelResolverService.resolve(originalUri) + .then(model => { + // TODO@Joao cast here? + const textEditorModel = model.textEditorModel as common.IModel; + this.toDispose.push(textEditorModel.onDidChangeContent(() => this.triggerDiff())); + + return originalUri; + })); + } + private triggerDiff(): winjs.Promise { if (!this.diffDelayer) { return winjs.TPromise.as(null); } - return this.dirtyDiffService.getDirtyDiffTextDocument(this.uri).then(originalUri => { - return this.diffDelayer.trigger(() => { - if (!this.model || this.model.isDisposed()) { - return winjs.TPromise.as([]); // disposed - } + return this.diffDelayer.trigger(() => { + if (!this.model || this.model.isDisposed()) { + return winjs.TPromise.as([]); // disposed + } - return this.editorWorkerService.computeDirtyDiff(originalUri, this.model.uri, true); - }).then((diff: common.IChange[]) => { - if (!this.model || this.model.isDisposed()) { - return; // disposed - } + return this.originalURIPromise + .then(uri => this.editorWorkerService.computeDirtyDiff(uri, this.model.uri, true)); - return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); - }); + }).then((diff: common.IChange[]) => { + if (!this.model || this.model.isDisposed()) { + return; // disposed + } + + return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); }); } From 89e4f964b8e1a5f49b2b4af06da857dbf1ee067f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 17 Nov 2016 15:36:41 +0100 Subject: [PATCH 03/28] ask all dirty diff providers --- .../services/scm/common/dirtydiffService.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/services/scm/common/dirtydiffService.ts b/src/vs/workbench/services/scm/common/dirtydiffService.ts index 18e25db657f..1d43f6d3a26 100644 --- a/src/vs/workbench/services/scm/common/dirtydiffService.ts +++ b/src/vs/workbench/services/scm/common/dirtydiffService.ts @@ -17,14 +17,13 @@ export class DirtyDiffService implements IDirtyDiffService { private providers: IDirtyDiffTextDocumentProvider[] = []; getDirtyDiffTextDocument(resource: URI): TPromise { - // TODO@Joao: just take the first - const [provider] = this.providers; + const promises = this.providers + .map(p => p.getDirtyDiffTextDocument(resource)); - if (!provider) { - return TPromise.as(null); - } - - return provider.getDirtyDiffTextDocument(resource); + return TPromise.join(promises).then(originalResources => { + // TODO@Joao: just take the first + return originalResources.filter(uri => !!uri)[0]; + }); } registerDirtyDiffTextDocumentProvider(provider: IDirtyDiffTextDocumentProvider): IDisposable { From a7dfc5ebdcb47428c967cd059eeecbd02541cd9b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 17 Nov 2016 15:41:22 +0100 Subject: [PATCH 04/28] adopt resolver service changes --- src/vs/workbench/parts/git/common/gitDirtyDiff.ts | 2 +- src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/git/common/gitDirtyDiff.ts b/src/vs/workbench/parts/git/common/gitDirtyDiff.ts index ecc113cbeea..e829e1ba5fc 100644 --- a/src/vs/workbench/parts/git/common/gitDirtyDiff.ts +++ b/src/vs/workbench/parts/git/common/gitDirtyDiff.ts @@ -10,7 +10,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; import { IModel } from 'vs/editor/common/editorCommon'; -import { ITextModelResolverService, ITextModelContentProvider } from 'vs/platform/textmodelResolver/common/resolver'; +import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IDirtyDiffTextDocumentProvider, IDirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiff'; import { IGitService, StatusType, ServiceEvents, ServiceOperations, ServiceState } from 'vs/workbench/parts/git/common/git'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index a86b479aad2..26f9380cf0f 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -16,7 +16,7 @@ import { IEventService } from 'vs/platform/event/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService } from 'vs/platform/message/common/message'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ITextModelResolverService } from 'vs/platform/textmodelResolver/common/resolver'; +import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; From 3f854202e5f48b920940154e32523d2c4ee2bdc9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 17 Nov 2016 16:04:04 +0100 Subject: [PATCH 05/28] dirtydiff -> baseline --- .../workbench/electron-browser/workbench.ts | 8 ++++---- ...{gitDirtyDiff.ts => gitContentProvider.ts} | 20 +++++++++++-------- .../git/electron-browser/git.contribution.ts | 2 +- .../parts/scm/browser/dirtydiffDecorator.ts | 6 +++--- .../scm/common/{dirtydiff.ts => scm.ts} | 12 +++++------ .../{dirtydiffService.ts => scmService.ts} | 12 +++++------ 6 files changed, 32 insertions(+), 28 deletions(-) rename src/vs/workbench/parts/git/common/{gitDirtyDiff.ts => gitContentProvider.ts} (80%) rename src/vs/workbench/services/scm/common/{dirtydiff.ts => scm.ts} (62%) rename src/vs/workbench/services/scm/common/{dirtydiffService.ts => scmService.ts} (70%) diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 65f7a070607..6ea5f968570 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -76,8 +76,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TextFileService } from 'vs/workbench/services/textfile/electron-browser/textFileService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IDirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiff'; -import { DirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiffService'; +import { ISCMService } from 'vs/workbench/services/scm/common/scm'; +import { SCMService } from 'vs/workbench/services/scm/common/scmService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -476,8 +476,8 @@ export class Workbench implements IPartService { // Text File Service serviceCollection.set(ITextFileService, this.instantiationService.createInstance(TextFileService)); - // DirtyDiff Service - serviceCollection.set(IDirtyDiffService, this.instantiationService.createInstance(DirtyDiffService)); + // SCM Service + serviceCollection.set(ISCMService, this.instantiationService.createInstance(SCMService)); // Backup Model Service serviceCollection.set(IBackupModelService, this.instantiationService.createInstance(BackupModelService)); diff --git a/src/vs/workbench/parts/git/common/gitDirtyDiff.ts b/src/vs/workbench/parts/git/common/gitContentProvider.ts similarity index 80% rename from src/vs/workbench/parts/git/common/gitDirtyDiff.ts rename to src/vs/workbench/parts/git/common/gitContentProvider.ts index e829e1ba5fc..d4637157f5c 100644 --- a/src/vs/workbench/parts/git/common/gitDirtyDiff.ts +++ b/src/vs/workbench/parts/git/common/gitContentProvider.ts @@ -7,27 +7,30 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import URI from 'vs/base/common/uri'; import { dispose } from 'vs/base/common/lifecycle'; +import { Throttler } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; import { IModel } from 'vs/editor/common/editorCommon'; import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { IDirtyDiffTextDocumentProvider, IDirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiff'; +import { IBaselineResourceProvider, ISCMService } from 'vs/workbench/services/scm/common/scm'; import { IGitService, StatusType, ServiceEvents, ServiceOperations, ServiceState } from 'vs/workbench/parts/git/common/git'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -export class GitContentProvider implements IWorkbenchContribution, ITextModelContentProvider, IDirtyDiffTextDocumentProvider { +export class GitContentProvider implements IWorkbenchContribution, ITextModelContentProvider, IBaselineResourceProvider { + + private throttler = new Throttler(); constructor( @ITextModelResolverService textModelResolverService: ITextModelResolverService, @IModelService private modelService: IModelService, - @IDirtyDiffService private dirtyDiffService: IDirtyDiffService, + @ISCMService private scmService: ISCMService, @IGitService private gitService: IGitService ) { - this.dirtyDiffService.registerDirtyDiffTextDocumentProvider(this); + this.scmService.registerBaselineResourceProvider(this); textModelResolverService.registerTextModelContentProvider('git-index', this); } - getDirtyDiffTextDocument(resource: URI): TPromise { + getBaselineResource(resource: URI): TPromise { return TPromise.as(resource.with({ scheme: 'git-index' })); } @@ -44,9 +47,10 @@ export class GitContentProvider implements IWorkbenchContribution, ITextModelCon .then(contents => this.modelService.createModel(contents, null, uri)) .then(model => { const trigger = () => { - this.gitService.buffer(path, treeish). - then(contents => model.setValue(contents)) - .done(null, onUnexpectedError); + this.throttler.queue(() => { + return this.gitService.buffer(path, treeish) + .then(contents => model.setValue(contents)); + }).done(null, onUnexpectedError); }; const onChanges = () => { diff --git a/src/vs/workbench/parts/git/electron-browser/git.contribution.ts b/src/vs/workbench/parts/git/electron-browser/git.contribution.ts index bd96031dca3..615963248c5 100644 --- a/src/vs/workbench/parts/git/electron-browser/git.contribution.ts +++ b/src/vs/workbench/parts/git/electron-browser/git.contribution.ts @@ -11,7 +11,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/platform'; import { CloneAction } from './gitActions'; -import { GitContentProvider } from '../common/gitDirtyDiff'; +import { GitContentProvider } from '../common/gitContentProvider'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actionRegistry'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index 26f9380cf0f..1566d91a0d8 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -22,7 +22,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import URI from 'vs/base/common/uri'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { IDirtyDiffService } from 'vs/workbench/services/scm/common/dirtydiff'; +import { ISCMService } from 'vs/workbench/services/scm/common/scm'; class DirtyDiffModelDecorator { @@ -63,7 +63,7 @@ class DirtyDiffModelDecorator { constructor( private model: common.IModel, private uri: URI, - @IDirtyDiffService private dirtyDiffService: IDirtyDiffService, + @ISCMService private dirtyDiffService: ISCMService, @IModelService private modelService: IModelService, @IEditorWorkerService private editorWorkerService: IEditorWorkerService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -79,7 +79,7 @@ class DirtyDiffModelDecorator { @memoize private get originalURIPromise(): winjs.TPromise { - return this.dirtyDiffService.getDirtyDiffTextDocument(this.uri) + return this.dirtyDiffService.getBaselineResource(this.uri) .then(originalUri => this.textModelResolverService.resolve(originalUri) .then(model => { // TODO@Joao cast here? diff --git a/src/vs/workbench/services/scm/common/dirtydiff.ts b/src/vs/workbench/services/scm/common/scm.ts similarity index 62% rename from src/vs/workbench/services/scm/common/dirtydiff.ts rename to src/vs/workbench/services/scm/common/scm.ts index bfe45f39c80..d5be391cdfc 100644 --- a/src/vs/workbench/services/scm/common/dirtydiff.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -10,16 +10,16 @@ import URI from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; -export interface IDirtyDiffTextDocumentProvider { - getDirtyDiffTextDocument(resource: URI): TPromise; +export interface IBaselineResourceProvider { + getBaselineResource(resource: URI): TPromise; } -export const IDirtyDiffService = createDecorator('dirtyDiff'); +export const ISCMService = createDecorator('scm'); -export interface IDirtyDiffService { +export interface ISCMService { _serviceBrand: any; - getDirtyDiffTextDocument(resource: URI): TPromise; - registerDirtyDiffTextDocumentProvider(provider: IDirtyDiffTextDocumentProvider): IDisposable; + getBaselineResource(resource: URI): TPromise; + registerBaselineResourceProvider(provider: IBaselineResourceProvider): IDisposable; } \ No newline at end of file diff --git a/src/vs/workbench/services/scm/common/dirtydiffService.ts b/src/vs/workbench/services/scm/common/scmService.ts similarity index 70% rename from src/vs/workbench/services/scm/common/dirtydiffService.ts rename to src/vs/workbench/services/scm/common/scmService.ts index 1d43f6d3a26..6249f985f21 100644 --- a/src/vs/workbench/services/scm/common/dirtydiffService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -8,17 +8,17 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IDirtyDiffService, IDirtyDiffTextDocumentProvider } from './dirtydiff'; +import { ISCMService, IBaselineResourceProvider } from './scm'; -export class DirtyDiffService implements IDirtyDiffService { +export class SCMService implements ISCMService { _serviceBrand; - private providers: IDirtyDiffTextDocumentProvider[] = []; + private providers: IBaselineResourceProvider[] = []; - getDirtyDiffTextDocument(resource: URI): TPromise { + getBaselineResource(resource: URI): TPromise { const promises = this.providers - .map(p => p.getDirtyDiffTextDocument(resource)); + .map(p => p.getBaselineResource(resource)); return TPromise.join(promises).then(originalResources => { // TODO@Joao: just take the first @@ -26,7 +26,7 @@ export class DirtyDiffService implements IDirtyDiffService { }); } - registerDirtyDiffTextDocumentProvider(provider: IDirtyDiffTextDocumentProvider): IDisposable { + registerBaselineResourceProvider(provider: IBaselineResourceProvider): IDisposable { this.providers = [provider, ...this.providers]; return toDisposable(() => { From ceb5ef613500e39c1841519e4d50151629253d9d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 17 Nov 2016 16:28:31 +0100 Subject: [PATCH 06/28] add comments, remove cast --- .../workbench/parts/git/common/gitContentProvider.ts | 10 +++++++++- .../workbench/parts/scm/browser/dirtydiffDecorator.ts | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/git/common/gitContentProvider.ts b/src/vs/workbench/parts/git/common/gitContentProvider.ts index d4637157f5c..ec68d1a41a0 100644 --- a/src/vs/workbench/parts/git/common/gitContentProvider.ts +++ b/src/vs/workbench/parts/git/common/gitContentProvider.ts @@ -49,7 +49,15 @@ export class GitContentProvider implements IWorkbenchContribution, ITextModelCon const trigger = () => { this.throttler.queue(() => { return this.gitService.buffer(path, treeish) - .then(contents => model.setValue(contents)); + .then(contents => { + // TODO@Joao who owns the model? this is confusing + // if (!contents) { + // model.destroy(); + // return; + // } + + model.setValue(contents); + }); }).done(null, onUnexpectedError); }; diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index 1566d91a0d8..6a3ca2c0d55 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -82,8 +82,7 @@ class DirtyDiffModelDecorator { return this.dirtyDiffService.getBaselineResource(this.uri) .then(originalUri => this.textModelResolverService.resolve(originalUri) .then(model => { - // TODO@Joao cast here? - const textEditorModel = model.textEditorModel as common.IModel; + const textEditorModel = model.textEditorModel; this.toDispose.push(textEditorModel.onDidChangeContent(() => this.triggerDiff())); return originalUri; From f68a4fccb6037ffaa102f216589869e7677a98ee Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Nov 2016 14:58:27 +0100 Subject: [PATCH 07/28] remove need to resolve inmemory resources --- .../services/editor/browser/editorService.ts | 51 +------------------ .../common/textModelResolverService.ts | 12 ----- 2 files changed, 1 insertion(+), 62 deletions(-) diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 06cf03b2ce7..0f4c79ef8aa 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -9,9 +9,6 @@ import URI from 'vs/base/common/uri'; import network = require('vs/base/common/network'); import { Registry } from 'vs/platform/platform'; import { basename, dirname } from 'vs/base/common/paths'; -import types = require('vs/base/common/types'); -import { IDiffEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICommonCodeEditor, IModel, EditorType, IEditor as ICommonEditor } from 'vs/editor/common/editorCommon'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorOptions, IFileEditorInput, TextEditorOptions, IEditorRegistry, Extensions } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; @@ -206,46 +203,9 @@ export class WorkbenchEditorService implements IWorkbenchEditorService { // Base Text Editor Support for inmemory resources const resourceInput = input; - if (resourceInput.resource instanceof URI && resourceInput.resource.scheme === network.Schemas.inMemory) { - - // For in-memory resources we only support to resolve the input from the current active editor - // because the workbench does not track editor models by in memory URL. This concept is only - // being used in the code editor. - const activeEditor = this.getActiveEditor(); - if (activeEditor) { - const control = activeEditor.getControl(); - if (types.isFunction(control.getEditorType)) { - - // Single Editor: If code editor model matches, return input from editor - if (control.getEditorType() === EditorType.ICodeEditor) { - const codeEditor = control; - const model = this.findModel(codeEditor, input); - if (model) { - return TPromise.as(activeEditor.input); - } - } - - // Diff Editor: If left or right code editor model matches, return associated input - else if (control.getEditorType() === EditorType.IDiffEditor) { - const diffInput = activeEditor.input; - const diffCodeEditor = control; - - const originalModel = this.findModel(diffCodeEditor.getOriginalEditor(), input); - if (originalModel) { - return TPromise.as(diffInput.originalInput); - } - - const modifiedModel = this.findModel(diffCodeEditor.getModifiedEditor(), input); - if (modifiedModel) { - return TPromise.as(diffInput.modifiedInput); - } - } - } - } - } // Untitled file support - else if (resourceInput.resource instanceof URI && (resourceInput.resource.scheme === UntitledEditorInput.SCHEMA)) { + if (resourceInput.resource instanceof URI && (resourceInput.resource.scheme === UntitledEditorInput.SCHEMA)) { return TPromise.as(this.untitledEditorService.createOrGet(resourceInput.resource)); } @@ -273,15 +233,6 @@ export class WorkbenchEditorService implements IWorkbenchEditorService { return typedFileInput; }); } - - private findModel(editor: ICommonCodeEditor, input: IResourceInput): IModel { - const model = editor.getModel(); - if (!model) { - return null; - } - - return model.uri.toString() === input.resource.toString() ? model : null; - } } export interface IDelegatingWorkbenchEditorServiceHandler { diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index aad206e758d..c2b28421265 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -19,7 +19,6 @@ import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorInput } from 'vs/workbench/common/editor'; export class TextModelResolverService implements ITextModelResolverService { @@ -49,17 +48,6 @@ export class TextModelResolverService implements ITextModelResolverService { return this.untitledEditorService.createOrGet(resource).resolve(); } - // In Memory: only works on the active editor - if (resource.scheme === network.Schemas.inMemory) { - return this.editorService.createInput({ resource }).then(input => { - if (input instanceof EditorInput) { - return input.resolve(); - } - - return null; - }); - } - // Any other resource: use content provider registry return this.resolveTextModelContent(this.modelService, resource).then(() => this.instantiationService.createInstance(ResourceEditorModel, resource)); } From 38799f885f0a7813157cfa6c7c17eeaeba8cd7d8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Nov 2016 15:37:07 +0100 Subject: [PATCH 08/28] remove duplicate ITextEditorModel --- src/vs/editor/browser/standalone/simpleServices.ts | 4 ++-- src/vs/editor/common/services/resolverService.ts | 4 ++-- src/vs/platform/editor/common/editor.ts | 4 ---- src/vs/workbench/common/editor/textEditorModel.ts | 2 +- src/vs/workbench/services/textfile/common/textfiles.ts | 2 +- .../textmodelResolver/common/textModelResolverService.ts | 3 +-- 6 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index 601388feff8..9228c22540f 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -9,7 +9,7 @@ import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, getConfigurationValue, IConfigurationKeys } from 'vs/platform/configuration/common/configuration'; -import { IEditor, IEditorInput, IEditorOptions, IEditorService, IResourceInput, ITextEditorModel, Position } from 'vs/platform/editor/common/editor'; +import { IEditor, IEditorInput, IEditorOptions, IEditorService, IResourceInput, Position } from 'vs/platform/editor/common/editor'; import { AbstractExtensionService, ActivatedExtension } from 'vs/platform/extensions/common/abstractExtensionService'; import { IExtensionDescription, IExtensionService } from 'vs/platform/extensions/common/extensions'; import { ICommandService, ICommand, ICommandHandler } from 'vs/platform/commands/common/commands'; @@ -26,7 +26,7 @@ import { getDefaultValues as getDefaultConfiguration } from 'vs/platform/configu import { CommandService } from 'vs/platform/commands/common/commandService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; -import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; +import { ITextModelResolverService, ITextModelContentProvider, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { IDisposable } from 'vs/base/common/lifecycle'; export class SimpleEditor implements IEditor { diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index e3116160fc5..b40512bf94d 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IModel } from 'vs/editor/common/editorCommon'; -import { ITextEditorModel as IBaseTextEditorModel } from 'vs/platform/editor/common/editor'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; import { IDisposable } from 'vs/base/common/lifecycle'; export const ITextModelResolverService = createDecorator('textModelResolverService'); @@ -37,7 +37,7 @@ export interface ITextModelContentProvider { provideTextContent(resource: URI): TPromise; } -export interface ITextEditorModel extends IBaseTextEditorModel { +export interface ITextEditorModel extends IEditorModel { /** * Provides access to the underlying IModel. diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index fd2de5827bf..9807700305f 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -37,10 +37,6 @@ export interface IEditorModel { dispose(): void; } -export interface ITextEditorModel extends IEditorModel { - textEditorModel: any; -} - export interface IResourceInput { /** diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index cdc13d587d9..7327ba2fefd 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -9,7 +9,7 @@ import { EndOfLinePreference, IModel, IRawText } from 'vs/editor/common/editorCo import { IMode } from 'vs/editor/common/modes'; import { EditorModel } from 'vs/workbench/common/editor'; import URI from 'vs/base/common/uri'; -import { ITextEditorModel } from 'vs/platform/editor/common/editor'; +import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { RawText } from 'vs/editor/common/model/textModel'; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 779f8b9f2b5..6e308646021 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -12,7 +12,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IEncodingSupport, ConfirmResult } from 'vs/workbench/common/editor'; import { IFileStat, IBaseStat, IResolveContentOptions } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ITextEditorModel } from 'vs/platform/editor/common/editor'; +import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { Event as BaseEvent, PropertyChangeEvent } from 'vs/base/common/events'; diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index c2b28421265..a611b42f65e 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -8,14 +8,13 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModel } from 'vs/editor/common/editorCommon'; -import { ITextEditorModel } from 'vs/platform/editor/common/editor'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; import { sequence } from 'vs/base/common/async'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import network = require('vs/base/common/network'); -import { ITextModelResolverService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; +import { ITextModelResolverService, ITextModelContentProvider, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; From 5ea4ab4a84b403dd0fe89b22b4282fb5e8f1b582 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Nov 2016 15:42:41 +0100 Subject: [PATCH 09/28] remove unnecessary casts --- .../browser/parts/editor/stringEditor.ts | 2 +- .../workbench/common/editor/diffEditorModel.ts | 4 ++-- .../common/editor/textDiffEditorModel.ts | 16 ++++++++++++---- .../test/textModelResolverService.test.ts | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/stringEditor.ts b/src/vs/workbench/browser/parts/editor/stringEditor.ts index 1654fc95136..dbe016509b4 100644 --- a/src/vs/workbench/browser/parts/editor/stringEditor.ts +++ b/src/vs/workbench/browser/parts/editor/stringEditor.ts @@ -107,7 +107,7 @@ export class StringEditor extends BaseTextEditor { // Set Editor Model const textEditor = this.getControl(); - const textEditorModel = (resolvedModel).textEditorModel; + const textEditorModel = resolvedModel.textEditorModel; textEditor.setModel(textEditorModel); // Apply Options from TextOptions diff --git a/src/vs/workbench/common/editor/diffEditorModel.ts b/src/vs/workbench/common/editor/diffEditorModel.ts index 5569b6c888a..e12b560ef53 100644 --- a/src/vs/workbench/common/editor/diffEditorModel.ts +++ b/src/vs/workbench/common/editor/diffEditorModel.ts @@ -12,8 +12,8 @@ import { EditorModel } from 'vs/workbench/common/editor'; * and the modified version. */ export class DiffEditorModel extends EditorModel { - private _originalModel: EditorModel; - private _modifiedModel: EditorModel; + protected _originalModel: EditorModel; + protected _modifiedModel: EditorModel; constructor(originalModel: EditorModel, modifiedModel: EditorModel) { super(); diff --git a/src/vs/workbench/common/editor/textDiffEditorModel.ts b/src/vs/workbench/common/editor/textDiffEditorModel.ts index 10738e61a61..dc199593c22 100644 --- a/src/vs/workbench/common/editor/textDiffEditorModel.ts +++ b/src/vs/workbench/common/editor/textDiffEditorModel.ts @@ -23,6 +23,14 @@ export class TextDiffEditorModel extends DiffEditorModel { this.updateTextDiffEditorModel(); } + get originalModel(): BaseTextEditorModel { + return this._originalModel as BaseTextEditorModel; + } + + get modifiedModel(): BaseTextEditorModel { + return this._modifiedModel as BaseTextEditorModel; + } + public load(): TPromise { return super.load().then(() => { this.updateTextDiffEditorModel(); @@ -37,15 +45,15 @@ export class TextDiffEditorModel extends DiffEditorModel { // Create new if (!this._textDiffEditorModel) { this._textDiffEditorModel = { - original: (this.originalModel).textEditorModel, - modified: (this.modifiedModel).textEditorModel + original: this.originalModel.textEditorModel, + modified: this.modifiedModel.textEditorModel }; } // Update existing else { - this._textDiffEditorModel.original = (this.originalModel).textEditorModel; - this._textDiffEditorModel.modified = (this.modifiedModel).textEditorModel; + this._textDiffEditorModel.original = this.originalModel.textEditorModel; + this._textDiffEditorModel.modified = this.modifiedModel.textEditorModel; } } } diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index 5f09abac3ae..c9d92e66373 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -83,7 +83,7 @@ suite('Workbench - TextModelResolverService', () => { (accessor.textFileService.models).add(model.getResource(), model); model.load().then(() => { accessor.textModelResolverServie.resolve(model.getResource()).then(model => { - const editorModel = model.textEditorModel as IModel; + const editorModel = model.textEditorModel; assert.ok(editorModel); assert.equal(editorModel.getValue(), 'Hello Html'); @@ -99,7 +99,7 @@ suite('Workbench - TextModelResolverService', () => { input.resolve().then(() => { accessor.textModelResolverServie.resolve(input.getResource()).then(model => { - const editorModel = model.textEditorModel as IModel; + const editorModel = model.textEditorModel; assert.ok(editorModel); From 9741f177a40038725a6a16dbd4dd769fb0c0877b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 18 Nov 2016 17:08:06 +0100 Subject: [PATCH 10/28] introduce ReferenceCollection --- src/vs/base/common/lifecycle.ts | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index ae0f900fb1c..698421fc9d9 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -67,3 +67,44 @@ export class Disposables extends Disposable { } } } + +export interface IReference extends IDisposable { + readonly object: T; +} + +export abstract class ReferenceCollection { + + private references: { [key: string]: { readonly object: R; counter: number; } } = Object.create(null); + + constructor() { } + + acquire(t: T): IReference { + const key = this.getKey(t); + let reference = this.references[key]; + + if (!reference) { + reference = this.references[key] = { counter: 0, object: this.create(key) }; + } + + const { object } = reference; + const dispose = () => { + if (--reference.counter === 0) { + this.destroy(reference.object); + delete this.references[key]; + } + }; + + reference.counter++; + + return { object, dispose }; + } + + abstract getKey(t: T): string; + abstract create(key: string): R; + abstract destroy(object: R): void; +} + +export class ImmortalReference implements IReference { + constructor(public object: T) { } + dispose(): void { /* noop */ } +} \ No newline at end of file From 17361598b8a6ac159bb951f10cf1c34d7b93e47e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Nov 2016 11:25:05 +0100 Subject: [PATCH 11/28] reference collection tests --- src/vs/base/common/lifecycle.ts | 6 ++-- src/vs/base/test/common/lifecycle.test.ts | 41 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 698421fc9d9..31bce27fc27 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -99,9 +99,9 @@ export abstract class ReferenceCollection { return { object, dispose }; } - abstract getKey(t: T): string; - abstract create(key: string): R; - abstract destroy(object: R): void; + protected abstract getKey(t: T): string; + protected abstract create(key: string): R; + protected abstract destroy(object: R): void; } export class ImmortalReference implements IReference { diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 6a815e02b54..1533f6f4c4b 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, ReferenceCollection } from 'vs/base/common/lifecycle'; class Disposable implements IDisposable { isDisposed = false; @@ -49,4 +49,43 @@ suite('Lifecycle', () => { assert(disposable.isDisposed); assert(disposable2.isDisposed); }); +}); + +suite('Reference Collection', () => { + class Collection extends ReferenceCollection { + private _count = 0; + get count() { return this._count; } + protected getKey(key): string { return key; } + protected create(key: string): number { this._count++; return key.length; } + protected destroy(object: number): void { this._count--; } + } + + test('simple', () => { + const collection = new Collection(); + + const ref1 = collection.acquire('test'); + assert(ref1); + assert.equal(ref1.object, 4); + assert.equal(collection.count, 1); + ref1.dispose(); + assert.equal(collection.count, 0); + + const ref2 = collection.acquire('test'); + const ref3 = collection.acquire('test'); + assert.equal(ref2.object, ref3.object); + assert.equal(collection.count, 1); + + const ref4 = collection.acquire('monkey'); + assert.equal(ref4.object, 6); + assert.equal(collection.count, 2); + + ref2.dispose(); + assert.equal(collection.count, 2); + + ref3.dispose(); + assert.equal(collection.count, 1); + + ref4.dispose(); + assert.equal(collection.count, 0); + }); }); \ No newline at end of file From 5e747c32bd57b6c40df3c621bcec9b087316ee1b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Nov 2016 11:30:37 +0100 Subject: [PATCH 12/28] ReferenceCollection in TextModelResolverService --- .../browser/standalone/simpleServices.ts | 8 +- src/vs/editor/common/services/bulkEdit.ts | 37 +++- .../editor/common/services/resolverService.ts | 11 +- .../browser/goToDeclaration.ts | 10 +- .../browser/referencesController.ts | 10 +- .../browser/referencesModel.ts | 42 +++- .../browser/referencesWidget.ts | 15 +- .../workbench/api/node/mainThreadDocuments.ts | 10 +- .../common/editor/resourceEditorInput.ts | 53 +++--- .../parts/html/browser/html.contribution.ts | 7 +- .../parts/html/browser/htmlPreviewPart.ts | 10 +- .../parts/search/browser/replaceService.ts | 22 ++- .../common/textModelResolverService.ts | 179 +++++++++--------- .../test/textModelResolverService.test.ts | 11 +- 14 files changed, 256 insertions(+), 169 deletions(-) diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index 9228c22540f..6ea534506f1 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -27,7 +27,7 @@ import { CommandService } from 'vs/platform/commands/common/commandService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; import { ITextModelResolverService, ITextModelContentProvider, ITextEditorModel } from 'vs/editor/common/services/resolverService'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, IReference, ImmortalReference } from 'vs/base/common/lifecycle'; export class SimpleEditor implements IEditor { @@ -173,7 +173,7 @@ export class SimpleEditorModelResolverService implements ITextModelResolverServi this.editor = new SimpleEditor(editor); } - public resolve(resource: URI): TPromise { + public getModelReference(resource: URI): IReference> { let model: editorCommon.IModel; model = this.editor.withTypedEditor( @@ -182,10 +182,10 @@ export class SimpleEditorModelResolverService implements ITextModelResolverServi ); if (!model) { - return TPromise.as(null); + return new ImmortalReference(TPromise.as(null)); } - return TPromise.as(new SimpleModel(model)); + return new ImmortalReference(TPromise.as(new SimpleModel(model))); } public registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { diff --git a/src/vs/editor/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index 0143ef864ab..5a93e61c2cd 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import { merge } from 'vs/base/common/arrays'; import { IStringDictionary, forEach, values } from 'vs/base/common/collections'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; @@ -64,14 +65,15 @@ class ChangeRecorder { } } -class EditTask { +class EditTask implements IDisposable { private _initialSelections: Selection[]; private _endCursorSelection: Selection; private _model: IModel; + private _modelReference: IDisposable; private _edits: IIdentifiedSingleEditOperation[]; - constructor(model: IModel) { + constructor(model: IModel, modelReference: IDisposable) { this._endCursorSelection = null; this._model = model; this._edits = []; @@ -138,14 +140,18 @@ class EditTask { private static _editCompare(a: IIdentifiedSingleEditOperation, b: IIdentifiedSingleEditOperation): number { return Range.compareRangesUsingStarts(a.range, b.range); } + + dispose() { + this._modelReference.dispose(); + } } class SourceModelEditTask extends EditTask { private _knownInitialSelections: Selection[]; - constructor(model: IModel, initialSelections: Selection[]) { - super(model); + constructor(model: IModel, modelReference: IDisposable, initialSelections: Selection[]) { + super(model, modelReference); this._knownInitialSelections = initialSelections; } @@ -154,7 +160,7 @@ class SourceModelEditTask extends EditTask { } } -class BulkEditModel { +class BulkEditModel implements IDisposable { private _textModelResolverService: ITextModelResolverService; private _numberOfResourcesToModify: number = 0; @@ -208,7 +214,11 @@ class BulkEditModel { } forEach(this._edits, entry => { - const promise = this._textModelResolverService.resolve(URI.parse(entry.key)).then(model => { + const modelReference = this._textModelResolverService.getModelReference(URI.parse(entry.key)); + const modelPromise = modelReference.object; + + // TODO@Joao TODO@Joh: missing reference dispose() + const promise = modelPromise.then(model => { if (!model || !model.textEditorModel) { throw new Error(`Cannot load file ${entry.key}`); } @@ -217,10 +227,10 @@ class BulkEditModel { let task: EditTask; if (this._sourceModel && textEditorModel.uri.toString() === this._sourceModel.toString()) { - this._sourceModelTask = new SourceModelEditTask(textEditorModel, this._sourceSelections); + this._sourceModelTask = new SourceModelEditTask(textEditorModel, modelReference, this._sourceSelections); task = this._sourceModelTask; } else { - task = new EditTask(textEditorModel); + task = new EditTask(textEditorModel, modelReference); } entry.value.forEach(edit => task.addEdit(edit)); @@ -251,6 +261,10 @@ class BulkEditModel { this.progress.worked(1); } } + + dispose(): void { + this._tasks = dispose(this._tasks); + } } export interface BulkEdit { @@ -314,7 +328,7 @@ export function createBulkEdit(eventService: IEventService, textModelResolverSer selections = editor.getSelections(); } - let model = new BulkEditModel(textModelResolverService, uri, selections, all, progressRunner); + const model = new BulkEditModel(textModelResolverService, uri, selections, all, progressRunner); return model.prepare().then(_ => { @@ -324,7 +338,10 @@ export function createBulkEdit(eventService: IEventService, textModelResolverSer } recording.stop(); - return model.apply(); + + const result = model.apply(); + model.dispose(); + return result; }); } diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index b40512bf94d..f3c9ec9f8c9 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -9,7 +9,7 @@ import URI from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IModel } from 'vs/editor/common/editorCommon'; import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, IReference } from 'vs/base/common/lifecycle'; export const ITextModelResolverService = createDecorator('textModelResolverService'); @@ -17,14 +17,13 @@ export interface ITextModelResolverService { _serviceBrand: any; /** - * Given a resource, tries to resolve a ITextEditorModel out of it. Will support many schemes like file://, untitled://, - * inMemory:// and for anything else fall back to the model content provider registry. + * Provided a resource URI, it will return a model reference + * which should be disposed once not needed anymore. */ - resolve(resource: URI): TPromise; + getModelReference(resource: URI): IReference>; /** - * For unknown resources, allows to register a content provider such as this service is able to resolve arbritrary - * resources to ITextEditorModels. + * Registers a specific `scheme` content provider. */ registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable; } diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index 3df5bc33b45..235cad2eef3 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -112,6 +112,8 @@ export class DefinitionAction extends EditorAction { this._openReference(editorService, next, this._configuration.openToSide).then(editor => { if (model.references.length > 1) { this._openInPeek(editorService, editor, model); + } else { + model.dispose(); } }); } @@ -142,6 +144,8 @@ export class DefinitionAction extends EditorAction { return this._openReference(editorService, reference, false); } }); + } else { + model.dispose(); } } } @@ -319,7 +323,9 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC // Single result else { let result = results[0]; - this.textModelResolverService.resolve(result.uri).then(model => { + const modelReference = this.textModelResolverService.getModelReference(result.uri); + + modelReference.object.then(model => { let hoverMessage: MarkedString; if (model && model.textEditorModel) { const editorModel = model.textEditorModel; @@ -369,6 +375,8 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC }; } + modelReference.dispose(); + this.addDecoration({ startLineNumber: position.lineNumber, startColumn: word.startColumn, diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts index 50e9720009c..a779cc24b7d 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts @@ -147,6 +147,11 @@ export class ReferencesController implements editorCommon.IEditorContribution { if (requestId !== this._requestIdPool || !this._widget) { return; } + + if (this._model) { + this._model.dispose(); + } + this._model = model; // measure time it stays open @@ -194,7 +199,10 @@ export class ReferencesController implements editorCommon.IEditorContribution { } this._referenceSearchVisible.reset(); this._disposables = dispose(this._disposables); - this._model = null; + if (this._model) { + this._model.dispose(); + this._model = null; + } this._editor.focus(); this._requestIdPool += 1; // Cancel pending requests } diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts index 9d5e770208c..78a1e0e7417 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts @@ -7,6 +7,7 @@ import { EventEmitter } from 'vs/base/common/eventEmitter'; import Event, { fromEventEmitter } from 'vs/base/common/event'; import { basename, dirname } from 'vs/base/common/paths'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import URI from 'vs/base/common/uri'; import { defaultGenerator } from 'vs/base/common/idGenerator'; @@ -62,30 +63,37 @@ export class OneReference { } } -export class FilePreview { +export class FilePreview implements IDisposable { - constructor(private _value: IModel) { + constructor(private _model: IModel, private _modelReference: IDisposable) { } public preview(range: IRange, n: number = 8): { before: string; inside: string; after: string } { const {startLineNumber, startColumn, endColumn} = range; - const word = this._value.getWordUntilPosition({ lineNumber: startLineNumber, column: startColumn - n }); + const word = this._model.getWordUntilPosition({ lineNumber: startLineNumber, column: startColumn - n }); const beforeRange = new Range(startLineNumber, word.startColumn, startLineNumber, startColumn); const afterRange = new Range(startLineNumber, endColumn, startLineNumber, Number.MAX_VALUE); const ret = { - before: this._value.getValueInRange(beforeRange).replace(/^\s+/, strings.empty), - inside: this._value.getValueInRange(range), - after: this._value.getValueInRange(afterRange).replace(/\s+$/, strings.empty) + before: this._model.getValueInRange(beforeRange).replace(/^\s+/, strings.empty), + inside: this._model.getValueInRange(range), + after: this._model.getValueInRange(afterRange).replace(/\s+$/, strings.empty) }; return ret; } + + dispose(): void { + if (this._modelReference) { + this._modelReference.dispose(); + this._modelReference = null; + } + } } -export class FileReferences { +export class FileReferences implements IDisposable { private _children: OneReference[]; private _preview: FilePreview; @@ -134,11 +142,14 @@ export class FileReferences { return TPromise.as(this); } - return textModelResolverService.resolve(this._uri).then(model => { + const modelReference = textModelResolverService.getModelReference(this._uri); + const modelPromise = modelReference.object; + + return modelPromise.then(model => { if (!model) { throw new Error(); } - this._preview = new FilePreview(model.textEditorModel); + this._preview = new FilePreview(model.textEditorModel, modelReference); this._resolved = true; return this; @@ -150,9 +161,16 @@ export class FileReferences { return this; }); } + + dispose(): void { + if (this._preview) { + this._preview.dispose(); + this._preview = null; + } + } } -export class ReferencesModel { +export class ReferencesModel implements IDisposable { private _groups: FileReferences[] = []; private _references: OneReference[] = []; @@ -239,6 +257,10 @@ export class ReferencesModel { } } + dispose(): void { + this._groups = dispose(this._groups); + } + private static _compareReferences(a: Location, b: Location): number { if (a.uri.toString() < b.uri.toString()) { return -1; diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts index da509cfb6c3..2247249ce85 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts @@ -10,7 +10,7 @@ import * as collections from 'vs/base/common/collections'; import { onUnexpectedError } from 'vs/base/common/errors'; import { getPathLabel } from 'vs/base/common/labels'; import Event, { Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose, Disposables } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposables, empty as EmptyDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as strings from 'vs/base/common/strings'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -494,6 +494,7 @@ export class ReferenceWidget extends PeekViewWidget { private _treeContainer: Builder; private _sash: VSash; private _preview: ICodeEditor; + private _previewModelReference: IDisposable = EmptyDisposable; private _previewNotAvailableMessage: Model; private _previewContainer: Builder; private _messageContainer: Builder; @@ -715,11 +716,12 @@ export class ReferenceWidget extends PeekViewWidget { this.setTitle(nls.localize('peekView.alternateTitle', "References")); } - return TPromise.join([ - this._textModelResolverService.resolve(reference.uri), - this._tree.reveal(reference) - ]).then(values => { + const modelReference = this._textModelResolverService.getModelReference(reference.uri); + const modelPromise = modelReference.object; + + return TPromise.join([modelPromise, this._tree.reveal(reference)]).then(values => { if (!this._model) { + modelReference.dispose(); // disposed return; } @@ -735,6 +737,9 @@ export class ReferenceWidget extends PeekViewWidget { this._preview.setModel(this._previewNotAvailableMessage); } + this._previewModelReference.dispose(); + this._previewModelReference = modelReference; + // show in tree this._tree.setSelection([reference]); this._tree.setFocus(reference); diff --git a/src/vs/workbench/api/node/mainThreadDocuments.ts b/src/vs/workbench/api/node/mainThreadDocuments.ts index 8a575e652df..6329ca68667 100644 --- a/src/vs/workbench/api/node/mainThreadDocuments.ts +++ b/src/vs/workbench/api/node/mainThreadDocuments.ts @@ -187,8 +187,14 @@ export class MainThreadDocuments extends MainThreadDocumentsShape { } private _handleAsResourceInput(uri: URI): TPromise { - return this._textModelResolverService.resolve(uri).then(model => { - return !!model; + const modelReference = this._textModelResolverService.getModelReference(uri); + const modelPromise = modelReference.object; + + return modelPromise.then(model => { + const result = !!model; + modelReference.dispose(); + + return result; }); } diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 5464bb982f9..ee01643116b 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -7,6 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { EditorInput, ITextEditorModel } from 'vs/workbench/common/editor'; import URI from 'vs/base/common/uri'; +import { IReference } from 'vs/base/common/lifecycle'; import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetry'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; @@ -17,9 +18,9 @@ import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorMo */ export class ResourceEditorInput extends EditorInput { - public static ID: string = 'workbench.editors.resourceEditorInput'; + static ID: string = 'workbench.editors.resourceEditorInput'; - protected cachedModel: ResourceEditorModel; + protected modelReference: IReference>; protected resource: URI; private name: string; @@ -38,64 +39,60 @@ export class ResourceEditorInput extends EditorInput { this.resource = resource; } - public getTypeId(): string { + getTypeId(): string { return ResourceEditorInput.ID; } - public getName(): string { + getName(): string { return this.name; } - public setName(name: string): void { + setName(name: string): void { if (this.name !== name) { this.name = name; this._onDidChangeLabel.fire(); } } - public getDescription(): string { + getDescription(): string { return this.description; } - public setDescription(description: string): void { + setDescription(description: string): void { if (this.description !== description) { this.description = description; this._onDidChangeLabel.fire(); } } - public getTelemetryDescriptor(): { [key: string]: any; } { + getTelemetryDescriptor(): { [key: string]: any; } { const descriptor = super.getTelemetryDescriptor(); descriptor['resource'] = telemetryURIDescriptor(this.resource); return descriptor; } - public resolve(refresh?: boolean): TPromise { - - // Use Cached Model - if (this.cachedModel) { - return TPromise.as(this.cachedModel); + resolve(refresh?: boolean): TPromise { + if (!this.modelReference) { + this.modelReference = this.textModelResolverService.getModelReference(this.resource); } - // Otherwise Create Model and handle dispose event - return this.textModelResolverService.resolve(this.resource).then(model => { + const modelPromise = this.modelReference.object; + + return modelPromise.then(model => { if (!(model instanceof ResourceEditorModel)) { + this.modelReference.dispose(); + this.modelReference = null; return TPromise.wrapError(`Unexpected model for ResourceInput: ${this.resource}`); // TODO@Ben eventually also files should be supported, but we guard due to the dangerous dispose of the model in dispose() } - this.cachedModel = model; + // TODO@Joao this should never happen + model.onDispose(() => this.dispose()); - const unbind = model.onDispose(() => { - this.cachedModel = null; // make sure we do not dispose model again - unbind.dispose(); - this.dispose(); - }); - - return this.cachedModel; + return model; }); } - public matches(otherInput: any): boolean { + matches(otherInput: any): boolean { if (super.matches(otherInput) === true) { return true; } @@ -110,10 +107,10 @@ export class ResourceEditorInput extends EditorInput { return false; } - public dispose(): void { - if (this.cachedModel) { - this.cachedModel.dispose(); - this.cachedModel = null; + dispose(): void { + if (this.modelReference) { + this.modelReference.dispose(); + this.modelReference = null; } super.dispose(); diff --git a/src/vs/workbench/parts/html/browser/html.contribution.ts b/src/vs/workbench/parts/html/browser/html.contribution.ts index ec74f6ad9c9..130bb9e3ae0 100644 --- a/src/vs/workbench/parts/html/browser/html.contribution.ts +++ b/src/vs/workbench/parts/html/browser/html.contribution.ts @@ -60,8 +60,13 @@ CommandsRegistry.registerCommand('_workbench.htmlZone', function (accessor: Serv return; } - return accessor.get(ITextModelResolverService).resolve(params.resource).then(model => { + const textModelResolverService = accessor.get(ITextModelResolverService); + const modelReference = textModelResolverService.getModelReference(params.resource); + + return modelReference.object.then(model => { const contents = model.textEditorModel.getValue(); + modelReference.dispose(); + HtmlZoneController.getInstance(codeEditor).addZone(params.lineNumber, contents); }); diff --git a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts index 2b0cafc32be..a79aff1e2b2 100644 --- a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts @@ -40,6 +40,7 @@ export class HtmlPreviewPart extends BaseEditor { private _baseUrl: URI; private _model: IModel; + private _modelReference = EmptyDisposable; private _modelChangeSubscription = EmptyDisposable; private _themeChangeSubscription = EmptyDisposable; @@ -65,6 +66,7 @@ export class HtmlPreviewPart extends BaseEditor { // unhook listeners this._themeChangeSubscription.dispose(); this._modelChangeSubscription.dispose(); + this._modelReference.dispose(); this._model = undefined; super.dispose(); } @@ -142,6 +144,7 @@ export class HtmlPreviewPart extends BaseEditor { } this._model = undefined; + this._modelReference.dispose(); this._modelChangeSubscription.dispose(); if (!(input instanceof HtmlInput)) { @@ -149,14 +152,17 @@ export class HtmlPreviewPart extends BaseEditor { } return super.setInput(input, options).then(() => { - let resourceUri = (input).getResource(); - return this._textModelResolverService.resolve(resourceUri).then(model => { + const resourceUri = (input).getResource(); + const modelReference = this._textModelResolverService.getModelReference(resourceUri); + + return modelReference.object.then(model => { if (model instanceof BaseTextEditorModel) { this._model = model.textEditorModel; } if (!this._model) { return TPromise.wrapError(localize('html.voidInput', "Invalid editor input.")); } + this._modelReference = modelReference; this._modelChangeSubscription = this._model.onDidChangeContent(() => this.webview.contents = this._model.getLinesContent()); this.webview.baseUrl = resourceUri.toString(true); this.webview.contents = this._model.getLinesContent(); diff --git a/src/vs/workbench/parts/search/browser/replaceService.ts b/src/vs/workbench/parts/search/browser/replaceService.ts index 518ad4d0453..07de8fbae6b 100644 --- a/src/vs/workbench/parts/search/browser/replaceService.ts +++ b/src/vs/workbench/parts/search/browser/replaceService.ts @@ -57,13 +57,18 @@ class EditorInputCache { if (editorInputPromise) { editorInputPromise.done(() => { if (reloadFromSource) { - this.textModelResolverService.resolve(fileMatch.resource()).then(editorModel => { - if (editorModel.textEditorModel) { + const modelReference = this.textModelResolverService.getModelReference(fileMatch.resource()); + const modelPromise = modelReference.object; + + modelPromise.done(model => { + if (model.textEditorModel) { let replaceResource = this.getReplaceResource(fileMatch.resource()); - this.modelService.getModel(replaceResource).setValue(editorModel.textEditorModel.getValue()); + this.modelService.getModel(replaceResource).setValue(model.textEditorModel.getValue()); this.replaceService.replace(fileMatch, null, replaceResource); + modelReference.dispose(); } }); + } else { let replaceResource = this.getReplaceResource(fileMatch.resource()); this.modelService.getModel(replaceResource).undo(); @@ -108,10 +113,15 @@ class EditorInputCache { private createRightInput(element: FileMatch): TPromise { return new TPromise((c, e, p) => { - this.textModelResolverService.resolve(element.resource()).then(value => { - let model = value.textEditorModel; + const modelReference = this.textModelResolverService.getModelReference(element.resource()); + const modelPromise = modelReference.object; + + modelPromise.then(model => { + let textEditorModel = model.textEditorModel; let replaceResource = this.getReplaceResource(element.resource()); - this.modelService.createModel(model.getValue(), model.getMode(), replaceResource); + this.modelService.createModel(textEditorModel.getValue(), textEditorModel.getMode(), replaceResource); + modelReference.dispose(); + c(this.editorService.createInput({ resource: replaceResource })); }); }); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index a611b42f65e..bc3dae035fd 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -8,9 +8,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModel } from 'vs/editor/common/editorCommon'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, IReference, ReferenceCollection, ImmortalReference } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { sequence } from 'vs/base/common/async'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import network = require('vs/base/common/network'); @@ -19,12 +18,9 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class TextModelResolverService implements ITextModelResolverService { +class ResourceModelCollection extends ReferenceCollection> { - public _serviceBrand: any; - - private loadingTextModels: { [uri: string]: TPromise } = Object.create(null); - private contentProviderRegistry: { [scheme: string]: ITextModelContentProvider[] } = Object.create(null); + private providers: { [scheme: string]: ITextModelContentProvider[] } = Object.create(null); constructor( @ITextFileService private textFileService: ITextFileService, @@ -33,102 +29,103 @@ export class TextModelResolverService implements ITextModelResolverService { @IInstantiationService private instantiationService: IInstantiationService, @IModelService private modelService: IModelService ) { + super(); } - public resolve(resource: URI): TPromise { + getKey(uri: URI): string { + return uri.toString(); + } + create(key: string): TPromise { + const resource = URI.parse(key); + + return this.resolveTextModelContent(this.modelService, key) + .then(() => this.instantiationService.createInstance(ResourceEditorModel, resource)); + } + + destroy(modelPromise: TPromise): void { + modelPromise.done(model => model.dispose()); + } + + registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { + const registry = this.providers; + const providers = registry[scheme] || (registry[scheme] = []); + + providers.unshift(provider); + + return toDisposable(() => { + const array = registry[scheme]; + + if (!array) { + return; + } + + const index = array.indexOf(provider); + + if (index === -1) { + return; + } + + array.splice(index, 1); + + if (array.length === 0) { + delete registry[scheme]; + } + }); + } + + private resolveTextModelContent(modelService: IModelService, key: string): TPromise { + const resource = URI.parse(key); + const model = modelService.getModel(resource); + + if (model) { + // TODO@Joao this should never happen + return TPromise.as(model); + } + + // TODO@Joao just take the first one for now + const provider = (this.providers[resource.scheme] || [])[0]; + + if (!provider) { + return TPromise.wrapError(`No model with uri '${resource}' nor a resolver for the scheme '${resource.scheme}'.`); + } + + return provider.provideTextContent(resource); + } +} + +export class TextModelResolverService implements ITextModelResolverService { + + _serviceBrand: any; + private resourceModelCollection: ResourceModelCollection; + + constructor( + @ITextFileService private textFileService: ITextFileService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IInstantiationService private instantiationService: IInstantiationService, + @IModelService private modelService: IModelService + ) { + this.resourceModelCollection = instantiationService.createInstance(ResourceModelCollection); + } + + getModelReference(resource: URI): IReference> { // File Schema: use text file service if (resource.scheme === network.Schemas.file) { - return this.textFileService.models.loadOrCreate(resource); + // TODO ImmortalReference is a hack + return new ImmortalReference(this.textFileService.models.loadOrCreate(resource)); } // Untitled Schema: go through cached input if (resource.scheme === UntitledEditorInput.SCHEMA) { - return this.untitledEditorService.createOrGet(resource).resolve(); + // TODO ImmortalReference is a hack + return new ImmortalReference(this.untitledEditorService.createOrGet(resource).resolve()); } - // Any other resource: use content provider registry - return this.resolveTextModelContent(this.modelService, resource).then(() => this.instantiationService.createInstance(ResourceEditorModel, resource)); + return this.resourceModelCollection.acquire(resource); } - public registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { - let array = this.contentProviderRegistry[scheme]; - if (!array) { - array = [provider]; - this.contentProviderRegistry[scheme] = array; - } else { - array.unshift(provider); - } - - const registry = this.contentProviderRegistry; - return { - dispose() { - const array = registry[scheme]; - const idx = array.indexOf(provider); - if (idx >= 0) { - array.splice(idx, 1); - if (array.length === 0) { - delete registry[scheme]; - } - } - } - }; - } - - private resolveTextModelContent(modelService: IModelService, resource: URI): TPromise { - const model = modelService.getModel(resource); - if (model) { - return TPromise.as(model); - } - - let loadingTextModel = this.loadingTextModels[resource.toString()]; - if (!loadingTextModel) { - - // make sure we have a provider this scheme - // the resource uses - const contentProviders = this.contentProviderRegistry[resource.scheme]; - if (!contentProviders) { - return TPromise.wrapError(`No model with uri '${resource}' nor a resolver for the scheme '${resource.scheme}'.`); - } - - // load the model-content from the provider and cache - // the loading such that we don't create the same model - // twice - this.loadingTextModels[resource.toString()] = loadingTextModel = new TPromise((resolve, reject) => { - let result: IModel; - let lastError: any; - - sequence(contentProviders.map(provider => { - return () => { - if (!result) { - const contentPromise = provider.provideTextContent(resource); - if (!contentPromise) { - return TPromise.wrapError(`No resolver for the scheme '${resource.scheme}' found.`); - } - - return contentPromise.then(value => { - result = value; - }, err => { - lastError = err; - }); - } - }; - })).then(() => { - if (!result && lastError) { - reject(lastError); - } else { - resolve(result); - } - }, reject); - - }, function () { - // no cancellation when caching promises - }); - - // remove the cached promise 'cos the model is now known to the model service (see above) - loadingTextModel.then(() => delete this.loadingTextModels[resource.toString()], () => delete this.loadingTextModels[resource.toString()]); - } - - return loadingTextModel; + registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { + return this.resourceModelCollection.registerTextModelContentProvider(scheme, provider); } } \ No newline at end of file diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index c9d92e66373..026afb8a8e3 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -82,11 +82,14 @@ suite('Workbench - TextModelResolverService', () => { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8'); (accessor.textFileService.models).add(model.getResource(), model); model.load().then(() => { - accessor.textModelResolverServie.resolve(model.getResource()).then(model => { + const reference = accessor.textModelResolverServie.getModelReference(model.getResource()); + + reference.object.then(model => { const editorModel = model.textEditorModel; assert.ok(editorModel); assert.equal(editorModel.getValue(), 'Hello Html'); + reference.dispose(); done(); }); @@ -98,10 +101,12 @@ suite('Workbench - TextModelResolverService', () => { const input = service.createOrGet(); input.resolve().then(() => { - accessor.textModelResolverServie.resolve(input.getResource()).then(model => { + const reference = accessor.textModelResolverServie.getModelReference(input.getResource()); + reference.object.then(model => { const editorModel = model.textEditorModel; assert.ok(editorModel); + reference.dispose(); input.dispose(); @@ -109,4 +114,6 @@ suite('Workbench - TextModelResolverService', () => { }); }); }); + + // TODO: add reference tests! }); \ No newline at end of file From d33aa52fa13c3a731088a7814f7905a75aacc296 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Nov 2016 12:30:15 +0100 Subject: [PATCH 13/28] ignore diffs for empty original documents --- .../workbench/parts/scm/browser/dirtydiffDecorator.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index 6a3ca2c0d55..04c7a4a2310 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -57,6 +57,7 @@ class DirtyDiffModelDecorator { }; private decorations: string[]; + private textEditorModel: common.IModel; private diffDelayer: ThrottledDelayer; private toDispose: lifecycle.IDisposable[]; @@ -82,8 +83,8 @@ class DirtyDiffModelDecorator { return this.dirtyDiffService.getBaselineResource(this.uri) .then(originalUri => this.textModelResolverService.resolve(originalUri) .then(model => { - const textEditorModel = model.textEditorModel; - this.toDispose.push(textEditorModel.onDidChangeContent(() => this.triggerDiff())); + this.textEditorModel = model.textEditorModel; + this.toDispose.push(this.textEditorModel.onDidChangeContent(() => this.triggerDiff())); return originalUri; })); @@ -100,13 +101,17 @@ class DirtyDiffModelDecorator { } return this.originalURIPromise - .then(uri => this.editorWorkerService.computeDirtyDiff(uri, this.model.uri, true)); + .then(originalURI => this.editorWorkerService.computeDirtyDiff(originalURI, this.model.uri, true)); }).then((diff: common.IChange[]) => { if (!this.model || this.model.isDisposed()) { return; // disposed } + if (this.textEditorModel.getValueLength() === 0) { + diff = []; + } + return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); }); } From 9b5fc0efcbaaddb24f9571276860a011326d958b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Nov 2016 12:32:00 +0100 Subject: [PATCH 14/28] :lipstick: --- src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index 04c7a4a2310..d264a3918c7 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -64,7 +64,7 @@ class DirtyDiffModelDecorator { constructor( private model: common.IModel, private uri: URI, - @ISCMService private dirtyDiffService: ISCMService, + @ISCMService private scmService: ISCMService, @IModelService private modelService: IModelService, @IEditorWorkerService private editorWorkerService: IEditorWorkerService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -80,7 +80,7 @@ class DirtyDiffModelDecorator { @memoize private get originalURIPromise(): winjs.TPromise { - return this.dirtyDiffService.getBaselineResource(this.uri) + return this.scmService.getBaselineResource(this.uri) .then(originalUri => this.textModelResolverService.resolve(originalUri) .then(model => { this.textEditorModel = model.textEditorModel; From 4783f2756d59a4b873f0622c919a07c7e0bc41d0 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Nov 2016 13:05:58 +0100 Subject: [PATCH 15/28] fix integration tests --- src/vs/base/common/async.ts | 31 ++++++++++++++++--- src/vs/editor/common/services/bulkEdit.ts | 7 ++++- .../common/textModelResolverService.ts | 15 +++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 2e1d8b27ca6..2c0b64453ec 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -368,15 +368,15 @@ export function always(promise: TPromise, f: Function): TPromise { * Runs the provided list of promise factories in sequential order. The returned * promise will complete to an array of results from each promise. */ -export function sequence(promiseFactory: ITask>[]): TPromise { +export function sequence(promiseFactories: ITask>[]): TPromise { const results: T[] = []; // reverse since we start with last element using pop() - promiseFactory = promiseFactory.reverse(); + promiseFactories = promiseFactories.reverse(); function next(): Promise { - if (promiseFactory.length) { - return promiseFactory.pop()(); + if (promiseFactories.length) { + return promiseFactories.pop()(); } return null; @@ -398,6 +398,29 @@ export function sequence(promiseFactory: ITask>[]): TPromise return TPromise.as(null).then(thenHandler); } +export function first(promiseFactories: ITask>[], shouldStop: (t: T) => boolean = t => !!t): TPromise { + promiseFactories = [...promiseFactories.reverse()]; + + const loop = () => { + if (promiseFactories.length === 0) { + return TPromise.as(null); + } + + const factory = promiseFactories.pop(); + const promise = factory(); + + return promise.then(result => { + if (shouldStop(result)) { + return TPromise.as(result); + } + + return loop(); + }); + }; + + return loop(); +} + export function once(fn: T): T { const _this = this; let didCall = false; diff --git a/src/vs/editor/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index 5a93e61c2cd..605a0a4c0ab 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -76,6 +76,7 @@ class EditTask implements IDisposable { constructor(model: IModel, modelReference: IDisposable) { this._endCursorSelection = null; this._model = model; + this._modelReference = modelReference; this._edits = []; } @@ -142,7 +143,11 @@ class EditTask implements IDisposable { } dispose() { - this._modelReference.dispose(); + if (this._model) { + this._modelReference.dispose(); + this._modelReference = null; + this._model = null; + } } } diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index bc3dae035fd..d714c0dd10d 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -6,6 +6,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; +import { first } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModel } from 'vs/editor/common/editorCommon'; import { IDisposable, toDisposable, IReference, ReferenceCollection, ImmortalReference } from 'vs/base/common/lifecycle'; @@ -83,14 +84,16 @@ class ResourceModelCollection extends ReferenceCollection () => p.provideTextContent(resource)); - if (!provider) { - return TPromise.wrapError(`No model with uri '${resource}' nor a resolver for the scheme '${resource.scheme}'.`); - } + return first(factories).then(uri => { + if (!uri) { + return TPromise.wrapError(`Could not resolve any model with uri '${resource}'.`); + } - return provider.provideTextContent(resource); + return uri; + }); } } From 0b6675494e9336b78e450fa5076e9d33dd1ad29d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Nov 2016 15:58:48 +0100 Subject: [PATCH 16/28] unwrap reference of promise --- .../browser/standalone/simpleServices.ts | 6 ++-- src/vs/editor/common/services/bulkEdit.ts | 26 +++++++-------- .../editor/common/services/resolverService.ts | 2 +- .../browser/goToDeclaration.ts | 6 ++-- .../browser/referencesModel.ts | 19 ++++++----- .../browser/referencesWidget.ts | 19 ++++++----- .../workbench/api/node/mainThreadDocuments.ts | 9 ++---- .../common/editor/resourceEditorInput.ts | 20 ++++++------ .../parts/html/browser/html.contribution.ts | 6 ++-- .../parts/html/browser/htmlPreviewPart.ts | 29 +++++++++-------- .../parts/search/browser/replaceService.ts | 27 ++++++---------- .../common/textModelResolverService.ts | 32 +++++++++++++++---- .../test/textModelResolverService.test.ts | 19 +++++------ 13 files changed, 115 insertions(+), 105 deletions(-) diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index 6ea534506f1..e75c5180f04 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -173,7 +173,7 @@ export class SimpleEditorModelResolverService implements ITextModelResolverServi this.editor = new SimpleEditor(editor); } - public getModelReference(resource: URI): IReference> { + public getModelReference(resource: URI): TPromise> { let model: editorCommon.IModel; model = this.editor.withTypedEditor( @@ -182,10 +182,10 @@ export class SimpleEditorModelResolverService implements ITextModelResolverServi ); if (!model) { - return new ImmortalReference(TPromise.as(null)); + return TPromise.as(new ImmortalReference(null)); } - return new ImmortalReference(TPromise.as(new SimpleModel(model))); + return TPromise.as(new ImmortalReference(new SimpleModel(model))); } public registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { diff --git a/src/vs/editor/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index 605a0a4c0ab..f0e0139ed4b 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -7,10 +7,10 @@ import * as nls from 'vs/nls'; import { merge } from 'vs/base/common/arrays'; import { IStringDictionary, forEach, values } from 'vs/base/common/collections'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; -import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; +import { ITextModelResolverService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { IEventService } from 'vs/platform/event/common/event'; import { EventType as FileEventType, FileChangesEvent, IFileChange } from 'vs/platform/files/common/files'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -69,13 +69,12 @@ class EditTask implements IDisposable { private _initialSelections: Selection[]; private _endCursorSelection: Selection; - private _model: IModel; - private _modelReference: IDisposable; + private get _model(): IModel { return this._modelReference.object.textEditorModel; } + private _modelReference: IReference; private _edits: IIdentifiedSingleEditOperation[]; - constructor(model: IModel, modelReference: IDisposable) { + constructor(modelReference: IReference) { this._endCursorSelection = null; - this._model = model; this._modelReference = modelReference; this._edits = []; } @@ -146,7 +145,6 @@ class EditTask implements IDisposable { if (this._model) { this._modelReference.dispose(); this._modelReference = null; - this._model = null; } } } @@ -155,8 +153,8 @@ class SourceModelEditTask extends EditTask { private _knownInitialSelections: Selection[]; - constructor(model: IModel, modelReference: IDisposable, initialSelections: Selection[]) { - super(model, modelReference); + constructor(modelReference: IReference, initialSelections: Selection[]) { + super(modelReference); this._knownInitialSelections = initialSelections; } @@ -219,11 +217,9 @@ class BulkEditModel implements IDisposable { } forEach(this._edits, entry => { - const modelReference = this._textModelResolverService.getModelReference(URI.parse(entry.key)); - const modelPromise = modelReference.object; + const promise = this._textModelResolverService.getModelReference(URI.parse(entry.key)).then(ref => { + const model = ref.object; - // TODO@Joao TODO@Joh: missing reference dispose() - const promise = modelPromise.then(model => { if (!model || !model.textEditorModel) { throw new Error(`Cannot load file ${entry.key}`); } @@ -232,10 +228,10 @@ class BulkEditModel implements IDisposable { let task: EditTask; if (this._sourceModel && textEditorModel.uri.toString() === this._sourceModel.toString()) { - this._sourceModelTask = new SourceModelEditTask(textEditorModel, modelReference, this._sourceSelections); + this._sourceModelTask = new SourceModelEditTask(ref, this._sourceSelections); task = this._sourceModelTask; } else { - task = new EditTask(textEditorModel, modelReference); + task = new EditTask(ref); } entry.value.forEach(edit => task.addEdit(edit)); diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index f3c9ec9f8c9..23ebaf331c0 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -20,7 +20,7 @@ export interface ITextModelResolverService { * Provided a resource URI, it will return a model reference * which should be disposed once not needed anymore. */ - getModelReference(resource: URI): IReference>; + getModelReference(resource: URI): TPromise>; /** * Registers a specific `scheme` content provider. diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index 235cad2eef3..cd076797ced 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -323,9 +323,9 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC // Single result else { let result = results[0]; - const modelReference = this.textModelResolverService.getModelReference(result.uri); - modelReference.object.then(model => { + this.textModelResolverService.getModelReference(result.uri).then(ref => { + const model = ref.object; let hoverMessage: MarkedString; if (model && model.textEditorModel) { const editorModel = model.textEditorModel; @@ -375,7 +375,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC }; } - modelReference.dispose(); + ref.dispose(); this.addDecoration({ startLineNumber: position.lineNumber, diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts index 78a1e0e7417..55714c77c1c 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts @@ -7,15 +7,15 @@ import { EventEmitter } from 'vs/base/common/eventEmitter'; import Event, { fromEventEmitter } from 'vs/base/common/event'; import { basename, dirname } from 'vs/base/common/paths'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import URI from 'vs/base/common/uri'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { TPromise } from 'vs/base/common/winjs.base'; import { Range } from 'vs/editor/common/core/range'; -import { IModel, IPosition, IRange } from 'vs/editor/common/editorCommon'; +import { IPosition, IRange } from 'vs/editor/common/editorCommon'; import { Location } from 'vs/editor/common/modes'; -import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; +import { ITextModelResolverService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; export class OneReference { @@ -65,10 +65,12 @@ export class OneReference { export class FilePreview implements IDisposable { - constructor(private _model: IModel, private _modelReference: IDisposable) { + constructor(private _modelReference: IReference) { } + private get _model() { return this._modelReference.object.textEditorModel; } + public preview(range: IRange, n: number = 8): { before: string; inside: string; after: string } { const {startLineNumber, startColumn, endColumn} = range; @@ -142,14 +144,15 @@ export class FileReferences implements IDisposable { return TPromise.as(this); } - const modelReference = textModelResolverService.getModelReference(this._uri); - const modelPromise = modelReference.object; + return textModelResolverService.getModelReference(this._uri).then(modelReference => { + const model = modelReference.object; - return modelPromise.then(model => { if (!model) { + modelReference.dispose(); throw new Error(); } - this._preview = new FilePreview(model.textEditorModel, modelReference); + + this._preview = new FilePreview(modelReference); this._resolved = true; return this; diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts index 2247249ce85..df7f0ca38fb 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts @@ -716,30 +716,33 @@ export class ReferenceWidget extends PeekViewWidget { this.setTitle(nls.localize('peekView.alternateTitle', "References")); } - const modelReference = this._textModelResolverService.getModelReference(reference.uri); - const modelPromise = modelReference.object; + const promise = this._textModelResolverService.getModelReference(reference.uri); + + return TPromise.join([promise, this._tree.reveal(reference)]).then(values => { + const ref = values[0]; - return TPromise.join([modelPromise, this._tree.reveal(reference)]).then(values => { if (!this._model) { - modelReference.dispose(); + ref.dispose(); // disposed return; } + this._previewModelReference.dispose(); + this._previewModelReference = EmptyDisposable; + // show in editor - let [model] = values; + const model = ref.object; if (model) { + this._previewModelReference = ref; this._preview.setModel(model.textEditorModel); var sel = Range.lift(reference.range).collapseToStart(); this._preview.setSelection(sel); this._preview.revealRangeInCenter(sel); } else { this._preview.setModel(this._previewNotAvailableMessage); + ref.dispose(); } - this._previewModelReference.dispose(); - this._previewModelReference = modelReference; - // show in tree this._tree.setSelection([reference]); this._tree.setFocus(reference); diff --git a/src/vs/workbench/api/node/mainThreadDocuments.ts b/src/vs/workbench/api/node/mainThreadDocuments.ts index 6329ca68667..0ce3cb8467b 100644 --- a/src/vs/workbench/api/node/mainThreadDocuments.ts +++ b/src/vs/workbench/api/node/mainThreadDocuments.ts @@ -187,12 +187,9 @@ export class MainThreadDocuments extends MainThreadDocumentsShape { } private _handleAsResourceInput(uri: URI): TPromise { - const modelReference = this._textModelResolverService.getModelReference(uri); - const modelPromise = modelReference.object; - - return modelPromise.then(model => { - const result = !!model; - modelReference.dispose(); + return this._textModelResolverService.getModelReference(uri).then(ref => { + const result = !!ref.object; + ref.dispose(); return result; }); diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index ee01643116b..6703707d2c1 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -20,7 +20,7 @@ export class ResourceEditorInput extends EditorInput { static ID: string = 'workbench.editors.resourceEditorInput'; - protected modelReference: IReference>; + protected promise: TPromise>; protected resource: URI; private name: string; @@ -72,16 +72,16 @@ export class ResourceEditorInput extends EditorInput { } resolve(refresh?: boolean): TPromise { - if (!this.modelReference) { - this.modelReference = this.textModelResolverService.getModelReference(this.resource); + if (!this.promise) { + this.promise = this.textModelResolverService.getModelReference(this.resource); } - const modelPromise = this.modelReference.object; + return this.promise.then(ref => { + const model = ref.object; - return modelPromise.then(model => { if (!(model instanceof ResourceEditorModel)) { - this.modelReference.dispose(); - this.modelReference = null; + ref.dispose(); + this.promise = null; return TPromise.wrapError(`Unexpected model for ResourceInput: ${this.resource}`); // TODO@Ben eventually also files should be supported, but we guard due to the dangerous dispose of the model in dispose() } @@ -108,9 +108,9 @@ export class ResourceEditorInput extends EditorInput { } dispose(): void { - if (this.modelReference) { - this.modelReference.dispose(); - this.modelReference = null; + if (this.promise) { + this.promise.done(ref => ref.dispose()); + this.promise = null; } super.dispose(); diff --git a/src/vs/workbench/parts/html/browser/html.contribution.ts b/src/vs/workbench/parts/html/browser/html.contribution.ts index 130bb9e3ae0..ff46441f2b2 100644 --- a/src/vs/workbench/parts/html/browser/html.contribution.ts +++ b/src/vs/workbench/parts/html/browser/html.contribution.ts @@ -61,11 +61,11 @@ CommandsRegistry.registerCommand('_workbench.htmlZone', function (accessor: Serv } const textModelResolverService = accessor.get(ITextModelResolverService); - const modelReference = textModelResolverService.getModelReference(params.resource); - return modelReference.object.then(model => { + return textModelResolverService.getModelReference(params.resource).then(ref => { + const model = ref.object; const contents = model.textEditorModel.getValue(); - modelReference.dispose(); + ref.dispose(); HtmlZoneController.getInstance(codeEditor).addZone(params.lineNumber, contents); }); diff --git a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts index a79aff1e2b2..51fdec812f6 100644 --- a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts @@ -10,7 +10,7 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IModel } from 'vs/editor/common/editorCommon'; import { Dimension, Builder } from 'vs/base/browser/builder'; -import { empty as EmptyDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { empty as EmptyDisposable, IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { Position } from 'vs/platform/editor/common/editor'; @@ -20,7 +20,7 @@ import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel' import { HtmlInput } from 'vs/workbench/parts/html/common/htmlInput'; import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; +import { ITextModelResolverService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import Webview from './webview'; /** @@ -39,8 +39,8 @@ export class HtmlPreviewPart extends BaseEditor { private _baseUrl: URI; - private _model: IModel; - private _modelReference = EmptyDisposable; + private _modelRef: IReference; + private get _model(): IModel { return this._modelRef.object.textEditorModel; } private _modelChangeSubscription = EmptyDisposable; private _themeChangeSubscription = EmptyDisposable; @@ -66,8 +66,9 @@ export class HtmlPreviewPart extends BaseEditor { // unhook listeners this._themeChangeSubscription.dispose(); this._modelChangeSubscription.dispose(); - this._modelReference.dispose(); - this._model = undefined; + if (this._modelRef) { + this._modelRef.dispose(); + } super.dispose(); } @@ -123,7 +124,7 @@ export class HtmlPreviewPart extends BaseEditor { } private _hasValidModel(): boolean { - return this._model && !this._model.isDisposed(); + return this._modelRef && this._model && !this._model.isDisposed(); } public layout(dimension: Dimension): void { @@ -143,8 +144,9 @@ export class HtmlPreviewPart extends BaseEditor { return TPromise.as(undefined); } - this._model = undefined; - this._modelReference.dispose(); + if (this._modelRef) { + this._modelRef.dispose(); + } this._modelChangeSubscription.dispose(); if (!(input instanceof HtmlInput)) { @@ -153,16 +155,17 @@ export class HtmlPreviewPart extends BaseEditor { return super.setInput(input, options).then(() => { const resourceUri = (input).getResource(); - const modelReference = this._textModelResolverService.getModelReference(resourceUri); + return this._textModelResolverService.getModelReference(resourceUri).then(ref => { + const model = ref.object; - return modelReference.object.then(model => { if (model instanceof BaseTextEditorModel) { - this._model = model.textEditorModel; + this._modelRef = ref; } + if (!this._model) { return TPromise.wrapError(localize('html.voidInput', "Invalid editor input.")); } - this._modelReference = modelReference; + this._modelChangeSubscription = this._model.onDidChangeContent(() => this.webview.contents = this._model.getLinesContent()); this.webview.baseUrl = resourceUri.toString(true); this.webview.contents = this._model.getLinesContent(); diff --git a/src/vs/workbench/parts/search/browser/replaceService.ts b/src/vs/workbench/parts/search/browser/replaceService.ts index 07de8fbae6b..c3747aac4c5 100644 --- a/src/vs/workbench/parts/search/browser/replaceService.ts +++ b/src/vs/workbench/parts/search/browser/replaceService.ts @@ -57,18 +57,15 @@ class EditorInputCache { if (editorInputPromise) { editorInputPromise.done(() => { if (reloadFromSource) { - const modelReference = this.textModelResolverService.getModelReference(fileMatch.resource()); - const modelPromise = modelReference.object; - - modelPromise.done(model => { + this.textModelResolverService.getModelReference(fileMatch.resource()).done(ref => { + const model = ref.object; if (model.textEditorModel) { let replaceResource = this.getReplaceResource(fileMatch.resource()); this.modelService.getModel(replaceResource).setValue(model.textEditorModel.getValue()); this.replaceService.replace(fileMatch, null, replaceResource); - modelReference.dispose(); + ref.dispose(); } }); - } else { let replaceResource = this.getReplaceResource(fileMatch.resource()); this.modelService.getModel(replaceResource).undo(); @@ -112,18 +109,14 @@ class EditorInputCache { } private createRightInput(element: FileMatch): TPromise { - return new TPromise((c, e, p) => { - const modelReference = this.textModelResolverService.getModelReference(element.resource()); - const modelPromise = modelReference.object; + return this.textModelResolverService.getModelReference(element.resource()).then(ref => { + const model = ref.object; + let textEditorModel = model.textEditorModel; + let replaceResource = this.getReplaceResource(element.resource()); + this.modelService.createModel(textEditorModel.getValue(), textEditorModel.getMode(), replaceResource); + ref.dispose(); - modelPromise.then(model => { - let textEditorModel = model.textEditorModel; - let replaceResource = this.getReplaceResource(element.resource()); - this.modelService.createModel(textEditorModel.getValue(), textEditorModel.getMode(), replaceResource); - modelReference.dispose(); - - c(this.editorService.createInput({ resource: replaceResource })); - }); + return this.editorService.createInput({ resource: replaceResource }); }); } diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index d714c0dd10d..52835fc3d18 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -6,7 +6,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; -import { first } from 'vs/base/common/async'; +import { first, always } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModel } from 'vs/editor/common/editorCommon'; import { IDisposable, toDisposable, IReference, ReferenceCollection, ImmortalReference } from 'vs/base/common/lifecycle'; @@ -100,6 +100,8 @@ class ResourceModelCollection extends ReferenceCollection> } = Object.create(null); private resourceModelCollection: ResourceModelCollection; constructor( @@ -112,20 +114,36 @@ export class TextModelResolverService implements ITextModelResolverService { this.resourceModelCollection = instantiationService.createInstance(ResourceModelCollection); } - getModelReference(resource: URI): IReference> { + getModelReference(resource: URI): TPromise> { + const uri = resource.toString(); + let promise = this.promiseCache[uri]; + + if (promise) { + return promise; + } + + promise = this.promiseCache[uri] = this.getModel(resource); + + return always(promise, () => delete this.promiseCache[uri]); + } + + private getModel(resource: URI): TPromise> { // File Schema: use text file service + // TODO ImmortalReference is a hack if (resource.scheme === network.Schemas.file) { - // TODO ImmortalReference is a hack - return new ImmortalReference(this.textFileService.models.loadOrCreate(resource)); + return this.textFileService.models.loadOrCreate(resource) + .then(model => new ImmortalReference(model)); } // Untitled Schema: go through cached input + // TODO ImmortalReference is a hack if (resource.scheme === UntitledEditorInput.SCHEMA) { - // TODO ImmortalReference is a hack - return new ImmortalReference(this.untitledEditorService.createOrGet(resource).resolve()); + return this.untitledEditorService.createOrGet(resource).resolve() + .then(model => new ImmortalReference(model)); } - return this.resourceModelCollection.acquire(resource); + const ref = this.resourceModelCollection.acquire(resource); + return ref.object.then(model => ({ object: model, dispose: () => ref.dispose() })); } registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index 026afb8a8e3..34897b4fa2c 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -78,20 +78,18 @@ suite('Workbench - TextModelResolverService', () => { }); }); - test('resolve file', function (done) { + test('resolve file', function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8'); (accessor.textFileService.models).add(model.getResource(), model); - model.load().then(() => { - const reference = accessor.textModelResolverServie.getModelReference(model.getResource()); - reference.object.then(model => { + return model.load().then(() => { + return accessor.textModelResolverServie.getModelReference(model.getResource()).then(ref => { + const model = ref.object; const editorModel = model.textEditorModel; assert.ok(editorModel); assert.equal(editorModel.getValue(), 'Hello Html'); - reference.dispose(); - - done(); + ref.dispose(); }); }); }); @@ -101,16 +99,15 @@ suite('Workbench - TextModelResolverService', () => { const input = service.createOrGet(); input.resolve().then(() => { - const reference = accessor.textModelResolverServie.getModelReference(input.getResource()); - reference.object.then(model => { + return accessor.textModelResolverServie.getModelReference(input.getResource()).then(ref => { + const model = ref.object; const editorModel = model.textEditorModel; assert.ok(editorModel); - reference.dispose(); + ref.dispose(); input.dispose(); - done(); }); }); }); From 3eddc6635773c47d746857060f727fe530d8ff7d Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 21 Nov 2016 16:00:06 +0100 Subject: [PATCH 17/28] createModelReference(...) --- src/vs/editor/browser/standalone/simpleServices.ts | 2 +- src/vs/editor/common/services/bulkEdit.ts | 2 +- src/vs/editor/common/services/resolverService.ts | 2 +- .../editor/contrib/goToDeclaration/browser/goToDeclaration.ts | 2 +- .../editor/contrib/referenceSearch/browser/referencesModel.ts | 2 +- .../contrib/referenceSearch/browser/referencesWidget.ts | 2 +- src/vs/workbench/api/node/mainThreadDocuments.ts | 2 +- src/vs/workbench/common/editor/resourceEditorInput.ts | 2 +- src/vs/workbench/parts/html/browser/html.contribution.ts | 2 +- src/vs/workbench/parts/html/browser/htmlPreviewPart.ts | 2 +- src/vs/workbench/parts/search/browser/replaceService.ts | 4 ++-- .../textmodelResolver/common/textModelResolverService.ts | 2 +- .../textmodelResolver/test/textModelResolverService.test.ts | 4 ++-- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index e75c5180f04..710c1145cb4 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -173,7 +173,7 @@ export class SimpleEditorModelResolverService implements ITextModelResolverServi this.editor = new SimpleEditor(editor); } - public getModelReference(resource: URI): TPromise> { + public createModelReference(resource: URI): TPromise> { let model: editorCommon.IModel; model = this.editor.withTypedEditor( diff --git a/src/vs/editor/common/services/bulkEdit.ts b/src/vs/editor/common/services/bulkEdit.ts index f0e0139ed4b..84a65ce432f 100644 --- a/src/vs/editor/common/services/bulkEdit.ts +++ b/src/vs/editor/common/services/bulkEdit.ts @@ -217,7 +217,7 @@ class BulkEditModel implements IDisposable { } forEach(this._edits, entry => { - const promise = this._textModelResolverService.getModelReference(URI.parse(entry.key)).then(ref => { + const promise = this._textModelResolverService.createModelReference(URI.parse(entry.key)).then(ref => { const model = ref.object; if (!model || !model.textEditorModel) { diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 23ebaf331c0..aacd94b9622 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -20,7 +20,7 @@ export interface ITextModelResolverService { * Provided a resource URI, it will return a model reference * which should be disposed once not needed anymore. */ - getModelReference(resource: URI): TPromise>; + createModelReference(resource: URI): TPromise>; /** * Registers a specific `scheme` content provider. diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index cd076797ced..7fbd1dbe432 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -324,7 +324,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC else { let result = results[0]; - this.textModelResolverService.getModelReference(result.uri).then(ref => { + this.textModelResolverService.createModelReference(result.uri).then(ref => { const model = ref.object; let hoverMessage: MarkedString; if (model && model.textEditorModel) { diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts index 55714c77c1c..e9b7289215b 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesModel.ts @@ -144,7 +144,7 @@ export class FileReferences implements IDisposable { return TPromise.as(this); } - return textModelResolverService.getModelReference(this._uri).then(modelReference => { + return textModelResolverService.createModelReference(this._uri).then(modelReference => { const model = modelReference.object; if (!model) { diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts index df7f0ca38fb..9d0d3e7e8f8 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesWidget.ts @@ -716,7 +716,7 @@ export class ReferenceWidget extends PeekViewWidget { this.setTitle(nls.localize('peekView.alternateTitle', "References")); } - const promise = this._textModelResolverService.getModelReference(reference.uri); + const promise = this._textModelResolverService.createModelReference(reference.uri); return TPromise.join([promise, this._tree.reveal(reference)]).then(values => { const ref = values[0]; diff --git a/src/vs/workbench/api/node/mainThreadDocuments.ts b/src/vs/workbench/api/node/mainThreadDocuments.ts index 0ce3cb8467b..fd123e00b57 100644 --- a/src/vs/workbench/api/node/mainThreadDocuments.ts +++ b/src/vs/workbench/api/node/mainThreadDocuments.ts @@ -187,7 +187,7 @@ export class MainThreadDocuments extends MainThreadDocumentsShape { } private _handleAsResourceInput(uri: URI): TPromise { - return this._textModelResolverService.getModelReference(uri).then(ref => { + return this._textModelResolverService.createModelReference(uri).then(ref => { const result = !!ref.object; ref.dispose(); diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 6703707d2c1..6aa853fed0a 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -73,7 +73,7 @@ export class ResourceEditorInput extends EditorInput { resolve(refresh?: boolean): TPromise { if (!this.promise) { - this.promise = this.textModelResolverService.getModelReference(this.resource); + this.promise = this.textModelResolverService.createModelReference(this.resource); } return this.promise.then(ref => { diff --git a/src/vs/workbench/parts/html/browser/html.contribution.ts b/src/vs/workbench/parts/html/browser/html.contribution.ts index ff46441f2b2..7c3fb2cdc9a 100644 --- a/src/vs/workbench/parts/html/browser/html.contribution.ts +++ b/src/vs/workbench/parts/html/browser/html.contribution.ts @@ -62,7 +62,7 @@ CommandsRegistry.registerCommand('_workbench.htmlZone', function (accessor: Serv const textModelResolverService = accessor.get(ITextModelResolverService); - return textModelResolverService.getModelReference(params.resource).then(ref => { + return textModelResolverService.createModelReference(params.resource).then(ref => { const model = ref.object; const contents = model.textEditorModel.getValue(); ref.dispose(); diff --git a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts index 51fdec812f6..03e4e08d058 100644 --- a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts @@ -155,7 +155,7 @@ export class HtmlPreviewPart extends BaseEditor { return super.setInput(input, options).then(() => { const resourceUri = (input).getResource(); - return this._textModelResolverService.getModelReference(resourceUri).then(ref => { + return this._textModelResolverService.createModelReference(resourceUri).then(ref => { const model = ref.object; if (model instanceof BaseTextEditorModel) { diff --git a/src/vs/workbench/parts/search/browser/replaceService.ts b/src/vs/workbench/parts/search/browser/replaceService.ts index c3747aac4c5..cffb8d16073 100644 --- a/src/vs/workbench/parts/search/browser/replaceService.ts +++ b/src/vs/workbench/parts/search/browser/replaceService.ts @@ -57,7 +57,7 @@ class EditorInputCache { if (editorInputPromise) { editorInputPromise.done(() => { if (reloadFromSource) { - this.textModelResolverService.getModelReference(fileMatch.resource()).done(ref => { + this.textModelResolverService.createModelReference(fileMatch.resource()).done(ref => { const model = ref.object; if (model.textEditorModel) { let replaceResource = this.getReplaceResource(fileMatch.resource()); @@ -109,7 +109,7 @@ class EditorInputCache { } private createRightInput(element: FileMatch): TPromise { - return this.textModelResolverService.getModelReference(element.resource()).then(ref => { + return this.textModelResolverService.createModelReference(element.resource()).then(ref => { const model = ref.object; let textEditorModel = model.textEditorModel; let replaceResource = this.getReplaceResource(element.resource()); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 52835fc3d18..766a4890d50 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -114,7 +114,7 @@ export class TextModelResolverService implements ITextModelResolverService { this.resourceModelCollection = instantiationService.createInstance(ResourceModelCollection); } - getModelReference(resource: URI): TPromise> { + createModelReference(resource: URI): TPromise> { const uri = resource.toString(); let promise = this.promiseCache[uri]; diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index 34897b4fa2c..59fa5d8f75b 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -83,7 +83,7 @@ suite('Workbench - TextModelResolverService', () => { (accessor.textFileService.models).add(model.getResource(), model); return model.load().then(() => { - return accessor.textModelResolverServie.getModelReference(model.getResource()).then(ref => { + return accessor.textModelResolverServie.createModelReference(model.getResource()).then(ref => { const model = ref.object; const editorModel = model.textEditorModel; @@ -99,7 +99,7 @@ suite('Workbench - TextModelResolverService', () => { const input = service.createOrGet(); input.resolve().then(() => { - return accessor.textModelResolverServie.getModelReference(input.getResource()).then(ref => { + return accessor.textModelResolverServie.createModelReference(input.getResource()).then(ref => { const model = ref.object; const editorModel = model.textEditorModel; From b560a80a6e5d47e5eb68e378e9e34cba3157f144 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Nov 2016 10:18:01 +0100 Subject: [PATCH 18/28] fix test --- .../textmodelResolver/test/textModelResolverService.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index 59fa5d8f75b..7562c7c1b62 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -94,11 +94,11 @@ suite('Workbench - TextModelResolverService', () => { }); }); - test('resolve untitled', function (done) { + test('resolve untitled', function () { const service = accessor.untitledEditorService; const input = service.createOrGet(); - input.resolve().then(() => { + return input.resolve().then(() => { return accessor.textModelResolverServie.createModelReference(input.getResource()).then(ref => { const model = ref.object; const editorModel = model.textEditorModel; @@ -107,7 +107,6 @@ suite('Workbench - TextModelResolverService', () => { ref.dispose(); input.dispose(); - }); }); }); From 6913eb58d39647f8cd3080be4ccc03bfe8f4e4e8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Nov 2016 10:43:08 +0100 Subject: [PATCH 19/28] simplify ReferenceCollection --- src/vs/base/common/lifecycle.ts | 16 ++++++------- src/vs/base/test/common/lifecycle.test.ts | 7 +++--- .../common/textModelResolverService.ts | 24 ++++++------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 31bce27fc27..7f24172f50c 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -72,24 +72,23 @@ export interface IReference extends IDisposable { readonly object: T; } -export abstract class ReferenceCollection { +export abstract class ReferenceCollection { - private references: { [key: string]: { readonly object: R; counter: number; } } = Object.create(null); + private references: { [key: string]: { readonly object: T; counter: number; } } = Object.create(null); constructor() { } - acquire(t: T): IReference { - const key = this.getKey(t); + acquire(key: string): IReference { let reference = this.references[key]; if (!reference) { - reference = this.references[key] = { counter: 0, object: this.create(key) }; + reference = this.references[key] = { counter: 0, object: this.createReferencedObject(key) }; } const { object } = reference; const dispose = () => { if (--reference.counter === 0) { - this.destroy(reference.object); + this.destroyReferencedObject(reference.object); delete this.references[key]; } }; @@ -99,9 +98,8 @@ export abstract class ReferenceCollection { return { object, dispose }; } - protected abstract getKey(t: T): string; - protected abstract create(key: string): R; - protected abstract destroy(object: R): void; + protected abstract createReferencedObject(key: string): T; + protected abstract destroyReferencedObject(object: T): void; } export class ImmortalReference implements IReference { diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 1533f6f4c4b..95e368eaa26 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -52,12 +52,11 @@ suite('Lifecycle', () => { }); suite('Reference Collection', () => { - class Collection extends ReferenceCollection { + class Collection extends ReferenceCollection { private _count = 0; get count() { return this._count; } - protected getKey(key): string { return key; } - protected create(key: string): number { this._count++; return key.length; } - protected destroy(object: number): void { this._count--; } + protected createReferencedObject(key: string): number { this._count++; return key.length; } + protected destroyReferencedObject(object: number): void { this._count--; } } test('simple', () => { diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 766a4890d50..4a033541e84 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -17,34 +17,26 @@ import network = require('vs/base/common/network'); import { ITextModelResolverService, ITextModelContentProvider, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; -import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -class ResourceModelCollection extends ReferenceCollection> { +class ResourceModelCollection extends ReferenceCollection> { private providers: { [scheme: string]: ITextModelContentProvider[] } = Object.create(null); constructor( - @ITextFileService private textFileService: ITextFileService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IInstantiationService private instantiationService: IInstantiationService, @IModelService private modelService: IModelService ) { super(); } - getKey(uri: URI): string { - return uri.toString(); - } - - create(key: string): TPromise { + createReferencedObject(key: string): TPromise { const resource = URI.parse(key); return this.resolveTextModelContent(this.modelService, key) .then(() => this.instantiationService.createInstance(ResourceEditorModel, resource)); } - destroy(modelPromise: TPromise): void { + destroyReferencedObject(modelPromise: TPromise): void { modelPromise.done(model => model.dispose()); } @@ -107,9 +99,7 @@ export class TextModelResolverService implements ITextModelResolverService { constructor( @ITextFileService private textFileService: ITextFileService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IInstantiationService private instantiationService: IInstantiationService, - @IModelService private modelService: IModelService + @IInstantiationService private instantiationService: IInstantiationService ) { this.resourceModelCollection = instantiationService.createInstance(ResourceModelCollection); } @@ -122,12 +112,12 @@ export class TextModelResolverService implements ITextModelResolverService { return promise; } - promise = this.promiseCache[uri] = this.getModel(resource); + promise = this.promiseCache[uri] = this._createModelReference(resource); return always(promise, () => delete this.promiseCache[uri]); } - private getModel(resource: URI): TPromise> { + private _createModelReference(resource: URI): TPromise> { // File Schema: use text file service // TODO ImmortalReference is a hack if (resource.scheme === network.Schemas.file) { @@ -142,7 +132,7 @@ export class TextModelResolverService implements ITextModelResolverService { .then(model => new ImmortalReference(model)); } - const ref = this.resourceModelCollection.acquire(resource); + const ref = this.resourceModelCollection.acquire(resource.toString()); return ref.object.then(model => ({ object: model, dispose: () => ref.dispose() })); } From 2e8ee916503776868d7f3c1b6adb9b4f49ba35c1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Nov 2016 10:47:43 +0100 Subject: [PATCH 20/28] move dirty diff css to contribution --- .../git/browser/media/git.contribution.css | 30 --------------- .../parts/scm/browser/dirtydiffDecorator.ts | 7 ++-- .../scm/browser/media/dirtydiffDecorator.css | 37 +++++++++++++++++++ 3 files changed, 41 insertions(+), 33 deletions(-) create mode 100644 src/vs/workbench/parts/scm/browser/media/dirtydiffDecorator.css diff --git a/src/vs/workbench/parts/git/browser/media/git.contribution.css b/src/vs/workbench/parts/git/browser/media/git.contribution.css index e8c1731508a..48a983ffd30 100644 --- a/src/vs/workbench/parts/git/browser/media/git.contribution.css +++ b/src/vs/workbench/parts/git/browser/media/git.contribution.css @@ -46,36 +46,6 @@ background-color: rgba(235, 59, 0, 0.3); } -/* Git dirty diff editor decorations */ -.monaco-editor .git-dirty-modified-diff-glyph { - border-left: 3px solid rgba(0, 122, 204, 0.6); - margin-left: 5px; -} -.monaco-editor.vs-dark .git-dirty-modified-diff-glyph { - border-left: 3px solid rgba(0, 188, 242, 0.6); - margin-left: 5px; -} -.monaco-editor .git-dirty-added-diff-glyph { - border-left: 3px solid rgba(45, 136, 62, 0.6); - margin-left: 5px; -} -.monaco-editor.vs-dark .git-dirty-added-diff-glyph { - border-left: 3px solid rgba(127, 186, 0, 0.6); - margin-left: 5px; -} -.monaco-editor .git-dirty-deleted-diff-glyph:after { - content: ''; - position: absolute; - bottom: -4px; - margin-left: 5px; - box-sizing: border-box; - border-left: 4px solid rgba(185, 19, 26, 0.76); - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - width: 4px; - height: 0; -} - .monaco-shell .git-branch-dropdown-menu .action-label.git-action.checkout.HEAD { font-weight: bold; } diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index d264a3918c7..440d386ce64 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -5,6 +5,7 @@ 'use strict'; +import 'vs/css!./media/dirtydiffDecorator'; import { ThrottledDelayer } from 'vs/base/common/async'; import * as lifecycle from 'vs/base/common/lifecycle'; import * as winjs from 'vs/base/common/winjs.base'; @@ -27,7 +28,7 @@ import { ISCMService } from 'vs/workbench/services/scm/common/scm'; class DirtyDiffModelDecorator { static MODIFIED_DECORATION_OPTIONS: common.IModelDecorationOptions = { - linesDecorationsClassName: 'git-dirty-modified-diff-glyph', + linesDecorationsClassName: 'dirty-diff-modified-glyph', isWholeLine: true, overviewRuler: { color: 'rgba(0, 122, 204, 0.6)', @@ -37,7 +38,7 @@ class DirtyDiffModelDecorator { }; static ADDED_DECORATION_OPTIONS: common.IModelDecorationOptions = { - linesDecorationsClassName: 'git-dirty-added-diff-glyph', + linesDecorationsClassName: 'dirty-diff-added-glyph', isWholeLine: true, overviewRuler: { color: 'rgba(0, 122, 204, 0.6)', @@ -47,7 +48,7 @@ class DirtyDiffModelDecorator { }; static DELETED_DECORATION_OPTIONS: common.IModelDecorationOptions = { - linesDecorationsClassName: 'git-dirty-deleted-diff-glyph', + linesDecorationsClassName: 'dirty-diff-deleted-glyph', isWholeLine: true, overviewRuler: { color: 'rgba(0, 122, 204, 0.6)', diff --git a/src/vs/workbench/parts/scm/browser/media/dirtydiffDecorator.css b/src/vs/workbench/parts/scm/browser/media/dirtydiffDecorator.css new file mode 100644 index 00000000000..f2a396f5c1d --- /dev/null +++ b/src/vs/workbench/parts/scm/browser/media/dirtydiffDecorator.css @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .dirty-diff-modified-glyph { + border-left: 3px solid rgba(0, 122, 204, 0.6); + margin-left: 5px; +} + +.monaco-editor.vs-dark .dirty-diff-modified-glyph { + border-left: 3px solid rgba(0, 188, 242, 0.6); + margin-left: 5px; +} + +.monaco-editor .dirty-diff-added-glyph { + border-left: 3px solid rgba(45, 136, 62, 0.6); + margin-left: 5px; +} + +.monaco-editor.vs-dark .dirty-diff-added-glyph { + border-left: 3px solid rgba(127, 186, 0, 0.6); + margin-left: 5px; +} + +.monaco-editor .dirty-diff-deleted-glyph:after { + content: ''; + position: absolute; + bottom: -4px; + margin-left: 5px; + box-sizing: border-box; + border-left: 4px solid rgba(185, 19, 26, 0.76); + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + width: 4px; + height: 0; +} \ No newline at end of file From 4993c080664bee8578a80ee229a13ae2c23d1194 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 22 Nov 2016 10:49:11 +0100 Subject: [PATCH 21/28] add test for #15834 --- .../test/node/api/extHostConfiguration.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/test/node/api/extHostConfiguration.test.ts b/src/vs/workbench/test/node/api/extHostConfiguration.test.ts index 2548e4d3c84..8caacae668b 100644 --- a/src/vs/workbench/test/node/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/node/api/extHostConfiguration.test.ts @@ -122,7 +122,7 @@ suite('ExtHostConfiguration', function () { assert.throws(() => config['get'] = 'get-prop'); }); - test('udate/section to key', function () { + test('update/section to key', function () { const shape = new RecordingShape(); const allConfig = createExtHostConfiguration({ @@ -144,6 +144,17 @@ suite('ExtHostConfiguration', function () { assert.equal(shape.lastArgs[1], 'foo.bar'); }); + test('update, what is #15834', function () { + const shape = new RecordingShape(); + const allConfig = createExtHostConfiguration({ + ['editor.formatOnSave']: createConfigurationValue(true) + }, shape); + + allConfig.getConfiguration('editor').update('formatOnSave', { extensions: ['ts'] }); + assert.equal(shape.lastArgs[1], 'editor.formatOnSave'); + assert.deepEqual(shape.lastArgs[2], { extensions: ['ts'] }); + }); + test('update/error-state not OK', function () { const shape = new class extends MainThreadConfigurationShape { From 09b67e21d99aa85d6d87c6c62283beab2f03c1ed Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Nov 2016 11:15:45 +0100 Subject: [PATCH 22/28] fix compile errors --- .../parts/scm/browser/dirtydiffDecorator.ts | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index 440d386ce64..8aff40576a2 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/dirtydiffDecorator'; import { ThrottledDelayer } from 'vs/base/common/async'; -import * as lifecycle from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as winjs from 'vs/base/common/winjs.base'; import { memoize } from 'vs/base/common/decorators'; import * as ext from 'vs/workbench/common/contributions'; @@ -58,9 +58,9 @@ class DirtyDiffModelDecorator { }; private decorations: string[]; - private textEditorModel: common.IModel; + private baselineModel: common.IModel; private diffDelayer: ThrottledDelayer; - private toDispose: lifecycle.IDisposable[]; + private toDispose: IDisposable[]; constructor( private model: common.IModel, @@ -82,10 +82,12 @@ class DirtyDiffModelDecorator { @memoize private get originalURIPromise(): winjs.TPromise { return this.scmService.getBaselineResource(this.uri) - .then(originalUri => this.textModelResolverService.resolve(originalUri) - .then(model => { - this.textEditorModel = model.textEditorModel; - this.toDispose.push(this.textEditorModel.onDidChangeContent(() => this.triggerDiff())); + .then(originalUri => this.textModelResolverService.createModelReference(originalUri) + .then(ref => { + this.baselineModel = ref.object.textEditorModel; + + this.toDispose.push(ref); + this.toDispose.push(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); return originalUri; })); @@ -96,25 +98,28 @@ class DirtyDiffModelDecorator { return winjs.TPromise.as(null); } - return this.diffDelayer.trigger(() => { - if (!this.model || this.model.isDisposed()) { - return winjs.TPromise.as([]); // disposed - } + return this.diffDelayer + .trigger(() => this.diff()) + .then((diff: common.IChange[]) => { + if (!this.model || this.model.isDisposed() || !this.baselineModel || this.baselineModel.isDisposed()) { + return; // disposed + } - return this.originalURIPromise - .then(originalURI => this.editorWorkerService.computeDirtyDiff(originalURI, this.model.uri, true)); + if (this.baselineModel.getValueLength() === 0) { + diff = []; + } - }).then((diff: common.IChange[]) => { - if (!this.model || this.model.isDisposed()) { - return; // disposed - } + return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); + }); + } - if (this.textEditorModel.getValueLength() === 0) { - diff = []; - } + private diff(): winjs.Promise { + if (!this.model || this.model.isDisposed()) { + return winjs.TPromise.as([]); // disposed + } - return this.decorations = this.model.deltaDecorations(this.decorations, DirtyDiffModelDecorator.changesToDecorations(diff || [])); - }); + return this.originalURIPromise + .then(originalURI => this.editorWorkerService.computeDirtyDiff(originalURI, this.model.uri, true)); } private static changesToDecorations(diff: common.IChange[]): common.IModelDeltaDecoration[] { @@ -156,12 +161,16 @@ class DirtyDiffModelDecorator { } dispose(): void { - this.toDispose = lifecycle.dispose(this.toDispose); + this.toDispose = dispose(this.toDispose); + if (this.model && !this.model.isDisposed()) { this.model.deltaDecorations(this.decorations, []); } + this.model = null; + this.baselineModel = null; this.decorations = null; + if (this.diffDelayer) { this.diffDelayer.cancel(); this.diffDelayer = null; @@ -173,7 +182,7 @@ export class DirtyDiffDecorator implements ext.IWorkbenchContribution { private models: common.IModel[] = []; private decorators: { [modelId: string]: DirtyDiffModelDecorator } = Object.create(null); - private toDispose: lifecycle.IDisposable[] = []; + private toDispose: IDisposable[] = []; constructor( @IMessageService private messageService: IMessageService, @@ -231,7 +240,7 @@ export class DirtyDiffDecorator implements ext.IWorkbenchContribution { } dispose(): void { - this.toDispose = lifecycle.dispose(this.toDispose); + this.toDispose = dispose(this.toDispose); this.models.forEach(m => this.decorators[m.id].dispose()); this.models = null; this.decorators = null; From 741bc632fc6ab5b4c820f373f037ea39913d021d Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 22 Nov 2016 11:18:19 +0100 Subject: [PATCH 23/28] debug: logToRepl and appendReplOutput should not be exposed by debugService --- src/vs/workbench/parts/debug/common/debug.ts | 11 ----------- .../workbench/parts/debug/common/debugModel.ts | 6 +++--- .../parts/debug/electron-browser/debugService.ts | 16 ++++------------ .../parts/debug/test/common/mockDebug.ts | 5 ----- 4 files changed, 7 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index b2a38cce499..752ce17beb1 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -7,7 +7,6 @@ import * as nls from 'vs/nls'; import uri from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import Event from 'vs/base/common/event'; -import severity from 'vs/base/common/severity'; import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IModel as EditorIModel, IEditorContribution, IRange } from 'vs/editor/common/editorCommon'; @@ -423,16 +422,6 @@ export interface IDebugService { */ removeReplExpressions(): void; - /** - * Adds a new log to the repl. Either a string value or a dictionary (used to inspect complex objects printed to the repl). - */ - logToRepl(value: string | { [key: string]: any }, severity?: severity): void; - - /** - * Appends new output to the repl. - */ - appendReplOutput(value: string, severity?: severity): void; - /** * Adds a new watch expression and evaluates it against the debug adapter. */ diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index ec359b44f3e..8de493c8544 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -842,9 +842,9 @@ export class Model implements debug.IModel { public appendReplOutput(value: string, severity?: severity): void { const elements: OutputElement[] = []; - let previousOutput = this.replElements.length && (this.replElements[this.replElements.length - 1]); - let lines = value.split('\n'); - let groupTogether = !!previousOutput && (previousOutput.category === 'output' && severity === previousOutput.severity); + const previousOutput = this.replElements.length && (this.replElements[this.replElements.length - 1]); + const lines = value.split('\n'); + const groupTogether = !!previousOutput && (previousOutput.category === 'output' && severity === previousOutput.severity); if (groupTogether) { // append to previous line if same group diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 53772e45d32..7bf8ada792d 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -195,12 +195,12 @@ export class DebugService implements debug.IDebugService { // flush any existing simple values logged if (simpleVals.length) { - this.logToRepl(simpleVals.join(' '), sev); + this.model.logToRepl(simpleVals.join(' '), sev); simpleVals = []; } // show object - this.logToRepl(a, sev); + this.model.logToRepl(a, sev); } // string: watch out for % replacement directive @@ -229,7 +229,7 @@ export class DebugService implements debug.IDebugService { // flush simple values if (simpleVals.length) { - this.logToRepl(simpleVals.join(' '), sev); + this.model.logToRepl(simpleVals.join(' '), sev); } } } @@ -368,7 +368,7 @@ export class DebugService implements debug.IDebugService { private onOutput(event: DebugProtocol.OutputEvent): void { const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info; - this.appendReplOutput(event.body.output, outputSeverity); + this.model.appendReplOutput(event.body.output, outputSeverity); } private loadBreakpoints(): Breakpoint[] { @@ -515,14 +515,6 @@ export class DebugService implements debug.IDebugService { .then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame)); } - public logToRepl(value: string | { [key: string]: any }, severity?: severity): void { - this.model.logToRepl(value, severity); - } - - public appendReplOutput(value: string, severity?: severity): void { - this.model.appendReplOutput(value, severity); - } - public removeReplExpressions(): void { this.model.removeReplExpressions(); } diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/parts/debug/test/common/mockDebug.ts index c589e362310..9c887f4f3ec 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebug.ts @@ -5,7 +5,6 @@ import uri from 'vs/base/common/uri'; import Event from 'vs/base/common/event'; -import severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import debug = require('vs/workbench/parts/debug/common/debug'); import { Source } from 'vs/workbench/parts/debug/common/debugSource'; @@ -63,10 +62,6 @@ export class MockDebugService implements debug.IDebugService { public removeReplExpressions(): void { } - public logToRepl(value: string | { [key: string]: any }, severity?: severity): void { } - - public appendReplOutput(value: string, severity?: severity): void { } - public addWatchExpression(name?: string): TPromise { return TPromise.as(null); } From b49d66ee0cb1977523c9a160ae9a1551ff408ef4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 22 Nov 2016 11:36:37 +0100 Subject: [PATCH 24/28] Zoom level setting should be rounded off before writing to settings file (fixes #15877) --- src/vs/workbench/electron-browser/actions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index bf1d9eb870c..3c243d2c02c 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -213,6 +213,8 @@ export abstract class BaseZoomAction extends Action { target = ConfigurationTarget.WORKSPACE; } + level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels + const applyZoom = () => { webFrame.setZoomLevel(level); browser.setZoomFactor(webFrame.getZoomFactor()); From beed7379999c99ff883a671476948d51a897a6de Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Tue, 22 Nov 2016 11:51:49 +0100 Subject: [PATCH 25/28] fixes #15878 --- .../workbench/parts/git/common/gitContentProvider.ts | 2 +- .../workbench/parts/scm/browser/dirtydiffDecorator.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/git/common/gitContentProvider.ts b/src/vs/workbench/parts/git/common/gitContentProvider.ts index ec68d1a41a0..8dad7f3d5ec 100644 --- a/src/vs/workbench/parts/git/common/gitContentProvider.ts +++ b/src/vs/workbench/parts/git/common/gitContentProvider.ts @@ -44,7 +44,7 @@ export class GitContentProvider implements IWorkbenchContribution, ITextModelCon const treeish = gitModel.getStatus().find(path, StatusType.INDEX) ? '~' : 'HEAD'; return this.gitService.buffer(path, treeish) - .then(contents => this.modelService.createModel(contents, null, uri)) + .then(contents => this.modelService.createModel(contents || '', null, uri)) .then(model => { const trigger = () => { this.throttler.queue(() => { diff --git a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts index 8aff40576a2..119f5834ca9 100644 --- a/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/browser/dirtydiffDecorator.ts @@ -114,12 +114,13 @@ class DirtyDiffModelDecorator { } private diff(): winjs.Promise { - if (!this.model || this.model.isDisposed()) { - return winjs.TPromise.as([]); // disposed - } + return this.originalURIPromise.then(originalURI => { + if (!this.model || this.model.isDisposed()) { + return winjs.TPromise.as([]); // disposed + } - return this.originalURIPromise - .then(originalURI => this.editorWorkerService.computeDirtyDiff(originalURI, this.model.uri, true)); + this.editorWorkerService.computeDirtyDiff(originalURI, this.model.uri, true); + }); } private static changesToDecorations(diff: common.IChange[]): common.IModelDeltaDecoration[] { From 9afd40fc215533c48560336421b6fa15a59dd1ec Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 22 Nov 2016 11:54:06 +0100 Subject: [PATCH 26/28] don't stop to log after first signal, #15456 --- src/vs/workbench/electron-browser/extensionHost.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/electron-browser/extensionHost.ts b/src/vs/workbench/electron-browser/extensionHost.ts index d40c4e369b2..73eb60ec53a 100644 --- a/src/vs/workbench/electron-browser/extensionHost.ts +++ b/src/vs/workbench/electron-browser/extensionHost.ts @@ -123,7 +123,7 @@ export class ExtensionHostProcessWorker { this.extHostWatchDog.start(); this.extHostWatchDog.onAlert(() => { - this.extHostWatchDog.stop(); + this.extHostWatchDog.reset(); // log the identifiers of those extensions that // have code and are loaded in the extension host @@ -134,7 +134,7 @@ export class ExtensionHostProcessWorker { ids.push(ext.id); } } - this.telemetryService.publicLog('extHostUnresponsive', { extensionIds: ids }); + this.telemetryService.publicLog('extHostUnresponsive2', { extensionIds: ids }); }); }); }); From d6834d01de9684856f27e9b42bacf7f365c1ba5b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 22 Nov 2016 11:57:36 +0100 Subject: [PATCH 27/28] fix #15349 --- src/vs/editor/contrib/quickFix/browser/quickFix.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/quickFix/browser/quickFix.ts b/src/vs/editor/contrib/quickFix/browser/quickFix.ts index 124e1fa30fa..e6ba08288a4 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFix.ts +++ b/src/vs/editor/contrib/quickFix/browser/quickFix.ts @@ -97,7 +97,7 @@ export class QuickFixController implements IEditorContribution { if (kb) { title = nls.localize('quickFixWithKb', "Show Fixes ({0})", this._keybindingService.getLabelFor(kb)); } else { - title = nls.localize('quickFix', "Show Fixes", this._keybindingService.getLabelFor(kb)); + title = nls.localize('quickFix', "Show Fixes"); } this._lightBulbWidget.getDomNode().title = title; } From 1816ae96b0222d1375a4d065e71b0e1da58f507f Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 22 Nov 2016 09:45:40 +0100 Subject: [PATCH 28/28] cosmetics --- .../workbench/parts/debug/electron-browser/rawDebugSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts index ead1b3f53e6..188341e19c8 100644 --- a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts @@ -385,7 +385,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.ISession { }); } else if (request.command === 'handshake') { try { - const vsda = (require.__$__nodeRequire('vsda')); + const vsda = require.__$__nodeRequire('vsda'); const obj = new vsda.signer(); const sig = obj.sign(request.arguments.value); response.body = {