Merge pull request #1416 from atom/ns-workspace-model

Drop a basic model out from WorkspaceView
This commit is contained in:
Nathan Sobo
2014-01-14 18:11:34 -08:00
13 changed files with 221 additions and 219 deletions

View File

@@ -52,7 +52,8 @@
"underscore-plus": "0.6.1",
"theorist": "~0.13.0",
"delegato": "~0.4.0",
"mixto": "~0.4.0"
"mixto": "~0.4.0",
"property-accessors": "~0.1.0"
},
"packageDependencies": {
"atom-dark-syntax": "0.10.0",

View File

@@ -42,7 +42,7 @@ describe "PaneContainerView", ->
describe ".focusPreviousPane()", ->
it "focuses the pane preceding the focused pane or the last pane if no pane has focus", ->
container.attachToDom()
$(document.body).focus() # clear focus
container.getPanes()[0].focus() # activate first pane
container.focusPreviousPane()
expect(pane3.activeItem).toMatchSelector ':focus'
@@ -121,7 +121,7 @@ describe "PaneContainerView", ->
describe "serialization", ->
it "can be serialized and deserialized, and correctly adjusts dimensions of deserialized panes after attach", ->
newContainer = atom.deserializers.deserialize(container.serialize())
newContainer = new PaneContainerView(container.model.testSerialization())
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()
@@ -133,7 +133,7 @@ describe "PaneContainerView", ->
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())
newContainer = new PaneContainerView(container.model.testSerialization())
expect(newContainer.find('.pane-row, .pane-column')).not.toExist()
expect(newContainer.find('> :contains(1)')).toExist()

View File

@@ -602,12 +602,12 @@ describe "PaneView", ->
describe "serialization", ->
it "can serialize and deserialize the pane and all its items", ->
newPane = pane.testSerialization()
newPane = new PaneView(pane.model.testSerialization())
expect(newPane.getItems()).toEqual [view1, editor1, view2, editor2]
it "restores the active item on deserialization", ->
pane.activateItem(editor2)
newPane = pane.testSerialization()
newPane = new PaneView(pane.model.testSerialization())
expect(newPane.activeItem).toEqual editor2
it "does not show items that cannot be deserialized", ->
@@ -618,7 +618,7 @@ describe "PaneView", ->
pane.activateItem(new Unserializable)
newPane = pane.testSerialization()
newPane = new PaneView(pane.model.testSerialization())
expect(newPane.activeItem).toEqual pane.items[0]
expect(newPane.items.length).toBe pane.items.length - 1
@@ -626,13 +626,13 @@ describe "PaneView", ->
container.attachToDom()
pane.focus()
container2 = container.testSerialization()
container2 = new PaneContainerView(container.model.testSerialization())
pane2 = container2.getRoot()
container2.attachToDom()
expect(pane2).toMatchSelector(':has(:focus)')
$(document.activeElement).blur()
container3 = container.testSerialization()
container3 = new PaneContainerView(container.model.testSerialization())
pane3 = container3.getRoot()
container3.attachToDom()
expect(pane3).not.toMatchSelector(':has(:focus)')

View File

@@ -107,7 +107,7 @@ afterEach ->
atom.workspaceView?.remove?()
atom.workspaceView = null
delete atom.state.workspaceView
delete atom.state.workspace
atom.project?.destroy?()
atom.project = null

View File

@@ -88,12 +88,12 @@ describe "Window", ->
describe ".unloadEditorWindow()", ->
it "saves the serialized state of the window so it can be deserialized after reload", ->
workspaceViewState = atom.workspaceView.serialize()
workspaceState = atom.workspace.serialize()
syntaxState = atom.syntax.serialize()
atom.unloadEditorWindow()
expect(atom.state.workspaceView).toEqual workspaceViewState
expect(atom.state.workspace).toEqual workspaceState
expect(atom.state.syntax).toEqual syntaxState
expect(atom.saveSync).toHaveBeenCalled()

View File

