diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 0c0e944ddf238cdd30ec5f203a03df8988884558..68b84190f4119f76a6e12fbf9f7ca9d2acad4b21 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -572,7 +572,7 @@ export class RunOnceScheduler { */ schedule(delay = this.timeout): void { this.cancel(); - this.timeoutToken = platform.setTimeout(this.timeoutHandler, this.timeout); + this.timeoutToken = platform.setTimeout(this.timeoutHandler, delay); } /** diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index ed667dbc89c07f0cbd821c8283c20c5268bf2a7c..7ff61102f23678ff8bbef0a373223a0668ea2ce6 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -15,7 +15,6 @@ import types = require('vs/base/common/types'); import {Position} from 'vs/platform/editor/common/editor'; import {IDiffEditor} from 'vs/editor/browser/editorBrowser'; import {IDiffEditorOptions, IEditorOptions} from 'vs/editor/common/editorCommon'; -import {BaseEditor} from 'vs/workbench/browser/parts/editor/baseEditor'; import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor'; import {TextEditorOptions, TextDiffEditorOptions, EditorModel, EditorInput, EditorOptions} from 'vs/workbench/common/editor'; import {StringEditorInput} from 'vs/workbench/common/editor/stringEditorInput'; @@ -85,16 +84,16 @@ export class TextDiffEditor extends BaseTextEditor { this.previousDiffAction = new NavigateAction(this, false); // Support navigation within the diff editor by overriding the editor service within - let delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService, this, (editor: BaseEditor, input: EditorInput, options?: EditorOptions, arg4?: any) => { + let delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService, (input: EditorInput, options?: EditorOptions, arg3?: any) => { // Check if arg4 is a position argument that differs from this editors position - if (types.isUndefinedOrNull(arg4) || arg4 === false || arg4 === this.position) { + if (types.isUndefinedOrNull(arg3) || arg3 === false || arg3 === this.position) { let activeDiffInput = this.getInput(); if (input && options && activeDiffInput) { // Input matches modified side of the diff editor: perform the action on modified side if (input.matches(activeDiffInput.modifiedInput)) { - return this.setInput(this.getInput(), options).then(() => true); + return this.setInput(this.getInput(), options).then(() => this); } // Input matches original side of the diff editor: perform the action on original side @@ -103,13 +102,13 @@ export class TextDiffEditor extends BaseTextEditor { if (options instanceof TextEditorOptions) { (options).apply(originalEditor); - return TPromise.as(true); + return TPromise.as(this); } } } } - return TPromise.as(false); + return TPromise.as(null); }); // Create a special child of instantiator that will delegate all calls to openEditor() to the same diff editor if the input matches with the modified one diff --git a/src/vs/workbench/parts/files/browser/explorerViewlet.ts b/src/vs/workbench/parts/files/browser/explorerViewlet.ts index ee035bbb30c9825b152c55025d7a289638cba1af..1cbcff1641bb6140ce3702e674c95d071832bcb3 100644 --- a/src/vs/workbench/parts/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/parts/files/browser/explorerViewlet.ts @@ -24,6 +24,12 @@ import {IStorageService} from 'vs/platform/storage/common/storage'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import {DelegatingWorkbenchEditorService} from 'vs/workbench/services/editor/browser/editorService'; +import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; +import {EditorInput, EditorOptions} from 'vs/workbench/common/editor'; +import {BaseEditor} from 'vs/workbench/browser/parts/editor/baseEditor'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; export class ExplorerViewlet extends Viewlet { private viewletContainer: Builder; @@ -44,6 +50,8 @@ export class ExplorerViewlet extends Viewlet { @ITelemetryService telemetryService: ITelemetryService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IStorageService private storageService: IStorageService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private instantiationService: IInstantiationService ) { @@ -71,6 +79,7 @@ export class ExplorerViewlet extends Viewlet { } private onConfigurationUpdated(config: IFilesConfiguration): TPromise { + // Open editors view should always be visible in no folder workspace. let openEditorsVisible = !this.contextService.getWorkspace() || config.explorer.openEditors.visible !== 0; @@ -104,8 +113,7 @@ export class ExplorerViewlet extends Viewlet { // Update title area since the title actions have changed. this.updateTitleArea(); - // Focus the viewlet since that triggers a rerender. - return this.setVisible(this.isVisible()).then(() => this.focus()); + return this.setVisible(this.isVisible()).then(() => this.focus()); // Focus the viewlet since that triggers a rerender. }); } @@ -124,9 +132,40 @@ export class ExplorerViewlet extends Viewlet { // With a Workspace if (this.contextService.getWorkspace()) { - // If open editors are not visible set header size explicitly to 0, otherwise let it be computed by super class. - const headerSize = this.openEditorsVisible ? undefined : 0; - this.explorerView = explorerView = this.instantiationService.createInstance(ExplorerView, this.viewletState, this.getActionRunner(), this.viewletSettings, headerSize); + + // Create a delegating editor service for the explorer to be able to delay the refresh in the opened + // editors view above. This is a workaround for being able to double click on a file to make it pinned + // without causing the animation in the opened editors view to kick in and change scroll position. + // We try to be smart and only use the delay if we recognize that the user action is likely to cause + // a new entry in the opened editors view. + const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService, (input: EditorInput, options?: EditorOptions, arg3?: any) => { + if (this.openEditorsView) { + let delay = 0; + if (arg3 === false /* not side by side */ || typeof arg3 !== 'number' /* no explicit position */) { + const activeGroup = this.editorGroupService.getStacksModel().activeGroup; + if (!activeGroup || !activeGroup.previewEditor) { + delay = 250; // a new editor entry is likely because there is either no group or no preview in group + } + } + + this.openEditorsView.setStructuralRefreshDelay(delay); + } + + const onSuccessOrError = (editor?: BaseEditor) => { + if (this.openEditorsView) { + this.openEditorsView.setStructuralRefreshDelay(0); + } + + return editor; + }; + + return this.editorService.openEditor(input, options, arg3).then(onSuccessOrError, onSuccessOrError); + }); + + const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService])); + + const headerSize = this.openEditorsVisible ? undefined : 0; // If open editors are not visible set header size explicitly to 0, otherwise let it be computed by super class. + this.explorerView = explorerView = explorerInstantiator.createInstance(ExplorerView, this.viewletState, this.getActionRunner(), this.viewletSettings, headerSize); } // No workspace diff --git a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts index 8787f5df93f76fef93e86461d15d2a3708196ccc..42b4c0b27bae8adb9f3d9667b6e11c67c6722202 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts @@ -15,7 +15,7 @@ import async = require('vs/base/common/async'); import paths = require('vs/base/common/paths'); import errors = require('vs/base/common/errors'); import {isString} from 'vs/base/common/types'; -import Actions = require('vs/base/common/actions'); +import {IAction, ActionRunner as BaseActionRunner, IActionRunner} from 'vs/base/common/actions'; import comparers = require('vs/base/common/comparers'); import {InputBox} from 'vs/base/browser/ui/inputbox/inputBox'; import {$} from 'vs/base/browser/builder'; @@ -143,7 +143,7 @@ export class FileActionProvider extends ContributableActionProvider { return super.hasActions(tree, stat); } - public getActions(tree: ITree, stat: FileStat): TPromise { + public getActions(tree: ITree, stat: FileStat): TPromise { if (stat instanceof NewStatPlaceholder) { return TPromise.as([]); } @@ -159,7 +159,7 @@ export class FileActionProvider extends ContributableActionProvider { return super.hasSecondaryActions(tree, stat); } - public getSecondaryActions(tree: ITree, stat: FileStat): TPromise { + public getSecondaryActions(tree: ITree, stat: FileStat): TPromise { if (stat instanceof NewStatPlaceholder) { return TPromise.as([]); } @@ -167,7 +167,7 @@ export class FileActionProvider extends ContributableActionProvider { return super.getSecondaryActions(tree, stat); } - public runAction(tree: ITree, stat: FileStat, action: Actions.IAction, context?: any): TPromise; + public runAction(tree: ITree, stat: FileStat, action: IAction, context?: any): TPromise; public runAction(tree: ITree, stat: FileStat, actionID: string, context?: any): TPromise; public runAction(tree: ITree, stat: FileStat, arg: any, context: any = {}): TPromise { context = objects.mixin({ @@ -176,7 +176,7 @@ export class FileActionProvider extends ContributableActionProvider { }, context); if (!isString(arg)) { - let action = arg; + let action = arg; if (action.enabled) { return action.run(context); } @@ -187,7 +187,7 @@ export class FileActionProvider extends ContributableActionProvider { let id = arg; let promise = this.hasActions(tree, stat) ? this.getActions(tree, stat) : TPromise.as([]); - return promise.then((actions: Actions.IAction[]) => { + return promise.then((actions: IAction[]) => { for (let i = 0, len = actions.length; i < len; i++) { if (actions[i].id === id && actions[i].enabled) { return actions[i].run(context); @@ -196,7 +196,7 @@ export class FileActionProvider extends ContributableActionProvider { promise = this.hasSecondaryActions(tree, stat) ? this.getSecondaryActions(tree, stat) : TPromise.as([]); - return promise.then((actions: Actions.IAction[]) => { + return promise.then((actions: IAction[]) => { for (let i = 0, len = actions.length; i < len; i++) { if (actions[i].id === id && actions[i].enabled) { return actions[i].run(context); @@ -237,7 +237,7 @@ export class FileViewletState implements IFileViewletState { } } -export class ActionRunner extends Actions.ActionRunner implements Actions.IActionRunner { +export class ActionRunner extends BaseActionRunner implements IActionRunner { private viewletState: FileViewletState; constructor(state: FileViewletState) { @@ -246,7 +246,7 @@ export class ActionRunner extends Actions.ActionRunner implements Actions.IActio this.viewletState = state; } - public run(action: Actions.IAction, context?: any): TPromise { + public run(action: IAction, context?: any): TPromise { return super.run(action, { viewletState: this.viewletState }); } } @@ -257,7 +257,7 @@ export class FileRenderer extends ActionsRenderer implements IRenderer { constructor( state: FileViewletState, - actionRunner: Actions.IActionRunner, + actionRunner: IActionRunner, @IContextViewService private contextViewService: IContextViewService ) { super({ diff --git a/src/vs/workbench/parts/files/browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/browser/views/openEditorsView.ts index 9cd5650f58e8335b65db191f37a82188ba174b05..c01ae41c16ec93d1ea782c3fc62b59da67119a64 100644 --- a/src/vs/workbench/parts/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/browser/views/openEditorsView.ts @@ -35,7 +35,6 @@ export class OpenEditorsView extends AdaptiveCollapsibleViewletView { private static MEMENTO_COLLAPSED = 'openEditors.memento.collapsed'; private static DEFAULT_VISIBLE_OPEN_EDITORS = 9; private static DEFAULT_DYNAMIC_HEIGHT = true; - private static STRUCTURAL_TREE_REFRESH_DELAY = 250; private settings: any; private visibleOpenEditors: number; @@ -44,6 +43,7 @@ export class OpenEditorsView extends AdaptiveCollapsibleViewletView { private model: IEditorStacksModel; private dirtyCountElement: HTMLElement; private structuralTreeRefreshScheduler: RunOnceScheduler; + private structuralRefreshDelay: number; private groupToRefresh: IEditorGroup; private fullRefreshNeeded: boolean; @@ -64,7 +64,8 @@ export class OpenEditorsView extends AdaptiveCollapsibleViewletView { this.settings = settings; this.model = editorGroupService.getStacksModel(); - this.structuralTreeRefreshScheduler = new RunOnceScheduler(() => this.structuralTreeUpdate(), OpenEditorsView.STRUCTURAL_TREE_REFRESH_DELAY); + this.structuralRefreshDelay = 0; + this.structuralTreeRefreshScheduler = new RunOnceScheduler(() => this.structuralTreeUpdate(), this.structuralRefreshDelay); } public renderHeader(container: HTMLElement): void { @@ -155,7 +156,7 @@ export class OpenEditorsView extends AdaptiveCollapsibleViewletView { } else { this.fullRefreshNeeded = true; } - this.structuralTreeRefreshScheduler.schedule(); + this.structuralTreeRefreshScheduler.schedule(this.structuralRefreshDelay); } else { const toRefresh = e.editor ? new OpenEditor(e.editor, e.group) : e.group; this.tree.refresh(toRefresh, false).done(null, errors.onUnexpectedError); @@ -248,6 +249,10 @@ export class OpenEditorsView extends AdaptiveCollapsibleViewletView { return itemsToShow * Renderer.ITEM_HEIGHT; } + public setStructuralRefreshDelay(delay: number): void { + this.structuralRefreshDelay = delay; + } + public getOptimalWidth():number { let parentNode = this.tree.getHTMLElement(); let childNodes = [].slice.call(parentNode.querySelectorAll('.monaco-file-label > .file-name')); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index a167c9a63fd540d1d0ca7b64a45430efd1aeb07b..8f04e636b80bd9103b25fe398a811a1b701266d8 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -284,25 +284,23 @@ export class WorkbenchEditorService implements IWorkbenchEditorService { } } -export interface IDelegatingWorkbenchEditorHandler { - (editor: BaseEditor, input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; - (editor: BaseEditor, input: EditorInput, options?: EditorOptions, position?: Position): TPromise; +export interface IDelegatingWorkbenchEditorServiceHandler { + (input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; + (input: EditorInput, options?: EditorOptions, position?: Position): TPromise; } /** * Subclass of workbench editor service that delegates all calls to the provided editor service. Subclasses can choose to override the behavior - * of openEditor() by providing a handler. The handler returns a promise that resolves to true or false to indicate if an action has been taken. - * If false is returned, the service will delegate to editor service for handling the call to openEditor(). + * of openEditor() by providing a handler. The handler returns a promise that resolves to an editor to indicate if an action has been taken. + * If falsify is returned, the service will delegate to editor service for handling the call to openEditor(). * * This gives clients a chance to override the behavior of openEditor() to match their context. */ export class DelegatingWorkbenchEditorService extends WorkbenchEditorService { - private editor: BaseEditor; - private handler: IDelegatingWorkbenchEditorHandler; + private handler: IDelegatingWorkbenchEditorServiceHandler; constructor( - editor: BaseEditor, - handler: IDelegatingWorkbenchEditorHandler, + handler: IDelegatingWorkbenchEditorServiceHandler, @IUntitledEditorService untitledEditorService: IUntitledEditorService, @IInstantiationService instantiationService: IInstantiationService, @IWorkbenchEditorService editorService: IWorkbenchEditorService @@ -313,16 +311,15 @@ export class DelegatingWorkbenchEditorService extends WorkbenchEditorService { instantiationService ); - this.editor = editor; this.handler = handler; } protected doOpenEditor(input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise; protected doOpenEditor(input: EditorInput, options?: EditorOptions, position?: Position): TPromise; protected doOpenEditor(input: EditorInput, options?: EditorOptions, arg3?: any): TPromise { - return this.handler(this.editor, input, options, arg3).then((result) => { - if (result) { - return TPromise.as(this.editor); + return this.handler(input, options, arg3).then(editor => { + if (editor) { + return TPromise.as(editor); } return super.doOpenEditor(input, options, arg3); diff --git a/src/vs/workbench/test/browser/services.test.ts b/src/vs/workbench/test/browser/services.test.ts index 75a5f110ed2d686b6e7be33531098ba04303d9bf..f7b26c803d11fb4ae5bfc238b2b34a5400964d49 100644 --- a/src/vs/workbench/test/browser/services.test.ts +++ b/src/vs/workbench/test/browser/services.test.ts @@ -377,11 +377,10 @@ suite('Workbench UI Services', () => { let ed = inst.createInstance(MyEditor, 'my.editor'); let inp = inst.createInstance(StringEditorInput, 'name', 'description', 'hello world', 'text/plain', false); - let delegate: any = inst.createInstance(DelegatingWorkbenchEditorService, ed, (editor: BaseEditor, input: EditorInput, options?: EditorOptions) => { + let delegate: any = inst.createInstance(DelegatingWorkbenchEditorService, (input: EditorInput, options?: EditorOptions, arg3?: any) => { assert.strictEqual(input, inp); - assert.strictEqual(editor, ed); - return TPromise.as(true); + return TPromise.as(ed); }); delegate.openEditor(inp);