From 41984a2317d64ea30c450075cceae0bd6f78afea Mon Sep 17 00:00:00 2001 From: Ross Allen Date: Fri, 2 Oct 2015 10:57:28 -0700 Subject: [PATCH] Add `onDidStopChangingActivePaneItem` for async callbacks `onDidChangeActivePaneItem` is called synchronously when the active pane item changes, and several non-critical actions preform work on that event. Critical UI feedback, like changing the active tab, needs to happen synchronously, but most other functionality should be run asynchronously. --- spec/pane-container-spec.coffee | 23 +++++++++++++++++++++++ src/pane-container.coffee | 19 +++++++++++++++++++ src/workspace.coffee | 25 ++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 58e159279..d0f2913aa 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -122,6 +122,29 @@ describe "PaneContainer", -> pane2.activate() expect(observed).toEqual [pane1.itemAtIndex(0), pane2.itemAtIndex(0)] + describe "::onDidStopChangingActivePaneItem()", -> + [container, pane1, pane2, observed] = [] + + beforeEach -> + container = new PaneContainer(root: new Pane(items: [new Object, new Object])) + container.getRoot().splitRight(items: [new Object, new Object]) + [pane1, pane2] = container.getPanes() + + observed = [] + container.onDidStopChangingActivePaneItem (item) -> observed.push(item) + + it "invokes observers when the active item of the active pane stops changing", -> + pane2.activateNextItem() + pane2.activateNextItem() + advanceClock(100) + expect(observed).toEqual [pane2.itemAtIndex(0)] + + it "invokes observers when the active pane stops changing", -> + pane1.activate() + pane2.activate() + advanceClock(100) + expect(observed).toEqual [pane2.itemAtIndex(0)] + describe "::observePanes()", -> it "invokes observers with all current and future panes", -> container = new PaneContainer diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 786c4b84f..a8d1fa5d2 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -12,6 +12,8 @@ class PaneContainer extends Model @version: 1 root: null + stoppedChangingActivePaneItemDelay: 100 + stoppedChangingActivePaneItemTimeout: null @deserialize: (state) -> container = Object.create(@prototype) # allows us to pass a self reference to our child before invoking constructor @@ -82,6 +84,9 @@ class PaneContainer extends Model onDidChangeActivePaneItem: (fn) -> @emitter.on 'did-change-active-pane-item', fn + onDidStopChangingActivePaneItem: (fn) -> + @emitter.on 'did-stop-changing-active-pane-item', fn + observeActivePaneItem: (fn) -> fn(@getActivePaneItem()) @onDidChangeActivePaneItem(fn) @@ -189,12 +194,18 @@ class PaneContainer extends Model # Called by Model superclass when destroyed destroyed: -> + @cancelStoppedChangingActivePaneItemTimeout() pane.destroy() for pane in @getPanes() @subscriptions.dispose() @emitter.dispose() + cancelStoppedChangingActivePaneItemTimeout: -> + if @stoppedChangingActivePaneItemTimeout? + clearTimeout(@stoppedChangingActivePaneItemTimeout) + monitorActivePaneItem: -> childSubscription = null + @subscriptions.add @observeActivePane (activePane) => if childSubscription? @subscriptions.remove(childSubscription) @@ -202,6 +213,14 @@ class PaneContainer extends Model childSubscription = activePane.observeActiveItem (activeItem) => @emitter.emit 'did-change-active-pane-item', activeItem + @cancelStoppedChangingActivePaneItemTimeout() + stoppedChangingActivePaneItemCallback = => + @stoppedChangingActivePaneItemTimeout = null + @emitter.emit 'did-stop-changing-active-pane-item', activeItem + @stoppedChangingActivePaneItemTimeout = + setTimeout( + stoppedChangingActivePaneItemCallback, + @stoppedChangingActivePaneItemDelay) @subscriptions.add(childSubscription) diff --git a/src/workspace.coffee b/src/workspace.coffee index bda6e9b45..2ff12ac19 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -209,11 +209,34 @@ class Workspace extends Model # Essential: Invoke the given callback when the active pane item changes. # + # Because observers are invoked synchronously, it's important not to perform + # any expensive operations via this method. Consider + # {::onDidStopChangingActivePaneItem} to delay operations until after changes + # stop occurring. + # # * `callback` {Function} to be called when the active pane item changes. # * `item` The active pane item. # # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. - onDidChangeActivePaneItem: (callback) -> @paneContainer.onDidChangeActivePaneItem(callback) + onDidChangeActivePaneItem: (callback) -> + @paneContainer.onDidChangeActivePaneItem(callback) + + # Essential: Invoke the given callback when the active pane item stops + # changing. + # + # Observers are called asynchronously 100ms after the last active pane item + # change. Handling changes here rather than in the synchronous + # {::onDidChangeActivePaneItem} prevents unneeded work if the user is quickly + # changing or closing tabs and ensures critical UI feedback, like changing the + # highlighted tab, gets priority over work that can be done asynchronously. + # + # * `callback` {Function} to be called when the active pane item stopts + # changing. + # * `item` The active pane item. + # + # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. + onDidStopChangingActivePaneItem: (callback) -> + @paneContainer.onDidStopChangingActivePaneItem(callback) # Essential: Invoke the given callback with the current active pane item and # with all future active pane items in the workspace.