@@ -3,6 +3,7 @@ Q = require 'q'
path = require 'path'
temp = require 'temp'
PaneView = require '../src/pane-view'
Workspace = require '../src/workspace'
describe "WorkspaceView", ->
pathToOpen = null
@@ -10,7 +11,8 @@ describe "WorkspaceView", ->
beforeEach ->
atom.project.setPath(atom.project.resolve('dir'))
pathToOpen = atom.project.resolve('a')
atom.workspaceView = new WorkspaceView
atom.workspace = new Workspace
atom.workspaceView = new WorkspaceView(atom.workspace)
atom.workspaceView.enableKeymap()
atom.workspaceView.openSync(pathToOpen)
atom.workspaceView.focus()
@@ -19,11 +21,12 @@ describe "WorkspaceView", ->
viewState = null
simulateReload = ->
workspaceState = atom.workspaceView.serialize()
workspaceState = atom.workspace.serialize()
projectState = atom.project.serialize()
atom.workspaceView.remove()
atom.project = atom.deserializers.deserialize(projectState)
atom.workspaceView = WorkspaceView.deserialize(workspaceState)
atom.workspace = Workspace.deserialize(workspaceState)
atom.workspaceView = new WorkspaceView(atom.workspace)
atom.workspaceView.attachToDom()
describe "when the serialized WorkspaceView has an unsaved buffer", ->
@@ -187,7 +190,7 @@ describe "WorkspaceView", ->
describe "when the root view is deserialized", ->
it "updates the title to contain the project's path", ->
workspaceView2 = atom.deserializers.deserialize(atom.workspaceView.serialize())
workspaceView2 = new WorkspaceView(atom.workspace.testSerialization())
item = atom.workspaceView.getActivePaneItem()
expect(workspaceView2.title).toBe "#{item.getTitle()} - #{atom.project.getPath()}"
workspaceView2.remove()

View File

@@ -230,8 +230,10 @@ class Atom extends Model
# Private:
deserializeWorkspaceView: ->
Workspace = require './workspace'
WorkspaceView = require './workspace-view'
@workspaceView = @deserializers.deserialize(@state.workspaceView) ? new WorkspaceView
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
@workspaceView = new WorkspaceView(@workspace)
$(@workspaceViewParentSelector).append(@workspaceView)
# Private:
@@ -277,7 +279,7 @@ class Atom extends Model
return if not @project and not @workspaceView
@state.syntax = @syntax.serialize()
@state.workspaceView = @workspaceView.serialize()
@state.workspace = @workspace.serialize()
@packages.deactivatePackages()
@state.packageStates = @packages.packageStates
@saveSync()

View File

@@ -1,4 +1,4 @@
Serializable = require 'serializable'
Delegator = require 'delegato'
{$, View} = require './space-pen-extensions'
PaneView = require './pane-view'
PaneContainer = require './pane-container'
@@ -6,11 +6,9 @@ PaneContainer = require './pane-container'
# Private: Manages the list of panes within a {WorkspaceView}
module.exports =
class PaneContainerView extends View
atom.deserializers.add(this)
Serializable.includeInto(this)
Delegator.includeInto(this)
@deserialize: (state) ->
new this(PaneContainer.deserialize(state.model))
@delegatesMethod 'saveAll', toProperty: 'model'
@content: ->
@div class: 'panes'
@@ -29,14 +27,8 @@ class PaneContainerView extends View
viewClass = model.getViewClass()
model._view ?= new viewClass(model)
serializeParams: ->
model: @model.serialize()
### Public ###
itemDestroyed: (item) ->
@trigger 'item-destroyed', [item]
getRoot: ->
@children().first().view()
@@ -65,9 +57,6 @@ class PaneContainerView extends View
@setRoot(null)
@trigger 'pane:removed', [child] if child instanceof PaneView
saveAll: ->
pane.saveItems() for pane in @getPanes()
confirmClose: ->
saved = true
for pane in @getPanes()
@@ -105,28 +94,10 @@ class PaneContainerView extends View
@getActivePane()?.activeView
paneForUri: (uri) ->
for pane in @getPanes()
view = pane.itemForUri(uri)
return pane if view?
null
@viewForModel(@model.paneForUri(uri))
focusNextPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@getFocusedPane())
nextIndex = (currentIndex + 1) % panes.length
panes[nextIndex].focus()
true
else
false
@model.activateNextPane()
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
@model.activatePreviousPane()

