From 1d04cbf5844ebac8c7eea25a0eaa6eec65f12939 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Jan 2014 14:58:51 -0700 Subject: [PATCH 01/89] Enable harmony proxies I want to use these in the pane-models branch, so it will be helpful if they are enabled in atom stable since it's an atom-shell level setting. --- src/browser/atom-application.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 5b480d2f2..3e9aacc18 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -107,7 +107,7 @@ class AtomApplication # Private: Configures required javascript environment flags. setupJavaScriptArguments: -> - app.commandLine.appendSwitch 'js-flags', '--harmony_collections' + app.commandLine.appendSwitch 'js-flags', '--harmony_collections --harmony-proxies' # Private: Enable updates unless running from a local build of Atom. checkForUpdates: -> From c99e211144be9fc5c56e33b04c6f9669c015510e Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 7 Jan 2014 15:42:23 -0700 Subject: [PATCH 02/89] Store Pane items in PaneModel --- package.json | 3 ++- src/pane-model.coffee | 5 +++++ src/pane.coffee | 14 ++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 src/pane-model.coffee diff --git a/package.json b/package.json index f9cca4db8..d438072a5 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "temp": "0.5.0", "text-buffer": "0.9.0", "underscore-plus": "0.6.1", - "theorist": "~0.7.0" + "theorist": "~0.7.0", + "delegato": "~0.4.0" }, "packageDependencies": { "atom-dark-syntax": "0.10.0", diff --git a/src/pane-model.coffee b/src/pane-model.coffee new file mode 100644 index 000000000..fd0c1e6ce --- /dev/null +++ b/src/pane-model.coffee @@ -0,0 +1,5 @@ +{Model} = require 'theorist' + +module.exports = +class PaneModel extends Model + constructor: ({@items}) -> diff --git a/src/pane.coffee b/src/pane.coffee index d976a95f0..234a50048 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -2,7 +2,9 @@ {$, View} = require './space-pen-extensions' _ = require 'underscore-plus' Serializable = require 'serializable' +Delegator = require 'delegato' +PaneModel = require './pane-model' PaneRow = require './pane-row' PaneColumn = require './pane-column' @@ -15,6 +17,7 @@ PaneColumn = require './pane-column' module.exports = class Pane extends View Serializable.includeInto(this) + Delegator.includeInto(this) @version: 1 @@ -22,18 +25,17 @@ class Pane extends View @div class: 'pane', tabindex: -1, => @div class: 'item-views', outlet: 'itemViews' + @delegatesProperty 'items', toProperty: 'model' + activeItem: null - items: null - viewsByItem: null # Views without a setModel() method are stored here # Private: initialize: (args...) -> if args[0]?.items # deserializing - {@items, activeItemUri, @focusOnAttach} = args[0] + {items, activeItemUri, @focusOnAttach} = args[0] + @model = new PaneModel({items}) else - @items = args - - @items ?= [] + @model = new PaneModel(items: args) @handleItemEvents(item) for item in @items From 14175d80eff417199db42ca2b8f73f69be333407 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 7 Jan 2014 15:46:17 -0700 Subject: [PATCH 03/89] Move Pane::activeItem to PaneModel --- src/pane-model.coffee | 2 ++ src/pane.coffee | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index fd0c1e6ce..6f5948466 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -2,4 +2,6 @@ module.exports = class PaneModel extends Model + activeItem: null + constructor: ({@items}) -> diff --git a/src/pane.coffee b/src/pane.coffee index 234a50048..f441b63b0 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -25,9 +25,7 @@ class Pane extends View @div class: 'pane', tabindex: -1, => @div class: 'item-views', outlet: 'itemViews' - @delegatesProperty 'items', toProperty: 'model' - - activeItem: null + @delegatesProperties 'items', 'activeItem', toProperty: 'model' # Private: initialize: (args...) -> From 25c099f3a227c3f4e987493f04c51a452535f2f7 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 7 Jan 2014 17:23:29 -0700 Subject: [PATCH 04/89] Serialize the items and activeItem via PaneModel This necessitates setting the ::activeView based on the model's ::activeItem instead of doing it in ::showItem. --- spec/pane-container-spec.coffee | 2 +- spec/pane-spec.coffee | 6 +++--- src/pane-model.coffee | 21 +++++++++++++++++++-- src/pane.coffee | 28 +++++++++++++++------------- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 13ff182f6..4dc770c16 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -16,7 +16,7 @@ describe "PaneContainer", -> serialize: -> { deserializer: 'TestView', @name } getUri: -> path.join(temp.dir, @name) save: -> @saved = true - isEqual: (other) -> @name is other.name + isEqual: (other) -> @name is other?.name container = new PaneContainer pane1 = new Pane(new TestView('1')) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 6851bb8f4..fd6339cab 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -13,7 +13,7 @@ describe "Pane", -> initialize: ({@id, @text}) -> serialize: -> { deserializer: 'TestView', @id, @text } getUri: -> @id - isEqual: (other) -> @id == other.id and @text == other.text + isEqual: (other) -> other? and @id == other.id and @text == other.text beforeEach -> atom.deserializers.add(TestView) @@ -558,12 +558,12 @@ describe "Pane", -> pane2 = pane1.splitRight() expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] expect(pane2.items).toEqual [] - expect(pane2.activeItem).toBe null + expect(pane2.activeItem).toBeUndefined() pane3 = pane2.splitRight() expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]] expect(pane3.items).toEqual [] - expect(pane3.activeItem).toBe null + expect(pane3.activeItem).toBeUndefined() describe "splitLeft(items...)", -> it "builds a row if needed, then appends a new pane before itself", -> diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 6f5948466..b91edb0e6 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -1,7 +1,24 @@ +{find, compact} = require 'underscore-plus' {Model} = require 'theorist' +Serializable = require 'serializable' module.exports = class PaneModel extends Model - activeItem: null + Serializable.includeInto(this) - constructor: ({@items}) -> + @properties + activeItem: null + + constructor: ({@items, @activeItem}) -> + @items ?= [] + @activeItem ?= @items[0] + + serializeParams: -> + items: compact(@items.map((item) -> item.serialize?())) + activeItemUri: @activeItem?.getUri?() + + deserializeParams: (params) -> + {items, activeItemUri} = params + params.items = items.map (itemState) -> atom.deserializers.deserialize(itemState) + params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri + params diff --git a/src/pane.coffee b/src/pane.coffee index f441b63b0..a463b4ac6 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -27,11 +27,12 @@ class Pane extends View @delegatesProperties 'items', 'activeItem', toProperty: 'model' + previousActiveItem: null + # Private: initialize: (args...) -> - if args[0]?.items # deserializing - {items, activeItemUri, @focusOnAttach} = args[0] - @model = new PaneModel({items}) + if args[0]?.model? + {@model, @focusOnAttach} = args[0] else @model = new PaneModel(items: args) @@ -39,8 +40,7 @@ class Pane extends View @viewsByItem = new WeakMap() - unless activeItemUri? and @showItemForUri(activeItemUri) - @showItem(@items[0]) if @items.length > 0 + @subscribe @model.$activeItem, 'value', @onActiveItemChanged @command 'pane:save-items', @saveItems @command 'pane:show-next-item', @showNextItem @@ -66,13 +66,12 @@ class Pane extends View @on 'focusin', => @makeActive() deserializeParams: (params) -> - params.items = _.compact(params.items.map (itemState) -> atom.deserializers.deserialize(itemState)) + params.model = PaneModel.deserialize(params.model) params serializeParams: -> - items: _.compact(@items.map (item) -> item.serialize?()) + model: @model.serialize() focusOnAttach: @is(':has(:focus)') - activeItemUri: @getActivePaneItem()?.getUri?() # Private: afterAttach: (onDom) -> @@ -146,20 +145,23 @@ class Pane extends View # Public: Focuses the given item. showItem: (item) -> - return if !item? or item is @activeItem + if item? + @addItem(item) + @model.activeItem = item - if @activeItem - @activeItem.off? 'title-changed', @activeItemTitleChanged + onActiveItemChanged: (item) => + @previousActiveItem?.off? 'title-changed', @activeItemTitleChanged + @previousActiveItem = item + + return unless item? isFocused = @is(':has(:focus)') - @addItem(item) item.on? 'title-changed', @activeItemTitleChanged view = @viewForItem(item) @itemViews.children().not(view).hide() @itemViews.append(view) unless view.parent().is(@itemViews) view.show() if @attached view.focus() if isFocused - @activeItem = item @activeView = view @trigger 'pane:active-item-changed', [item] From cd97de76fcdf19dcc776734721fb965f4af532d0 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 7 Jan 2014 17:28:50 -0700 Subject: [PATCH 05/89] :lipstick: --- src/pane.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index a463b4ac6..cd31b67b1 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -40,7 +40,12 @@ class Pane extends View @viewsByItem = new WeakMap() + @handleEvents() + + handleEvents: -> @subscribe @model.$activeItem, 'value', @onActiveItemChanged + @subscribe this, 'focus', => @activeView?.focus(); false + @subscribe this, 'focusin', => @makeActive() @command 'pane:save-items', @saveItems @command 'pane:show-next-item', @showNextItem @@ -62,8 +67,6 @@ class Pane extends View @command 'pane:split-down', => @splitDown(@copyActiveItem()) @command 'pane:close', => @destroyItems() @command 'pane:close-other-items', => @destroyInactiveItems() - @on 'focus', => @activeView?.focus(); false - @on 'focusin', => @makeActive() deserializeParams: (params) -> params.model = PaneModel.deserialize(params.model) From 2938a8e65022e23a6ddc67a2088ac9cc3678716d Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 7 Jan 2014 17:47:42 -0700 Subject: [PATCH 06/89] Move item-oriented methods to PaneModel --- src/pane-model.coffee | 48 ++++++++++++++++++++++++++++- src/pane.coffee | 70 ++++++++----------------------------------- 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index b91edb0e6..8b0be5d62 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -1,4 +1,4 @@ -{find, compact} = require 'underscore-plus' +{find, compact, clone} = require 'underscore-plus' {Model} = require 'theorist' Serializable = require 'serializable' @@ -22,3 +22,49 @@ class PaneModel extends Model params.items = items.map (itemState) -> atom.deserializers.deserialize(itemState) params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri params + + # Public: Returns all contained views. + getItems: -> + clone(@items) + + # Public: Switches to the next contained item. + showNextItem: => + index = @getActiveItemIndex() + if index < @items.length - 1 + @showItemAtIndex(index + 1) + else + @showItemAtIndex(0) + + # Public: Switches to the previous contained item. + showPreviousItem: => + index = @getActiveItemIndex() + if index > 0 + @showItemAtIndex(index - 1) + else + @showItemAtIndex(@items.length - 1) + + # Public: Returns the index of the currently active item. + getActiveItemIndex: -> + @items.indexOf(@activeItem) + + # Public: Switch to the item associated with the given index. + showItemAtIndex: (index) -> + @showItem(@itemAtIndex(index)) + + # Public: Focuses the given item. + showItem: (item) -> + if item? + @addItem(item) + @activeItem = item + + # Public: Add an additional item at the specified index. + addItem: (item, index=@getActiveItemIndex() + 1) -> + return if item in @items + + @items.splice(index, 0, item) + @emit 'item-added', item, index + item + + # Public: Returns the item at the specified index. + itemAtIndex: (index) -> + @items[index] diff --git a/src/pane.coffee b/src/pane.coffee index cd31b67b1..0e3a7496d 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -26,6 +26,8 @@ class Pane extends View @div class: 'item-views', outlet: 'itemViews' @delegatesProperties 'items', 'activeItem', toProperty: 'model' + @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', + 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', toProperty: 'model' previousActiveItem: null @@ -36,20 +38,20 @@ class Pane extends View else @model = new PaneModel(items: args) - @handleItemEvents(item) for item in @items - + @onItemAdded(item) for item in @items @viewsByItem = new WeakMap() - @handleEvents() handleEvents: -> @subscribe @model.$activeItem, 'value', @onActiveItemChanged + @subscribe @model, 'item-added', @onItemAdded + @subscribe this, 'focus', => @activeView?.focus(); false @subscribe this, 'focusin', => @makeActive() - @command 'pane:save-items', @saveItems - @command 'pane:show-next-item', @showNextItem - @command 'pane:show-previous-item', @showPreviousItem + @command 'pane:save-items', => @saveItems() + @command 'pane:show-next-item', => @showNextItem() + @command 'pane:show-previous-item', => @showPreviousItem() @command 'pane:show-item-1', => @showItemAtIndex(0) @command 'pane:show-item-2', => @showItemAtIndex(1) @@ -111,47 +113,9 @@ class Pane extends View nextIndex = (panes.indexOf(this) + 1) % panes.length panes[nextIndex] - # Public: Returns all contained views. - getItems: -> - new Array(@items...) - - # Public: Switches to the next contained item. - showNextItem: => - index = @getActiveItemIndex() - if index < @items.length - 1 - @showItemAtIndex(index + 1) - else - @showItemAtIndex(0) - - # Public: Switches to the previous contained item. - showPreviousItem: => - index = @getActiveItemIndex() - if index > 0 - @showItemAtIndex(index - 1) - else - @showItemAtIndex(@items.length - 1) - getActivePaneItem: -> @activeItem - # Public: Returns the index of the currently active item. - getActiveItemIndex: -> - @items.indexOf(@activeItem) - - # Public: Switch to the item associated with the given index. - showItemAtIndex: (index) -> - @showItem(@itemAtIndex(index)) - - # Public: Returns the item at the specified index. - itemAtIndex: (index) -> - @items[index] - - # Public: Focuses the given item. - showItem: (item) -> - if item? - @addItem(item) - @model.activeItem = item - onActiveItemChanged: (item) => @previousActiveItem?.off? 'title-changed', @activeItemTitleChanged @previousActiveItem = item @@ -168,23 +132,15 @@ class Pane extends View @activeView = view @trigger 'pane:active-item-changed', [item] + onItemAdded: (item, index) => + if typeof item.on is 'function' + @subscribe item, 'destroyed', => @destroyItem(item) + @trigger 'pane:item-added', [item, index] + # Private: activeItemTitleChanged: => @trigger 'pane:active-item-title-changed' - # Public: Add an additional item at the specified index. - addItem: (item, index=@getActiveItemIndex() + 1) -> - return if _.include(@items, item) - - @items.splice(index, 0, item) - @trigger 'pane:item-added', [item, index] - @handleItemEvents(item) - item - - handleItemEvents: (item) -> - if _.isFunction(item.on) - @subscribe item, 'destroyed', => @destroyItem(item) - # Public: Remove the currently active item. destroyActiveItem: => @destroyItem(@activeItem) From 1a487db29f2c3e0eed915136c80ef59259de9bc7 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 7 Jan 2014 17:55:56 -0700 Subject: [PATCH 07/89] Move item removal events to PaneModel --- src/pane-model.coffee | 12 ++++++++++++ src/pane.coffee | 22 +++++++--------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 8b0be5d62..8d3f13d40 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -65,6 +65,18 @@ class PaneModel extends Model @emit 'item-added', item, index item + # Public: + removeItem: (item) -> + index = @items.indexOf(item) + @removeItemAtIndex(index) if index >= 0 + + # Public: Just remove the item at the given index. + removeItemAtIndex: (index) -> + item = @items[index] + @showNextItem() if item is @activeItem and @items.length > 1 + @items.splice(index, 1) + @emit 'item-removed', item, index + # Public: Returns the item at the specified index. itemAtIndex: (index) -> @items[index] diff --git a/src/pane.coffee b/src/pane.coffee index 0e3a7496d..7f1f6056c 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -27,7 +27,8 @@ class Pane extends View @delegatesProperties 'items', 'activeItem', toProperty: 'model' @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', - 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', toProperty: 'model' + 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', 'removeItem', 'removeItemAtIndex', + toProperty: 'model' previousActiveItem: null @@ -45,6 +46,7 @@ class Pane extends View handleEvents: -> @subscribe @model.$activeItem, 'value', @onActiveItemChanged @subscribe @model, 'item-added', @onItemAdded + @subscribe @model, 'item-removed', @onItemRemoved @subscribe this, 'focus', => @activeView?.focus(); false @subscribe this, 'focusin', => @makeActive() @@ -137,6 +139,10 @@ class Pane extends View @subscribe item, 'destroyed', => @destroyItem(item) @trigger 'pane:item-added', [item, index] + onItemRemoved: (item, index) => + @cleanupItemView(item) + @trigger 'pane:item-removed', [item, index] + # Private: activeItemTitleChanged: => @trigger 'pane:active-item-title-changed' @@ -214,20 +220,6 @@ class Pane extends View saveItems: => @saveItem(item) for item in @getItems() - # Public: - removeItem: (item) -> - index = @items.indexOf(item) - @removeItemAtIndex(index) if index >= 0 - - # Public: Just remove the item at the given index. - removeItemAtIndex: (index) -> - item = @items[index] - @activeItem.off? 'title-changed', @activeItemTitleChanged if item is @activeItem - @showNextItem() if item is @activeItem and @items.length > 1 - _.remove(@items, item) - @cleanupItemView(item) - @trigger 'pane:item-removed', [item, index] - # Public: Moves the given item to a the new index. moveItem: (item, newIndex) -> oldIndex = @items.indexOf(item) From 5837b7cfda0007fec005f7e3c29a5226ffd16427 Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Tue, 7 Jan 2014 18:39:39 -0700 Subject: [PATCH 08/89] Move methods related to item movement to PaneModel --- src/pane-model.coffee | 26 +++++++++++++++++++------- src/pane.coffee | 34 ++++++++++++---------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 8d3f13d40..605ed3a14 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -27,6 +27,10 @@ class PaneModel extends Model getItems: -> clone(@items) + # Public: Returns the item at the specified index. + itemAtIndex: (index) -> + @items[index] + # Public: Switches to the next contained item. showNextItem: => index = @getActiveItemIndex() @@ -66,17 +70,25 @@ class PaneModel extends Model item # Public: - removeItem: (item) -> + removeItem: (item, detach) -> index = @items.indexOf(item) - @removeItemAtIndex(index) if index >= 0 + @removeItemAtIndex(index, detach) if index >= 0 # Public: Just remove the item at the given index. - removeItemAtIndex: (index) -> + removeItemAtIndex: (index, detach) -> item = @items[index] @showNextItem() if item is @activeItem and @items.length > 1 @items.splice(index, 1) - @emit 'item-removed', item, index + @emit 'item-removed', item, index, detach - # Public: Returns the item at the specified index. - itemAtIndex: (index) -> - @items[index] + # Public: Moves the given item to a the new index. + moveItem: (item, newIndex) -> + oldIndex = @items.indexOf(item) + @items.splice(oldIndex, 1) + @items.splice(newIndex, 0, item) + @emit 'item-moved', item, newIndex + + # Public: Moves the given item to another pane. + moveItemToPane: (item, pane, index) -> + pane.addItem(item, index) + @removeItem(item, true) diff --git a/src/pane.coffee b/src/pane.coffee index 7f1f6056c..13f1fc087 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -28,7 +28,7 @@ class Pane extends View @delegatesProperties 'items', 'activeItem', toProperty: 'model' @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', 'removeItem', 'removeItemAtIndex', - toProperty: 'model' + 'moveItem', 'moveItemToPane', toProperty: 'model' previousActiveItem: null @@ -47,6 +47,7 @@ class Pane extends View @subscribe @model.$activeItem, 'value', @onActiveItemChanged @subscribe @model, 'item-added', @onItemAdded @subscribe @model, 'item-removed', @onItemRemoved + @subscribe @model, 'item-moved', @onItemMoved @subscribe this, 'focus', => @activeView?.focus(); false @subscribe this, 'focusin', => @makeActive() @@ -139,10 +140,13 @@ class Pane extends View @subscribe item, 'destroyed', => @destroyItem(item) @trigger 'pane:item-added', [item, index] - onItemRemoved: (item, index) => - @cleanupItemView(item) + onItemRemoved: (item, index, detach) => + @cleanupItemView(item, detach) @trigger 'pane:item-removed', [item, index] + onItemMoved: (item, newIndex) => + @trigger 'pane:item-moved', [item, newIndex] + # Private: activeItemTitleChanged: => @trigger 'pane:active-item-title-changed' @@ -220,20 +224,6 @@ class Pane extends View saveItems: => @saveItem(item) for item in @getItems() - # Public: Moves the given item to a the new index. - moveItem: (item, newIndex) -> - oldIndex = @items.indexOf(item) - @items.splice(oldIndex, 1) - @items.splice(newIndex, 0, item) - @trigger 'pane:item-moved', [item, newIndex] - - # Public: Moves the given item to another pane. - moveItemToPane: (item, pane, index) -> - @isMovingItem = true - pane.addItem(item, index) - @removeItem(item) - @isMovingItem = false - # Public: Finds the first item that matches the given uri. itemForUri: (uri) -> _.detect @items, (item) -> item.getUri?() is uri @@ -247,24 +237,24 @@ class Pane extends View false # Private: - cleanupItemView: (item) -> + cleanupItemView: (item, detach) -> if item instanceof $ viewToRemove = item else if viewToRemove = @viewsByItem.get(item) @viewsByItem.delete(item) if @items.length > 0 - if @isMovingItem and item is viewToRemove + if detach and item is viewToRemove viewToRemove?.detach() - else if @isMovingItem and viewToRemove?.setModel + else if detach and viewToRemove?.setModel viewToRemove.setModel(null) # dont want to destroy the model, so set to null viewToRemove.remove() else viewToRemove?.remove() else - if @isMovingItem and item is viewToRemove + if detach and item is viewToRemove viewToRemove?.detach() - else if @isMovingItem and viewToRemove?.setModel + else if detach and viewToRemove?.setModel viewToRemove.setModel(null) # dont want to destroy the model, so set to null @parent().view().removeChild(this) From 626e22e4aec924446b48813b054732a029a60597 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Jan 2014 18:45:18 -0700 Subject: [PATCH 09/89] Avoid exceptions when a live editor is compared with a destroyed one --- src/editor.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor.coffee b/src/editor.coffee index cff06ddfc..c144aa10d 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -174,7 +174,8 @@ class Editor extends Model # Returns a {Boolean}. isEqual: (other) -> return false unless other instanceof Editor - @buffer.getPath() == other.buffer.getPath() and + @isAlive() == other.isAlive() and + @buffer.getPath() == other.buffer.getPath() and @getScrollTop() == other.getScrollTop() and @getScrollLeft() == other.getScrollLeft() and @getCursorScreenPosition().isEqual(other.getCursorScreenPosition()) From a379d47230853a501555573ec8a37f63c5c76cb7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 11:12:27 -0700 Subject: [PATCH 10/89] Move methods related to item destruction and saving to PaneModel --- src/pane-model.coffee | 84 +++++++++++++++++++++++++++++++++++ src/pane.coffee | 100 +++++------------------------------------- 2 files changed, 96 insertions(+), 88 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 605ed3a14..ef236a655 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -1,4 +1,5 @@ {find, compact, clone} = require 'underscore-plus' +{dirname} = require 'path' {Model} = require 'theorist' Serializable = require 'serializable' @@ -92,3 +93,86 @@ class PaneModel extends Model moveItemToPane: (item, pane, index) -> pane.addItem(item, index) @removeItem(item, true) + + # Public: Remove the currently active item. + destroyActiveItem: -> + @destroyItem(@activeItem) + false + + # Public: Remove the specified item. + destroyItem: (item, options) -> + @emit 'before-item-destroyed', item + if @promptToSaveItem(item) + @emit 'item-destroyed', item + @removeItem(item, options) + item.destroy?() + true + else + false + + # Public: Remove and delete all items. + destroyItems: -> + @destroyItem(item) for item in @getItems() + + # Public: Remove and delete all but the currently focused item. + destroyInactiveItems: -> + @destroyItem(item) for item in @getItems() when item isnt @activeItem + + # Public: Prompt the user to save the given item. + promptToSaveItem: (item) -> + return true unless item.shouldPromptToSave?() + + uri = item.getUri() + chosen = atom.confirm + message: "'#{item.getTitle?() ? item.getUri()}' has changes, do you want to save them?" + detailedMessage: "Your changes will be lost if you close this item without saving." + buttons: ["Save", "Cancel", "Don't Save"] + + switch chosen + when 0 then @saveItem(item, -> true) + when 1 then false + when 2 then true + + # Public: Saves the currently focused item. + saveActiveItem: => + @saveItem(@activeItem) + + # Public: Save and prompt for path for the currently focused item. + saveActiveItemAs: => + @saveItemAs(@activeItem) + + # Public: Saves the specified item and call the next action when complete. + saveItem: (item, nextAction) -> + if item.getUri?() + item.save?() + nextAction?() + else + @saveItemAs(item, nextAction) + + # Public: Prompts for path and then saves the specified item. Upon completion + # it also calls the next action. + saveItemAs: (item, nextAction) -> + return unless item.saveAs? + + itemPath = item.getPath?() + itemPath = dirname(itemPath) if itemPath + path = atom.showSaveDialogSync(itemPath) + if path + item.saveAs(path) + nextAction?() + + # Public: Saves all items in this pane. + saveItems: => + @saveItem(item) for item in @getItems() + + # Public: Finds the first item that matches the given uri. + itemForUri: (uri) -> + find @items, (item) -> item.getUri?() is uri + + # Public: Focuses the first item that matches the given uri. + showItemForUri: (uri) -> + if item = @itemForUri(uri) + @showItem(item) + true + else + false diff --git a/src/pane.coffee b/src/pane.coffee index 13f1fc087..54cc64cf4 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -1,6 +1,4 @@ -{dirname} = require 'path' {$, View} = require './space-pen-extensions' -_ = require 'underscore-plus' Serializable = require 'serializable' Delegator = require 'delegato' @@ -28,7 +26,9 @@ class Pane extends View @delegatesProperties 'items', 'activeItem', toProperty: 'model' @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', 'removeItem', 'removeItemAtIndex', - 'moveItem', 'moveItemToPane', toProperty: 'model' + 'moveItem', 'moveItemToPane', 'destroyItem', 'destroyItems', 'destroyActiveItem', + 'destroyInactiveItems', 'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', + 'saveItems', 'itemForUri', 'showItemForUri', 'promptToSaveItem', toProperty: 'model' previousActiveItem: null @@ -48,6 +48,8 @@ class Pane extends View @subscribe @model, 'item-added', @onItemAdded @subscribe @model, 'item-removed', @onItemRemoved @subscribe @model, 'item-moved', @onItemMoved + @subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed + @subscribe @model, 'item-destroyed', @onItemDestroyed @subscribe this, 'focus', => @activeView?.focus(); false @subscribe this, 'focusin', => @makeActive() @@ -147,95 +149,17 @@ class Pane extends View onItemMoved: (item, newIndex) => @trigger 'pane:item-moved', [item, newIndex] + onBeforeItemDestroyed: (item) => + @unsubscribe(item) if typeof item.off is 'function' + @trigger 'pane:before-item-destroyed', [item] + + onItemDestroyed: (item) => + @getContainer()?.itemDestroyed(item) + # Private: activeItemTitleChanged: => @trigger 'pane:active-item-title-changed' - # Public: Remove the currently active item. - destroyActiveItem: => - @destroyItem(@activeItem) - false - - # Public: Remove the specified item. - destroyItem: (item, options) -> - @unsubscribe(item) if _.isFunction(item.off) - @trigger 'pane:before-item-destroyed', [item] - - if @promptToSaveItem(item) - @getContainer()?.itemDestroyed(item) - @removeItem(item, options) - item.destroy?() - true - else - false - - # Public: Remove and delete all items. - destroyItems: -> - @destroyItem(item) for item in @getItems() - - # Public: Remove and delete all but the currently focused item. - destroyInactiveItems: -> - @destroyItem(item) for item in @getItems() when item isnt @activeItem - - # Public: Prompt the user to save the given item. - promptToSaveItem: (item) -> - return true unless item.shouldPromptToSave?() - - uri = item.getUri() - chosen = atom.confirm - message: "'#{item.getTitle?() ? item.getUri()}' has changes, do you want to save them?" - detailedMessage: "Your changes will be lost if you close this item without saving." - buttons: ["Save", "Cancel", "Don't Save"] - - switch chosen - when 0 then @saveItem(item, -> true) - when 1 then false - when 2 then true - - # Public: Saves the currently focused item. - saveActiveItem: => - @saveItem(@activeItem) - - # Public: Save and prompt for path for the currently focused item. - saveActiveItemAs: => - @saveItemAs(@activeItem) - - # Public: Saves the specified item and call the next action when complete. - saveItem: (item, nextAction) -> - if item.getUri?() - item.save?() - nextAction?() - else - @saveItemAs(item, nextAction) - - # Public: Prompts for path and then saves the specified item. Upon completion - # it also calls the next action. - saveItemAs: (item, nextAction) -> - return unless item.saveAs? - - itemPath = item.getPath?() - itemPath = dirname(itemPath) if itemPath - path = atom.showSaveDialogSync(itemPath) - if path - item.saveAs(path) - nextAction?() - - # Public: Saves all items in this pane. - saveItems: => - @saveItem(item) for item in @getItems() - - # Public: Finds the first item that matches the given uri. - itemForUri: (uri) -> - _.detect @items, (item) -> item.getUri?() is uri - - # Public: Focuses the first item that matches the given uri. - showItemForUri: (uri) -> - if item = @itemForUri(uri) - @showItem(item) - true - else - false - # Private: cleanupItemView: (item, detach) -> if item instanceof $ From ee9b78afb6e5561135361e8e742ab653b7e1501e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 11:14:02 -0700 Subject: [PATCH 11/89] Move copyActiveItem to PaneModel --- src/pane-model.coffee | 4 ++++ src/pane.coffee | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index ef236a655..94cb78d82 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -176,3 +176,7 @@ class PaneModel extends Model true else false + + # Private: + copyActiveItem: -> + @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize()) diff --git a/src/pane.coffee b/src/pane.coffee index 54cc64cf4..f9bb2b25c 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -28,7 +28,8 @@ class Pane extends View 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', 'removeItem', 'removeItemAtIndex', 'moveItem', 'moveItemToPane', 'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems', 'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', - 'saveItems', 'itemForUri', 'showItemForUri', 'promptToSaveItem', toProperty: 'model' + 'saveItems', 'itemForUri', 'showItemForUri', 'promptToSaveItem', 'copyActiveItem', + toProperty: 'model' previousActiveItem: null @@ -260,10 +261,6 @@ class Pane extends View getContainer: -> @closest('.panes').view() - # Private: - copyActiveItem: -> - @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize()) - # Private: remove: (selector, keepData) -> return super if keepData From 2acde6a727f71a90554790112c761ddff6bbf06d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 11:20:15 -0700 Subject: [PATCH 12/89] Call `super` in constructor to assign PaneModel properties The theorist model superclass constructor will automatically assign all declared properties, or substitute the specified default value if no param key is present for that property. --- src/pane-model.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 94cb78d82..c39174083 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -8,10 +8,11 @@ class PaneModel extends Model Serializable.includeInto(this) @properties + items: -> [] activeItem: null - constructor: ({@items, @activeItem}) -> - @items ?= [] + constructor: -> + super @activeItem ?= @items[0] serializeParams: -> From c281eb9596a48532bc28b7ca171bb6b80fe2776a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 11:23:06 -0700 Subject: [PATCH 13/89] Move serialization of focus into PaneModel --- src/pane-model.coffee | 8 ++++++++ src/pane.coffee | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index c39174083..def6d3a6c 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -10,6 +10,7 @@ class PaneModel extends Model @properties items: -> [] activeItem: null + focused: false constructor: -> super @@ -18,6 +19,7 @@ class PaneModel extends Model serializeParams: -> items: compact(@items.map((item) -> item.serialize?())) activeItemUri: @activeItem?.getUri?() + focused: @focused deserializeParams: (params) -> {items, activeItemUri} = params @@ -25,6 +27,12 @@ class PaneModel extends Model params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri params + focus: -> + @focused = true + + blur: -> + @focused = false + # Public: Returns all contained views. getItems: -> clone(@items) diff --git a/src/pane.coffee b/src/pane.coffee index f9bb2b25c..8896d95bd 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -32,11 +32,13 @@ class Pane extends View toProperty: 'model' previousActiveItem: null + focusOnAttach: false # Private: initialize: (args...) -> if args[0]?.model? - {@model, @focusOnAttach} = args[0] + {@model} = args[0] + @focusOnAttach = @model.focused else @model = new PaneModel(items: args) @@ -53,7 +55,8 @@ class Pane extends View @subscribe @model, 'item-destroyed', @onItemDestroyed @subscribe this, 'focus', => @activeView?.focus(); false - @subscribe this, 'focusin', => @makeActive() + @subscribe this, 'focusin', => @makeActive(); @model.focus() + @subscribe this, 'focusout', => @model.blur() @command 'pane:save-items', => @saveItems() @command 'pane:show-next-item', => @showNextItem() @@ -82,7 +85,6 @@ class Pane extends View serializeParams: -> model: @model.serialize() - focusOnAttach: @is(':has(:focus)') # Private: afterAttach: (onDom) -> From 7eba9d3a23c6cad4a33f18433dbd58208c7b43f8 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Wed, 8 Jan 2014 13:39:58 -0800 Subject: [PATCH 14/89] Use flexbox to arrange panes --- src/pane-axis.coffee | 8 ------- src/pane-column.coffee | 21 ----------------- src/pane-container.coffee | 8 ------- src/pane-row.coffee | 21 ----------------- src/pane.coffee | 10 --------- static/panes.less | 41 +++++++++++++++++++++++++++++---- static/workspace-view.less | 46 -------------------------------------- 7 files changed, 37 insertions(+), 118 deletions(-) diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index e256c58be..c2edc63c1 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -18,7 +18,6 @@ class PaneAxis extends View addChild: (child, index=@children().length) -> @insertAt(index, child) - @getContainer()?.adjustPaneDimensions() removeChild: (child) -> parent = @parent().view() @@ -47,7 +46,6 @@ class PaneAxis extends View else primitiveRemove(child) - container.adjustPaneDimensions() Pane = require './pane' container.trigger 'pane:removed', [child] if child instanceof Pane @@ -68,9 +66,3 @@ class PaneAxis extends View insertChildAfter: (child, newChild) -> newChild.insertAfter(child) - - horizontalChildUnits: -> - $(child).view().horizontalGridUnits() for child in @children() - - verticalChildUnits: -> - $(child).view().verticalGridUnits() for child in @children() diff --git a/src/pane-column.coffee b/src/pane-column.coffee index 522e54ca7..5fe964c33 100644 --- a/src/pane-column.coffee +++ b/src/pane-column.coffee @@ -11,24 +11,3 @@ class PaneColumn extends PaneAxis className: -> "PaneColumn" - - adjustDimensions: -> - totalUnits = @verticalGridUnits() - unitsSoFar = 0 - for child in @children() - child = $(child).view() - childUnits = child.verticalGridUnits() - child.css - width: '100%' - height: "#{childUnits / totalUnits * 100}%" - top: "#{unitsSoFar / totalUnits * 100}%" - left: 0 - - child.adjustDimensions() - unitsSoFar += childUnits - - horizontalGridUnits: -> - Math.max(@horizontalChildUnits()...) - - verticalGridUnits: -> - _.sum(@verticalChildUnits()) diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 4ecda8359..623c1baf5 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -127,14 +127,6 @@ class PaneContainer extends View return pane if view? null - adjustPaneDimensions: -> - if root = @getRoot() - root.css(width: '100%', height: '100%', top: 0, left: 0) - root.adjustDimensions() - removeEmptyPanes: -> for pane in @getPanes() when pane.getItems().length == 0 pane.remove() - - afterAttach: -> - @adjustPaneDimensions() diff --git a/src/pane-row.coffee b/src/pane-row.coffee index 2c9b7774a..3a8e8d7bc 100644 --- a/src/pane-row.coffee +++ b/src/pane-row.coffee @@ -11,24 +11,3 @@ class PaneRow extends PaneAxis className: -> "PaneRow" - - adjustDimensions: -> - totalUnits = @horizontalGridUnits() - unitsSoFar = 0 - for child in @children() - child = $(child).view() - childUnits = child.horizontalGridUnits() - child.css - width: "#{childUnits / totalUnits * 100}%" - height: '100%' - top: 0 - left: "#{unitsSoFar / totalUnits * 100}%" - - child.adjustDimensions() - unitsSoFar += childUnits - - horizontalGridUnits: -> - _.sum(@horizontalChildUnits()) - - verticalGridUnits: -> - Math.max(@verticalChildUnits()...) diff --git a/src/pane.coffee b/src/pane.coffee index 8896d95bd..e2cd866cb 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -202,15 +202,6 @@ class Pane extends View viewForActiveItem: -> @viewForItem(@activeItem) - # Private: - adjustDimensions: -> # do nothing - - # Private: - horizontalGridUnits: -> 1 - - # Private: - verticalGridUnits: -> 1 - # Public: Creates a new pane above with a copy of the currently focused item. splitUp: (items...) -> @split(items, 'column', 'before') @@ -248,7 +239,6 @@ class Pane extends View switch side when 'before' then parent.insertChildBefore(this, newPane) when 'after' then parent.insertChildAfter(this, newPane) - @getContainer().adjustPaneDimensions() newPane.makeActive() newPane.focus() newPane diff --git a/static/panes.less b/static/panes.less index 33fdbe91a..38981bd5b 100644 --- a/static/panes.less +++ b/static/panes.less @@ -3,8 +3,41 @@ // Pane-items are things that go inside a pane. Like the UI-Demo, the // settings-view, the archive-view, the image-view. Etc. Basically a non- // editor resource with a tab. -.pane-item { - overflow: auto; - color: @text-color; - background-color: @pane-item-background-color; +.panes { + display: -webkit-flex; + -webkit-flex: 1; + + .column { + display: -webkit-flex; + -webkit-flex: 1; + -webkit-flex-direction: column; + } + + .row { + display: -webkit-flex; + -webkit-flex: 1; + -webkit-flex-direction: row; + } + + .pane { + display: -webkit-flex; + -webkit-flex: 1; + -webkit-flex-flow: column; + + .pane-item { + color: @text-color; + background-color: @pane-item-background-color; + } + + .item-views { + -webkit-flex: 1; + display: -webkit-flex; + min-height: 0; + } + + .item-views > * { + -webkit-flex: 1; + min-width: 0; + } + } } diff --git a/static/workspace-view.less b/static/workspace-view.less index ae0afcf56..55fdbd03b 100644 --- a/static/workspace-view.less +++ b/static/workspace-view.less @@ -39,49 +39,3 @@ h6 { -webkit-flex-flow: column; } } - -.panes { - position: relative; - -webkit-flex: 1; - - .column { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - overflow-y: hidden; - } - - .row { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - overflow-x: hidden; - margin: 0; - } - - .pane { - position: absolute; - display: -webkit-flex; - -webkit-flex-flow: column; - top: 0; - bottom: 0; - left: 0; - right: 0; - box-sizing: border-box; - } - - .pane .item-views { - -webkit-flex: 1; - display: -webkit-flex; - min-height: 0; - } - - .pane .item-views > * { - -webkit-flex: 1; - min-width: 0; - } -} From 6f766acac8ba8d2b304b43555364790952d9b64f Mon Sep 17 00:00:00 2001 From: probablycorey Date: Wed, 8 Jan 2014 14:30:33 -0800 Subject: [PATCH 15/89] Rename .row and .column to .pane-row and .pane-column Bootstrap's .row and .column css was influencing our pane rows and columns. --- src/pane-column.coffee | 2 +- src/pane-row.coffee | 2 +- static/panes.less | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pane-column.coffee b/src/pane-column.coffee index 5fe964c33..4a89e0a6a 100644 --- a/src/pane-column.coffee +++ b/src/pane-column.coffee @@ -7,7 +7,7 @@ module.exports = class PaneColumn extends PaneAxis @content: -> - @div class: 'column' + @div class: 'pane-column' className: -> "PaneColumn" diff --git a/src/pane-row.coffee b/src/pane-row.coffee index 3a8e8d7bc..76106ab61 100644 --- a/src/pane-row.coffee +++ b/src/pane-row.coffee @@ -7,7 +7,7 @@ PaneAxis = require './pane-axis' module.exports = class PaneRow extends PaneAxis @content: -> - @div class: 'row' + @div class: 'pane-row' className: -> "PaneRow" diff --git a/static/panes.less b/static/panes.less index 38981bd5b..3aac5db27 100644 --- a/static/panes.less +++ b/static/panes.less @@ -7,13 +7,13 @@ display: -webkit-flex; -webkit-flex: 1; - .column { + .pane-column { display: -webkit-flex; -webkit-flex: 1; -webkit-flex-direction: column; } - .row { + .pane-row { display: -webkit-flex; -webkit-flex: 1; -webkit-flex-direction: row; From d908c8b02606db15cf0bd4b6b796953a19c1c382 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Wed, 8 Jan 2014 14:31:46 -0800 Subject: [PATCH 16/89] Use absolute divs to limit repaints on keypresses --- src/pane.coffee | 3 ++- static/panes.less | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index e2cd866cb..fee6294e2 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -21,7 +21,8 @@ class Pane extends View @content: (wrappedView) -> @div class: 'pane', tabindex: -1, => - @div class: 'item-views', outlet: 'itemViews' + @div class: 'flexbox-repaint-hack', => + @div class: 'item-views', outlet: 'itemViews' @delegatesProperties 'items', 'activeItem', toProperty: 'model' @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', diff --git a/static/panes.less b/static/panes.less index 3aac5db27..238cdb4a0 100644 --- a/static/panes.less +++ b/static/panes.less @@ -20,9 +20,19 @@ } .pane { - display: -webkit-flex; + position: relative; -webkit-flex: 1; - -webkit-flex-flow: column; + + .flexbox-repaint-hack { + display: -webkit-flex; + -webkit-flex-flow: column; + + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } .pane-item { color: @text-color; From 196942d1267312f1ef6069709257bed2b2406803 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Wed, 8 Jan 2014 16:31:56 -0800 Subject: [PATCH 17/89] Rename .row and .column in pane specs --- spec/pane-container-spec.coffee | 12 ++++++------ spec/pane-spec.coffee | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 4dc770c16..cd2f7264e 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -124,19 +124,19 @@ describe "PaneContainer", -> describe "serialization", -> it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", -> newContainer = atom.deserializers.deserialize(container.serialize()) - expect(newContainer.find('.row > :contains(1)')).toExist() - expect(newContainer.find('.row > .column > :contains(2)')).toExist() - expect(newContainer.find('.row > .column > :contains(3)')).toExist() + expect(newContainer.find('.pane-row > :contains(1)')).toExist() + expect(newContainer.find('.pane-row > .pane-column > :contains(2)')).toExist() + expect(newContainer.find('.pane-row > .pane-column > :contains(3)')).toExist() newContainer.height(200).width(300).attachToDom() - expect(newContainer.find('.row > :contains(1)').width()).toBe 150 - expect(newContainer.find('.row > .column > :contains(2)').height()).toBe 100 + expect(newContainer.find('.pane-row > :contains(1)').width()).toBe 150 + expect(newContainer.find('.pane-row > .pane-column > :contains(2)').height()).toBe 100 xit "removes empty panes on deserialization", -> # only deserialize pane 1's view successfully TestView.deserialize = ({name}) -> new TestView(name) if name is '1' newContainer = atom.deserializers.deserialize(container.serialize()) - expect(newContainer.find('.row, .column')).not.toExist() + expect(newContainer.find('.pane-row, .pane-column')).not.toExist() expect(newContainer.find('> :contains(1)')).toExist() describe "pane-container:active-pane-item-changed", -> diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index fd6339cab..53e3073ed 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -4,7 +4,7 @@ Pane = require '../src/pane' path = require 'path' temp = require 'temp' -describe "Pane", -> +fdescribe "Pane", -> [container, view1, view2, editor1, editor2, pane] = [] class TestView extends View @@ -545,23 +545,23 @@ describe "Pane", -> it "builds a row if needed, then appends a new pane after itself", -> # creates the new pane with a copy of the active item if none are given pane2 = pane1.splitRight(pane1.copyActiveItem()) - expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] expect(pane2.items).toEqual [editor1] expect(pane2.activeItem).not.toBe editor1 # it's a copy pane3 = pane2.splitRight(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] - expect(container.find('.row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]] + expect(container.find('.pane-row .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]] it "builds a row if needed, then appends a new pane after itself ", -> # creates the new pane with a copy of the active item if none are given pane2 = pane1.splitRight() - expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] expect(pane2.items).toEqual [] expect(pane2.activeItem).toBeUndefined() pane3 = pane2.splitRight() - expect(container.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]] + expect(container.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0], pane3[0]] expect(pane3.items).toEqual [] expect(pane3.activeItem).toBeUndefined() @@ -569,37 +569,37 @@ describe "Pane", -> it "builds a row if needed, then appends a new pane before itself", -> # creates the new pane with a copy of the active item if none are given pane2 = pane.splitLeft(pane1.copyActiveItem()) - expect(container.find('.row .pane').toArray()).toEqual [pane2[0], pane[0]] + expect(container.find('.pane-row .pane').toArray()).toEqual [pane2[0], pane[0]] expect(pane2.items).toEqual [editor1] expect(pane2.activeItem).not.toBe editor1 # it's a copy pane3 = pane2.splitLeft(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] - expect(container.find('.row .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]] + expect(container.find('.pane-row .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]] describe "splitDown(items...)", -> it "builds a column if needed, then appends a new pane after itself", -> # creates the new pane with a copy of the active item if none are given pane2 = pane.splitDown(pane1.copyActiveItem()) - expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0]] + expect(container.find('.pane-column .pane').toArray()).toEqual [pane[0], pane2[0]] expect(pane2.items).toEqual [editor1] expect(pane2.activeItem).not.toBe editor1 # it's a copy pane3 = pane2.splitDown(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] - expect(container.find('.column .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]] + expect(container.find('.pane-column .pane').toArray()).toEqual [pane[0], pane2[0], pane3[0]] describe "splitUp(items...)", -> it "builds a column if needed, then appends a new pane before itself", -> # creates the new pane with a copy of the active item if none are given pane2 = pane.splitUp(pane1.copyActiveItem()) - expect(container.find('.column .pane').toArray()).toEqual [pane2[0], pane[0]] + expect(container.find('.pane-column .pane').toArray()).toEqual [pane2[0], pane[0]] expect(pane2.items).toEqual [editor1] expect(pane2.activeItem).not.toBe editor1 # it's a copy pane3 = pane2.splitUp(view3, view4) expect(pane3.getItems()).toEqual [view3, view4] - expect(container.find('.column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]] + expect(container.find('.pane-column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]] it "lays out nested panes by equally dividing their containing row / column", -> container.width(520).height(240).attachToDom() From 99a67ca1ab343a6d8dc2d207f4dbc25a57625595 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Wed, 8 Jan 2014 16:37:57 -0800 Subject: [PATCH 18/89] Remove focused spec --- spec/pane-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 53e3073ed..00364ac77 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -4,7 +4,7 @@ Pane = require '../src/pane' path = require 'path' temp = require 'temp' -fdescribe "Pane", -> +describe "Pane", -> [container, view1, view2, editor1, editor2, pane] = [] class TestView extends View From ad60594c138e2b8f1cb63cd63b8a1aad51969340 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 12:10:58 -0700 Subject: [PATCH 19/89] Add mixto module --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e390b9700..5e3ca675f 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "text-buffer": "0.12.0", "underscore-plus": "0.6.1", "theorist": "~0.7.0", - "delegato": "~0.4.0" + "delegato": "~0.4.0", + "mixto": "~0.4.0" }, "packageDependencies": { "atom-dark-syntax": "0.10.0", From 101326a130cc3943646eaa435c47ec954c51d789 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 12:11:21 -0700 Subject: [PATCH 20/89] Upgrade theorist to 0.9.0 for sequences --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e3ca675f..e23497b3e 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "temp": "0.5.0", "text-buffer": "0.12.0", "underscore-plus": "0.6.1", - "theorist": "~0.7.0", + "theorist": "~0.9.0", "delegato": "~0.4.0", "mixto": "~0.4.0" }, From bb595ab08a5726deb6ea1a74d98685518e4941af Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 13:06:21 -0700 Subject: [PATCH 21/89] Start adding PaneAxisModel --- src/pane-axis-model.coffee | 21 +++++++++++++++++++++ src/pane-axis.coffee | 24 ++++++++++++++++++------ src/pane.coffee | 2 +- 3 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/pane-axis-model.coffee diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee new file mode 100644 index 000000000..cacade828 --- /dev/null +++ b/src/pane-axis-model.coffee @@ -0,0 +1,21 @@ +{Model, Sequence} = require 'theorist' + +module.exports = +class PaneAxisModel extends Model + constructor: (params) -> + @children = Sequence.fromArray(params?.children ? []) + + addChild: (child, index=@children.length) -> + @children.splice(index, 0, child) + + removeChild: (child) -> + index = @children.indexOf(child) + @children.splice(index, 1) unless index is -1 + + insertChildBefore: (currentChild, newChild) -> + index = @children.indexOf(currentChild) + @children.splice(index, 0, newChild) + + insertChildAfter: (currentChild, newChild) -> + index = @children.indexOf(currentChild) + @children.splice(index + 1, 0, newChild) diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index c2edc63c1..61d29cda9 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -1,5 +1,6 @@ Serializable = require 'serializable' {$, View} = require './space-pen-extensions' +PaneAxisModel = require './pane-axis-model' ### Internal ### module.exports = @@ -7,6 +8,11 @@ class PaneAxis extends View Serializable.includeInto(this) initialize: ({children}={}) -> + @model = new PaneAxisModel + @model.children.on 'changed', ({index, removedValues, insertedValues}) => + @onChildRemoved(child, index) for child in removedValues + @onChildAdded(child, index) for child in insertedValues + @addChild(child) for child in children ? [] serializeParams: -> @@ -16,10 +22,16 @@ class PaneAxis extends View params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState) params - addChild: (child, index=@children().length) -> - @insertAt(index, child) + addChild: (child, index) -> + @model.addChild(child, index) removeChild: (child) -> + @model.removeChild(child) + + onChildAdded: (child, index) => + @insertAt(index, child) + + onChildRemoved: (child) => parent = @parent().view() container = @getContainer() childWasInactive = not child.isActive?() @@ -61,8 +73,8 @@ class PaneAxis extends View getActivePane: -> @find('.pane.active').view() ? @find('.pane:first').view() - insertChildBefore: (child, newChild) -> - newChild.insertBefore(child) + insertChildBefore: (currentChild, newChild) -> + @model.insertChildBefore(currentChild, newChild) - insertChildAfter: (child, newChild) -> - newChild.insertAfter(child) + insertChildAfter: (currentChild, newChild) -> + @model.insertChildAfter(currentChild, newChild) diff --git a/src/pane.coffee b/src/pane.coffee index fee6294e2..b44b59680 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -223,7 +223,7 @@ class Pane extends View split: (items, axis, side) -> PaneContainer = require './pane-container' - parent = @parent().view() + parent = @parentModel ? @parent().view() unless parent.hasClass(axis) axis = @buildPaneAxis(axis) if parent instanceof PaneContainer From eb7f3ff5af9d5406eb51b4cbfb466a7e7412f8d7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 14:55:22 -0700 Subject: [PATCH 22/89] Support splitting at the model layer Splitting in the view will need to be removed and mapped to splits in the model. --- spec/pane-model-spec.coffee | 79 +++++++++++++++++++++++++++++++++ src/pane-axis-model.coffee | 13 ++++-- src/pane-container-model.coffee | 14 ++++++ src/pane-model.coffee | 23 ++++++++++ 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 spec/pane-model-spec.coffee create mode 100644 src/pane-container-model.coffee diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee new file mode 100644 index 000000000..d27d1da25 --- /dev/null +++ b/spec/pane-model-spec.coffee @@ -0,0 +1,79 @@ +PaneModel = require '../src/pane-model' +PaneAxisModel = require '../src/pane-axis-model' +PaneContainerModel = require '../src/pane-container-model' + +describe "PaneModel", -> + describe "split methods", -> + [pane1, container] = [] + + beforeEach -> + pane1 = new PaneModel(items: ["A"]) + container = new PaneContainerModel(root: pane1) + + describe "::splitLeft(params)", -> + describe "when the parent is the container root", -> + it "replaces itself with a row and inserts a new pane to the left of itself", -> + pane2 = pane1.splitLeft(items: ["B"]) + pane3 = pane1.splitLeft(items: ["C"]) + expect(container.root.orientation).toBe 'horizontal' + expect(container.root.children).toEqual [pane2, pane3, pane1] + + describe "when the parent is a column", -> + it "replaces itself with a row and inserts a new pane to the left of itself", -> + pane1.splitDown() + pane2 = pane1.splitLeft(items: ["B"]) + pane3 = pane1.splitLeft(items: ["C"]) + row = container.root.children[0] + expect(row.orientation).toBe 'horizontal' + expect(row.children).toEqual [pane2, pane3, pane1] + + describe "::splitRight(params)", -> + describe "when the parent is the container root", -> + it "replaces itself with a row and inserts a new pane to the right of itself", -> + pane2 = pane1.splitRight(items: ["B"]) + pane3 = pane1.splitRight(items: ["C"]) + expect(container.root.orientation).toBe 'horizontal' + expect(container.root.children).toEqual [pane1, pane3, pane2] + + describe "when the parent is a column", -> + it "replaces itself with a row and inserts a new pane to the right of itself", -> + pane1.splitDown() + pane2 = pane1.splitRight(items: ["B"]) + pane3 = pane1.splitRight(items: ["C"]) + row = container.root.children[0] + expect(row.orientation).toBe 'horizontal' + expect(row.children).toEqual [pane1, pane3, pane2] + + describe "::splitUp(params)", -> + describe "when the parent is the container root", -> + it "replaces itself with a column and inserts a new pane above itself", -> + pane2 = pane1.splitUp(items: ["B"]) + pane3 = pane1.splitUp(items: ["C"]) + expect(container.root.orientation).toBe 'vertical' + expect(container.root.children).toEqual [pane2, pane3, pane1] + + describe "when the parent is a row", -> + it "replaces itself with a column and inserts a new pane above itself", -> + pane1.splitRight() + pane2 = pane1.splitUp(items: ["B"]) + pane3 = pane1.splitUp(items: ["C"]) + column = container.root.children[0] + expect(column.orientation).toBe 'vertical' + expect(column.children).toEqual [pane2, pane3, pane1] + + describe "::splitDown(params)", -> + describe "when the parent is the container root", -> + it "replaces itself with a column and inserts a new pane below itself", -> + pane2 = pane1.splitDown(items: ["B"]) + pane3 = pane1.splitDown(items: ["C"]) + expect(container.root.orientation).toBe 'vertical' + expect(container.root.children).toEqual [pane1, pane3, pane2] + + describe "when the parent is a row", -> + it "replaces itself with a column and inserts a new pane below itself", -> + pane1.splitRight() + pane2 = pane1.splitDown(items: ["B"]) + pane3 = pane1.splitDown(items: ["C"]) + column = container.root.children[0] + expect(column.orientation).toBe 'vertical' + expect(column.children).toEqual [pane1, pane3, pane2] diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index cacade828..fcbe4d2cf 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -2,15 +2,22 @@ module.exports = class PaneAxisModel extends Model - constructor: (params) -> - @children = Sequence.fromArray(params?.children ? []) + constructor: ({@orientation, children}) -> + @children = Sequence.fromArray(children ? []) + @children.onEach (child) => child.parent = this addChild: (child, index=@children.length) -> @children.splice(index, 0, child) removeChild: (child) -> index = @children.indexOf(child) - @children.splice(index, 1) unless index is -1 + throw new Error("Removing non-existent child") if index is -1 + @children.splice(index, 1) + + replaceChild: (oldChild, newChild) -> + index = @children.indexOf(oldChild) + throw new Error("Replacing non-existent child") if index is -1 + @children.splice(index, 1, newChild) insertChildBefore: (currentChild, newChild) -> index = @children.indexOf(currentChild) diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee new file mode 100644 index 000000000..f421d9b3b --- /dev/null +++ b/src/pane-container-model.coffee @@ -0,0 +1,14 @@ +{Model} = require 'theorist' + +module.exports = +class PaneContainerModel extends Model + @properties + root: null + + constructor: -> + super + @subscribe @$root, (root) => root?.parent = this + + replaceChild: (oldChild, newChild) -> + throw new Error("Replacing non-existent child") if oldChild isnt @root + @root = newChild diff --git a/src/pane-model.coffee b/src/pane-model.coffee index def6d3a6c..d12376eb9 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -2,6 +2,7 @@ {dirname} = require 'path' {Model} = require 'theorist' Serializable = require 'serializable' +PaneAxisModel = require './pane-axis-model' module.exports = class PaneModel extends Model @@ -189,3 +190,25 @@ class PaneModel extends Model # Private: copyActiveItem: -> @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize()) + + splitLeft: (params) -> + @split('horizontal', 'before', params) + + splitRight: (params) -> + @split('horizontal', 'after', params) + + splitUp: (params) -> + @split('vertical', 'before', params) + + splitDown: (params) -> + @split('vertical', 'after', params) + + split: (orientation, side, params) -> + if @parent.orientation isnt orientation + @parent.replaceChild(this, new PaneAxisModel({orientation, children: [this]})) + + newPane = new @constructor(params) + switch side + when 'before' then @parent.insertChildBefore(this, newPane) + when 'after' then @parent.insertChildAfter(this, newPane) + newPane From f031a9706d03ff19e2135ac6234409d02921baec Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 18:46:21 -0700 Subject: [PATCH 23/89] Support unsplitting in the model layer --- spec/pane-model-spec.coffee | 28 ++++++++++++++++++++++++++++ src/pane-axis-model.coffee | 13 ++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index d27d1da25..6117091ce 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -77,3 +77,31 @@ describe "PaneModel", -> column = container.root.children[0] expect(column.orientation).toBe 'vertical' expect(column.children).toEqual [pane1, pane3, pane2] + + describe "::destroy()", -> + [pane1, container] = [] + + beforeEach -> + pane1 = new PaneModel(items: ["A"]) + container = new PaneContainerModel(root: pane1) + + describe "if the pane's parent has more than two children", -> + it "removes the pane from its parent", -> + pane2 = pane1.splitRight() + pane3 = pane2.splitRight() + + expect(container.root.children).toEqual [pane1, pane2, pane3] + pane2.destroy() + expect(container.root.children).toEqual [pane1, pane3] + + describe "if the pane's parent has two children", -> + it "replaces the parent with its last remaining child", -> + pane2 = pane1.splitRight() + pane3 = pane2.splitDown() + + expect(container.root.children[0]).toBe pane1 + expect(container.root.children[1].children).toEqual [pane2, pane3] + pane3.destroy() + expect(container.root.children).toEqual [pane1, pane2] + pane2.destroy() + expect(container.root).toBe pane1 diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index fcbe4d2cf..b8edfbbc5 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -4,7 +4,15 @@ module.exports = class PaneAxisModel extends Model constructor: ({@orientation, children}) -> @children = Sequence.fromArray(children ? []) - @children.onEach (child) => child.parent = this + + @children.onEach (child) => + child.parent = this + @subscribe child, 'destroyed', => @removeChild(child) + + @children.onRemoval (child) => @unsubscribe(child) + + @when @children.$length.becomesLessThan(2), 'reparentLastChild' + @when @children.$length.becomesLessThan(1), 'destroy' addChild: (child, index=@children.length) -> @children.splice(index, 0, child) @@ -26,3 +34,6 @@ class PaneAxisModel extends Model insertChildAfter: (currentChild, newChild) -> index = @children.indexOf(currentChild) @children.splice(index + 1, 0, newChild) + + reparentLastChild: -> + @parent.replaceChild(this, @children[0]) From 8efcb1abfa4a0edece4e1574d25e1c868d79ab82 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 18:49:50 -0700 Subject: [PATCH 24/89] Remove pane splitting/unsplitting logic from view There's still some failing specs around focus management, but it's getting closer. --- src/pane-axis-model.coffee | 20 +++++++++++ src/pane-axis.coffee | 64 +++++++++------------------------ src/pane-container-model.coffee | 11 ++++++ src/pane-container.coffee | 36 ++++++++++++------- src/pane-model.coffee | 4 +++ src/pane.coffee | 61 +++++++------------------------ 6 files changed, 88 insertions(+), 108 deletions(-) diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index b8edfbbc5..824727815 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -1,7 +1,14 @@ {Model, Sequence} = require 'theorist' +Serializable = require 'serializable' + +PaneRow = null +PaneColumn = null module.exports = class PaneAxisModel extends Model + atom.deserializers.add(this) + Serializable.includeInto(this) + constructor: ({@orientation, children}) -> @children = Sequence.fromArray(children ? []) @@ -14,6 +21,19 @@ class PaneAxisModel extends Model @when @children.$length.becomesLessThan(2), 'reparentLastChild' @when @children.$length.becomesLessThan(1), 'destroy' + deserializeParams: (params) -> + params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState) + params + + serializeParams: -> + children: @children.map (child) -> child.serialize() + + getViewClass: -> + if @orientation is 'vertical' + PaneColumn ?= require './pane-column' + else + PaneRow ?= require './pane-row' + addChild: (child, index=@children.length) -> @children.splice(index, 0, child) diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index 61d29cda9..bdee142ad 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -1,68 +1,36 @@ Serializable = require 'serializable' {$, View} = require './space-pen-extensions' PaneAxisModel = require './pane-axis-model' +Pane = null ### Internal ### module.exports = class PaneAxis extends View - Serializable.includeInto(this) + initialize: (@model) -> + @subscribe @model.children.onRemoval @onChildRemoved + @subscribe @model.children.onEach @onChildAdded - initialize: ({children}={}) -> - @model = new PaneAxisModel - @model.children.on 'changed', ({index, removedValues, insertedValues}) => - @onChildRemoved(child, index) for child in removedValues - @onChildAdded(child, index) for child in insertedValues + @onChildAdded(child) for child in children ? [] - @addChild(child) for child in children ? [] - - serializeParams: -> - children: @children().views().map (child) -> child.serialize() - - deserializeParams: (params) -> - params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState) - params + viewForModel: (model) -> + viewClass = model.getViewClass() + model._view ?= new viewClass(model) addChild: (child, index) -> - @model.addChild(child, index) + @model.addChild(child.model, index) removeChild: (child) -> - @model.removeChild(child) + @model.removeChild(child.model) onChildAdded: (child, index) => - @insertAt(index, child) + view = @viewForModel(child) + @insertAt(index, view) onChildRemoved: (child) => - parent = @parent().view() - container = @getContainer() - childWasInactive = not child.isActive?() - - primitiveRemove = (child) => - node = child[0] - $.cleanData(node.getElementsByTagName('*')) - $.cleanData([node]) - this[0].removeChild(node) - - # use primitive .removeChild() dom method instead of .remove() to avoid recursive loop - if @children().length == 2 - primitiveRemove(child) - sibling = @children().view() - siblingFocused = sibling.is(':has(:focus)') - sibling.detach() - - if parent.setRoot? - parent.setRoot(sibling, suppressPaneItemChangeEvents: childWasInactive) - else - parent.insertChildBefore(this, sibling) - parent.removeChild(this) - sibling.focus() if siblingFocused - else - primitiveRemove(child) - - Pane = require './pane' - container.trigger 'pane:removed', [child] if child instanceof Pane - - detachChild: (child) -> - child.detach() + view = @viewForModel(child) + view.detach() + Pane ?= require './pane' + @getContainer()?.trigger 'pane:removed', [view] if view instanceof Pane getContainer: -> @closest('.panes').view() diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index f421d9b3b..1aa975cd7 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -1,7 +1,11 @@ {Model} = require 'theorist' +Serializable = require 'serializable' module.exports = class PaneContainerModel extends Model + atom.deserializers.add(this) + Serializable.includeInto(this) + @properties root: null @@ -9,6 +13,13 @@ class PaneContainerModel extends Model super @subscribe @$root, (root) => root?.parent = this + deserializeParams: (params) -> + params.root = atom.deserializers.deserialize(params.root) + params + + serializeParams: (params) -> + root: @root?.serialize() + replaceChild: (oldChild, newChild) -> throw new Error("Replacing non-existent child") if oldChild isnt @root @root = newChild diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 623c1baf5..bc5f0e2f4 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -1,18 +1,26 @@ Serializable = require 'serializable' {$, View} = require './space-pen-extensions' Pane = require './pane' +PaneContainerModel = require './pane-container-model' # Private: Manages the list of panes within a {WorkspaceView} module.exports = class PaneContainer extends View Serializable.includeInto(this) - atom.deserializers.add(this) + + @deserialize: (state) -> + new this(PaneContainerModel.deserialize(state.model)) @content: -> @div class: 'panes' - initialize: ({root}={}) -> - @setRoot(root) + initialize: (params) -> + if params instanceof PaneContainerModel + @model = params + else + @model = new PaneContainerModel({root: params?.root?.model}) + + @subscribe @model.$root, 'value', @onRootChanged @subscribe this, 'pane:attached', (event, pane) => @triggerActiveItemChange() if @getActivePane() is pane @@ -26,15 +34,15 @@ class PaneContainer extends View @subscribe this, 'pane:active-item-changed', (event, item) => @triggerActiveItemChange() if @getActivePaneItem() is item + viewForModel: (model) -> + viewClass = model.getViewClass() + model._view ?= new viewClass(model) + triggerActiveItemChange: -> @trigger 'pane-container:active-pane-item-changed', [@getActivePaneItem()] serializeParams: -> - root: @getRoot()?.serialize() - - deserializeParams: (params) -> - params.root = atom.deserializers.deserialize(params.root) - params + model: @model.serialize() ### Public ### @@ -71,11 +79,15 @@ class PaneContainer extends View getRoot: -> @children().first().view() - setRoot: (root, {suppressPaneItemChangeEvents}={}) -> - @empty() + setRoot: (root) -> + @model.root = root?.model + + onRootChanged: (root) => + @children().detach() if root? - @append(root) - root.makeActive?() + view = @viewForModel(root) + @append(view) + view.makeActive?() removeChild: (child) -> throw new Error("Removing non-existant child") unless @getRoot() is child diff --git a/src/pane-model.coffee b/src/pane-model.coffee index d12376eb9..9b42aaf87 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -3,9 +3,11 @@ {Model} = require 'theorist' Serializable = require 'serializable' PaneAxisModel = require './pane-axis-model' +Pane = null module.exports = class PaneModel extends Model + atom.deserializers.add(this) Serializable.includeInto(this) @properties @@ -28,6 +30,8 @@ class PaneModel extends Model params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri params + getViewClass: -> Pane ?= require './pane' + focus: -> @focused = true diff --git a/src/pane.coffee b/src/pane.coffee index b44b59680..540cbf9f9 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -19,6 +19,9 @@ class Pane extends View @version: 1 + @deserialize: (state) -> + new this(PaneModel.deserialize(state.model)) + @content: (wrappedView) -> @div class: 'pane', tabindex: -1, => @div class: 'flexbox-repaint-hack', => @@ -37,11 +40,12 @@ class Pane extends View # Private: initialize: (args...) -> - if args[0]?.model? - {@model} = args[0] + if args[0] instanceof PaneModel + @model = args[0] @focusOnAttach = @model.focused else @model = new PaneModel(items: args) + @model._view = this @onItemAdded(item) for item in @items @viewsByItem = new WeakMap() @@ -203,52 +207,13 @@ class Pane extends View viewForActiveItem: -> @viewForItem(@activeItem) - # Public: Creates a new pane above with a copy of the currently focused item. - splitUp: (items...) -> - @split(items, 'column', 'before') + splitLeft: (items...) -> @model.splitLeft({items})._view - # Public: Creates a new pane below with a copy of the currently focused item. - splitDown: (items...) -> - @split(items, 'column', 'after') + splitRight: (items...) -> @model.splitRight({items})._view - # Public: Creates a new pane left with a copy of the currently focused item. - splitLeft: (items...) -> - @split(items, 'row', 'before') + splitUp: (items...) -> @model.splitUp({items})._view - # Public: Creates a new pane right with a copy of the currently focused item. - splitRight: (items...) -> - @split(items, 'row', 'after') - - # Private: - split: (items, axis, side) -> - PaneContainer = require './pane-container' - - parent = @parentModel ? @parent().view() - unless parent.hasClass(axis) - axis = @buildPaneAxis(axis) - if parent instanceof PaneContainer - @detach() - axis.addChild(this) - parent.setRoot(axis) - else - parent.insertChildBefore(this, axis) - axis.addChild(this) - parent = axis - - newPane = new Pane(items...) - - switch side - when 'before' then parent.insertChildBefore(this, newPane) - when 'after' then parent.insertChildAfter(this, newPane) - newPane.makeActive() - newPane.focus() - newPane - - # Private: - buildPaneAxis: (axis) -> - switch axis - when 'row' then new PaneRow() - when 'column' then new PaneColumn() + splitDown: (items...) -> @model.splitDown({items})._view # Private: getContainer: -> @@ -257,13 +222,13 @@ class Pane extends View # Private: remove: (selector, keepData) -> return super if keepData - @parent().view().removeChild(this) - # Private: - beforeRemove: -> if @is(':has(:focus)') @getContainer().focusNextPane() or atom.workspaceView?.focus() else if @isActive() @getContainer().makeNextPaneActive() + @parent().view().removeChild(this) unless keepData item.destroy?() for item in @getItems() + + super From 1a5e10c1d2a439d4cc38d855910b630c7eef5641 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 8 Jan 2014 19:05:35 -0700 Subject: [PATCH 25/89] Focus the new pane when splitting --- spec/pane-model-spec.coffee | 5 +++++ src/pane-model.coffee | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index 6117091ce..db6bdac48 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -78,6 +78,11 @@ describe "PaneModel", -> expect(column.orientation).toBe 'vertical' expect(column.children).toEqual [pane1, pane3, pane2] + it "focuses the new pane, even if the current pane isn't focused", -> + expect(pane1.focused).toBe false + pane2 = pane1.split() + expect(pane2.focused).toBe true + describe "::destroy()", -> [pane1, container] = [] diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 9b42aaf87..be9b651cb 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -1,4 +1,4 @@ -{find, compact, clone} = require 'underscore-plus' +{find, compact, clone, extend} = require 'underscore-plus' {dirname} = require 'path' {Model} = require 'theorist' Serializable = require 'serializable' @@ -211,7 +211,7 @@ class PaneModel extends Model if @parent.orientation isnt orientation @parent.replaceChild(this, new PaneAxisModel({orientation, children: [this]})) - newPane = new @constructor(params) + newPane = new @constructor(extend({focused: true}, params)) switch side when 'before' then @parent.insertChildBefore(this, newPane) when 'after' then @parent.insertChildAfter(this, newPane) From 4e99d003ee7afb1fef5e267e1af1f25d60c139a5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 09:53:14 -0700 Subject: [PATCH 26/89] Add Focusable mixin and FocusContext Focusable objects have ::focus and ::blur methods and a ::focused property. Focusable objects can be assigned a ::focusContext, and the ::focused property will only be true for at most one object with the same context. --- spec/focusable-spec.coffee | 36 ++++++++++++++++++++++++++++++++++++ src/focus-context.coffee | 5 +++++ src/focusable.coffee | 18 ++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 spec/focusable-spec.coffee create mode 100644 src/focus-context.coffee create mode 100644 src/focusable.coffee diff --git a/spec/focusable-spec.coffee b/spec/focusable-spec.coffee new file mode 100644 index 000000000..0cb26b908 --- /dev/null +++ b/spec/focusable-spec.coffee @@ -0,0 +1,36 @@ +{Model} = require 'theorist' +Focusable = require '../src/focusable' +FocusContext = require '../src/focus-context' + +describe "Focusable mixin", -> + it "ensures that only a single model is focused for a given focus manager", -> + class Item extends Model + Focusable.includeInto(this) + + focusContext = new FocusContext + item1 = new Item({focusContext}) + item2 = new Item({focusContext}) + item3 = new Item({focusContext}) + + expect(focusContext.focusedObject).toBe null + expect(item1.focused).toBe false + expect(item2.focused).toBe false + expect(item3.focused).toBe false + + item1.focus() + expect(focusContext.focusedObject).toBe item1 + expect(item1.focused).toBe true + expect(item2.focused).toBe false + expect(item3.focused).toBe false + + item2.focus() + expect(focusContext.focusedObject).toBe item2 + expect(item1.focused).toBe false + expect(item2.focused).toBe true + expect(item3.focused).toBe false + + item2.blur() + expect(focusContext.focusedObject).toBe null + expect(item1.focused).toBe false + expect(item2.focused).toBe false + expect(item3.focused).toBe false diff --git a/src/focus-context.coffee b/src/focus-context.coffee new file mode 100644 index 000000000..e52b0487f --- /dev/null +++ b/src/focus-context.coffee @@ -0,0 +1,5 @@ +{Model} = require 'theorist' + +module.exports = +class FocusContext extends Model + @property 'focusedObject', null diff --git a/src/focusable.coffee b/src/focusable.coffee new file mode 100644 index 000000000..6441a74eb --- /dev/null +++ b/src/focusable.coffee @@ -0,0 +1,18 @@ +Mixin = require 'mixto' + +module.exports = +class Focusable extends Mixin + @included: -> + @property 'focusContext' + @behavior 'focused', -> + @$focusContext + .flatMapLatest((context) -> context?.$focusedObject) + .map((focusedObject) => focusedObject is this) + + focus: -> + throw new Error("Object must be assigned a focusContext to be focus") unless @focusContext + @focusContext.focusedObject = this + + blur: -> + throw new Error("Object must be assigned a focusContext to be blurred") unless @focusContext + @focusContext.focusedObject = null if @focused From 2317c6835e9bfaa920b7cd6197644629261a934f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 10:41:48 -0700 Subject: [PATCH 27/89] At the model layer: Focus next pane when a focused pane is destroyed This incorporates the Focusable mixin into PaneModel and ensures that all panes in the same pane container share a single focus context. --- spec/pane-model-spec.coffee | 12 +++++++++++- src/pane-axis-model.coffee | 18 +++++++++++++++-- src/pane-container-model.coffee | 34 ++++++++++++++++++++++++++++++++- src/pane-model.coffee | 17 ++++++++++------- 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index db6bdac48..794507ba5 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -80,7 +80,7 @@ describe "PaneModel", -> it "focuses the new pane, even if the current pane isn't focused", -> expect(pane1.focused).toBe false - pane2 = pane1.split() + pane2 = pane1.splitRight() expect(pane2.focused).toBe true describe "::destroy()", -> @@ -110,3 +110,13 @@ describe "PaneModel", -> expect(container.root.children).toEqual [pane1, pane2] pane2.destroy() expect(container.root).toBe pane1 + + describe "if the pane is focused", -> + it "shifts focus to the next pane", -> + pane2 = pane1.splitRight() + pane3 = pane2.splitRight() + pane2.focus() + expect(pane2.focused).toBe true + expect(pane3.focused).toBe false + pane2.destroy() + expect(pane3.focused).toBe true diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index 824727815..d23e93493 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -1,4 +1,6 @@ {Model, Sequence} = require 'theorist' +{flatten} = require 'underscore-plus' +Delegator = require 'delegato' Serializable = require 'serializable' PaneRow = null @@ -8,15 +10,24 @@ module.exports = class PaneAxisModel extends Model atom.deserializers.add(this) Serializable.includeInto(this) + Delegator.includeInto(this) + + @delegatesMethod 'focusNextPane', toProperty: 'parent' + + @property 'focusContext' constructor: ({@orientation, children}) -> @children = Sequence.fromArray(children ? []) - @children.onEach (child) => + @subscribe @$focusContext, (focusContext) => + child.focusContext = focusContext for child in @children + + @subscribe @children.onEach (child) => child.parent = this + child.focusContext = @focusContext @subscribe child, 'destroyed', => @removeChild(child) - @children.onRemoval (child) => @unsubscribe(child) + @subscribe @children.onRemoval (child) => @unsubscribe(child) @when @children.$length.becomesLessThan(2), 'reparentLastChild' @when @children.$length.becomesLessThan(1), 'destroy' @@ -34,6 +45,9 @@ class PaneAxisModel extends Model else PaneRow ?= require './pane-row' + getPanes: -> + flatten(@children.map (child) -> child.getPanes()) + addChild: (child, index=@children.length) -> @children.splice(index, 0, child) diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 1aa975cd7..d4029886e 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -1,5 +1,7 @@ {Model} = require 'theorist' Serializable = require 'serializable' +{find} = require 'underscore-plus' +FocusContext = require './focus-context' module.exports = class PaneContainerModel extends Model @@ -8,10 +10,13 @@ class PaneContainerModel extends Model @properties root: null + focusContext: -> new FocusContext constructor: -> super - @subscribe @$root, (root) => root?.parent = this + @subscribe @$root.filterDefined(), (root) => + root.parent = this + root.focusContext = @focusContext deserializeParams: (params) -> params.root = atom.deserializers.deserialize(params.root) @@ -23,3 +28,30 @@ class PaneContainerModel extends Model replaceChild: (oldChild, newChild) -> throw new Error("Replacing non-existent child") if oldChild isnt @root @root = newChild + + getPanes: -> + @root?.getPanes() ? [] + + getFocusedPane: -> + find @getPanes(), (pane) -> pane.focused + + focusNextPane: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@getFocusedPane()) + nextIndex = (currentIndex + 1) % panes.length + panes[nextIndex].focus() + true + else + false + + focusPreviousPane: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@getFocusedPane()) + previousIndex = currentIndex - 1 + previousIndex = panes.length - 1 if previousIndex < 0 + panes[previousIndex].focus() + true + else + false diff --git a/src/pane-model.coffee b/src/pane-model.coffee index be9b651cb..ed3a5e8de 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -3,17 +3,18 @@ {Model} = require 'theorist' Serializable = require 'serializable' PaneAxisModel = require './pane-axis-model' +Focusable = require './focusable' Pane = null module.exports = class PaneModel extends Model atom.deserializers.add(this) Serializable.includeInto(this) + Focusable.includeInto(this) @properties items: -> [] activeItem: null - focused: false constructor: -> super @@ -32,11 +33,7 @@ class PaneModel extends Model getViewClass: -> Pane ?= require './pane' - focus: -> - @focused = true - - blur: -> - @focused = false + getPanes: -> [this] # Public: Returns all contained views. getItems: -> @@ -132,6 +129,10 @@ class PaneModel extends Model destroyInactiveItems: -> @destroyItem(item) for item in @getItems() when item isnt @activeItem + # Private: Called by model superclass + destroyed: -> + @parent.focusNextPane() if @focused + # Public: Prompt the user to save the given item. promptToSaveItem: (item) -> return true unless item.shouldPromptToSave?() @@ -211,8 +212,10 @@ class PaneModel extends Model if @parent.orientation isnt orientation @parent.replaceChild(this, new PaneAxisModel({orientation, children: [this]})) - newPane = new @constructor(extend({focused: true}, params)) + newPane = new @constructor(params) switch side when 'before' then @parent.insertChildBefore(this, newPane) when 'after' then @parent.insertChildAfter(this, newPane) + + newPane.focus() newPane From 466868e63919d6fabb8d75caca930fd348c1908f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 10:47:07 -0700 Subject: [PATCH 28/89] Fix access to undefined root property The ::filterDefined transform unfortunately doesn't prevent an undefined initial value when applied to behaviors. --- src/pane-container-model.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index d4029886e..10ab34292 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -14,9 +14,10 @@ class PaneContainerModel extends Model constructor: -> super - @subscribe @$root.filterDefined(), (root) => - root.parent = this - root.focusContext = @focusContext + @subscribe @$root, (root) => + if root? + root.parent = this + root.focusContext = @focusContext deserializeParams: (params) -> params.root = atom.deserializers.deserialize(params.root) From af3ca5709409b9bf2d0b5db1ff6adaee7010e7b9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 15:43:05 -0700 Subject: [PATCH 29/89] Make focus state distinct until changed on Focusable objects --- src/focusable.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/focusable.coffee b/src/focusable.coffee index 6441a74eb..69c966896 100644 --- a/src/focusable.coffee +++ b/src/focusable.coffee @@ -8,6 +8,7 @@ class Focusable extends Mixin @$focusContext .flatMapLatest((context) -> context?.$focusedObject) .map((focusedObject) => focusedObject is this) + .distinctUntilChanged() focus: -> throw new Error("Object must be assigned a focusContext to be focus") unless @focusContext From 5309d5f24d17c0cc808eecdb50de470890272fe0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 15:46:32 -0700 Subject: [PATCH 30/89] Add ability to suppress blur on all focusable objects in a focus context When a view receives a 'focusout' event, we relay that to the model by calling ::blur. This is great for when users initiate the change in focus, but 'focusout' events can *also* be caused by elements being temporarily detached from the DOM. The ::suppressBlur method gives the ability to ignore blur calls during a certain operation. This is helpful, for example, when we want to detach a model and reattach it somewhere else without changing its focus state. --- src/focus-context.coffee | 10 ++++++++++ src/focusable.coffee | 13 +++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/focus-context.coffee b/src/focus-context.coffee index e52b0487f..c17514471 100644 --- a/src/focus-context.coffee +++ b/src/focus-context.coffee @@ -3,3 +3,13 @@ module.exports = class FocusContext extends Model @property 'focusedObject', null + + blurSuppressionCounter: 0 + + isBlurSuppressed: -> + @blurSuppressionCounter > 0 + + suppressBlur: (fn) -> + @blurSuppressionCounter++ + fn() + @blurSuppressionCounter-- diff --git a/src/focusable.coffee b/src/focusable.coffee index 69c966896..c046e7cd2 100644 --- a/src/focusable.coffee +++ b/src/focusable.coffee @@ -12,8 +12,17 @@ class Focusable extends Mixin focus: -> throw new Error("Object must be assigned a focusContext to be focus") unless @focusContext - @focusContext.focusedObject = this + unless @focused + @suppressBlur => + @focusContext.focusedObject = this blur: -> throw new Error("Object must be assigned a focusContext to be blurred") unless @focusContext - @focusContext.focusedObject = null if @focused + if @focused and not @focusContext.isBlurSuppressed() + @focusContext.focusedObject = null + + suppressBlur: (fn) -> + if @focusContext? + @focusContext.suppressBlur(fn) + else + fn() From a67f0d4d5704b31454db8eaf547bcb7cf43e251e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 15:53:52 -0700 Subject: [PATCH 31/89] Make PaneModel::items an observable sequence --- src/pane-model.coffee | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index ed3a5e8de..6d310c47c 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -1,6 +1,6 @@ {find, compact, clone, extend} = require 'underscore-plus' {dirname} = require 'path' -{Model} = require 'theorist' +{Model, Sequence} = require 'theorist' Serializable = require 'serializable' PaneAxisModel = require './pane-axis-model' Focusable = require './focusable' @@ -16,10 +16,22 @@ class PaneModel extends Model items: -> [] activeItem: null - constructor: -> + constructor: (params) -> super + + @items = Sequence.fromArray(params?.items ? []) @activeItem ?= @items[0] + @subscribe @items.onEach (item) => + if typeof item.on is 'function' + @subscribe item, 'destroyed', => @removeItem(item) + + @subscribe @items.onRemoval (item, index) => + @unsubscribe item + @emit 'item-removed', item, index + + @when @items.$length.becomesLessThan(1), 'destroy' + serializeParams: -> items: compact(@items.map((item) -> item.serialize?())) activeItemUri: @activeItem?.getUri?() @@ -37,7 +49,7 @@ class PaneModel extends Model # Public: Returns all contained views. getItems: -> - clone(@items) + @items.slice() # Public: Returns the item at the specified index. itemAtIndex: (index) -> From 2965d2e9742ec1f7fc695087404de6235d13e0d1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 16:01:52 -0700 Subject: [PATCH 32/89] Bind Pane view's focus to focus state on the model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's a bit tricky… we don't want to blur the model when focusing the pane's active view causes a focusout event on the pane, so we use ::suppressBlur on the model to prevent it from blurring as the focus is transferred. --- src/pane.coffee | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 540cbf9f9..0121c8d36 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -36,13 +36,11 @@ class Pane extends View toProperty: 'model' previousActiveItem: null - focusOnAttach: false # Private: initialize: (args...) -> if args[0] instanceof PaneModel @model = args[0] - @focusOnAttach = @model.focused else @model = new PaneModel(items: args) @model._view = this @@ -51,6 +49,9 @@ class Pane extends View @viewsByItem = new WeakMap() @handleEvents() + hasFocus: -> + @is(':focus') or @is(':has(:focus)') + handleEvents: -> @subscribe @model.$activeItem, 'value', @onActiveItemChanged @subscribe @model, 'item-added', @onItemAdded @@ -59,9 +60,21 @@ class Pane extends View @subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed @subscribe @model, 'item-destroyed', @onItemDestroyed - @subscribe this, 'focus', => @activeView?.focus(); false - @subscribe this, 'focusin', => @makeActive(); @model.focus() - @subscribe this, 'focusout', => @model.blur() + @subscribe @model.$focused, 'value', (focused) => + if focused + @focus() unless @hasFocus() + else + @blur() if @hasFocus() + + @subscribe this, 'focus', => + @model.suppressBlur => @activeView?.focus() + false + + @subscribe this, 'focusin', => + @makeActive() + @model.focus() + + @subscribe this, 'focusout', (e) => @model.blur() @command 'pane:save-items', => @saveItems() @command 'pane:show-next-item', => @showNextItem() @@ -93,9 +106,7 @@ class Pane extends View # Private: afterAttach: (onDom) -> - if @focusOnAttach and onDom - @focusOnAttach = null - @focus() + @focus() if @model.focused and onDom return if @attached @attached = true @@ -141,7 +152,8 @@ class Pane extends View @itemViews.children().not(view).hide() @itemViews.append(view) unless view.parent().is(@itemViews) view.show() if @attached - view.focus() if isFocused + if isFocused + @model.suppressBlur -> view.focus() @activeView = view @trigger 'pane:active-item-changed', [item] @@ -223,12 +235,10 @@ class Pane extends View remove: (selector, keepData) -> return super if keepData - if @is(':has(:focus)') - @getContainer().focusNextPane() or atom.workspaceView?.focus() - else if @isActive() + @unsubscribe() + @model.destroy() unless @model.isDestroyed() + + if @isActive() @getContainer().makeNextPaneActive() - @parent().view().removeChild(this) unless keepData - item.destroy?() for item in @getItems() - super From 073ea84d69293ec964ccb2a3dcb9e6cd1639984b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 16:05:00 -0700 Subject: [PATCH 33/89] Handle consequences of item removal in the model --- spec/pane-model-spec.coffee | 20 ++++++++++++++++++++ src/pane-model.coffee | 11 +++++------ src/pane.coffee | 31 +++++++++++-------------------- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index 794507ba5..2b9cb40bb 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -1,6 +1,7 @@ PaneModel = require '../src/pane-model' PaneAxisModel = require '../src/pane-axis-model' PaneContainerModel = require '../src/pane-container-model' +FocusContext = require '../src/focus-context' describe "PaneModel", -> describe "split methods", -> @@ -83,6 +84,25 @@ describe "PaneModel", -> pane2 = pane1.splitRight() expect(pane2.focused).toBe true + describe "::removeItemAtIndex(index)", -> + describe "when the removal of the item causes blur to be called on the pane model", -> + it "remains focused if it was before the item was removed", -> + pane = new PaneModel(items: ["A", "B", "C"], focusContext: new FocusContext) + pane.on 'item-removed', -> pane.blur() + pane.focus() + pane.removeItemAtIndex(0) + expect(pane.focused).toBe true + + pane.blur() + pane.removeItemAtIndex(0) + expect(pane.focused).toBe false + + describe "when the last item is removed", -> + it "destroys the pane", -> + pane = new PaneModel(items: ["A", "B"]) + pane.removeItemAtIndex(0) + pane.removeItemAtIndex(0) + expect(pane.isDestroyed()).toBe true describe "::destroy()", -> [pane1, container] = [] diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 6d310c47c..1930e93db 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -94,16 +94,15 @@ class PaneModel extends Model item # Public: - removeItem: (item, detach) -> + removeItem: (item) -> index = @items.indexOf(item) - @removeItemAtIndex(index, detach) if index >= 0 + @removeItemAtIndex(index) if index >= 0 # Public: Just remove the item at the given index. - removeItemAtIndex: (index, detach) -> + removeItemAtIndex: (index) -> item = @items[index] @showNextItem() if item is @activeItem and @items.length > 1 - @items.splice(index, 1) - @emit 'item-removed', item, index, detach + @suppressBlur => @items.splice(index, 1) # Public: Moves the given item to a the new index. moveItem: (item, newIndex) -> @@ -115,7 +114,7 @@ class PaneModel extends Model # Public: Moves the given item to another pane. moveItemToPane: (item, pane, index) -> pane.addItem(item, index) - @removeItem(item, true) + @removeItem(item) # Public: Remove the currently active item. destroyActiveItem: -> diff --git a/src/pane.coffee b/src/pane.coffee index 0121c8d36..c145c5b6b 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -53,6 +53,8 @@ class Pane extends View @is(':focus') or @is(':has(:focus)') handleEvents: -> + @subscribe @model, 'destroyed', => @remove() + @subscribe @model.$activeItem, 'value', @onActiveItemChanged @subscribe @model, 'item-added', @onItemAdded @subscribe @model, 'item-removed', @onItemRemoved @@ -158,12 +160,10 @@ class Pane extends View @trigger 'pane:active-item-changed', [item] onItemAdded: (item, index) => - if typeof item.on is 'function' - @subscribe item, 'destroyed', => @destroyItem(item) @trigger 'pane:item-added', [item, index] - onItemRemoved: (item, index, detach) => - @cleanupItemView(item, detach) + onItemRemoved: (item, index) => + @cleanupItemView(item) @trigger 'pane:item-removed', [item, index] onItemMoved: (item, newIndex) => @@ -181,27 +181,15 @@ class Pane extends View @trigger 'pane:active-item-title-changed' # Private: - cleanupItemView: (item, detach) -> + cleanupItemView: (item) -> if item instanceof $ viewToRemove = item else if viewToRemove = @viewsByItem.get(item) @viewsByItem.delete(item) - if @items.length > 0 - if detach and item is viewToRemove - viewToRemove?.detach() - else if detach and viewToRemove?.setModel - viewToRemove.setModel(null) # dont want to destroy the model, so set to null - viewToRemove.remove() - else - viewToRemove?.remove() - else - if detach and item is viewToRemove - viewToRemove?.detach() - else if detach and viewToRemove?.setModel - viewToRemove.setModel(null) # dont want to destroy the model, so set to null - - @parent().view().removeChild(this) + if viewToRemove? + viewToRemove.setModel?(null) + viewToRemove.detach() # Private: viewForItem: (item) -> @@ -231,6 +219,9 @@ class Pane extends View getContainer: -> @closest('.panes').view() + beforeRemove: -> + @trigger 'pane:removed', [this] + # Private: remove: (selector, keepData) -> return super if keepData From 4dcba4bb27fbdab67f34d7feb95178e2b592b319 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 16:06:02 -0700 Subject: [PATCH 34/89] Destroy remaining items when a pane is destroyed --- spec/pane-model-spec.coffee | 8 +++++++- src/pane-model.coffee | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index 2b9cb40bb..c036232e4 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -107,9 +107,15 @@ describe "PaneModel", -> [pane1, container] = [] beforeEach -> - pane1 = new PaneModel(items: ["A"]) + pane1 = new PaneModel(items: [new Model, new Model]) container = new PaneContainerModel(root: pane1) + it "destroys the pane's destroyable items", -> + [item1, item2] = pane1.items + pane1.destroy() + expect(item1.isDestroyed()).toBe true + expect(item2.isDestroyed()).toBe true + describe "if the pane's parent has more than two children", -> it "removes the pane from its parent", -> pane2 = pane1.splitRight() diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 1930e93db..881fc2f48 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -142,6 +142,7 @@ class PaneModel extends Model # Private: Called by model superclass destroyed: -> + item.destroy?() for item in @items.slice() @parent.focusNextPane() if @focused # Public: Prompt the user to save the given item. From 5ca7ad3bceb2c80980d2a9235f61ebe095b77bf8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 16:07:57 -0700 Subject: [PATCH 35/89] Add spec for destroyed pane items getting removed at the model layer --- spec/pane-model-spec.coffee | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index c036232e4..7add9e949 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -1,3 +1,4 @@ +{Model} = require 'theorist' PaneModel = require '../src/pane-model' PaneAxisModel = require '../src/pane-axis-model' PaneContainerModel = require '../src/pane-container-model' @@ -103,6 +104,14 @@ describe "PaneModel", -> pane.removeItemAtIndex(0) pane.removeItemAtIndex(0) expect(pane.isDestroyed()).toBe true + + describe "when an item emits a destroyed event", -> + it "removes it from the list of items", -> + pane = new PaneModel(items: [new Model, new Model, new Model]) + [item1, item2, item3] = pane.items + pane.items[1].destroy() + expect(pane.items).toEqual [item1, item3] + describe "::destroy()", -> [pane1, container] = [] From 964abd3141dd4081427e1d01e3cb9804e11dd9f0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 16:08:52 -0700 Subject: [PATCH 36/89] Suppress blur when replacing a pane axis with its last child If the pane axis contains a child pane, its temporary removal from the DOM causes a blur event that we don't want to screw up our focused state. --- src/pane-axis-model.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index d23e93493..064ad95b8 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -70,4 +70,5 @@ class PaneAxisModel extends Model @children.splice(index + 1, 0, newChild) reparentLastChild: -> - @parent.replaceChild(this, @children[0]) + @focusContext.suppressBlur => + @parent.replaceChild(this, @children[0]) From 4026e6ca5c3dcbaa4f259e41659168227629243a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 16:09:22 -0700 Subject: [PATCH 37/89] Properly serialize pane focus state --- spec/pane-container-model-spec.coffee | 16 ++++++++++++++++ src/pane-axis-model.coffee | 5 +++-- src/pane-container-model.coffee | 3 ++- src/pane-model.coffee | 3 ++- 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 spec/pane-container-model-spec.coffee diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee new file mode 100644 index 000000000..9379a2c8d --- /dev/null +++ b/spec/pane-container-model-spec.coffee @@ -0,0 +1,16 @@ +PaneContainerModel = require '../src/pane-container-model' +PaneModel = require '../src/pane-model' + +describe "PaneContainerModel", -> + describe "serialization", -> + it "preserves the focused pane across serialization", -> + pane1A = new PaneModel + containerA = new PaneContainerModel(root: pane1A) + pane2A = pane1A.splitRight() + pane3A = pane2A.splitDown() + expect(pane3A.focused).toBe true + + containerB = containerA.testSerialization() + [pane1A, pane2A, pane3A] = containerB.getPanes() + expect(pane3A.focusContext).toBe containerB.focusContext + expect(pane3A.focused).toBe true diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index 064ad95b8..a4495f417 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -16,7 +16,7 @@ class PaneAxisModel extends Model @property 'focusContext' - constructor: ({@orientation, children}) -> + constructor: ({@focusContext, @orientation, children}) -> @children = Sequence.fromArray(children ? []) @subscribe @$focusContext, (focusContext) => @@ -33,7 +33,8 @@ class PaneAxisModel extends Model @when @children.$length.becomesLessThan(1), 'destroy' deserializeParams: (params) -> - params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState) + {focusContext} = params + params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {focusContext}) params serializeParams: -> diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 10ab34292..7d704a41c 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -20,7 +20,8 @@ class PaneContainerModel extends Model root.focusContext = @focusContext deserializeParams: (params) -> - params.root = atom.deserializers.deserialize(params.root) + params.focusContext ?= new FocusContext + params.root = atom.deserializers.deserialize(params.root, focusContext: params.focusContext) params serializeParams: (params) -> diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 881fc2f48..60a768b56 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -13,12 +13,13 @@ class PaneModel extends Model Focusable.includeInto(this) @properties - items: -> [] activeItem: null constructor: (params) -> super + @focus() if params?.focused + @items = Sequence.fromArray(params?.items ? []) @activeItem ?= @items[0] From 5e1b2e269608c3aa57cc400729574e40be95cc46 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 17:40:04 -0700 Subject: [PATCH 38/89] Manage the active pane at the model level --- package.json | 2 +- spec/pane-container-model-spec.coffee | 36 ++++++++++++++++++++++++--- spec/pane-model-spec.coffee | 3 ++- src/pane-axis-model.coffee | 15 ++++------- src/pane-container-model.coffee | 23 ++++++++++++++--- src/pane-model.coffee | 29 +++++++++++++++++---- 6 files changed, 84 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index e23497b3e..90da3ba15 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "temp": "0.5.0", "text-buffer": "0.12.0", "underscore-plus": "0.6.1", - "theorist": "~0.9.0", + "theorist": "~0.10.0", "delegato": "~0.4.0", "mixto": "~0.4.0" }, diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index 9379a2c8d..5052b2830 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -11,6 +11,36 @@ describe "PaneContainerModel", -> expect(pane3A.focused).toBe true containerB = containerA.testSerialization() - [pane1A, pane2A, pane3A] = containerB.getPanes() - expect(pane3A.focusContext).toBe containerB.focusContext - expect(pane3A.focused).toBe true + [pane1B, pane2B, pane3B] = containerB.getPanes() + expect(pane3B.focusContext).toBe containerB.focusContext + expect(pane3B.focused).toBe true + + describe "::activePane", -> + [container, pane1, pane2] = [] + + beforeEach -> + pane1 = new PaneModel + container = new PaneContainerModel(root: pane1) + + it "references the first pane if no pane has been focused", -> + expect(container.activePane).toBe pane1 + expect(pane1.active).toBe true + + it "references the most recently focused pane", -> + pane2 = pane1.splitRight() + expect(container.activePane).toBe pane2 + expect(pane1.active).toBe false + expect(pane2.active).toBe true + pane1.focus() + expect(container.activePane).toBe pane1 + expect(pane1.active).toBe true + expect(pane2.active).toBe false + + it "is reassigned to the next pane if the current active pane is unfocused and destroyed", -> + pane2 = pane1.splitRight() + pane2.blur() + pane2.destroy() + expect(container.activePane).toBe pane1 + expect(pane1.active).toBe true + pane1.destroy() + expect(container.activePane).toBe null diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index 7add9e949..f17b141c9 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -88,7 +88,8 @@ describe "PaneModel", -> describe "::removeItemAtIndex(index)", -> describe "when the removal of the item causes blur to be called on the pane model", -> it "remains focused if it was before the item was removed", -> - pane = new PaneModel(items: ["A", "B", "C"], focusContext: new FocusContext) + pane = new PaneModel(items: ["A", "B", "C"]) + container = new PaneContainerModel(root: pane) pane.on 'item-removed', -> pane.blur() pane.focus() pane.removeItemAtIndex(0) diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index a4495f417..d35e88032 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -12,19 +12,14 @@ class PaneAxisModel extends Model Serializable.includeInto(this) Delegator.includeInto(this) - @delegatesMethod 'focusNextPane', toProperty: 'parent' + @delegatesProperty 'focusContext', toProperty: 'container' - @property 'focusContext' - - constructor: ({@focusContext, @orientation, children}) -> + constructor: ({@container, @orientation, children}) -> @children = Sequence.fromArray(children ? []) - @subscribe @$focusContext, (focusContext) => - child.focusContext = focusContext for child in @children - @subscribe @children.onEach (child) => child.parent = this - child.focusContext = @focusContext + child.container = @container @subscribe child, 'destroyed', => @removeChild(child) @subscribe @children.onRemoval (child) => @unsubscribe(child) @@ -33,8 +28,8 @@ class PaneAxisModel extends Model @when @children.$length.becomesLessThan(1), 'destroy' deserializeParams: (params) -> - {focusContext} = params - params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {focusContext}) + {container} = params + params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {container}) params serializeParams: -> diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 7d704a41c..ebbf250db 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -2,6 +2,7 @@ Serializable = require 'serializable' {find} = require 'underscore-plus' FocusContext = require './focus-context' +PaneModel = require './pane-model' module.exports = class PaneContainerModel extends Model @@ -10,18 +11,23 @@ class PaneContainerModel extends Model @properties root: null - focusContext: -> new FocusContext + focusContext: null + activePane: null constructor: -> super + + @focusContext ?= new FocusContext + @subscribe @$root, (root) => if root? root.parent = this - root.focusContext = @focusContext + root.container = this + @activePane ?= root if root instanceof PaneModel deserializeParams: (params) -> - params.focusContext ?= new FocusContext - params.root = atom.deserializers.deserialize(params.root, focusContext: params.focusContext) + @focusContext ?= params.focusContext ? new FocusContext + params.root = atom.deserializers.deserialize(params.root, container: this) params serializeParams: (params) -> @@ -57,3 +63,12 @@ class PaneContainerModel extends Model true else false + + makeNextPaneActive: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@activePane) + nextIndex = (currentIndex + 1) % panes.length + @activePane = panes[nextIndex] + else + @activePane = null diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 60a768b56..8fc2e0fa3 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -13,26 +13,38 @@ class PaneModel extends Model Focusable.includeInto(this) @properties + container: null activeItem: null + @behavior 'active', -> + @$container + .flatMapLatest((container) -> container?.$activePane) + .map((activePane) => activePane is this) + .distinctUntilChanged() + constructor: (params) -> super - @focus() if params?.focused - @items = Sequence.fromArray(params?.items ? []) @activeItem ?= @items[0] + @subscribe @$container, (container) => + @focusContext = container?.focusContext + @subscribe @items.onEach (item) => if typeof item.on is 'function' @subscribe item, 'destroyed', => @removeItem(item) @subscribe @items.onRemoval (item, index) => - @unsubscribe item + @unsubscribe item if typeof item.on is 'function' @emit 'item-removed', item, index @when @items.$length.becomesLessThan(1), 'destroy' + @when @$focused, => @makeActive() + + @focus() if params?.focused + serializeParams: -> items: compact(@items.map((item) -> item.serialize?())) activeItemUri: @activeItem?.getUri?() @@ -46,6 +58,10 @@ class PaneModel extends Model getViewClass: -> Pane ?= require './pane' + isActive: -> @active + + makeActive: -> @container.activePane = this + getPanes: -> [this] # Public: Returns all contained views. @@ -144,7 +160,10 @@ class PaneModel extends Model # Private: Called by model superclass destroyed: -> item.destroy?() for item in @items.slice() - @parent.focusNextPane() if @focused + if @focused + @container.focusNextPane() + else if @isActive() + @container.makeNextPaneActive() # Public: Prompt the user to save the given item. promptToSaveItem: (item) -> @@ -223,7 +242,7 @@ class PaneModel extends Model split: (orientation, side, params) -> if @parent.orientation isnt orientation - @parent.replaceChild(this, new PaneAxisModel({orientation, children: [this]})) + @parent.replaceChild(this, new PaneAxisModel({@container, orientation, children: [this]})) newPane = new @constructor(params) switch side From 86471379525fa28564bd54cfdaca40c659acb0e3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 18:00:54 -0700 Subject: [PATCH 39/89] Remove active status management from Pane view; rely on model instead --- src/pane.coffee | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index c145c5b6b..3d968e742 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -33,7 +33,7 @@ class Pane extends View 'moveItem', 'moveItemToPane', 'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems', 'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems', 'itemForUri', 'showItemForUri', 'promptToSaveItem', 'copyActiveItem', - toProperty: 'model' + 'isActive', 'makeActive', toProperty: 'model' previousActiveItem: null @@ -61,6 +61,7 @@ class Pane extends View @subscribe @model, 'item-moved', @onItemMoved @subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed @subscribe @model, 'item-destroyed', @onItemDestroyed + @subscribe @model.$active, 'value', @onActiveStatusChanged @subscribe @model.$focused, 'value', (focused) => if focused @@ -68,16 +69,12 @@ class Pane extends View else @blur() if @hasFocus() + @subscribe this, 'focusin', => @model.focus() + @subscribe this, 'focusout', => @model.blur() @subscribe this, 'focus', => @model.suppressBlur => @activeView?.focus() false - @subscribe this, 'focusin', => - @makeActive() - @model.focus() - - @subscribe this, 'focusout', (e) => @model.blur() - @command 'pane:save-items', => @saveItems() @command 'pane:show-next-item', => @showNextItem() @command 'pane:show-previous-item', => @showPreviousItem() @@ -114,23 +111,13 @@ class Pane extends View @attached = true @trigger 'pane:attached', [this] - # Public: Focus this pane. - makeActive: -> - wasActive = @isActive() - for pane in @getContainer().getPanes() when pane isnt this - pane.makeInactive() - @addClass('active') - @trigger 'pane:became-active' unless wasActive - - # Public: Unfocus this pane. - makeInactive: -> - wasActive = @isActive() - @removeClass('active') - @trigger 'pane:became-inactive' if wasActive - - # Public: Returns whether this pane is currently focused. - isActive: -> - @getContainer()?.getActivePane() == this + onActiveStatusChanged: (active) => + if active + @addClass('active') + @trigger 'pane:became-active' + else + @removeClass('active') + @trigger 'pane:became-inactive' # Public: Returns the next pane, ordered by creation. getNextPane: -> @@ -225,11 +212,6 @@ class Pane extends View # Private: remove: (selector, keepData) -> return super if keepData - @unsubscribe() @model.destroy() unless @model.isDestroyed() - - if @isActive() - @getContainer().makeNextPaneActive() - super From cd699d8b9b0a721d7e0609872b9acf00a8dda08e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 18:17:02 -0700 Subject: [PATCH 40/89] Transfer focus to the root view when the last focused pane is destroyed --- spec/pane-container-model-spec.coffee | 20 ++++++++++++++++++++ src/pane-container-model.coffee | 1 + src/pane-container.coffee | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index 5052b2830..7897387b4 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -44,3 +44,23 @@ describe "PaneContainerModel", -> expect(pane1.active).toBe true pane1.destroy() expect(container.activePane).toBe null + + # TODO: Remove this behavior when we have a proper workspace model we can explicitly focus + describe "when the last pane is removed", -> + [container, pane, surrenderedFocusHandler] = [] + + beforeEach -> + pane = new PaneModel + container = new PaneContainerModel(root: pane) + container.on 'surrendered-focus', surrenderedFocusHandler = jasmine.createSpy("surrenderedFocusHandler") + + describe "if the pane is focused", -> + it "emits a 'surrendered-focus' event", -> + pane.focus() + pane.destroy() + expect(surrenderedFocusHandler).toHaveBeenCalled() + + describe "if the pane is not focused", -> + it "does not emit an event", -> + expect(pane.focused).toBe false + expect(surrenderedFocusHandler).not.toHaveBeenCalled() diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index ebbf250db..70834cc7e 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -51,6 +51,7 @@ class PaneContainerModel extends Model panes[nextIndex].focus() true else + @emit 'surrendered-focus' false focusPreviousPane: -> diff --git a/src/pane-container.coffee b/src/pane-container.coffee index bc5f0e2f4..8f2d71750 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -21,6 +21,7 @@ class PaneContainer extends View @model = new PaneContainerModel({root: params?.root?.model}) @subscribe @model.$root, 'value', @onRootChanged + @subscribe @model, 'surrendered-focus', @onSurrenderedFocus @subscribe this, 'pane:attached', (event, pane) => @triggerActiveItemChange() if @getActivePane() is pane @@ -89,6 +90,9 @@ class PaneContainer extends View @append(view) view.makeActive?() + onSurrenderedFocus: => + atom?.workspaceView?.focus() + removeChild: (child) -> throw new Error("Removing non-existant child") unless @getRoot() is child @setRoot(null) From 60daa483e6855e08a6af7a324ab671146894dc52 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 18:34:09 -0700 Subject: [PATCH 41/89] Include orientation when serializing PaneAxisModel --- src/pane-axis-model.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index d35e88032..ce6a2b54b 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -34,6 +34,7 @@ class PaneAxisModel extends Model serializeParams: -> children: @children.map (child) -> child.serialize() + orientation: @orientation getViewClass: -> if @orientation is 'vertical' From e87b8dc46392de2665c6e74cba82e89d9960c3cd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 18:34:50 -0700 Subject: [PATCH 42/89] Remove focusNext/PreviousPane methods from PaneContainer view --- spec/pane-container-spec.coffee | 2 ++ src/pane-container.coffee | 31 ++++--------------------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index cd2f7264e..0b60ccf64 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -42,6 +42,8 @@ describe "PaneContainer", -> describe ".focusPreviousPane()", -> it "focuses the pane preceding the focused pane or the last pane if no pane has focus", -> container.attachToDom() + pane3.focusout() + container.focusPreviousPane() expect(pane3.activeItem).toMatchSelector ':focus' container.focusPreviousPane() diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 8f2d71750..1c39da151 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -1,4 +1,5 @@ Serializable = require 'serializable' +Delegator = require 'delegato' {$, View} = require './space-pen-extensions' Pane = require './pane' PaneContainerModel = require './pane-container-model' @@ -7,6 +8,7 @@ PaneContainerModel = require './pane-container-model' module.exports = class PaneContainer extends View Serializable.includeInto(this) + Delegator.includeInto(this) @deserialize: (state) -> new this(PaneContainerModel.deserialize(state.model)) @@ -14,6 +16,8 @@ class PaneContainer extends View @content: -> @div class: 'panes' + @delegatesMethods 'focusNextPane', 'focusPreviousPane', toProperty: 'model' + initialize: (params) -> if params instanceof PaneContainerModel @model = params @@ -47,33 +51,6 @@ class PaneContainer extends View ### Public ### - focusNextPane: -> - panes = @getPanes() - if panes.length > 1 - currentIndex = panes.indexOf(@getFocusedPane()) - nextIndex = (currentIndex + 1) % panes.length - panes[nextIndex].focus() - true - else - false - - focusPreviousPane: -> - panes = @getPanes() - if panes.length > 1 - currentIndex = panes.indexOf(@getFocusedPane()) - previousIndex = currentIndex - 1 - previousIndex = panes.length - 1 if previousIndex < 0 - panes[previousIndex].focus() - true - else - false - - makeNextPaneActive: -> - panes = @getPanes() - currentIndex = panes.indexOf(@getActivePane()) - nextIndex = (currentIndex + 1) % panes.length - panes[nextIndex].makeActive() - itemDestroyed: (item) -> @trigger 'item-destroyed', item From 5a3353ec2873096864f21d025dd32baef1477b9e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 18:35:16 -0700 Subject: [PATCH 43/89] Add PaneContainer view to deserializers --- src/pane-container.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 1c39da151..102281b8c 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -7,6 +7,7 @@ PaneContainerModel = require './pane-container-model' # Private: Manages the list of panes within a {WorkspaceView} module.exports = class PaneContainer extends View + atom.deserializers.add(this) Serializable.includeInto(this) Delegator.includeInto(this) From 9aefafb8318928e28563f2ba652f631628a89310 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 9 Jan 2014 18:58:01 -0700 Subject: [PATCH 44/89] Use PaneContainerModel::$activePaneItem for 'active-item-changed' events Yay behaviors --- package.json | 2 +- src/pane-container-model.coffee | 3 +++ src/pane-container.coffee | 19 ++++--------------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 90da3ba15..f440ad28a 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "temp": "0.5.0", "text-buffer": "0.12.0", "underscore-plus": "0.6.1", - "theorist": "~0.10.0", + "theorist": "~0.11.0", "delegato": "~0.4.0", "mixto": "~0.4.0" }, diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 70834cc7e..fc6154057 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -14,6 +14,9 @@ class PaneContainerModel extends Model focusContext: null activePane: null + @behavior 'activePaneItem', -> + @$activePane.flatMapLatest (activePane) -> activePane?.$activeItem + constructor: -> super diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 102281b8c..0ce46ed38 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -26,27 +26,13 @@ class PaneContainer extends View @model = new PaneContainerModel({root: params?.root?.model}) @subscribe @model.$root, 'value', @onRootChanged + @subscribe @model.$activePaneItem.changes, 'value', @onActivePaneItemChanged @subscribe @model, 'surrendered-focus', @onSurrenderedFocus - @subscribe this, 'pane:attached', (event, pane) => - @triggerActiveItemChange() if @getActivePane() is pane - - @subscribe this, 'pane:removed', (event, pane) => - @triggerActiveItemChange() unless @getActivePane()? - - @subscribe this, 'pane:became-active', => - @triggerActiveItemChange() - - @subscribe this, 'pane:active-item-changed', (event, item) => - @triggerActiveItemChange() if @getActivePaneItem() is item - viewForModel: (model) -> viewClass = model.getViewClass() model._view ?= new viewClass(model) - triggerActiveItemChange: -> - @trigger 'pane-container:active-pane-item-changed', [@getActivePaneItem()] - serializeParams: -> model: @model.serialize() @@ -68,6 +54,9 @@ class PaneContainer extends View @append(view) view.makeActive?() + onActivePaneItemChanged: (activeItem) => + @trigger 'pane-container:active-pane-item-changed', [activeItem] + onSurrenderedFocus: => atom?.workspaceView?.focus() From a0b733b53d0d87dc7ad0d6c778ff4985784bb553 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:20:12 -0700 Subject: [PATCH 45/89] Skip the flexbox-repaint-hack when seeking editor pane with ::parents --- src/editor-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index eb8d2cfbc..3c1d76490 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -1070,7 +1070,7 @@ class EditorView extends View # # Returns a {Pane}. getPane: -> - @parent('.item-views').parent('.pane').view() + @parents('.pane').view() remove: (selector, keepData) -> return super if keepData or @removed From a9d7564f3e3b540864598629d5544f660f8af53b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:21:10 -0700 Subject: [PATCH 46/89] Account for the .flexbox-repaint-hack in WorkspaceView::getEditorViews --- src/workspace-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index e13dab341..55e37a0b7 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -240,7 +240,7 @@ class WorkspaceView extends View # Private: Returns an Array of all of the application's {EditorView}s. getEditorViews: -> - @panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray() + @panes.find('.pane > .flexbox-repaint-hack > .item-views > .editor').map(-> $(this).view()).toArray() # Private: Retrieves all of the modified buffers that are open and unsaved. # From c69febd44d757141c5e2139838ac8dc28f68e3a0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:21:46 -0700 Subject: [PATCH 47/89] Preserve the active pane across serialization --- spec/pane-container-model-spec.coffee | 14 +++++++++++++- src/pane-model.coffee | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index 7897387b4..bf249e3f8 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -3,11 +3,15 @@ PaneModel = require '../src/pane-model' describe "PaneContainerModel", -> describe "serialization", -> - it "preserves the focused pane across serialization", -> + [containerA, pane1A, pane2A, pane3A] = [] + + beforeEach -> pane1A = new PaneModel containerA = new PaneContainerModel(root: pane1A) pane2A = pane1A.splitRight() pane3A = pane2A.splitDown() + + it "preserves the focused pane across serialization", -> expect(pane3A.focused).toBe true containerB = containerA.testSerialization() @@ -15,6 +19,14 @@ describe "PaneContainerModel", -> expect(pane3B.focusContext).toBe containerB.focusContext expect(pane3B.focused).toBe true + it "preserves the active pane across serialization, independent of focus", -> + pane3A.blur() + expect(containerA.activePane).toBe pane3A + + containerB = containerA.testSerialization() + [pane1B, pane2B, pane3B] = containerB.getPanes() + expect(containerB.activePane).toBe pane3B + describe "::activePane", -> [container, pane1, pane2] = [] diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 8fc2e0fa3..f834ec3d5 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -44,11 +44,13 @@ class PaneModel extends Model @when @$focused, => @makeActive() @focus() if params?.focused + @makeActive() if params?.active serializeParams: -> items: compact(@items.map((item) -> item.serialize?())) activeItemUri: @activeItem?.getUri?() focused: @focused + active: @active deserializeParams: (params) -> {items, activeItemUri} = params From 5e1e092650b5cc8875b02bd0a51f721aa0fe4234 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:22:19 -0700 Subject: [PATCH 48/89] When the last pane is destroyed, null out the root and active pane --- spec/pane-container-model-spec.coffee | 5 +++++ src/pane-container-model.coffee | 22 +++++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index bf249e3f8..22c0f8668 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -66,6 +66,11 @@ describe "PaneContainerModel", -> container = new PaneContainerModel(root: pane) container.on 'surrendered-focus', surrenderedFocusHandler = jasmine.createSpy("surrenderedFocusHandler") + it "assigns null to the root and the activePane", -> + pane.destroy() + expect(container.root).toBe null + expect(container.activePane).toBe null + describe "if the pane is focused", -> it "emits a 'surrendered-focus' event", -> pane.focus() diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index fc6154057..2e7ebb3bd 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -14,6 +14,8 @@ class PaneContainerModel extends Model focusContext: null activePane: null + previousRoot: null + @behavior 'activePaneItem', -> @$activePane.flatMapLatest (activePane) -> activePane?.$activeItem @@ -22,11 +24,7 @@ class PaneContainerModel extends Model @focusContext ?= new FocusContext - @subscribe @$root, (root) => - if root? - root.parent = this - root.container = this - @activePane ?= root if root instanceof PaneModel + @subscribe @$root, @onRootChanged deserializeParams: (params) -> @focusContext ?= params.focusContext ? new FocusContext @@ -76,3 +74,17 @@ class PaneContainerModel extends Model @activePane = panes[nextIndex] else @activePane = null + + onRootChanged: (root) => + @unsubscribe(@previousRoot) if @previousRoot? + @previousRoot = root + return unless root? + + root.parent = this + root.container = this + + if root instanceof PaneModel + @activePane ?= root + @subscribe root, 'destroyed', => + @activePane = null + @root = null From 339e30d973c781133d8fba1001ad0a0313a6e386 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:23:58 -0700 Subject: [PATCH 49/89] Account for pane axis class name changes in pane-container-spec --- spec/workspace-view-spec.coffee | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/workspace-view-spec.coffee b/spec/workspace-view-spec.coffee index 216466aee..8db9b479a 100644 --- a/spec/workspace-view-spec.coffee +++ b/spec/workspace-view-spec.coffee @@ -58,10 +58,10 @@ describe "WorkspaceView", -> simulateReload() expect(atom.workspaceView.getEditorViews().length).toBe 4 - editor1 = atom.workspaceView.panes.find('.row > .pane .editor:eq(0)').view() - editor3 = atom.workspaceView.panes.find('.row > .pane .editor:eq(1)').view() - editor2 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(0)').view() - editor4 = atom.workspaceView.panes.find('.row > .column > .pane .editor:eq(1)').view() + editor1 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(0)').view() + editor3 = atom.workspaceView.panes.find('.pane-row > .pane .editor:eq(1)').view() + editor2 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(0)').view() + editor4 = atom.workspaceView.panes.find('.pane-row > .pane-column > .pane .editor:eq(1)').view() expect(editor1.getPath()).toBe atom.project.resolve('a') expect(editor2.getPath()).toBe atom.project.resolve('b') @@ -298,14 +298,14 @@ describe "WorkspaceView", -> expect(pane2[0]).not.toBe pane1[0] expect(editor.getPath()).toBe require.resolve('./fixtures/dir/b') - expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] editor = atom.workspaceView.openSync('file1', split: 'right') pane3 = atom.workspaceView.getActivePane() expect(pane3[0]).toBe pane2[0] expect(editor.getPath()).toBe require.resolve('./fixtures/dir/file1') - expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] describe ".openSingletonSync(filePath, options)", -> describe "when there is an active pane", -> @@ -320,7 +320,7 @@ describe "WorkspaceView", -> expect(pane2[0]).not.toBe pane1[0] expect(pane1.itemForUri('b')).toBeFalsy() expect(pane2.itemForUri('b')).not.toBeFalsy() - expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] pane1.focus() expect(atom.workspaceView.getActivePane()[0]).toBe pane1[0] @@ -330,7 +330,7 @@ describe "WorkspaceView", -> expect(pane3[0]).toBe pane2[0] expect(pane1.itemForUri('b')).toBeFalsy() expect(pane2.itemForUri('b')).not.toBeFalsy() - expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] it "handles split: left by opening to the left pane when necessary", -> atom.workspaceView.openSingletonSync('b', split: 'right') @@ -344,7 +344,7 @@ describe "WorkspaceView", -> expect(pane1.itemForUri('file1')).toBeTruthy() expect(pane2.itemForUri('file1')).toBeFalsy() - expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] pane2.focus() expect(atom.workspaceView.getActivePane()[0]).toBe pane2[0] @@ -352,7 +352,7 @@ describe "WorkspaceView", -> atom.workspaceView.openSingletonSync('file1', split: 'left') activePane = atom.workspaceView.getActivePane() expect(activePane[0]).toBe pane1[0] - expect(atom.workspaceView.panes.find('.row .pane').toArray()).toEqual [pane1[0], pane2[0]] + expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] it "reuses the file when already open", -> atom.workspaceView.openSync('b') From edfc86f1533216b279deaa4b9ec27a1348a106d9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:34:26 -0700 Subject: [PATCH 50/89] Make PaneContainer::getActivePaneItem retrieve it from the model --- src/pane-container.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 7602057b6..c2d67f244 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -99,7 +99,7 @@ class PaneContainer extends View @find('.pane.active').view() ? @find('.pane:first').view() getActivePaneItem: -> - @getActivePane()?.activeItem + @model.activePaneItem getActiveView: -> @getActivePane()?.activeView From 732d36af2872a64d47ca1e148e226fb77780cb91 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:37:42 -0700 Subject: [PATCH 51/89] Null guard container in Pane::makeActive Panes can exist outside of containers, albeit only briefly. If ::makeActive is called when the pane is in this state, consider it a no-op. --- src/pane-model.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index f834ec3d5..4b0e518b2 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -62,7 +62,7 @@ class PaneModel extends Model isActive: -> @active - makeActive: -> @container.activePane = this + makeActive: -> @container?.activePane = this getPanes: -> [this] From d2146f9b2ec4b295006c963592b2bb40c4bae4cc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:45:08 -0700 Subject: [PATCH 52/89] Emit 'pane:removed' event in container when last pane is removed Doing it in the pane view is awkward because the view may have already been detached. --- src/pane-container.coffee | 4 +++- src/pane.coffee | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pane-container.coffee b/src/pane-container.coffee index c2d67f244..eb9297e04 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -48,7 +48,9 @@ class PaneContainer extends View @model.root = root?.model onRootChanged: (root) => - @children().detach() + oldRoot = @getRoot() + @trigger 'pane:removed', [oldRoot] if @getRoot() instanceof Pane + oldRoot?.detach() if root? view = @viewForModel(root) @append(view) diff --git a/src/pane.coffee b/src/pane.coffee index 3d968e742..cc8173554 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -206,9 +206,6 @@ class Pane extends View getContainer: -> @closest('.panes').view() - beforeRemove: -> - @trigger 'pane:removed', [this] - # Private: remove: (selector, keepData) -> return super if keepData From d34327a6670ca3e4288573fce6baa1f3dfe9c525 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 11:56:19 -0700 Subject: [PATCH 53/89] Remove the EditorView when Editor is destroyed --- spec/editor-view-spec.coffee | 2 +- src/editor-view.coffee | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/editor-view-spec.coffee b/spec/editor-view-spec.coffee index 96d10e98c..2f428dddd 100644 --- a/spec/editor-view-spec.coffee +++ b/spec/editor-view-spec.coffee @@ -2809,7 +2809,7 @@ describe "EditorView", -> atom.workspaceView.attachToDom() editorView = atom.workspaceView.getActiveView() - willBeRemovedHandler = jasmine.createSpy('fileChange') + willBeRemovedHandler = jasmine.createSpy('willBeRemovedHandler') editorView.on 'editor:will-be-removed', willBeRemovedHandler editorView.getPane().destroyActiveItem() expect(willBeRemovedHandler).toHaveBeenCalled() diff --git a/src/editor-view.coffee b/src/editor-view.coffee index 3c1d76490..770c7895f 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -827,6 +827,9 @@ class EditorView extends View @editor.setVisible(true) + @editor.on "destroyed", => + @remove() + @editor.on "contents-conflicted.editor", => @showBufferConflictAlert(@editor) From 720b2ad47da964f80017d6ff12a8354cb4e6cc8b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 12:11:19 -0700 Subject: [PATCH 54/89] Upgrade to serializable 0.3.0 to handle undefined states --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d3b8ef04e..9d67bd7d2 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "mkdirp": "0.3.5", "keytar": "0.13.0", "less-cache": "0.10.0", - "serializable": "0.1.0", + "serializable": "0.3.0", "nslog": "0.1.0", "oniguruma": "0.24.0", "optimist": "0.4.0", From 4b0d22917b60a4ef0fdb3939ca6acb3ee488092c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 12:12:44 -0700 Subject: [PATCH 55/89] Trust CSS to perform layout now that we've switched to flexbox The behavior of flexbox is actually slightly different in that it divides space evenly among the immediate children of a row or column rather than accounting for all splits. But it's actually not that big a deal. --- spec/pane-spec.coffee | 69 ------------------------------------------- 1 file changed, 69 deletions(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 00364ac77..d032ce92c 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -601,75 +601,6 @@ describe "Pane", -> expect(pane3.getItems()).toEqual [view3, view4] expect(container.find('.pane-column .pane').toArray()).toEqual [pane3[0], pane2[0], pane[0]] - it "lays out nested panes by equally dividing their containing row / column", -> - container.width(520).height(240).attachToDom() - pane1.showItem($("1")) - pane1 - .splitLeft($("2")) - .splitUp($("3")) - .splitLeft($("4")) - .splitDown($("5")) - - row1 = container.children(':eq(0)') - expect(row1.children().length).toBe 2 - column1 = row1.children(':eq(0)').view() - pane1 = row1.children(':eq(1)').view() - expect(column1.outerWidth()).toBe Math.round(2/3 * container.width()) - expect(column1.outerHeight()).toBe container.height() - expect(pane1.outerWidth()).toBe Math.round(1/3 * container.width()) - expect(pane1.outerHeight()).toBe container.height() - expect(Math.round(pane1.position().left)).toBe column1.outerWidth() - - expect(column1.children().length).toBe 2 - row2 = column1.children(':eq(0)').view() - pane2 = column1.children(':eq(1)').view() - expect(row2.outerWidth()).toBe column1.outerWidth() - expect(row2.height()).toBe 2/3 * container.height() - expect(pane2.outerWidth()).toBe column1.outerWidth() - expect(pane2.outerHeight()).toBe 1/3 * container.height() - expect(Math.round(pane2.position().top)).toBe row2.height() - - expect(row2.children().length).toBe 2 - column3 = row2.children(':eq(0)').view() - pane3 = row2.children(':eq(1)').view() - expect(column3.outerWidth()).toBe Math.round(1/3 * container.width()) - expect(column3.outerHeight()).toBe row2.outerHeight() - # the built in rounding seems to be rounding x.5 down, but we need to go up. this sucks. - expect(Math.round(pane3.trueWidth())).toBe Math.round(1/3 * container.width()) - expect(pane3.height()).toBe row2.outerHeight() - expect(Math.round(pane3.position().left)).toBe column3.width() - - expect(column3.children().length).toBe 2 - pane4 = column3.children(':eq(0)').view() - pane5 = column3.children(':eq(1)').view() - expect(pane4.outerWidth()).toBe column3.width() - expect(pane4.outerHeight()).toBe 1/3 * container.height() - expect(pane5.outerWidth()).toBe column3.width() - expect(Math.round(pane5.position().top)).toBe pane4.outerHeight() - expect(pane5.outerHeight()).toBe 1/3 * container.height() - - pane5.remove() - expect(column3.parent()).not.toExist() - expect(pane2.outerHeight()).toBe Math.floor(1/2 * container.height()) - expect(pane3.outerHeight()).toBe Math.floor(1/2 * container.height()) - expect(pane4.outerHeight()).toBe Math.floor(1/2 * container.height()) - - pane4.remove() - expect(row2.parent()).not.toExist() - expect(pane1.outerWidth()).toBe Math.floor(1/2 * container.width()) - expect(pane2.outerWidth()).toBe Math.floor(1/2 * container.width()) - expect(pane3.outerWidth()).toBe Math.floor(1/2 * container.width()) - - pane3.remove() - expect(column1.parent()).not.toExist() - expect(pane2.outerHeight()).toBe container.height() - - pane2.remove() - expect(row1.parent()).not.toExist() - expect(container.children().length).toBe 1 - expect(container.children('.pane').length).toBe 1 - expect(pane1.outerWidth()).toBe container.width() - describe "::itemForUri(uri)", -> it "returns the item for which a call to .getUri() returns the given uri", -> expect(pane.itemForUri(editor1.getUri())).toBe editor1 From 7665cd1a6afbad635270dba73c115e7663be71e9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 12:35:12 -0700 Subject: [PATCH 56/89] When a pane view is removed in anyway, make sure its model is destroyed --- src/pane.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pane.coffee b/src/pane.coffee index cc8173554..88fac9c61 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -206,9 +206,11 @@ class Pane extends View getContainer: -> @closest('.panes').view() + beforeRemove: -> + @model.destroy() unless @model.isDestroyed() + # Private: remove: (selector, keepData) -> return super if keepData @unsubscribe() - @model.destroy() unless @model.isDestroyed() super From 104271861c57a6bb562067bb968d2e877cfe28a4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 13:07:39 -0700 Subject: [PATCH 57/89] Upgrade markdown-preview to 0.24.0 for .flexbox-repaint-hack fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9d67bd7d2..821a0ef0b 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "grammar-selector": "0.16.0", "image-view": "0.14.0", "keybinding-resolver": "0.8.0", - "markdown-preview": "0.23.0", + "markdown-preview": "0.24.0", "metrics": "0.20.0", "package-generator": "0.23.0", "release-notes": "0.15.0", From 7801d8562f7a00aa39b6b0d13568458380302ec2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 13:08:47 -0700 Subject: [PATCH 58/89] Upgrade archive-view to 0.19.0 for .flexbox-repaint-hack fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 821a0ef0b..e0754a2af 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "base16-tomorrow-dark-theme": "0.8.0", "solarized-dark-syntax": "0.6.0", "solarized-light-syntax": "0.2.0", - "archive-view": "0.18.0", + "archive-view": "0.19.0", "autocomplete": "0.19.0", "autoflow": "0.11.0", "autosave": "0.10.0", From 47870a1214075cc378db50e20a8064dd9372ad96 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 13:35:18 -0700 Subject: [PATCH 59/89] Remove views (rather than detaching) if Pane::destroyItem is called --- spec/pane-spec.coffee | 5 +++++ src/pane-model.coffee | 8 ++++++-- src/pane.coffee | 11 +++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index d032ce92c..204730aee 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -196,6 +196,11 @@ describe "Pane", -> expect(pane.getItems().indexOf(editor2)).not.toBe -1 expect(editor2.isDestroyed()).toBe false + it "removes the item's associated view", -> + view1.remove = (selector, keepData) -> @wasRemoved = not keepData + pane.destroyItem(view1) + expect(view1.wasRemoved).toBe true + describe "::removeItem(item)", -> it "removes the item from the items list and shows the next item if it was showing", -> pane.removeItem(view1) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 4b0e518b2..840924baf 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -22,6 +22,8 @@ class PaneModel extends Model .map((activePane) => activePane is this) .distinctUntilChanged() + destroyingItem: false + constructor: (params) -> super @@ -37,7 +39,7 @@ class PaneModel extends Model @subscribe @items.onRemoval (item, index) => @unsubscribe item if typeof item.on is 'function' - @emit 'item-removed', item, index + @emit 'item-removed', item, index, @destroyingItem @when @items.$length.becomesLessThan(1), 'destroy' @@ -145,7 +147,9 @@ class PaneModel extends Model @emit 'before-item-destroyed', item if @promptToSaveItem(item) @emit 'item-destroyed', item - @removeItem(item, options) + @destroyingItem = true + @removeItem(item) + @destroyingItem = false item.destroy?() true else diff --git a/src/pane.coffee b/src/pane.coffee index 88fac9c61..dcee94658 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -149,8 +149,8 @@ class Pane extends View onItemAdded: (item, index) => @trigger 'pane:item-added', [item, index] - onItemRemoved: (item, index) => - @cleanupItemView(item) + onItemRemoved: (item, index, destroyed) => + @cleanupItemView(item, destroyed) @trigger 'pane:item-removed', [item, index] onItemMoved: (item, newIndex) => @@ -168,7 +168,7 @@ class Pane extends View @trigger 'pane:active-item-title-changed' # Private: - cleanupItemView: (item) -> + cleanupItemView: (item, destroyed) -> if item instanceof $ viewToRemove = item else if viewToRemove = @viewsByItem.get(item) @@ -176,7 +176,10 @@ class Pane extends View if viewToRemove? viewToRemove.setModel?(null) - viewToRemove.detach() + if destroyed + viewToRemove.remove() + else + viewToRemove.detach() # Private: viewForItem: (item) -> From 1ee783fdb9fa1ff3d19775b89e1b8e21b6688a1a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 13:44:02 -0700 Subject: [PATCH 60/89] Base PaneContainer::getActivePane on the model's active pane --- src/pane-container.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pane-container.coffee b/src/pane-container.coffee index eb9297e04..c5371afd0 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -30,8 +30,9 @@ class PaneContainer extends View @subscribe @model, 'surrendered-focus', @onSurrenderedFocus viewForModel: (model) -> - viewClass = model.getViewClass() - model._view ?= new viewClass(model) + if model? + viewClass = model.getViewClass() + model._view ?= new viewClass(model) serializeParams: -> model: @model.serialize() @@ -98,7 +99,7 @@ class PaneContainer extends View @find('.pane:has(:focus)').view() getActivePane: -> - @find('.pane.active').view() ? @find('.pane:first').view() + @viewForModel(@model.activePane) getActivePaneItem: -> @model.activePaneItem From 284d823ad5cc75983246edab6496548ce893b4d9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 14:36:07 -0700 Subject: [PATCH 61/89] Tighten up search for parent pane The introduction of the .flexbox-redraw-hack required the query to be loosened, but I went too far. We don't want to return a pane for mini editors that happen to be nested on another view that's inside a pane. --- src/editor-view.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index 770c7895f..9ae326c54 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -1073,7 +1073,7 @@ class EditorView extends View # # Returns a {Pane}. getPane: -> - @parents('.pane').view() + @parent('.item-views').parents('.pane').view() remove: (selector, keepData) -> return super if keepData or @removed From c7fded0d7f367be8b0884ba39c7bae69d4651d5f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 15:04:51 -0700 Subject: [PATCH 62/89] Only emit 'pane:removed' events if the pane is actually destroyed The pane may only be detached temporarily during splitting/unsplitting --- src/pane-axis.coffee | 4 +++- src/pane-container.coffee | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index bdee142ad..5d535da4a 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -30,7 +30,9 @@ class PaneAxis extends View view = @viewForModel(child) view.detach() Pane ?= require './pane' - @getContainer()?.trigger 'pane:removed', [view] if view instanceof Pane + + if view instanceof Pane and view.model.isDestroyed() + @getContainer()?.trigger 'pane:removed', [view] getContainer: -> @closest('.panes').view() diff --git a/src/pane-container.coffee b/src/pane-container.coffee index c5371afd0..bf7c1657d 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -50,7 +50,8 @@ class PaneContainer extends View onRootChanged: (root) => oldRoot = @getRoot() - @trigger 'pane:removed', [oldRoot] if @getRoot() instanceof Pane + if oldRoot instanceof Pane and oldRoot.model.isDestroyed() + @trigger 'pane:removed', [oldRoot] oldRoot?.detach() if root? view = @viewForModel(root) From 23e805fe9e2fbf0bb42d8706a3a81100bf961959 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 15:28:17 -0700 Subject: [PATCH 63/89] Remove assertion for impossible situation We were testing the behavior of returning the active pane by removing the active class from its view. But "active" is a model-level concept now, so this assertion makes no sense. --- spec/pane-container-spec.coffee | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 0b60ccf64..efd43f262 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -71,10 +71,6 @@ describe "PaneContainer", -> expect(container.getFocusedPane()).toBe pane3 expect(container.getActivePane()).toBe pane3 - # returns the first pane if none have been set to active - container.find('.pane.active').removeClass('active') - expect(container.getActivePane()).toBe pane1 - describe ".eachPane(callback)", -> it "runs the callback with all current and future panes until the subscription is cancelled", -> panes = [] From 9694d255f0819711425b8d207962e3a05bbc1430 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 15:48:08 -0700 Subject: [PATCH 64/89] Simplify item removal - Move emitting of item removal event into ::removeItemAtIndex. Pass a destroying param instead of setting state. - Destroy the pane if ::items is empty at the in the item removal method, rather than with a condition. This gives the item removal event a chance to fire first. --- src/pane-model.coffee | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 840924baf..db25259e1 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -22,8 +22,6 @@ class PaneModel extends Model .map((activePane) => activePane is this) .distinctUntilChanged() - destroyingItem: false - constructor: (params) -> super @@ -39,9 +37,6 @@ class PaneModel extends Model @subscribe @items.onRemoval (item, index) => @unsubscribe item if typeof item.on is 'function' - @emit 'item-removed', item, index, @destroyingItem - - @when @items.$length.becomesLessThan(1), 'destroy' @when @$focused, => @makeActive() @@ -115,15 +110,17 @@ class PaneModel extends Model item # Public: - removeItem: (item) -> + removeItem: (item, destroying) -> index = @items.indexOf(item) - @removeItemAtIndex(index) if index >= 0 + @removeItemAtIndex(index, destroying) if index >= 0 # Public: Just remove the item at the given index. - removeItemAtIndex: (index) -> + removeItemAtIndex: (index, destroying) -> item = @items[index] @showNextItem() if item is @activeItem and @items.length > 1 - @suppressBlur => @items.splice(index, 1) + @items.splice(index, 1) + @suppressBlur => @emit 'item-removed', item, index, destroying + @destroy() if @items.length is 0 # Public: Moves the given item to a the new index. moveItem: (item, newIndex) -> @@ -147,9 +144,7 @@ class PaneModel extends Model @emit 'before-item-destroyed', item if @promptToSaveItem(item) @emit 'item-destroyed', item - @destroyingItem = true - @removeItem(item) - @destroyingItem = false + @removeItem(item, true) item.destroy?() true else From c127237cc677ba421fd169c8e401b6a5fc73c450 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 15:49:07 -0700 Subject: [PATCH 65/89] Upgrade tabs to 0.17.0 so they unsubscribe when the pane is removed --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0754a2af..4de193c38 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "status-bar": "0.30.0", "styleguide": "0.19.0", "symbols-view": "0.27.0", - "tabs": "0.16.0", + "tabs": "0.17.0", "terminal": "0.23.0", "timecop": "0.12.0", "to-the-hubs": "0.17.0", From ddf7c04e66edcc9f780635493aa0b85b083600fc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 15:51:27 -0700 Subject: [PATCH 66/89] Use -> arrows on methods moved to the model --- src/pane-model.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index db25259e1..e29dd405d 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -72,7 +72,7 @@ class PaneModel extends Model @items[index] # Public: Switches to the next contained item. - showNextItem: => + showNextItem: -> index = @getActiveItemIndex() if index < @items.length - 1 @showItemAtIndex(index + 1) @@ -80,7 +80,7 @@ class PaneModel extends Model @showItemAtIndex(0) # Public: Switches to the previous contained item. - showPreviousItem: => + showPreviousItem: -> index = @getActiveItemIndex() if index > 0 @showItemAtIndex(index - 1) @@ -182,11 +182,11 @@ class PaneModel extends Model when 2 then true # Public: Saves the currently focused item. - saveActiveItem: => + saveActiveItem: -> @saveItem(@activeItem) # Public: Save and prompt for path for the currently focused item. - saveActiveItemAs: => + saveActiveItemAs: -> @saveItemAs(@activeItem) # Public: Saves the specified item and call the next action when complete. @@ -210,7 +210,7 @@ class PaneModel extends Model nextAction?() # Public: Saves all items in this pane. - saveItems: => + saveItems: -> @saveItem(item) for item in @getItems() # Public: Finds the first item that matches the given uri. From 72fe5861018d1ef9ef9162c549f0e9565beec42f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 17:25:30 -0700 Subject: [PATCH 67/89] Remove the concept of focus from the model --- spec/focusable-spec.coffee | 36 ------------------- spec/pane-container-model-spec.coffee | 26 ++++---------- spec/pane-container-spec.coffee | 2 +- spec/pane-model-spec.coffee | 26 +------------- src/focus-context.coffee | 15 -------- src/focusable.coffee | 28 --------------- src/pane-axis-model.coffee | 5 +-- src/pane-axis.coffee | 12 ++++--- src/pane-container-model.coffee | 36 +++---------------- src/pane-container.coffee | 32 +++++++++++++---- src/pane-model.coffee | 27 +++++++-------- src/pane.coffee | 50 ++++++++++++--------------- src/space-pen-extensions.coffee | 3 ++ 13 files changed, 85 insertions(+), 213 deletions(-) delete mode 100644 spec/focusable-spec.coffee delete mode 100644 src/focus-context.coffee delete mode 100644 src/focusable.coffee diff --git a/spec/focusable-spec.coffee b/spec/focusable-spec.coffee deleted file mode 100644 index 0cb26b908..000000000 --- a/spec/focusable-spec.coffee +++ /dev/null @@ -1,36 +0,0 @@ -{Model} = require 'theorist' -Focusable = require '../src/focusable' -FocusContext = require '../src/focus-context' - -describe "Focusable mixin", -> - it "ensures that only a single model is focused for a given focus manager", -> - class Item extends Model - Focusable.includeInto(this) - - focusContext = new FocusContext - item1 = new Item({focusContext}) - item2 = new Item({focusContext}) - item3 = new Item({focusContext}) - - expect(focusContext.focusedObject).toBe null - expect(item1.focused).toBe false - expect(item2.focused).toBe false - expect(item3.focused).toBe false - - item1.focus() - expect(focusContext.focusedObject).toBe item1 - expect(item1.focused).toBe true - expect(item2.focused).toBe false - expect(item3.focused).toBe false - - item2.focus() - expect(focusContext.focusedObject).toBe item2 - expect(item1.focused).toBe false - expect(item2.focused).toBe true - expect(item3.focused).toBe false - - item2.blur() - expect(focusContext.focusedObject).toBe null - expect(item1.focused).toBe false - expect(item2.focused).toBe false - expect(item3.focused).toBe false diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index 22c0f8668..dd6e14e60 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -16,11 +16,10 @@ describe "PaneContainerModel", -> containerB = containerA.testSerialization() [pane1B, pane2B, pane3B] = containerB.getPanes() - expect(pane3B.focusContext).toBe containerB.focusContext expect(pane3B.focused).toBe true it "preserves the active pane across serialization, independent of focus", -> - pane3A.blur() + pane3A.makeActive() expect(containerA.activePane).toBe pane3A containerB = containerA.testSerialization() @@ -34,30 +33,30 @@ describe "PaneContainerModel", -> pane1 = new PaneModel container = new PaneContainerModel(root: pane1) - it "references the first pane if no pane has been focused", -> + it "references the first pane if no pane has been made active", -> expect(container.activePane).toBe pane1 expect(pane1.active).toBe true - it "references the most recently focused pane", -> + it "references the most pane on which ::makeActive was most recently called", -> pane2 = pane1.splitRight() + pane2.makeActive() expect(container.activePane).toBe pane2 expect(pane1.active).toBe false expect(pane2.active).toBe true - pane1.focus() + pane1.makeActive() expect(container.activePane).toBe pane1 expect(pane1.active).toBe true expect(pane2.active).toBe false - it "is reassigned to the next pane if the current active pane is unfocused and destroyed", -> + it "is reassigned to the next pane if the current active pane is destroyed", -> pane2 = pane1.splitRight() - pane2.blur() + pane2.makeActive() pane2.destroy() expect(container.activePane).toBe pane1 expect(pane1.active).toBe true pane1.destroy() expect(container.activePane).toBe null - # TODO: Remove this behavior when we have a proper workspace model we can explicitly focus describe "when the last pane is removed", -> [container, pane, surrenderedFocusHandler] = [] @@ -70,14 +69,3 @@ describe "PaneContainerModel", -> pane.destroy() expect(container.root).toBe null expect(container.activePane).toBe null - - describe "if the pane is focused", -> - it "emits a 'surrendered-focus' event", -> - pane.focus() - pane.destroy() - expect(surrenderedFocusHandler).toHaveBeenCalled() - - describe "if the pane is not focused", -> - it "does not emit an event", -> - expect(pane.focused).toBe false - expect(surrenderedFocusHandler).not.toHaveBeenCalled() diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index efd43f262..2dd46707f 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -42,7 +42,7 @@ describe "PaneContainer", -> describe ".focusPreviousPane()", -> it "focuses the pane preceding the focused pane or the last pane if no pane has focus", -> container.attachToDom() - pane3.focusout() + $(document.body).focus() # clear focus container.focusPreviousPane() expect(pane3.activeItem).toMatchSelector ':focus' diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index f17b141c9..77f1db022 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -2,7 +2,6 @@ PaneModel = require '../src/pane-model' PaneAxisModel = require '../src/pane-axis-model' PaneContainerModel = require '../src/pane-container-model' -FocusContext = require '../src/focus-context' describe "PaneModel", -> describe "split methods", -> @@ -80,25 +79,12 @@ describe "PaneModel", -> expect(column.orientation).toBe 'vertical' expect(column.children).toEqual [pane1, pane3, pane2] - it "focuses the new pane, even if the current pane isn't focused", -> + it "sets up the new pane to be focused", -> expect(pane1.focused).toBe false pane2 = pane1.splitRight() expect(pane2.focused).toBe true describe "::removeItemAtIndex(index)", -> - describe "when the removal of the item causes blur to be called on the pane model", -> - it "remains focused if it was before the item was removed", -> - pane = new PaneModel(items: ["A", "B", "C"]) - container = new PaneContainerModel(root: pane) - pane.on 'item-removed', -> pane.blur() - pane.focus() - pane.removeItemAtIndex(0) - expect(pane.focused).toBe true - - pane.blur() - pane.removeItemAtIndex(0) - expect(pane.focused).toBe false - describe "when the last item is removed", -> it "destroys the pane", -> pane = new PaneModel(items: ["A", "B"]) @@ -146,13 +132,3 @@ describe "PaneModel", -> expect(container.root.children).toEqual [pane1, pane2] pane2.destroy() expect(container.root).toBe pane1 - - describe "if the pane is focused", -> - it "shifts focus to the next pane", -> - pane2 = pane1.splitRight() - pane3 = pane2.splitRight() - pane2.focus() - expect(pane2.focused).toBe true - expect(pane3.focused).toBe false - pane2.destroy() - expect(pane3.focused).toBe true diff --git a/src/focus-context.coffee b/src/focus-context.coffee deleted file mode 100644 index c17514471..000000000 --- a/src/focus-context.coffee +++ /dev/null @@ -1,15 +0,0 @@ -{Model} = require 'theorist' - -module.exports = -class FocusContext extends Model - @property 'focusedObject', null - - blurSuppressionCounter: 0 - - isBlurSuppressed: -> - @blurSuppressionCounter > 0 - - suppressBlur: (fn) -> - @blurSuppressionCounter++ - fn() - @blurSuppressionCounter-- diff --git a/src/focusable.coffee b/src/focusable.coffee deleted file mode 100644 index c046e7cd2..000000000 --- a/src/focusable.coffee +++ /dev/null @@ -1,28 +0,0 @@ -Mixin = require 'mixto' - -module.exports = -class Focusable extends Mixin - @included: -> - @property 'focusContext' - @behavior 'focused', -> - @$focusContext - .flatMapLatest((context) -> context?.$focusedObject) - .map((focusedObject) => focusedObject is this) - .distinctUntilChanged() - - focus: -> - throw new Error("Object must be assigned a focusContext to be focus") unless @focusContext - unless @focused - @suppressBlur => - @focusContext.focusedObject = this - - blur: -> - throw new Error("Object must be assigned a focusContext to be blurred") unless @focusContext - if @focused and not @focusContext.isBlurSuppressed() - @focusContext.focusedObject = null - - suppressBlur: (fn) -> - if @focusContext? - @focusContext.suppressBlur(fn) - else - fn() diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index ce6a2b54b..f019856e6 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -12,8 +12,6 @@ class PaneAxisModel extends Model Serializable.includeInto(this) Delegator.includeInto(this) - @delegatesProperty 'focusContext', toProperty: 'container' - constructor: ({@container, @orientation, children}) -> @children = Sequence.fromArray(children ? []) @@ -67,5 +65,4 @@ class PaneAxisModel extends Model @children.splice(index + 1, 0, newChild) reparentLastChild: -> - @focusContext.suppressBlur => - @parent.replaceChild(this, @children[0]) + @parent.replaceChild(this, @children[0]) diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index 5d535da4a..79ed2cfb1 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -7,10 +7,8 @@ Pane = null module.exports = class PaneAxis extends View initialize: (@model) -> - @subscribe @model.children.onRemoval @onChildRemoved - @subscribe @model.children.onEach @onChildAdded - - @onChildAdded(child) for child in children ? [] + @onChildAdded(child) for child in @model.children + @subscribe @model.children, 'changed', @onChildrenChanged viewForModel: (model) -> viewClass = model.getViewClass() @@ -22,6 +20,12 @@ class PaneAxis extends View removeChild: (child) -> @model.removeChild(child.model) + onChildrenChanged: ({index, removedValues, insertedValues}) => + focusedElement = document.activeElement if @hasFocus() + @onChildRemoved(child, index) for child in removedValues + @onChildAdded(child, index + i) for child, i in insertedValues + focusedElement?.focus() if document.activeElement is document.body + onChildAdded: (child, index) => view = @viewForModel(child) @insertAt(index, view) diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 2e7ebb3bd..b32f28135 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -1,7 +1,6 @@ {Model} = require 'theorist' Serializable = require 'serializable' {find} = require 'underscore-plus' -FocusContext = require './focus-context' PaneModel = require './pane-model' module.exports = @@ -11,7 +10,6 @@ class PaneContainerModel extends Model @properties root: null - focusContext: null activePane: null previousRoot: null @@ -21,13 +19,9 @@ class PaneContainerModel extends Model constructor: -> super - - @focusContext ?= new FocusContext - @subscribe @$root, @onRootChanged deserializeParams: (params) -> - @focusContext ?= params.focusContext ? new FocusContext params.root = atom.deserializers.deserialize(params.root, container: this) params @@ -41,31 +35,6 @@ class PaneContainerModel extends Model getPanes: -> @root?.getPanes() ? [] - getFocusedPane: -> - find @getPanes(), (pane) -> pane.focused - - focusNextPane: -> - panes = @getPanes() - if panes.length > 1 - currentIndex = panes.indexOf(@getFocusedPane()) - nextIndex = (currentIndex + 1) % panes.length - panes[nextIndex].focus() - true - else - @emit 'surrendered-focus' - false - - focusPreviousPane: -> - panes = @getPanes() - if panes.length > 1 - currentIndex = panes.indexOf(@getFocusedPane()) - previousIndex = currentIndex - 1 - previousIndex = panes.length - 1 if previousIndex < 0 - panes[previousIndex].focus() - true - else - false - makeNextPaneActive: -> panes = @getPanes() if panes.length > 1 @@ -78,7 +47,10 @@ class PaneContainerModel extends Model onRootChanged: (root) => @unsubscribe(@previousRoot) if @previousRoot? @previousRoot = root - return unless root? + + unless root? + @activePane = null + return root.parent = this root.container = this diff --git a/src/pane-container.coffee b/src/pane-container.coffee index bf7c1657d..eeb54f74b 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -17,8 +17,6 @@ class PaneContainer extends View @content: -> @div class: 'panes' - @delegatesMethods 'focusNextPane', 'focusPreviousPane', toProperty: 'model' - initialize: (params) -> if params instanceof PaneContainerModel @model = params @@ -27,7 +25,6 @@ class PaneContainer extends View @subscribe @model.$root, 'value', @onRootChanged @subscribe @model.$activePaneItem.changes, 'value', @onActivePaneItemChanged - @subscribe @model, 'surrendered-focus', @onSurrenderedFocus viewForModel: (model) -> if model? @@ -49,6 +46,8 @@ class PaneContainer extends View @model.root = root?.model onRootChanged: (root) => + focusedElement = document.activeElement if @hasFocus() + oldRoot = @getRoot() if oldRoot instanceof Pane and oldRoot.model.isDestroyed() @trigger 'pane:removed', [oldRoot] @@ -56,14 +55,11 @@ class PaneContainer extends View if root? view = @viewForModel(root) @append(view) - view.makeActive?() + focusedElement?.focus() onActivePaneItemChanged: (activeItem) => @trigger 'pane-container:active-pane-item-changed', [activeItem] - onSurrenderedFocus: => - atom?.workspaceView?.focus() - removeChild: (child) -> throw new Error("Removing non-existant child") unless @getRoot() is child @setRoot(null) @@ -117,3 +113,25 @@ class PaneContainer extends View removeEmptyPanes: -> for pane in @getPanes() when pane.getItems().length == 0 pane.remove() + + focusNextPane: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@getFocusedPane()) + nextIndex = (currentIndex + 1) % panes.length + panes[nextIndex].focus() + true + else + atom.workspaceView?.focus() + false + + focusPreviousPane: -> + panes = @getPanes() + if panes.length > 1 + currentIndex = panes.indexOf(@getFocusedPane()) + previousIndex = currentIndex - 1 + previousIndex = panes.length - 1 if previousIndex < 0 + panes[previousIndex].focus() + true + else + false diff --git a/src/pane-model.coffee b/src/pane-model.coffee index e29dd405d..d4f2e6662 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -3,18 +3,17 @@ {Model, Sequence} = require 'theorist' Serializable = require 'serializable' PaneAxisModel = require './pane-axis-model' -Focusable = require './focusable' Pane = null module.exports = class PaneModel extends Model atom.deserializers.add(this) Serializable.includeInto(this) - Focusable.includeInto(this) @properties container: null activeItem: null + focused: false @behavior 'active', -> @$container @@ -28,9 +27,6 @@ class PaneModel extends Model @items = Sequence.fromArray(params?.items ? []) @activeItem ?= @items[0] - @subscribe @$container, (container) => - @focusContext = container?.focusContext - @subscribe @items.onEach (item) => if typeof item.on is 'function' @subscribe item, 'destroyed', => @removeItem(item) @@ -38,9 +34,6 @@ class PaneModel extends Model @subscribe @items.onRemoval (item, index) => @unsubscribe item if typeof item.on is 'function' - @when @$focused, => @makeActive() - - @focus() if params?.focused @makeActive() if params?.active serializeParams: -> @@ -59,6 +52,13 @@ class PaneModel extends Model isActive: -> @active + focus: -> + @focused = true + @makeActive() + + blur: -> + @focused = false + makeActive: -> @container?.activePane = this getPanes: -> [this] @@ -119,7 +119,7 @@ class PaneModel extends Model item = @items[index] @showNextItem() if item is @activeItem and @items.length > 1 @items.splice(index, 1) - @suppressBlur => @emit 'item-removed', item, index, destroying + @emit 'item-removed', item, index, destroying @destroy() if @items.length is 0 # Public: Moves the given item to a the new index. @@ -160,11 +160,8 @@ class PaneModel extends Model # Private: Called by model superclass destroyed: -> + @container.makeNextPaneActive() if @isActive() item.destroy?() for item in @items.slice() - if @focused - @container.focusNextPane() - else if @isActive() - @container.makeNextPaneActive() # Public: Prompt the user to save the given item. promptToSaveItem: (item) -> @@ -245,10 +242,10 @@ class PaneModel extends Model if @parent.orientation isnt orientation @parent.replaceChild(this, new PaneAxisModel({@container, orientation, children: [this]})) - newPane = new @constructor(params) + newPane = new @constructor(extend({focused: true}, params)) switch side when 'before' then @parent.insertChildBefore(this, newPane) when 'after' then @parent.insertChildAfter(this, newPane) - newPane.focus() + newPane.makeActive() newPane diff --git a/src/pane.coffee b/src/pane.coffee index dcee94658..2c45de329 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -49,9 +49,6 @@ class Pane extends View @viewsByItem = new WeakMap() @handleEvents() - hasFocus: -> - @is(':focus') or @is(':has(:focus)') - handleEvents: -> @subscribe @model, 'destroyed', => @remove() @@ -63,16 +60,10 @@ class Pane extends View @subscribe @model, 'item-destroyed', @onItemDestroyed @subscribe @model.$active, 'value', @onActiveStatusChanged - @subscribe @model.$focused, 'value', (focused) => - if focused - @focus() unless @hasFocus() - else - @blur() if @hasFocus() - @subscribe this, 'focusin', => @model.focus() @subscribe this, 'focusout', => @model.blur() @subscribe this, 'focus', => - @model.suppressBlur => @activeView?.focus() + @activeView?.focus() false @command 'pane:save-items', => @saveItems() @@ -141,8 +132,8 @@ class Pane extends View @itemViews.children().not(view).hide() @itemViews.append(view) unless view.parent().is(@itemViews) view.show() if @attached - if isFocused - @model.suppressBlur -> view.focus() + view.focus() if isFocused + @activeView = view @trigger 'pane:active-item-changed', [item] @@ -150,7 +141,25 @@ class Pane extends View @trigger 'pane:item-added', [item, index] onItemRemoved: (item, index, destroyed) => - @cleanupItemView(item, destroyed) + if item instanceof $ + viewToRemove = item + else if viewToRemove = @viewsByItem.get(item) + @viewsByItem.delete(item) + + removingLastItem = @model.items.length is 0 + hasFocus = @hasFocus() + + @getContainer().focusNextPane() if hasFocus and removingLastItem + + if viewToRemove? + viewToRemove.setModel?(null) + if destroyed + viewToRemove.remove() + else + viewToRemove.detach() + + # @focus() if hasFocus and not removingLastItem + @trigger 'pane:item-removed', [item, index] onItemMoved: (item, newIndex) => @@ -167,20 +176,6 @@ class Pane extends View activeItemTitleChanged: => @trigger 'pane:active-item-title-changed' - # Private: - cleanupItemView: (item, destroyed) -> - if item instanceof $ - viewToRemove = item - else if viewToRemove = @viewsByItem.get(item) - @viewsByItem.delete(item) - - if viewToRemove? - viewToRemove.setModel?(null) - if destroyed - viewToRemove.remove() - else - viewToRemove.detach() - # Private: viewForItem: (item) -> if item instanceof $ @@ -210,6 +205,7 @@ class Pane extends View @closest('.panes').view() beforeRemove: -> + @getContainer()?.focusNextPane() if @hasFocus() @model.destroy() unless @model.isDestroyed() # Private: diff --git a/src/space-pen-extensions.coffee b/src/space-pen-extensions.coffee index d7504ebe2..02d08f8ab 100644 --- a/src/space-pen-extensions.coffee +++ b/src/space-pen-extensions.coffee @@ -59,6 +59,9 @@ jQuery.fn.destroyTooltip = -> @hideTooltip() @tooltip('destroy') +jQuery.fn.hasFocus = -> + @is(':focus') or @is(':has(:focus)') + jQuery.fn.setTooltip.getKeystroke = getKeystroke jQuery.fn.setTooltip.humanizeKeystrokes = humanizeKeystrokes From 578ca8b1978864d592574dd1eadd0ec322b65446 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 17:56:04 -0700 Subject: [PATCH 68/89] Don't update highlighted gutter lines unless the editor is alive --- src/editor-view.coffee | 2 ++ src/gutter.coffee | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index 9ae326c54..43b1c9079 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -217,6 +217,8 @@ class EditorView extends View do (name, method) => @command name, (e) => method.call(this, e); false + isAlive: -> @editor?.isAlive() + # {Delegates to: Editor.getCursor} getCursor: -> @editor.getCursor() diff --git a/src/gutter.coffee b/src/gutter.coffee index 7b7ee15a1..f3471fad0 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -230,6 +230,8 @@ class Gutter extends View @highlightedLineNumbers.push(highlightedLineNumber) highlightLines: -> + return unless @getEditorView().isAlive() + if @getEditorView().getSelection().isEmpty() row = @getEditorView().getCursorScreenPosition().row rowRange = new Range([row, 0], [row, 0]) From 3afbcbe25fa7bac2c25c675ccd3d767122487983 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 18:07:10 -0700 Subject: [PATCH 69/89] Allow focusout events to bubble out of panes --- src/pane-model.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index d4f2e6662..7e767ed21 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -58,6 +58,7 @@ class PaneModel extends Model blur: -> @focused = false + true # if this is called from an event handler, don't cancel it makeActive: -> @container?.activePane = this From 263ab3b4a6b26c4813e771a5a744922f9b671653 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 18:27:11 -0700 Subject: [PATCH 70/89] Kill unused require --- src/pane-container-model.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index b32f28135..0128049d8 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -1,6 +1,5 @@ {Model} = require 'theorist' Serializable = require 'serializable' -{find} = require 'underscore-plus' PaneModel = require './pane-model' module.exports = From 8b0b997db60c09bb9295fae1b29e2487d622cb5e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 18:28:01 -0700 Subject: [PATCH 71/89] Kill EditorView::isAlive and just tunnel to the model where it was used --- src/editor-view.coffee | 2 -- src/gutter.coffee | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index 43b1c9079..9ae326c54 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -217,8 +217,6 @@ class EditorView extends View do (name, method) => @command name, (e) => method.call(this, e); false - isAlive: -> @editor?.isAlive() - # {Delegates to: Editor.getCursor} getCursor: -> @editor.getCursor() diff --git a/src/gutter.coffee b/src/gutter.coffee index f3471fad0..be02dc6d4 100644 --- a/src/gutter.coffee +++ b/src/gutter.coffee @@ -230,7 +230,7 @@ class Gutter extends View @highlightedLineNumbers.push(highlightedLineNumber) highlightLines: -> - return unless @getEditorView().isAlive() + return unless @getEditorView().editor?.isAlive() if @getEditorView().getSelection().isEmpty() row = @getEditorView().getCursorScreenPosition().row From 738bfd7253af4c825a880a87c2b1e328160543f4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 10 Jan 2014 19:02:19 -0700 Subject: [PATCH 72/89] Fix layout of tabs with flexbox repaint hack The .item-views div needs to be the first child of pane and contain the absolutely positioned repaint hack div inside it, otherwise the tabs don't get honored as flexbox items. --- src/editor-view.coffee | 2 +- src/pane.coffee | 4 ++-- src/workspace-view.coffee | 2 +- static/panes.less | 29 ++++++++++++++--------------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index 9ae326c54..220dd699a 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -1073,7 +1073,7 @@ class EditorView extends View # # Returns a {Pane}. getPane: -> - @parent('.item-views').parents('.pane').view() + @parent('.flexbox-repaint-hack').parents('.pane').view() remove: (selector, keepData) -> return super if keepData or @removed diff --git a/src/pane.coffee b/src/pane.coffee index 2c45de329..1c644d6de 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -24,8 +24,8 @@ class Pane extends View @content: (wrappedView) -> @div class: 'pane', tabindex: -1, => - @div class: 'flexbox-repaint-hack', => - @div class: 'item-views', outlet: 'itemViews' + @div class: 'item-views', => + @div class: 'flexbox-repaint-hack', outlet: 'itemViews' @delegatesProperties 'items', 'activeItem', toProperty: 'model' @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index 55e37a0b7..d4281c7e9 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -240,7 +240,7 @@ class WorkspaceView extends View # Private: Returns an Array of all of the application's {EditorView}s. getEditorViews: -> - @panes.find('.pane > .flexbox-repaint-hack > .item-views > .editor').map(-> $(this).view()).toArray() + @panes.find('.pane > .item-views > .flexbox-repaint-hack > .editor').map(-> $(this).view()).toArray() # Private: Retrieves all of the modified buffers that are open and unsaved. # diff --git a/static/panes.less b/static/panes.less index 238cdb4a0..d5e016427 100644 --- a/static/panes.less +++ b/static/panes.less @@ -21,18 +21,9 @@ .pane { position: relative; + display: -webkit-flex; -webkit-flex: 1; - - .flexbox-repaint-hack { - display: -webkit-flex; - -webkit-flex-flow: column; - - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } + -webkit-flex-direction: column; .pane-item { color: @text-color; @@ -43,11 +34,19 @@ -webkit-flex: 1; display: -webkit-flex; min-height: 0; - } + position: relative; - .item-views > * { - -webkit-flex: 1; - min-width: 0; + .flexbox-repaint-hack { + display: -webkit-flex; + -webkit-flex-flow: column; + + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + min-width: 0; + } } } } From 1fdc78a157ee9fbdc0833c66e34fe79d26458c5a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 10:24:18 -0700 Subject: [PATCH 73/89] Upgrade find-and-replace to 0.74.0 for fix to parent pane dom query --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4de193c38..5a4d95784 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "editor-stats": "0.12.0", "exception-reporting": "0.10.0", "feedback": "0.22.0", - "find-and-replace": "0.70.0", + "find-and-replace": "0.74.0", "fuzzy-finder": "0.30.0", "gists": "0.13.0", "git-diff": "0.21.0", From 545bf4bd9809be84829fef8733c26f7585e7675d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 10:38:47 -0700 Subject: [PATCH 74/89] Upgrade to theorist 0.12.0 to remove dependence on harmony proxies They're turning out to cause instability in the current version of v8 or atom-shell. Perhaps we can revisit this after the upgrade to chromium 31. Once we have Object.observe, we could at least throw an exception when someone assigns a sequence index directly via ::[] or assigns to ::length. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a4d95784..b4a59b860 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "temp": "0.5.0", "text-buffer": "0.12.0", "underscore-plus": "0.6.1", - "theorist": "~0.11.0", + "theorist": "~0.12.0", "delegato": "~0.4.0", "mixto": "~0.4.0" }, From e2170ea907a966f2320e0a33f4f6f379d24583f2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 10:45:33 -0700 Subject: [PATCH 75/89] Remove unused requires and mixins --- src/pane-axis-model.coffee | 2 -- src/pane-axis.coffee | 4 +--- src/pane-container.coffee | 2 -- src/pane-model.coffee | 2 +- src/pane.coffee | 2 -- 5 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/pane-axis-model.coffee b/src/pane-axis-model.coffee index f019856e6..bbea4f969 100644 --- a/src/pane-axis-model.coffee +++ b/src/pane-axis-model.coffee @@ -1,6 +1,5 @@ {Model, Sequence} = require 'theorist' {flatten} = require 'underscore-plus' -Delegator = require 'delegato' Serializable = require 'serializable' PaneRow = null @@ -10,7 +9,6 @@ module.exports = class PaneAxisModel extends Model atom.deserializers.add(this) Serializable.includeInto(this) - Delegator.includeInto(this) constructor: ({@container, @orientation, children}) -> @children = Sequence.fromArray(children ? []) diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index 79ed2cfb1..27757dfbf 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -1,6 +1,4 @@ -Serializable = require 'serializable' -{$, View} = require './space-pen-extensions' -PaneAxisModel = require './pane-axis-model' +{View} = require './space-pen-extensions' Pane = null ### Internal ### diff --git a/src/pane-container.coffee b/src/pane-container.coffee index eeb54f74b..ee875254e 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -1,5 +1,4 @@ Serializable = require 'serializable' -Delegator = require 'delegato' {$, View} = require './space-pen-extensions' Pane = require './pane' PaneContainerModel = require './pane-container-model' @@ -9,7 +8,6 @@ module.exports = class PaneContainer extends View atom.deserializers.add(this) Serializable.includeInto(this) - Delegator.includeInto(this) @deserialize: (state) -> new this(PaneContainerModel.deserialize(state.model)) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 7e767ed21..3c255b34a 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -1,4 +1,4 @@ -{find, compact, clone, extend} = require 'underscore-plus' +{find, compact, extend} = require 'underscore-plus' {dirname} = require 'path' {Model, Sequence} = require 'theorist' Serializable = require 'serializable' diff --git a/src/pane.coffee b/src/pane.coffee index 1c644d6de..c374bcb79 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -3,8 +3,6 @@ Serializable = require 'serializable' Delegator = require 'delegato' PaneModel = require './pane-model' -PaneRow = require './pane-row' -PaneColumn = require './pane-column' # Public: A container which can contains multiple items to be switched between. # From 6fe1bae40d930839deaf317098ee9c13ccf92dad Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 10:46:57 -0700 Subject: [PATCH 76/89] Upgrade to image-view 0.15.0 for fix to parent pane query --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b4a59b860..2bba21140 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "github-sign-in": "0.15.0", "go-to-line": "0.14.0", "grammar-selector": "0.16.0", - "image-view": "0.14.0", + "image-view": "0.15.0", "keybinding-resolver": "0.8.0", "markdown-preview": "0.24.0", "metrics": "0.20.0", From 452d86ac0cf3557b0c55e5bf161a421040e241fd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 11:04:37 -0700 Subject: [PATCH 77/89] Upgrade ui themes for rename of `.row` to `.pane-row` --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 2bba21140..1a1442f5e 100644 --- a/package.json +++ b/package.json @@ -56,9 +56,9 @@ }, "packageDependencies": { "atom-dark-syntax": "0.10.0", - "atom-dark-ui": "0.18.0", + "atom-dark-ui": "0.19.0", "atom-light-syntax": "0.10.0", - "atom-light-ui": "0.17.0", + "atom-light-ui": "0.18.0", "base16-tomorrow-dark-theme": "0.8.0", "solarized-dark-syntax": "0.6.0", "solarized-light-syntax": "0.2.0", From b04f9f9488528dc53303f8b9977c666c49822ef9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 11:11:04 -0700 Subject: [PATCH 78/89] Remove unused methods from PaneAxis --- src/pane-axis.coffee | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/pane-axis.coffee b/src/pane-axis.coffee index 27757dfbf..0a37f1bbb 100644 --- a/src/pane-axis.coffee +++ b/src/pane-axis.coffee @@ -12,12 +12,6 @@ class PaneAxis extends View viewClass = model.getViewClass() model._view ?= new viewClass(model) - addChild: (child, index) -> - @model.addChild(child.model, index) - - removeChild: (child) -> - @model.removeChild(child.model) - onChildrenChanged: ({index, removedValues, insertedValues}) => focusedElement = document.activeElement if @hasFocus() @onChildRemoved(child, index) for child in removedValues @@ -38,15 +32,3 @@ class PaneAxis extends View getContainer: -> @closest('.panes').view() - - getActivePaneItem: -> - @getActivePane()?.activeItem - - getActivePane: -> - @find('.pane.active').view() ? @find('.pane:first').view() - - insertChildBefore: (currentChild, newChild) -> - @model.insertChildBefore(currentChild, newChild) - - insertChildAfter: (currentChild, newChild) -> - @model.insertChildAfter(currentChild, newChild) From ef8b7531b045a86147ce9d283cbe8ddf2f71c093 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 11:19:19 -0700 Subject: [PATCH 79/89] Upgrade to theorist 0.13.0 to rename Signal::flatMapLatest -> ::switch Shorter, simpler, less intimidating. --- package.json | 2 +- src/pane-container-model.coffee | 2 +- src/pane-model.coffee | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1a1442f5e..7b2756acb 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "temp": "0.5.0", "text-buffer": "0.12.0", "underscore-plus": "0.6.1", - "theorist": "~0.12.0", + "theorist": "~0.13.0", "delegato": "~0.4.0", "mixto": "~0.4.0" }, diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 0128049d8..92fcd4558 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -14,7 +14,7 @@ class PaneContainerModel extends Model previousRoot: null @behavior 'activePaneItem', -> - @$activePane.flatMapLatest (activePane) -> activePane?.$activeItem + @$activePane.switch (activePane) -> activePane?.$activeItem constructor: -> super diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 3c255b34a..711dd731f 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -17,7 +17,7 @@ class PaneModel extends Model @behavior 'active', -> @$container - .flatMapLatest((container) -> container?.$activePane) + .switch((container) -> container?.$activePane) .map((activePane) => activePane is this) .distinctUntilChanged() From 3ab7836ab277b0018a036ce1fae591f0d8ae18f1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 11:22:31 -0700 Subject: [PATCH 80/89] Upgrade to emissary 0.31.0 for implicit 'value' subscriptions on signals Previously, when you always had to specify the event name of 'value' when calling `::subscribe` with a signal. Now, if you don't specify an event name, 'value' is assumed. --- package.json | 2 +- src/display-buffer.coffee | 2 +- src/editor.coffee | 4 ++-- src/pane-container.coffee | 4 ++-- src/pane.coffee | 4 ++-- src/tokenized-buffer.coffee | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 7b2756acb..e2a29142b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "clear-cut": "0.2.0", "coffee-script": "1.6.3", "coffeestack": "0.6.0", - "emissary": "0.19.0", + "emissary": "0.31.0", "first-mate": "0.14.0", "fs-plus": "0.14.0", "fstream": "0.1.24", diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index a7904fcad..6974ad9ba 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -35,7 +35,7 @@ class DisplayBuffer extends Model @subscribe @buffer, 'markers-updated', @handleBufferMarkersUpdated @subscribe @buffer, 'marker-created', @handleBufferMarkerCreated - @subscribe @$softWrap, 'value', (softWrap) => + @subscribe @$softWrap, (softWrap) => @emit 'soft-wrap-changed', softWrap @updateWrappedScreenLines() diff --git a/src/editor.coffee b/src/editor.coffee index 291024c7b..085a68ef0 100644 --- a/src/editor.coffee +++ b/src/editor.coffee @@ -73,8 +73,8 @@ class Editor extends Model @languageMode = new LanguageMode(this, @buffer.getExtension()) - @subscribe @$scrollTop, 'value', (scrollTop) => @emit 'scroll-top-changed', scrollTop - @subscribe @$scrollLeft, 'value', (scrollLeft) => @emit 'scroll-left-changed', scrollLeft + @subscribe @$scrollTop, (scrollTop) => @emit 'scroll-top-changed', scrollTop + @subscribe @$scrollLeft, (scrollLeft) => @emit 'scroll-left-changed', scrollLeft atom.project.addEditor(this) if registerEditor diff --git a/src/pane-container.coffee b/src/pane-container.coffee index ee875254e..60393d634 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -21,8 +21,8 @@ class PaneContainer extends View else @model = new PaneContainerModel({root: params?.root?.model}) - @subscribe @model.$root, 'value', @onRootChanged - @subscribe @model.$activePaneItem.changes, 'value', @onActivePaneItemChanged + @subscribe @model.$root, @onRootChanged + @subscribe @model.$activePaneItem.changes, @onActivePaneItemChanged viewForModel: (model) -> if model? diff --git a/src/pane.coffee b/src/pane.coffee index c374bcb79..11b564dbe 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -50,13 +50,13 @@ class Pane extends View handleEvents: -> @subscribe @model, 'destroyed', => @remove() - @subscribe @model.$activeItem, 'value', @onActiveItemChanged + @subscribe @model.$activeItem, @onActiveItemChanged @subscribe @model, 'item-added', @onItemAdded @subscribe @model, 'item-removed', @onItemRemoved @subscribe @model, 'item-moved', @onItemMoved @subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed @subscribe @model, 'item-destroyed', @onItemDestroyed - @subscribe @model.$active, 'value', @onActiveStatusChanged + @subscribe @model.$active, @onActiveStatusChanged @subscribe this, 'focusin', => @model.focus() @subscribe this, 'focusout', => @model.blur() diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index bb28a708e..f6a4c3681 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -35,7 +35,7 @@ class TokenizedBuffer extends Model @subscribe @buffer, "changed", (e) => @handleBufferChange(e) @subscribe @buffer, "path-changed", => @bufferPath = @buffer.getPath() - @subscribe @$tabLength.changes.onValue (tabLength) => + @subscribe @$tabLength.changes, (tabLength) => lastRow = @buffer.getLastRow() @tokenizedLines = @buildPlaceholderTokenizedLinesForRows(0, lastRow) @invalidateRow(0) From b21eb6f9348179878ed98448a1f98d05ff0da7c5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 19:13:00 -0700 Subject: [PATCH 81/89] Drop .flexbox-repaint-hack div and use pseudo selector instead We don't actually need structural markup to ensure that all pane views are absolutely positioned. We can just use the `> *` selector inside of .pane-items. /cc @probablycorey is there anything I'm missing here? --- src/editor-view.coffee | 2 +- src/pane.coffee | 3 +-- src/workspace-view.coffee | 2 +- static/panes.less | 15 ++++++--------- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/editor-view.coffee b/src/editor-view.coffee index 220dd699a..9ae326c54 100644 --- a/src/editor-view.coffee +++ b/src/editor-view.coffee @@ -1073,7 +1073,7 @@ class EditorView extends View # # Returns a {Pane}. getPane: -> - @parent('.flexbox-repaint-hack').parents('.pane').view() + @parent('.item-views').parents('.pane').view() remove: (selector, keepData) -> return super if keepData or @removed diff --git a/src/pane.coffee b/src/pane.coffee index 11b564dbe..fd1b3bb44 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -22,8 +22,7 @@ class Pane extends View @content: (wrappedView) -> @div class: 'pane', tabindex: -1, => - @div class: 'item-views', => - @div class: 'flexbox-repaint-hack', outlet: 'itemViews' + @div class: 'item-views', outlet: 'itemViews' @delegatesProperties 'items', 'activeItem', toProperty: 'model' @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index d4281c7e9..e13dab341 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -240,7 +240,7 @@ class WorkspaceView extends View # Private: Returns an Array of all of the application's {EditorView}s. getEditorViews: -> - @panes.find('.pane > .item-views > .flexbox-repaint-hack > .editor').map(-> $(this).view()).toArray() + @panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray() # Private: Retrieves all of the modified buffers that are open and unsaved. # diff --git a/static/panes.less b/static/panes.less index d5e016427..f2fcb14ab 100644 --- a/static/panes.less +++ b/static/panes.less @@ -25,27 +25,24 @@ -webkit-flex: 1; -webkit-flex-direction: column; - .pane-item { - color: @text-color; - background-color: @pane-item-background-color; - } - .item-views { -webkit-flex: 1; display: -webkit-flex; min-height: 0; + min-width: 0; position: relative; - .flexbox-repaint-hack { - display: -webkit-flex; - -webkit-flex-flow: column; + .pane-item { + color: @text-color; + background-color: @pane-item-background-color; + } + > * { position: absolute; top: 0; right: 0; bottom: 0; left: 0; - min-width: 0; } } } From 2188dd201ddab34099769139768569d13da26c3a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 19:28:33 -0700 Subject: [PATCH 82/89] Update wrap-guide to 0.11.0 for specs fix with flexbox panes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2a29142b..2d4a75b4a 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "visual-bell": "0.6.0", "welcome": "0.4.0", "whitespace": "0.10.0", - "wrap-guide": "0.10.0", + "wrap-guide": "0.11.0", "language-c": "0.2.0", "language-clojure": "0.1.0", "language-coffee-script": "0.4.0", From f5bc71e559a7159267b5d5d2308eb3bc0000916d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 19:49:25 -0700 Subject: [PATCH 83/89] Move $.fn.hasFocus to space-pen --- package.json | 2 +- src/space-pen-extensions.coffee | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index 2d4a75b4a..5e4927e83 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "scandal": "0.11.0", "season": "0.14.0", "semver": "1.1.4", - "space-pen": "3.0.3", + "space-pen": "3.1.0", "temp": "0.5.0", "text-buffer": "0.12.0", "underscore-plus": "0.6.1", diff --git a/src/space-pen-extensions.coffee b/src/space-pen-extensions.coffee index 02d08f8ab..d7504ebe2 100644 --- a/src/space-pen-extensions.coffee +++ b/src/space-pen-extensions.coffee @@ -59,9 +59,6 @@ jQuery.fn.destroyTooltip = -> @hideTooltip() @tooltip('destroy') -jQuery.fn.hasFocus = -> - @is(':focus') or @is(':has(:focus)') - jQuery.fn.setTooltip.getKeystroke = getKeystroke jQuery.fn.setTooltip.humanizeKeystrokes = humanizeKeystrokes From 28b085be1c92be3a4949ac5d6109f2631362da18 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 20:48:01 -0700 Subject: [PATCH 84/89] Rename ::makeActive to ::activate and focus panes when they're activated --- spec/pane-container-model-spec.coffee | 10 +++++----- spec/pane-container-spec.coffee | 2 +- spec/pane-spec.coffee | 18 ++++-------------- spec/workspace-view-spec.coffee | 5 ++--- src/pane-container-model.coffee | 4 ++-- src/pane-container.coffee | 3 ++- src/pane-model.coffee | 12 +++++++----- src/pane.coffee | 18 +++++++----------- src/workspace-view.coffee | 6 +++--- 9 files changed, 33 insertions(+), 45 deletions(-) diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index dd6e14e60..a37772d7d 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -19,7 +19,7 @@ describe "PaneContainerModel", -> expect(pane3B.focused).toBe true it "preserves the active pane across serialization, independent of focus", -> - pane3A.makeActive() + pane3A.activate() expect(containerA.activePane).toBe pane3A containerB = containerA.testSerialization() @@ -37,20 +37,20 @@ describe "PaneContainerModel", -> expect(container.activePane).toBe pane1 expect(pane1.active).toBe true - it "references the most pane on which ::makeActive was most recently called", -> + it "references the most pane on which ::activate was most recently called", -> pane2 = pane1.splitRight() - pane2.makeActive() + pane2.activate() expect(container.activePane).toBe pane2 expect(pane1.active).toBe false expect(pane2.active).toBe true - pane1.makeActive() + pane1.activate() expect(container.activePane).toBe pane1 expect(pane1.active).toBe true expect(pane2.active).toBe false it "is reassigned to the next pane if the current active pane is destroyed", -> pane2 = pane1.splitRight() - pane2.makeActive() + pane2.activate() pane2.destroy() expect(container.activePane).toBe pane1 expect(pane1.active).toBe true diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 2dd46707f..30a89328d 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -249,7 +249,7 @@ describe "PaneContainer", -> expect(activeItemChangedHandler).not.toHaveBeenCalled() it "is triggered when the active pane is changed", -> - pane1.makeActive() + pane1.activate() expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index 204730aee..cf85d9828 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -41,7 +41,7 @@ describe "Pane", -> expect(pane.activeItem).toBe view2 it "triggers 'pane:active-item-changed' if the item isn't already the activeItem", -> - pane.makeActive() + pane.activate() itemChangedHandler = jasmine.createSpy("itemChangedHandler") container.on 'pane:active-item-changed', itemChangedHandler @@ -447,24 +447,14 @@ describe "Pane", -> paneToRight = pane.splitRight(pane.copyActiveItem()) container.attachToDom() - describe "when the removed pane is focused", -> - it "activates and focuses the next pane", -> - pane.focus() + describe "when the removed pane is active", -> + it "makes the next the next pane active and focuses it", -> + pane.activate() pane.remove() expect(paneToLeft.isActive()).toBeFalsy() expect(paneToRight.isActive()).toBeTruthy() expect(paneToRight).toMatchSelector ':has(:focus)' - describe "when the removed pane is active but not focused", -> - it "activates the next pane, but does not focus it", -> - $(document.activeElement).blur() - expect(pane).not.toMatchSelector ':has(:focus)' - pane.makeActive() - pane.remove() - expect(paneToLeft.isActive()).toBeFalsy() - expect(paneToRight.isActive()).toBeTruthy() - expect(paneToRight).not.toMatchSelector ':has(:focus)' - describe "when the removed pane is not active", -> it "does not affect the active pane or the focus", -> paneToLeft.focus() diff --git a/spec/workspace-view-spec.coffee b/spec/workspace-view-spec.coffee index 8db9b479a..51625a590 100644 --- a/spec/workspace-view-spec.coffee +++ b/spec/workspace-view-spec.coffee @@ -311,7 +311,6 @@ describe "WorkspaceView", -> describe "when there is an active pane", -> [pane1] = [] beforeEach -> - spyOn(Pane.prototype, 'focus').andCallFake -> @makeActive() pane1 = atom.workspaceView.getActivePane() it "creates a new pane and reuses the file when already open", -> @@ -322,7 +321,7 @@ describe "WorkspaceView", -> expect(pane2.itemForUri('b')).not.toBeFalsy() expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] - pane1.focus() + pane1.activate() expect(atom.workspaceView.getActivePane()[0]).toBe pane1[0] atom.workspaceView.openSingletonSync('b', split: 'right') @@ -346,7 +345,7 @@ describe "WorkspaceView", -> expect(pane2.itemForUri('file1')).toBeFalsy() expect(atom.workspaceView.panes.find('.pane-row .pane').toArray()).toEqual [pane1[0], pane2[0]] - pane2.focus() + pane2.activate() expect(atom.workspaceView.getActivePane()[0]).toBe pane2[0] atom.workspaceView.openSingletonSync('file1', split: 'left') diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 92fcd4558..81f93df64 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -34,12 +34,12 @@ class PaneContainerModel extends Model getPanes: -> @root?.getPanes() ? [] - makeNextPaneActive: -> + activateNextPane: -> panes = @getPanes() if panes.length > 1 currentIndex = panes.indexOf(@activePane) nextIndex = (currentIndex + 1) % panes.length - @activePane = panes[nextIndex] + panes[nextIndex].activate() else @activePane = null diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 60393d634..6c03ddbe2 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -54,6 +54,8 @@ class PaneContainer extends View view = @viewForModel(root) @append(view) focusedElement?.focus() + else + atom.workspaceView?.focus() if focusedElement? onActivePaneItemChanged: (activeItem) => @trigger 'pane-container:active-pane-item-changed', [activeItem] @@ -120,7 +122,6 @@ class PaneContainer extends View panes[nextIndex].focus() true else - atom.workspaceView?.focus() false focusPreviousPane: -> diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 711dd731f..48547e8aa 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -34,7 +34,7 @@ class PaneModel extends Model @subscribe @items.onRemoval (item, index) => @unsubscribe item if typeof item.on is 'function' - @makeActive() if params?.active + @activate() if params?.active serializeParams: -> items: compact(@items.map((item) -> item.serialize?())) @@ -54,13 +54,15 @@ class PaneModel extends Model focus: -> @focused = true - @makeActive() + @activate() unless @isActive() blur: -> @focused = false true # if this is called from an event handler, don't cancel it - makeActive: -> @container?.activePane = this + activate: -> + @container?.activePane = this + @emit 'activated' getPanes: -> [this] @@ -161,7 +163,7 @@ class PaneModel extends Model # Private: Called by model superclass destroyed: -> - @container.makeNextPaneActive() if @isActive() + @container.activateNextPane() if @isActive() item.destroy?() for item in @items.slice() # Public: Prompt the user to save the given item. @@ -248,5 +250,5 @@ class PaneModel extends Model when 'before' then @parent.insertChildBefore(this, newPane) when 'after' then @parent.insertChildAfter(this, newPane) - newPane.makeActive() + newPane.activate() newPane diff --git a/src/pane.coffee b/src/pane.coffee index fd1b3bb44..37c15add7 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -30,7 +30,7 @@ class Pane extends View 'moveItem', 'moveItemToPane', 'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems', 'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems', 'itemForUri', 'showItemForUri', 'promptToSaveItem', 'copyActiveItem', - 'isActive', 'makeActive', toProperty: 'model' + 'isActive', 'activate', toProperty: 'model' previousActiveItem: null @@ -55,6 +55,7 @@ class Pane extends View @subscribe @model, 'item-moved', @onItemMoved @subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed @subscribe @model, 'item-destroyed', @onItemDestroyed + @subscribe @model, 'activated', @onActivated @subscribe @model.$active, @onActiveStatusChanged @subscribe this, 'focusin', => @model.focus() @@ -99,6 +100,9 @@ class Pane extends View @attached = true @trigger 'pane:attached', [this] + onActivated: => + @focus() unless @hasFocus() + onActiveStatusChanged: (active) => if active @addClass('active') @@ -123,13 +127,13 @@ class Pane extends View return unless item? - isFocused = @is(':has(:focus)') + hasFocus = @hasFocus() item.on? 'title-changed', @activeItemTitleChanged view = @viewForItem(item) @itemViews.children().not(view).hide() @itemViews.append(view) unless view.parent().is(@itemViews) view.show() if @attached - view.focus() if isFocused + view.focus() if hasFocus @activeView = view @trigger 'pane:active-item-changed', [item] @@ -143,11 +147,6 @@ class Pane extends View else if viewToRemove = @viewsByItem.get(item) @viewsByItem.delete(item) - removingLastItem = @model.items.length is 0 - hasFocus = @hasFocus() - - @getContainer().focusNextPane() if hasFocus and removingLastItem - if viewToRemove? viewToRemove.setModel?(null) if destroyed @@ -155,8 +154,6 @@ class Pane extends View else viewToRemove.detach() - # @focus() if hasFocus and not removingLastItem - @trigger 'pane:item-removed', [item, index] onItemMoved: (item, newIndex) => @@ -202,7 +199,6 @@ class Pane extends View @closest('.panes').view() beforeRemove: -> - @getContainer()?.focusNextPane() if @hasFocus() @model.destroy() unless @model.isDestroyed() # Private: diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index e13dab341..daac10423 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -173,7 +173,7 @@ class WorkspaceView extends View @itemOpened(editor) activePane.showItem(editor) - activePane.focus() if changeFocus + activePane.activate() if changeFocus @trigger "uri-opened" editor .catch (error) -> @@ -208,7 +208,7 @@ class WorkspaceView extends View @itemOpened(paneItem) - pane.focus() if changeFocus + pane.activate() if changeFocus paneItem openSingletonSync: (uri, {changeFocus, initialLine, split}={}) -> @@ -219,7 +219,7 @@ class WorkspaceView extends View if pane paneItem = pane.itemForUri(uri) pane.showItem(paneItem) - pane.focus() if changeFocus + pane.activate() if changeFocus paneItem else @openSync(uri, {changeFocus, initialLine, split}) From 2a8a5268c68f5f410361ccf523c94b453c970e72 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 11 Jan 2014 22:06:37 -0700 Subject: [PATCH 85/89] Clean up PaneModel api docs --- src/pane-model.coffee | 107 ++++++++++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 48547e8aa..8ccef977e 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -5,6 +5,9 @@ Serializable = require 'serializable' PaneAxisModel = require './pane-axis-model' Pane = null +# Public: A container for multiple items, one of which is *active* at a given +# time. With the default packages, a tab is displayed for each item and the +# active item's view is displayed. module.exports = class PaneModel extends Model atom.deserializers.add(this) @@ -15,12 +18,16 @@ class PaneModel extends Model activeItem: null focused: false + # Public: Only one pane is considered *active* at a time. A pane is activated + # when it is focused, and when focus returns to the pane container after + # moving to another element such as a panel, it returns to the active pane. @behavior 'active', -> @$container .switch((container) -> container?.$activePane) .map((activePane) => activePane is this) .distinctUntilChanged() + # Private: constructor: (params) -> super @@ -36,37 +43,45 @@ class PaneModel extends Model @activate() if params?.active + # Private: Called by the Serializable mixin during serialization. serializeParams: -> items: compact(@items.map((item) -> item.serialize?())) activeItemUri: @activeItem?.getUri?() focused: @focused active: @active + # Private: Called by the Serializable mixin during deserialization. deserializeParams: (params) -> {items, activeItemUri} = params params.items = items.map (itemState) -> atom.deserializers.deserialize(itemState) params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri params + # Private: Called by the view layer to construct a view for this model. getViewClass: -> Pane ?= require './pane' isActive: -> @active + # Private: Called by the view layer to indicate that the pane has gained focus. focus: -> @focused = true @activate() unless @isActive() + # Private: Called by the view layer to indicate that the pane has lost focus. blur: -> @focused = false true # if this is called from an event handler, don't cancel it + # Public: Makes this pane the *active* pane, causing it to gain focus + # immediately. activate: -> @container?.activePane = this @emit 'activated' + # Private: getPanes: -> [this] - # Public: Returns all contained views. + # Public: getItems: -> @items.slice() @@ -74,7 +89,7 @@ class PaneModel extends Model itemAtIndex: (index) -> @items[index] - # Public: Switches to the next contained item. + # Public: Makes the next item active. showNextItem: -> index = @getActiveItemIndex() if index < @items.length - 1 @@ -82,7 +97,7 @@ class PaneModel extends Model else @showItemAtIndex(0) - # Public: Switches to the previous contained item. + # Public: Makes the previous item active. showPreviousItem: -> index = @getActiveItemIndex() if index > 0 @@ -90,21 +105,29 @@ class PaneModel extends Model else @showItemAtIndex(@items.length - 1) - # Public: Returns the index of the currently active item. + # Public: Returns the index of the current active item. getActiveItemIndex: -> @items.indexOf(@activeItem) - # Public: Switch to the item associated with the given index. + # Public: Makes the item at the given index active. showItemAtIndex: (index) -> @showItem(@itemAtIndex(index)) - # Public: Focuses the given item. + # Public: Makes the given item active, adding the item if necessary. showItem: (item) -> if item? @addItem(item) @activeItem = item - # Public: Add an additional item at the specified index. + # Public: Adds the item to the pane. + # + # * item: + # The item to add. It can be a model with an associated view or a view. + # * index: + # An optional index at which to add the item. If omitted, the item is + # added to the end. + # + # Returns the added item addItem: (item, index=@getActiveItemIndex() + 1) -> return if item in @items @@ -112,12 +135,10 @@ class PaneModel extends Model @emit 'item-added', item, index item - # Public: removeItem: (item, destroying) -> index = @items.indexOf(item) @removeItemAtIndex(index, destroying) if index >= 0 - # Public: Just remove the item at the given index. removeItemAtIndex: (index, destroying) -> item = @items[index] @showNextItem() if item is @activeItem and @items.length > 1 @@ -125,25 +146,26 @@ class PaneModel extends Model @emit 'item-removed', item, index, destroying @destroy() if @items.length is 0 - # Public: Moves the given item to a the new index. + # Public: Moves the given item to the specified index. moveItem: (item, newIndex) -> oldIndex = @items.indexOf(item) @items.splice(oldIndex, 1) @items.splice(newIndex, 0, item) @emit 'item-moved', item, newIndex - # Public: Moves the given item to another pane. + # Public: Moves the given item to the given index at another pane. moveItemToPane: (item, pane, index) -> pane.addItem(item, index) @removeItem(item) - # Public: Remove the currently active item. + # Public: Destroys the currently active item and make the next item active. destroyActiveItem: -> @destroyItem(@activeItem) false - # Public: Remove the specified item. - destroyItem: (item, options) -> + # Public: Destroys the given item. If it is the active item, activate the next + # one. If this is the last item, also destroys the pane. + destroyItem: (item) -> @emit 'before-item-destroyed', item if @promptToSaveItem(item) @emit 'item-destroyed', item @@ -153,20 +175,21 @@ class PaneModel extends Model else false - # Public: Remove and delete all items. + # Public: Destroys all items and destroys the pane. destroyItems: -> @destroyItem(item) for item in @getItems() - # Public: Remove and delete all but the currently focused item. + # Public: Destroys all items but the active one. destroyInactiveItems: -> @destroyItem(item) for item in @getItems() when item isnt @activeItem - # Private: Called by model superclass + # Private: Called by model superclass. destroyed: -> @container.activateNextPane() if @isActive() item.destroy?() for item in @items.slice() - # Public: Prompt the user to save the given item. + # Public: Prompts the user to save the given item if it can be saved and is + # currently unsaved. promptToSaveItem: (item) -> return true unless item.shouldPromptToSave?() @@ -181,15 +204,18 @@ class PaneModel extends Model when 1 then false when 2 then true - # Public: Saves the currently focused item. + # Public: Saves the active item. saveActiveItem: -> @saveItem(@activeItem) - # Public: Save and prompt for path for the currently focused item. + # Public: Saves the active item at a prompted-for location. saveActiveItemAs: -> @saveItemAs(@activeItem) - # Public: Saves the specified item and call the next action when complete. + # Public: Saves the specified item. + # + # * item: The item to save. + # * nextAction: An optional function which will be called after the item is saved. saveItem: (item, nextAction) -> if item.getUri?() item.save?() @@ -197,8 +223,10 @@ class PaneModel extends Model else @saveItemAs(item, nextAction) - # Public: Prompts for path and then saves the specified item. Upon completion - # it also calls the next action. + # Public: Saves the given item at a prompted-for location. + # + # * item: The item to save. + # * nextAction: An optional function which will be called after the item is saved. saveItemAs: (item, nextAction) -> return unless item.saveAs? @@ -209,15 +237,17 @@ class PaneModel extends Model item.saveAs(path) nextAction?() - # Public: Saves all items in this pane. + # Public: Saves all items. saveItems: -> @saveItem(item) for item in @getItems() - # Public: Finds the first item that matches the given uri. + # Public: Returns the first item that matches the given URI or undefined if + # none exists. itemForUri: (uri) -> find @items, (item) -> item.getUri?() is uri - # Public: Focuses the first item that matches the given uri. + # Public: Activates the first item that matches the given URI. Returns a + # boolean indicating whether a matching item was found. showItemForUri: (uri) -> if item = @itemForUri(uri) @showItem(item) @@ -229,18 +259,43 @@ class PaneModel extends Model copyActiveItem: -> @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize()) + # Public: Creates a new pane to the left of the receiver. + # + # * params: + # + items: An optional array of items with which to construct the new pane. + # + # Returns the new {PaneModel}. splitLeft: (params) -> @split('horizontal', 'before', params) + # Public: Creates a new pane to the right of the receiver. + # + # * params: + # + items: An optional array of items with which to construct the new pane. + # + # Returns the new {PaneModel}. splitRight: (params) -> @split('horizontal', 'after', params) + # Public: Creates a new pane above the receiver. + # + # * params: + # + items: An optional array of items with which to construct the new pane. + # + # Returns the new {PaneModel}. splitUp: (params) -> @split('vertical', 'before', params) + # Public: Creates a new pane below the receiver. + # + # * params: + # + items: An optional array of items with which to construct the new pane. + # + # Returns the new {PaneModel}. splitDown: (params) -> @split('vertical', 'after', params) + # Private: split: (orientation, side, params) -> if @parent.orientation isnt orientation @parent.replaceChild(this, new PaneAxisModel({@container, orientation, children: [this]})) From 561e31c0c53ff0478778532bf9403242ffbf6c8d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 12 Jan 2014 17:25:51 -0700 Subject: [PATCH 86/89] Remove ::removeItemAtIndex and make ::removeItem private Call ::destroyItem or just destroy the item directly (it should emit the 'destroyed' event) --- spec/pane-container-spec.coffee | 28 ++++++++++++++-------------- spec/pane-model-spec.coffee | 8 ++++---- spec/pane-spec.coffee | 27 +++++++++++++-------------- src/pane-model.coffee | 6 ++---- src/pane.coffee | 10 +++++----- 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 30a89328d..b9cb64d7d 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -183,27 +183,27 @@ describe "PaneContainer", -> expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a - it "is triggered when the active pane item is removed", -> + it "is triggered when the active pane item is destroyed", -> pane1.showItem(item1b) activeItemChangedHandler.reset() - pane1.removeItem(item1b) + pane1.destroyItem(item1b) expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a - it "is not triggered when an inactive pane item is removed", -> + it "is not triggered when an inactive pane item is destroyed", -> pane1.showItem(item1b) activeItemChangedHandler.reset() - pane1.removeItem(item1a) + pane1.destroyItem(item1a) expect(activeItemChangedHandler).not.toHaveBeenCalled() - it "is triggered when all pane items are removed", -> - pane1.removeItem(item1a) + it "is triggered when all pane items are destroyed", -> + pane1.destroyItem(item1a) expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined - it "is triggered when the pane is removed", -> + it "is triggered when the pane is destroyed", -> pane1.remove() expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toBe undefined @@ -224,27 +224,27 @@ describe "PaneContainer", -> pane1.showItem(item1b) expect(activeItemChangedHandler).not.toHaveBeenCalled() - it "is triggered when the active pane item removed from the active pane", -> + it "is triggered when the active pane's active item is destroyed", -> pane2.showItem(item2b) activeItemChangedHandler.reset() - pane2.removeItem(item2b) + pane2.destroyItem(item2b) expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2a - it "is not triggered when the active pane item removed from an inactive pane", -> + it "is not triggered when an inactive pane's active item is destroyed", -> pane1.showItem(item1b) activeItemChangedHandler.reset() - pane1.removeItem(item1b) + pane1.destroyItem(item1b) expect(activeItemChangedHandler).not.toHaveBeenCalled() - it "is triggered when the active pane is removed", -> + it "is triggered when the active pane is destroyed", -> pane2.remove() expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a - it "is not triggered when an inactive pane is removed", -> + it "is not triggered when an inactive pane is destroyed", -> pane1.remove() expect(activeItemChangedHandler).not.toHaveBeenCalled() @@ -263,7 +263,7 @@ describe "PaneContainer", -> expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item3a - it "is not triggered when the non active pane is removed", -> + it "is not triggered when an inactive pane is destroyed", -> pane3 = pane2.splitDown(item3a) activeItemChangedHandler.reset() diff --git a/spec/pane-model-spec.coffee b/spec/pane-model-spec.coffee index 77f1db022..6eb897b06 100644 --- a/spec/pane-model-spec.coffee +++ b/spec/pane-model-spec.coffee @@ -84,12 +84,12 @@ describe "PaneModel", -> pane2 = pane1.splitRight() expect(pane2.focused).toBe true - describe "::removeItemAtIndex(index)", -> - describe "when the last item is removed", -> + describe "::destroyItem(item)", -> + describe "when the last item is destroyed", -> it "destroys the pane", -> pane = new PaneModel(items: ["A", "B"]) - pane.removeItemAtIndex(0) - pane.removeItemAtIndex(0) + pane.destroyItem("A") + pane.destroyItem("B") expect(pane.isDestroyed()).toBe true describe "when an item emits a destroyed event", -> diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index cf85d9828..c24d10447 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -125,10 +125,10 @@ describe "Pane", -> pane.showPreviousItem() expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2 - pane.removeItem(model2) + pane.destroyItem(model2) expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 1 - pane.removeItem(model1) + pane.destroyItem(model1) expect(pane.itemViews.find('.test-view').length).toBe initialViewCount describe "when showing a view item", -> @@ -201,27 +201,26 @@ describe "Pane", -> pane.destroyItem(view1) expect(view1.wasRemoved).toBe true - describe "::removeItem(item)", -> it "removes the item from the items list and shows the next item if it was showing", -> - pane.removeItem(view1) + pane.destroyItem(view1) expect(pane.getItems()).toEqual [editor1, view2, editor2] expect(pane.activeItem).toBe editor1 pane.showItem(editor2) - pane.removeItem(editor2) + pane.destroyItem(editor2) expect(pane.getItems()).toEqual [editor1, view2] expect(pane.activeItem).toBe editor1 it "triggers 'pane:item-removed' with the item and its former index", -> itemRemovedHandler = jasmine.createSpy("itemRemovedHandler") pane.on 'pane:item-removed', itemRemovedHandler - pane.removeItem(editor1) + pane.destroyItem(editor1) expect(itemRemovedHandler).toHaveBeenCalled() expect(itemRemovedHandler.argsForCall[0][1..2]).toEqual [editor1, 1] describe "when removing the last item", -> it "removes the pane", -> - pane.removeItem(item) for item in pane.getItems() + pane.destroyItem(item) for item in pane.getItems() expect(pane.hasParent()).toBeFalsy() describe "when the pane is focused", -> @@ -231,22 +230,22 @@ describe "Pane", -> pane2 = pane.splitRight(new TestView(id: 'view-3', text: 'View 3')) pane.focus() expect(pane).toMatchSelector(':has(:focus)') - pane.removeItem(item) for item in pane.getItems() + pane.destroyItem(item) for item in pane.getItems() expect(pane2).toMatchSelector ':has(:focus)' describe "when the item is a view", -> it "removes the item from the 'item-views' div", -> expect(view1.parent()).toMatchSelector pane.itemViews - pane.removeItem(view1) + pane.destroyItem(view1) expect(view1.parent()).not.toMatchSelector pane.itemViews describe "when the item is a model", -> it "removes the associated view only when all items that require it have been removed", -> pane.showItem(editor1) pane.showItem(editor2) - pane.removeItem(editor2) + pane.destroyItem(editor2) expect(pane.itemViews.find('.editor')).toExist() - pane.removeItem(editor1) + pane.destroyItem(editor1) expect(pane.itemViews.find('.editor')).not.toExist() describe "::moveItem(item, index)", -> @@ -286,9 +285,9 @@ describe "Pane", -> describe "when it is the last item on the source pane", -> it "removes the source pane, but does not destroy the item", -> - pane.removeItem(view1) - pane.removeItem(view2) - pane.removeItem(editor2) + pane.destroyItem(view1) + pane.destroyItem(view2) + pane.destroyItem(editor2) expect(pane.getItems()).toEqual [editor1] pane.moveItemToPane(editor1, pane2, 1) diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 8ccef977e..15db07345 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -135,12 +135,10 @@ class PaneModel extends Model @emit 'item-added', item, index item + # Private: removeItem: (item, destroying) -> index = @items.indexOf(item) - @removeItemAtIndex(index, destroying) if index >= 0 - - removeItemAtIndex: (index, destroying) -> - item = @items[index] + return if index is -1 @showNextItem() if item is @activeItem and @items.length > 1 @items.splice(index, 1) @emit 'item-removed', item, index, destroying diff --git a/src/pane.coffee b/src/pane.coffee index 37c15add7..b52329127 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -26,11 +26,11 @@ class Pane extends View @delegatesProperties 'items', 'activeItem', toProperty: 'model' @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', - 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', 'removeItem', 'removeItemAtIndex', - 'moveItem', 'moveItemToPane', 'destroyItem', 'destroyItems', 'destroyActiveItem', - 'destroyInactiveItems', 'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', - 'saveItems', 'itemForUri', 'showItemForUri', 'promptToSaveItem', 'copyActiveItem', - 'isActive', 'activate', toProperty: 'model' + 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', 'moveItem', 'moveItemToPane', + 'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems', + 'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems', + 'itemForUri', 'showItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive', + 'activate', toProperty: 'model' previousActiveItem: null From b438b311f341375e4052bcc918849449cfd57902 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 12 Jan 2014 17:40:57 -0700 Subject: [PATCH 87/89] Rename showItem methods to activateItem These methods set the *active* item, so the verb activate provides a clearer correspondence. We could change the noun to "shown" item, but that's awkward and having both active panes and active items is a nice correspondence in terminology. --- spec/editor-view-spec.coffee | 4 +- spec/pane-container-spec.coffee | 22 +++++------ spec/pane-spec.coffee | 66 ++++++++++++++++----------------- spec/workspace-view-spec.coffee | 10 ++--- src/pane-model.coffee | 24 ++++++------ src/pane.coffee | 43 ++++++++++++++------- src/workspace-view.coffee | 6 +-- 7 files changed, 95 insertions(+), 80 deletions(-) diff --git a/spec/editor-view-spec.coffee b/spec/editor-view-spec.coffee index 2f428dddd..6ab18ccdf 100644 --- a/spec/editor-view-spec.coffee +++ b/spec/editor-view-spec.coffee @@ -2756,7 +2756,7 @@ describe "EditorView", -> editorView = atom.workspaceView.getActiveView() view = $$ -> @div id: 'view', tabindex: -1, 'View' - editorView.getPane().showItem(view) + editorView.getPane().activateItem(view) expect(editorView.isVisible()).toBeFalsy() editorView.setText('hidden changes') @@ -2764,7 +2764,7 @@ describe "EditorView", -> displayUpdatedHandler = jasmine.createSpy("displayUpdatedHandler") editorView.on 'editor:display-updated', displayUpdatedHandler - editorView.getPane().showItem(editorView.getModel()) + editorView.getPane().activateItem(editorView.getModel()) expect(editorView.isVisible()).toBeTruthy() waitsFor -> diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index b9cb64d7d..5fb759860 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -88,7 +88,7 @@ describe "PaneContainer", -> describe ".saveAll()", -> it "saves all open pane items", -> - pane1.showItem(new TestView('4')) + pane1.activateItem(new TestView('4')) container.saveAll() @@ -167,24 +167,24 @@ describe "PaneContainer", -> describe "when there is one pane", -> it "is triggered when a new pane item is added", -> - pane1.showItem(item1b) + pane1.activateItem(item1b) expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1b it "is not triggered when the active pane item is shown again", -> - pane1.showItem(item1a) + pane1.activateItem(item1a) expect(activeItemChangedHandler).not.toHaveBeenCalled() it "is triggered when switching to an existing pane item", -> - pane1.showItem(item1b) + pane1.activateItem(item1b) activeItemChangedHandler.reset() - pane1.showItem(item1a) + pane1.activateItem(item1a) expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a it "is triggered when the active pane item is destroyed", -> - pane1.showItem(item1b) + pane1.activateItem(item1b) activeItemChangedHandler.reset() pane1.destroyItem(item1b) @@ -192,7 +192,7 @@ describe "PaneContainer", -> expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item1a it "is not triggered when an inactive pane item is destroyed", -> - pane1.showItem(item1b) + pane1.activateItem(item1b) activeItemChangedHandler.reset() pane1.destroyItem(item1a) @@ -216,16 +216,16 @@ describe "PaneContainer", -> activeItemChangedHandler.reset() it "is triggered when a new pane item is added to the active pane", -> - pane2.showItem(item2b) + pane2.activateItem(item2b) expect(activeItemChangedHandler.callCount).toBe 1 expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2b it "is not triggered when a new pane item is added to an inactive pane", -> - pane1.showItem(item1b) + pane1.activateItem(item1b) expect(activeItemChangedHandler).not.toHaveBeenCalled() it "is triggered when the active pane's active item is destroyed", -> - pane2.showItem(item2b) + pane2.activateItem(item2b) activeItemChangedHandler.reset() pane2.destroyItem(item2b) @@ -233,7 +233,7 @@ describe "PaneContainer", -> expect(activeItemChangedHandler.argsForCall[0][1]).toEqual item2a it "is not triggered when an inactive pane's active item is destroyed", -> - pane1.showItem(item1b) + pane1.activateItem(item1b) activeItemChangedHandler.reset() pane1.destroyItem(item1b) diff --git a/spec/pane-spec.coffee b/spec/pane-spec.coffee index c24d10447..d1d031240 100644 --- a/spec/pane-spec.coffee +++ b/spec/pane-spec.coffee @@ -32,10 +32,10 @@ describe "Pane", -> it "displays the first item in the pane", -> expect(pane.itemViews.find('#view-1')).toExist() - describe "::showItem(item)", -> + describe "::activateItem(item)", -> it "hides all item views except the one being shown and sets the activeItem", -> expect(pane.activeItem).toBe view1 - pane.showItem(view2) + pane.activateItem(view2) expect(view1.css('display')).toBe 'none' expect(view2.css('display')).not.toBe 'none' expect(pane.activeItem).toBe view2 @@ -46,35 +46,35 @@ describe "Pane", -> container.on 'pane:active-item-changed', itemChangedHandler expect(pane.activeItem).toBe view1 - pane.showItem(view2) - pane.showItem(view2) + pane.activateItem(view2) + pane.activateItem(view2) expect(itemChangedHandler.callCount).toBe 1 expect(itemChangedHandler.argsForCall[0][1]).toBe view2 itemChangedHandler.reset() - pane.showItem(editor1) + pane.activateItem(editor1) expect(itemChangedHandler).toHaveBeenCalled() expect(itemChangedHandler.argsForCall[0][1]).toBe editor1 itemChangedHandler.reset() - describe "if the pane's active view is focused before calling showItem", -> + describe "if the pane's active view is focused before calling activateItem", -> it "focuses the new active view", -> container.attachToDom() pane.focus() expect(pane.activeView).not.toBe view2 expect(pane.activeView).toMatchSelector ':focus' - pane.showItem(view2) + pane.activateItem(view2) expect(view2).toMatchSelector ':focus' describe "when the given item isn't yet in the items list on the pane", -> view3 = null beforeEach -> view3 = new TestView(id: 'view-3', text: "View 3") - pane.showItem(editor1) + pane.activateItem(editor1) expect(pane.getActiveItemIndex()).toBe 1 it "adds it to the items list after the active item", -> - pane.showItem(view3) + pane.activateItem(view3) expect(pane.getItems()).toEqual [view1, editor1, view3, view2, editor2] expect(pane.activeItem).toBe view3 expect(pane.getActiveItemIndex()).toBe 2 @@ -83,21 +83,21 @@ describe "Pane", -> events = [] container.on 'pane:item-added', (e, item, index) -> events.push(['pane:item-added', item, index]) container.on 'pane:active-item-changed', (e, item) -> events.push(['pane:active-item-changed', item]) - pane.showItem(view3) + pane.activateItem(view3) expect(events).toEqual [['pane:item-added', view3, 2], ['pane:active-item-changed', view3]] describe "when showing a model item", -> describe "when no view has yet been appended for that item", -> it "appends and shows a view to display the item based on its `.getViewClass` method", -> - pane.showItem(editor1) + pane.activateItem(editor1) editorView = pane.activeView expect(editorView.css('display')).not.toBe 'none' expect(editorView.editor).toBe editor1 describe "when a valid view has already been appended for another item", -> it "multiple views are created for multiple items", -> - pane.showItem(editor1) - pane.showItem(editor2) + pane.activateItem(editor1) + pane.activateItem(editor2) expect(pane.itemViews.find('.editor').length).toBe 2 editorView = pane.activeView expect(editorView.css('display')).not.toBe 'none' @@ -118,11 +118,11 @@ describe "Pane", -> serialize: -> {@id, @text} getViewClass: -> TestView - pane.showItem(model1) - pane.showItem(model2) + pane.activateItem(model1) + pane.activateItem(model2) expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2 - pane.showPreviousItem() + pane.activatePreviousItem() expect(pane.itemViews.find('.test-view').length).toBe initialViewCount + 2 pane.destroyItem(model2) @@ -134,7 +134,7 @@ describe "Pane", -> describe "when showing a view item", -> it "appends it to the itemViews div if it hasn't already been appended and shows it", -> expect(pane.itemViews.find('#view-2')).not.toExist() - pane.showItem(view2) + pane.activateItem(view2) expect(pane.itemViews.find('#view-2')).toExist() expect(pane.activeView).toBe view2 @@ -206,7 +206,7 @@ describe "Pane", -> expect(pane.getItems()).toEqual [editor1, view2, editor2] expect(pane.activeItem).toBe editor1 - pane.showItem(editor2) + pane.activateItem(editor2) pane.destroyItem(editor2) expect(pane.getItems()).toEqual [editor1, view2] expect(pane.activeItem).toBe editor1 @@ -241,8 +241,8 @@ describe "Pane", -> describe "when the item is a model", -> it "removes the associated view only when all items that require it have been removed", -> - pane.showItem(editor1) - pane.showItem(editor2) + pane.activateItem(editor1) + pane.activateItem(editor2) pane.destroyItem(editor2) expect(pane.itemViews.find('.editor')).toExist() pane.destroyItem(editor1) @@ -300,12 +300,12 @@ describe "Pane", -> it "preserves data by detaching instead of removing", -> view1.data('preservative', 1234) pane.moveItemToPane(view1, pane2, 1) - pane2.showItemAtIndex(1) + pane2.activateItemAtIndex(1) expect(pane2.activeView.data('preservative')).toBe 1234 describe "pane:close", -> it "destroys all items and removes the pane", -> - pane.showItem(editor1) + pane.activateItem(editor1) pane.trigger 'pane:close' expect(pane.hasParent()).toBeFalsy() expect(editor2.isDestroyed()).toBe true @@ -313,7 +313,7 @@ describe "Pane", -> describe "pane:close-other-items", -> it "destroys all items except the current", -> - pane.showItem(editor1) + pane.activateItem(editor1) pane.trigger 'pane:close-other-items' expect(editor2.isDestroyed()).toBe true expect(pane.getItems()).toEqual [editor1] @@ -323,7 +323,7 @@ describe "Pane", -> describe "when the current item has a save method", -> it "saves the current item", -> spyOn(editor2, 'save') - pane.showItem(editor2) + pane.activateItem(editor2) pane.saveActiveItem() expect(editor2.save).toHaveBeenCalled() @@ -341,7 +341,7 @@ describe "Pane", -> it "opens a save dialog and saves the current item as the selected path", -> newEditor = atom.project.openSync() spyOn(newEditor, 'saveAs') - pane.showItem(newEditor) + pane.activateItem(newEditor) pane.saveActiveItem() @@ -361,7 +361,7 @@ describe "Pane", -> describe "when the current item has a saveAs method", -> it "opens the save dialog and calls saveAs on the item with the selected path", -> spyOn(editor2, 'saveAs') - pane.showItem(editor2) + pane.activateItem(editor2) pane.saveActiveItemAs() @@ -409,7 +409,7 @@ describe "Pane", -> expect(activeItemTitleChangedHandler).toHaveBeenCalled() activeItemTitleChangedHandler.reset() - pane.showItem(view2) + pane.activateItem(view2) view2.trigger 'title-changed' expect(activeItemTitleChangedHandler).toHaveBeenCalled() @@ -417,7 +417,7 @@ describe "Pane", -> it "removes the pane item", -> filePath = temp.openSync('atom').path editor = atom.project.openSync(filePath) - pane.showItem(editor) + pane.activateItem(editor) expect(pane.items).toHaveLength(5) fs.removeSync(filePath) @@ -441,7 +441,7 @@ describe "Pane", -> [paneToLeft, paneToRight] = [] beforeEach -> - pane.showItem(editor1) + pane.activateItem(editor1) paneToLeft = pane.splitLeft(pane.copyActiveItem()) paneToRight = pane.splitRight(pane.copyActiveItem()) container.attachToDom() @@ -485,7 +485,7 @@ describe "Pane", -> describe "::getNextPane()", -> it "returns the next pane if one exists, wrapping around from the last pane to the first", -> - pane.showItem(editor1) + pane.activateItem(editor1) expect(pane.getNextPane()).toBeUndefined pane2 = pane.splitRight(pane.copyActiveItem()) expect(pane.getNextPane()).toBe pane2 @@ -531,7 +531,7 @@ describe "Pane", -> [pane1, view3, view4] = [] beforeEach -> pane1 = pane - pane.showItem(editor1) + pane.activateItem(editor1) view3 = new TestView(id: 'view-3', text: 'View 3') view4 = new TestView(id: 'view-4', text: 'View 4') @@ -606,7 +606,7 @@ describe "Pane", -> expect(newPane.getItems()).toEqual [view1, editor1, view2, editor2] it "restores the active item on deserialization", -> - pane.showItem(editor2) + pane.activateItem(editor2) newPane = pane.testSerialization() expect(newPane.activeItem).toEqual editor2 @@ -616,7 +616,7 @@ describe "Pane", -> class Unserializable getViewClass: -> TestView - pane.showItem(new Unserializable) + pane.activateItem(new Unserializable) newPane = pane.testSerialization() expect(newPane.activeItem).toEqual pane.items[0] diff --git a/spec/workspace-view-spec.coffee b/spec/workspace-view-spec.coffee index 51625a590..a47854dae 100644 --- a/spec/workspace-view-spec.coffee +++ b/spec/workspace-view-spec.coffee @@ -48,10 +48,10 @@ describe "WorkspaceView", -> pane2 = pane1.splitRight() pane3 = pane2.splitRight() pane4 = pane2.splitDown() - pane2.showItem(atom.project.openSync('b')) - pane3.showItem(atom.project.openSync('../sample.js')) + pane2.activateItem(atom.project.openSync('b')) + pane3.activateItem(atom.project.openSync('../sample.js')) pane3.activeItem.setCursorScreenPosition([2, 4]) - pane4.showItem(atom.project.openSync('../sample.txt')) + pane4.activateItem(atom.project.openSync('../sample.txt')) pane4.activeItem.setCursorScreenPosition([0, 2]) pane2.focus() @@ -570,7 +570,7 @@ describe "WorkspaceView", -> it "saves active editor until there are none", -> editor = atom.project.openSync('../sample.txt') spyOn(editor, 'save') - atom.workspaceView.getActivePane().showItem(editor) + atom.workspaceView.getActivePane().activateItem(editor) atom.workspaceView.trigger('core:save') expect(editor.save).toHaveBeenCalled() @@ -581,6 +581,6 @@ describe "WorkspaceView", -> it "saves active editor until there are none", -> editor = atom.project.openSync('../sample.txt') spyOn(editor, 'saveAs') - atom.workspaceView.getActivePane().showItem(editor) + atom.workspaceView.getActivePane().activateItem(editor) atom.workspaceView.trigger('core:save-as') expect(editor.saveAs).toHaveBeenCalled() diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 15db07345..66b231f85 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -90,31 +90,31 @@ class PaneModel extends Model @items[index] # Public: Makes the next item active. - showNextItem: -> + activateNextItem: -> index = @getActiveItemIndex() if index < @items.length - 1 - @showItemAtIndex(index + 1) + @activateItemAtIndex(index + 1) else - @showItemAtIndex(0) + @activateItemAtIndex(0) # Public: Makes the previous item active. - showPreviousItem: -> + activatePreviousItem: -> index = @getActiveItemIndex() if index > 0 - @showItemAtIndex(index - 1) + @activateItemAtIndex(index - 1) else - @showItemAtIndex(@items.length - 1) + @activateItemAtIndex(@items.length - 1) # Public: Returns the index of the current active item. getActiveItemIndex: -> @items.indexOf(@activeItem) # Public: Makes the item at the given index active. - showItemAtIndex: (index) -> - @showItem(@itemAtIndex(index)) + activateItemAtIndex: (index) -> + @activateItem(@itemAtIndex(index)) # Public: Makes the given item active, adding the item if necessary. - showItem: (item) -> + activateItem: (item) -> if item? @addItem(item) @activeItem = item @@ -139,7 +139,7 @@ class PaneModel extends Model removeItem: (item, destroying) -> index = @items.indexOf(item) return if index is -1 - @showNextItem() if item is @activeItem and @items.length > 1 + @activateNextItem() if item is @activeItem and @items.length > 1 @items.splice(index, 1) @emit 'item-removed', item, index, destroying @destroy() if @items.length is 0 @@ -246,9 +246,9 @@ class PaneModel extends Model # Public: Activates the first item that matches the given URI. Returns a # boolean indicating whether a matching item was found. - showItemForUri: (uri) -> + activateItemForUri: (uri) -> if item = @itemForUri(uri) - @showItem(item) + @activateItem(item) true else false diff --git a/src/pane.coffee b/src/pane.coffee index b52329127..0385bcf23 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -25,11 +25,11 @@ class Pane extends View @div class: 'item-views', outlet: 'itemViews' @delegatesProperties 'items', 'activeItem', toProperty: 'model' - @delegatesMethods 'getItems', 'showNextItem', 'showPreviousItem', 'getActiveItemIndex', - 'showItemAtIndex', 'showItem', 'addItem', 'itemAtIndex', 'moveItem', 'moveItemToPane', + @delegatesMethods 'getItems', 'activateNextItem', 'activatePreviousItem', 'getActiveItemIndex', + 'activateItemAtIndex', 'activateItem', 'addItem', 'itemAtIndex', 'moveItem', 'moveItemToPane', 'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems', 'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems', - 'itemForUri', 'showItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive', + 'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive', 'activate', toProperty: 'model' previousActiveItem: null @@ -65,18 +65,18 @@ class Pane extends View false @command 'pane:save-items', => @saveItems() - @command 'pane:show-next-item', => @showNextItem() - @command 'pane:show-previous-item', => @showPreviousItem() + @command 'pane:show-next-item', => @activateNextItem() + @command 'pane:show-previous-item', => @activatePreviousItem() - @command 'pane:show-item-1', => @showItemAtIndex(0) - @command 'pane:show-item-2', => @showItemAtIndex(1) - @command 'pane:show-item-3', => @showItemAtIndex(2) - @command 'pane:show-item-4', => @showItemAtIndex(3) - @command 'pane:show-item-5', => @showItemAtIndex(4) - @command 'pane:show-item-6', => @showItemAtIndex(5) - @command 'pane:show-item-7', => @showItemAtIndex(6) - @command 'pane:show-item-8', => @showItemAtIndex(7) - @command 'pane:show-item-9', => @showItemAtIndex(8) + @command 'pane:show-item-1', => @activateItemAtIndex(0) + @command 'pane:show-item-2', => @activateItemAtIndex(1) + @command 'pane:show-item-3', => @activateItemAtIndex(2) + @command 'pane:show-item-4', => @activateItemAtIndex(3) + @command 'pane:show-item-5', => @activateItemAtIndex(4) + @command 'pane:show-item-6', => @activateItemAtIndex(5) + @command 'pane:show-item-7', => @activateItemAtIndex(6) + @command 'pane:show-item-8', => @activateItemAtIndex(7) + @command 'pane:show-item-9', => @activateItemAtIndex(8) @command 'pane:split-left', => @splitLeft(@copyActiveItem()) @command 'pane:split-right', => @splitRight(@copyActiveItem()) @@ -92,6 +92,21 @@ class Pane extends View serializeParams: -> model: @model.serialize() + # Deprecated: Use ::activateItem + showItem: (item) -> @activateItem(item) + + # Deprecated: Use ::activateItemForUri + showItemForUri: (item) -> @activateItemForUri(item) + + # Deprecated: Use ::activateItemAtIndex + showItemAtIndex: (index) -> @activateItemAtIndex(index) + + # Deprecated: Use ::activateNextItem + showNextItem: -> @activateNextItem() + + # Deprecated: Use ::activatePreviousItem + showPreviousItem: -> @activatePreviousItem() + # Private: afterAttach: (onDom) -> @focus() if @model.focused and onDom diff --git a/src/workspace-view.coffee b/src/workspace-view.coffee index daac10423..cd5075784 100644 --- a/src/workspace-view.coffee +++ b/src/workspace-view.coffee @@ -172,7 +172,7 @@ class WorkspaceView extends View @panes.setRoot(activePane) @itemOpened(editor) - activePane.showItem(editor) + activePane.activateItem(editor) activePane.activate() if changeFocus @trigger "uri-opened" editor @@ -200,7 +200,7 @@ class WorkspaceView extends View else if split == 'left' pane = @getPanes()[0] - pane.showItem(paneItem) + pane.activateItem(paneItem) else paneItem = atom.project.openSync(uri, {initialLine}) pane = new Pane(paneItem) @@ -218,7 +218,7 @@ class WorkspaceView extends View if pane paneItem = pane.itemForUri(uri) - pane.showItem(paneItem) + pane.activateItem(paneItem) pane.activate() if changeFocus paneItem else From 3fc3d48deff4f3a86c01cd6f29141b95105b9a82 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 12 Jan 2014 17:49:08 -0700 Subject: [PATCH 88/89] Destroy empty panes after deserializing Fixes #1399 --- spec/pane-container-model-spec.coffee | 12 +++++++++--- spec/pane-container-spec.coffee | 2 +- src/pane-container-model.coffee | 7 ++++++- src/pane-container.coffee | 4 ---- src/pane-model.coffee | 2 +- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/spec/pane-container-model-spec.coffee b/spec/pane-container-model-spec.coffee index a37772d7d..027272218 100644 --- a/spec/pane-container-model-spec.coffee +++ b/spec/pane-container-model-spec.coffee @@ -6,10 +6,16 @@ describe "PaneContainerModel", -> [containerA, pane1A, pane2A, pane3A] = [] beforeEach -> - pane1A = new PaneModel + # This is a dummy item to prevent panes from being empty on deserialization + class Item + atom.deserializers.add(this) + @deserialize: -> new this + serialize: -> deserializer: 'Item' + + pane1A = new PaneModel(items: [new Item]) containerA = new PaneContainerModel(root: pane1A) - pane2A = pane1A.splitRight() - pane3A = pane2A.splitDown() + pane2A = pane1A.splitRight(items: [new Item]) + pane3A = pane2A.splitDown(items: [new Item]) it "preserves the focused pane across serialization", -> expect(pane3A.focused).toBe true diff --git a/spec/pane-container-spec.coffee b/spec/pane-container-spec.coffee index 5fb759860..374b84a00 100644 --- a/spec/pane-container-spec.coffee +++ b/spec/pane-container-spec.coffee @@ -130,7 +130,7 @@ describe "PaneContainer", -> expect(newContainer.find('.pane-row > :contains(1)').width()).toBe 150 expect(newContainer.find('.pane-row > .pane-column > :contains(2)').height()).toBe 100 - xit "removes empty panes on deserialization", -> + it "removes empty panes on deserialization", -> # only deserialize pane 1's view successfully TestView.deserialize = ({name}) -> new TestView(name) if name is '1' newContainer = atom.deserializers.deserialize(container.serialize()) diff --git a/src/pane-container-model.coffee b/src/pane-container-model.coffee index 81f93df64..f7fa4c0c6 100644 --- a/src/pane-container-model.coffee +++ b/src/pane-container-model.coffee @@ -16,12 +16,14 @@ class PaneContainerModel extends Model @behavior 'activePaneItem', -> @$activePane.switch (activePane) -> activePane?.$activeItem - constructor: -> + constructor: (params) -> super @subscribe @$root, @onRootChanged + @destroyEmptyPanes() if params?.destroyEmptyPanes deserializeParams: (params) -> params.root = atom.deserializers.deserialize(params.root, container: this) + params.destroyEmptyPanes = true params serializeParams: (params) -> @@ -59,3 +61,6 @@ class PaneContainerModel extends Model @subscribe root, 'destroyed', => @activePane = null @root = null + + destroyEmptyPanes: -> + pane.destroy() for pane in @getPanes() when pane.items.length is 0 diff --git a/src/pane-container.coffee b/src/pane-container.coffee index 6c03ddbe2..4b258ff7d 100644 --- a/src/pane-container.coffee +++ b/src/pane-container.coffee @@ -110,10 +110,6 @@ class PaneContainer extends View return pane if view? null - removeEmptyPanes: -> - for pane in @getPanes() when pane.getItems().length == 0 - pane.remove() - focusNextPane: -> panes = @getPanes() if panes.length > 1 diff --git a/src/pane-model.coffee b/src/pane-model.coffee index 66b231f85..4866e068b 100644 --- a/src/pane-model.coffee +++ b/src/pane-model.coffee @@ -53,7 +53,7 @@ class PaneModel extends Model # Private: Called by the Serializable mixin during deserialization. deserializeParams: (params) -> {items, activeItemUri} = params - params.items = items.map (itemState) -> atom.deserializers.deserialize(itemState) + params.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState)) params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri params From 4179d9d2682c329c968fb43a4206d0b8f24c26d3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 12 Jan 2014 17:55:29 -0700 Subject: [PATCH 89/89] Add deprecated Pane::removeItem --- src/pane.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pane.coffee b/src/pane.coffee index 0385bcf23..879e530cc 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -92,6 +92,9 @@ class Pane extends View serializeParams: -> model: @model.serialize() + # Deprecated: Use ::destroyItem + removeItem: (item) -> @destroyItem(item) + # Deprecated: Use ::activateItem showItem: (item) -> @activateItem(item)