View File

@@ -1,3 +1,4 @@
{find} = require 'underscore-plus'
{Model} = require 'theorist'
Serializable = require 'serializable'
Pane = require './pane'
@@ -36,14 +37,32 @@ class PaneContainer extends Model
getPanes: ->
@root?.getPanes() ? []
paneForUri: (uri) ->
find @getPanes(), (pane) -> pane.itemForUri(uri)?
saveAll: ->
pane.saveItems() for pane in @getPanes()
activateNextPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@activePane)
nextIndex = (currentIndex + 1) % panes.length
panes[nextIndex].activate()
true
else
@activePane = null
false
activatePreviousPane: ->
panes = @getPanes()
if panes.length > 1
currentIndex = panes.indexOf(@activePane)
previousIndex = currentIndex - 1
previousIndex = panes.length - 1 if previousIndex < 0
panes[previousIndex].activate()
true
else
false
onRootChanged: (root) =>
@unsubscribe(@previousRoot) if @previousRoot?
@@ -64,3 +83,6 @@ class PaneContainer extends Model
destroyEmptyPanes: ->
pane.destroy() for pane in @getPanes() when pane.items.length is 0
itemDestroyed: (item) ->
@emit 'item-destroyed', item

View File

@@ -1,6 +1,6 @@
{$, View} = require './space-pen-extensions'
Serializable = require 'serializable'
Delegator = require 'delegato'
PropertyAccessors = require 'property-accessors'
Pane = require './pane'
@@ -12,14 +12,11 @@ Pane = require './pane'
# building a package that deals with switching between panes or tiems.
module.exports =
class PaneView extends View
Serializable.includeInto(this)
Delegator.includeInto(this)
PropertyAccessors.includeInto(this)
@version: 1
@deserialize: (state) ->
new this(Pane.deserialize(state.model))
@content: (wrappedView) ->
@div class: 'pane', tabindex: -1, =>
@div class: 'item-views', outlet: 'itemViews'
@@ -54,7 +51,6 @@ class PaneView extends View
@subscribe @model, 'item-removed', @onItemRemoved
@subscribe @model, 'item-moved', @onItemMoved
@subscribe @model, 'before-item-destroyed', @onBeforeItemDestroyed
@subscribe @model, 'item-destroyed', @onItemDestroyed
@subscribe @model, 'activated', @onActivated
@subscribe @model.$active, @onActiveStatusChanged
@@ -85,13 +81,6 @@ class PaneView extends View
@command 'pane:close', => @destroyItems()
@command 'pane:close-other-items', => @destroyInactiveItems()
deserializeParams: (params) ->
params.model = Pane.deserialize(params.model)
params
serializeParams: ->
model: @model.serialize()
# Deprecated: Use ::destroyItem
removeItem: (item) -> @destroyItem(item)
@@ -153,7 +142,6 @@ class PaneView extends View
view.show() if @attached
view.focus() if hasFocus
@activeView = view
@trigger 'pane:active-item-changed', [item]
onItemAdded: (item, index) =>
@@ -181,15 +169,13 @@ class PaneView extends View
@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'
# Private:
viewForItem: (item) ->
return unless item?
if item instanceof $
item
else if view = @viewsByItem.get(item)
@@ -201,8 +187,7 @@ class PaneView extends View
view
# Private:
viewForActiveItem: ->
@viewForItem(@activeItem)
@::accessor 'activeView', -> @viewForItem(@activeItem)
splitLeft: (items...) -> @model.splitLeft({items})._view

View File

@@ -36,7 +36,7 @@ class Pane extends Model
@subscribe @items.onEach (item) =>
if typeof item.on is 'function'
@subscribe item, 'destroyed', => @removeItem(item)
@subscribe item, 'destroyed', => @removeItem(item, true)
@subscribe @items.onRemoval (item, index) =>
@unsubscribe item if typeof item.on is 'function'
@@ -142,6 +142,7 @@ class Pane extends Model
@activateNextItem() if item is @activeItem and @items.length > 1
@items.splice(index, 1)
@emit 'item-removed', item, index, destroying
@container?.itemDestroyed(item) if destroying
@destroy() if @items.length is 0
# Public: Moves the given item to the specified index.
@@ -166,7 +167,6 @@ class Pane extends Model
destroyItem: (item) ->
@emit 'before-item-destroyed', item
if @promptToSaveItem(item)
@emit 'item-destroyed', item
@removeItem(item, true)
item.destroy?()
true

View File

@@ -1,10 +1,11 @@
ipc = require 'ipc'
path = require 'path'
Q = require 'q'
{$, $$, View} = require './space-pen-extensions'
_ = require 'underscore-plus'
Delegator = require 'delegato'
{$, $$, View} = require './space-pen-extensions'
fs = require 'fs-plus'
Serializable = require 'serializable'
Workspace = require './workspace'
EditorView = require './editor-view'
PaneView = require './pane-view'
PaneColumnView = require './pane-column-view'
@@ -38,10 +39,14 @@ Editor = require './editor'
#
module.exports =
class WorkspaceView extends View
Serializable.includeInto(this)
atom.deserializers.add(this, PaneView, PaneRowView, PaneColumnView, EditorView)
Delegator.includeInto(this)
@version: 3
@delegatesProperty 'fullScreen', 'destroyedItemUris', toProperty: 'model'
@delegatesMethods 'open', 'openSync', 'openSingletonSync', 'reopenItemSync',
'saveActivePaneItem', 'saveActivePaneItemAs', 'saveAll', 'destroyActivePaneItem',
toProperty: 'model'
@version: 4
@configDefaults:
ignoredNames: [".git", ".svn", ".DS_Store"]
@@ -59,13 +64,14 @@ class WorkspaceView extends View
@div class: 'panes', outlet: 'panes'
# Private:
initialize: ({panes, @fullScreen}={}) ->
panes ?= new PaneContainerView
initialize: (@model) ->
@model ?= new Workspace
panes = new PaneContainerView(@model.paneContainer)
@panes.replaceWith(panes)
@panes = panes
@destroyedItemUris = []
@subscribe @panes, 'item-destroyed', @onPaneItemDestroyed
@subscribe @model, 'uri-opened', => @trigger 'uri-opened'
@updateTitle()
@@ -116,16 +122,6 @@ class WorkspaceView extends View
@command 'core:save', => @saveActivePaneItem()
@command 'core:save-as', => @saveActivePaneItemAs()
# Private:
deserializeParams: (params) ->
params.panes = atom.deserializers.deserialize(params.panes)
params
# Private:
serializeParams: ->
panes: @panes.serialize()
fullScreen: atom.isFullScreen()
# Private:
handleFocus: (e) ->
if @getActivePane()
@@ -149,81 +145,6 @@ class WorkspaceView extends View
confirmClose: ->
@panes.confirmClose()
# Public: Asynchronously opens a given a filepath in Atom.
#
# * filePath: A file path
# * options
# + initialLine: The buffer line number to open to.
#
# Returns a promise that resolves to the {Editor} for the file URI.
open: (filePath, options={}) ->
changeFocus = options.changeFocus ? true
filePath = atom.project.resolve(filePath)
initialLine = options.initialLine
activePane = @getActivePane()
editor = activePane.itemForUri(atom.project.relativize(filePath)) if activePane and filePath
promise = atom.project.open(filePath, {initialLine}) if not editor
Q(editor ? promise)
.then (editor) =>
if not activePane
activePane = new PaneView(editor)
@panes.setRoot(activePane)
@itemOpened(editor)
activePane.activateItem(editor)
activePane.activate() if changeFocus
@trigger "uri-opened"
editor
.catch (error) ->
console.error(error.stack ? error)
# Private: Only used in specs
openSync: (uri, {changeFocus, initialLine, pane, split}={}) ->
changeFocus ?= true
pane ?= @getActivePane()
uri = atom.project.relativize(uri)
if pane
if uri
paneItem = pane.itemForUri(uri) ? atom.project.openSync(uri, {initialLine})
else
paneItem = atom.project.openSync()
if split == 'right'
panes = @getPanes()
if panes.length == 1
pane = panes[0].splitRight()
else
pane = _.last(panes)
else if split == 'left'
pane = @getPanes()[0]
pane.activateItem(paneItem)
else
paneItem = atom.project.openSync(uri, {initialLine})
pane = new PaneView(paneItem)
@panes.setRoot(pane)
@itemOpened(paneItem)
pane.activate() if changeFocus
paneItem
openSingletonSync: (uri, {changeFocus, initialLine, split}={}) ->
changeFocus ?= true
uri = atom.project.relativize(uri)
pane = @panes.paneForUri(uri)
if pane
paneItem = pane.itemForUri(uri)
pane.activateItem(paneItem)
pane.activate() if changeFocus
paneItem
else
@openSync(uri, {changeFocus, initialLine, split})
# Public: Updates the application's title, based on whichever file is open.
updateTitle: ->
if projectPath = atom.project.getPath()
@@ -242,22 +163,6 @@ class WorkspaceView extends View
getEditorViews: ->
@panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray()
# Private: Retrieves all of the modified buffers that are open and unsaved.
#
# Returns an {Array} of {TextBuffer}s.
getModifiedBuffers: ->
modifiedBuffers = []
for pane in @getPanes()
for item in pane.getItems() when item instanceof Editor
modifiedBuffers.push item.buffer if item.buffer.isModified()
modifiedBuffers
# Private: Retrieves all of the paths to open files.
#
# Returns an {Array} of {String}s.
getOpenBufferPaths: ->
_.uniq(_.flatten(@getEditorViews().map (editorView) -> editorView.getOpenBufferPaths()))
# Public: Prepends the element to the top of the window.
prependToTop: (element) ->
@vertical.prepend(element)
@@ -296,39 +201,23 @@ class WorkspaceView extends View
# Public: Returns the currently focused item from within the focused {PaneView}
getActivePaneItem: ->
@panes.getActivePaneItem()
@model.activePaneItem
# Public: Returns the view of the currently focused item.
getActiveView: ->
@panes.getActiveView()
# Public: destroy/close the active item.
destroyActivePaneItem: ->
@getActivePane()?.destroyActiveItem()
# Public: save the active item.
saveActivePaneItem: ->
@getActivePane()?.saveActiveItem()
# Public: save the active item as.
saveActivePaneItemAs: ->
@getActivePane()?.saveActiveItemAs()
# Public: Focuses the previous pane by id.
focusPreviousPane: -> @panes.focusPreviousPane()
focusPreviousPane: -> @model.activatePreviousPane()
# Public: Focuses the next pane by id.
focusNextPane: -> @panes.focusNextPane()
focusNextPane: -> @model.activateNextPane()
# Public:
#
# FIXME: Difference between active and focused pane?
getFocusedPane: -> @panes.getFocusedPane()
# Public: Saves all of the open items within panes.
saveAll: ->
@panes.saveAll()
# Public: Fires a callback on each open {PaneView}.
eachPane: (callback) ->
@panes.eachPane(callback)
@@ -350,20 +239,6 @@ class WorkspaceView extends View
# Private: Destroys everything.
remove: ->
@model.destroy()
editorView.remove() for editorView in @getEditorViews()
super
# Private: Adds the destroyed item's uri to the list of items to reopen.
onPaneItemDestroyed: (e, item) =>
if uri = item.getUri?()
@destroyedItemUris.push(uri)
# Public: Reopens the last-closed item uri if it hasn't already been reopened.
reopenItemSync: ->
if uri = @destroyedItemUris.pop()
@openSync(uri)
# Private: Removes the item's uri from the list of potential items to reopen.
itemOpened: (item) ->
if uri = item.getUri?()
_.remove(@destroyedItemUris, uri)

143
src/workspace.coffee Normal file
View File

@@ -0,0 +1,143 @@
{remove, last} = require 'underscore-plus'
{Model} = require 'theorist'
Q = require 'q'
Serializable = require 'serializable'
Delegator = require 'delegato'
PaneContainer = require './pane-container'
Pane = require './pane'
# Public: Represents the view state of the entire window, including the panes at
# the center and panels around the periphery. You can access the singleton
# instance via `atom.workspace`.
module.exports =
class Workspace extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
@delegatesProperty 'activePane', 'activePaneItem', toProperty: 'paneContainer'
@delegatesMethod 'getPanes', 'saveAll', 'activateNextPane', 'activatePreviousPane',
toProperty: 'paneContainer'
@properties
paneContainer: -> new PaneContainer
fullScreen: false
destroyedItemUris: -> []
# Private:
constructor: ->
super
@subscribe @paneContainer, 'item-destroyed', @onPaneItemDestroyed
# Private: Called by the Serializable mixin during deserialization
deserializeParams: (params) ->
params.paneContainer = PaneContainer.deserialize(params.paneContainer)
params
# Private: Called by the Serializable mixin during serialization.
serializeParams: ->
paneContainer: @paneContainer.serialize()
fullScreen: atom.isFullScreen()
# Public: Asynchronously opens a given a filepath in Atom.
#
# * filePath: A file path
# * options
# + initialLine: The buffer line number to open to.
#
# Returns a promise that resolves to the {Editor} for the file URI.
open: (filePath, options={}) ->
changeFocus = options.changeFocus ? true
filePath = atom.project.resolve(filePath)
initialLine = options.initialLine
activePane = @activePane
editor = activePane.itemForUri(atom.project.relativize(filePath)) if activePane and filePath
promise = atom.project.open(filePath, {initialLine}) if not editor
Q(editor ? promise)
.then (editor) =>
if not activePane
activePane = new Pane(items: [editor])
@paneContainer.root = activePane
@itemOpened(editor)
activePane.activateItem(editor)
activePane.activate() if changeFocus
@emit "uri-opened"
editor
.catch (error) ->
console.error(error.stack ? error)
# Private: Only used in specs
openSync: (uri, {changeFocus, initialLine, pane, split}={}) ->
changeFocus ?= true
pane ?= @activePane
uri = atom.project.relativize(uri)
if pane
if uri
paneItem = pane.itemForUri(uri) ? atom.project.openSync(uri, {initialLine})
else
paneItem = atom.project.openSync()
if split == 'right'
panes = @getPanes()
if panes.length == 1
pane = panes[0].splitRight()
else
pane = last(panes)
else if split == 'left'
pane = @getPanes()[0]
pane.activateItem(paneItem)
else
paneItem = atom.project.openSync(uri, {initialLine})
pane = new Pane(items: [paneItem])
@paneContainer.root = pane
@itemOpened(paneItem)
pane.activate() if changeFocus
paneItem
# Public: Synchronously open an editor for the given URI or activate an existing
# editor in any pane if one already exists.
openSingletonSync: (uri, {changeFocus, initialLine, split}={}) ->
changeFocus ?= true
uri = atom.project.relativize(uri)
pane = @paneContainer.paneForUri(uri)
if pane
paneItem = pane.itemForUri(uri)
pane.activateItem(paneItem)
pane.activate() if changeFocus
paneItem
else
@openSync(uri, {changeFocus, initialLine, split})
# Public: Reopens the last-closed item uri if it hasn't already been reopened.
reopenItemSync: ->
if uri = @destroyedItemUris.pop()
@openSync(uri)
# Public: save the active item.
saveActivePaneItem: ->
@activePane?.saveActiveItem()
# Public: save the active item as.
saveActivePaneItemAs: ->
@activePane?.saveActiveItemAs()
# Public: destroy/close the active item.
destroyActivePaneItem: ->
@activePane?.destroyActiveItem()
# Private: Removes the item's uri from the list of potential items to reopen.
itemOpened: (item) ->
if uri = item.getUri?()
remove(@destroyedItemUris, uri)
# Private: Adds the destroyed item's uri to the list of items to reopen.
onPaneItemDestroyed: (item) =>
if uri = item.getUri?()
@destroyedItemUris.push(uri)