mirror of
https://github.com/atom/atom.git
synced 2026-01-25 06:48:28 -05:00
This change fixes an issue that appears when the user removes the last project from the workspace and then re-adds it. At this time an error is thrown from within ItemRegistry because the PaneContainer that holds the TreeView in the left-most dock does not clear out its existing ItemRegistry before deserializing the old TreeView state. The fix is to create a new ItemRegistry when a PaneContainer is deserialized so that the previous Pane's items are not retained.
300 lines
8.2 KiB
JavaScript
300 lines
8.2 KiB
JavaScript
const {find} = require('underscore-plus')
|
|
const {Emitter, CompositeDisposable} = require('event-kit')
|
|
const Pane = require('./pane')
|
|
const ItemRegistry = require('./item-registry')
|
|
const PaneContainerElement = require('./pane-container-element')
|
|
|
|
const SERIALIZATION_VERSION = 1
|
|
const STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY = 100
|
|
|
|
module.exports =
|
|
class PaneContainer {
|
|
constructor (params) {
|
|
let applicationDelegate, deserializerManager, notificationManager;
|
|
({config: this.config, applicationDelegate, notificationManager, deserializerManager, viewRegistry: this.viewRegistry, location: this.location} = params)
|
|
this.emitter = new Emitter()
|
|
this.subscriptions = new CompositeDisposable()
|
|
this.itemRegistry = new ItemRegistry()
|
|
this.alive = true
|
|
this.stoppedChangingActivePaneItemTimeout = null
|
|
|
|
this.setRoot(new Pane({container: this, config: this.config, applicationDelegate, notificationManager, deserializerManager, viewRegistry: this.viewRegistry}))
|
|
this.didActivatePane(this.getRoot())
|
|
}
|
|
|
|
getLocation () { return this.location }
|
|
|
|
getElement () {
|
|
return this.element != null ? this.element : (this.element = new PaneContainerElement().initialize(this, {views: this.viewRegistry}))
|
|
}
|
|
|
|
destroy () {
|
|
this.alive = false
|
|
for (let pane of this.getRoot().getPanes()) { pane.destroy() }
|
|
this.cancelStoppedChangingActivePaneItemTimeout()
|
|
this.subscriptions.dispose()
|
|
this.emitter.dispose()
|
|
}
|
|
|
|
isAlive () { return this.alive }
|
|
|
|
isDestroyed () { return !this.isAlive() }
|
|
|
|
serialize (params) {
|
|
return {
|
|
deserializer: 'PaneContainer',
|
|
version: SERIALIZATION_VERSION,
|
|
root: this.root ? this.root.serialize() : null,
|
|
activePaneId: this.activePane.id
|
|
}
|
|
}
|
|
|
|
deserialize (state, deserializerManager) {
|
|
if (state.version !== SERIALIZATION_VERSION) return
|
|
this.itemRegistry = new ItemRegistry()
|
|
this.setRoot(deserializerManager.deserialize(state.root))
|
|
this.activePane = find(this.getRoot().getPanes(), pane => pane.id === state.activePaneId) || this.getPanes()[0]
|
|
if (this.config.get('core.destroyEmptyPanes')) this.destroyEmptyPanes()
|
|
}
|
|
|
|
onDidChangeRoot (fn) {
|
|
return this.emitter.on('did-change-root', fn)
|
|
}
|
|
|
|
observeRoot (fn) {
|
|
fn(this.getRoot())
|
|
return this.onDidChangeRoot(fn)
|
|
}
|
|
|
|
onDidAddPane (fn) {
|
|
return this.emitter.on('did-add-pane', fn)
|
|
}
|
|
|
|
observePanes (fn) {
|
|
for (let pane of this.getPanes()) { fn(pane) }
|
|
return this.onDidAddPane(({pane}) => fn(pane))
|
|
}
|
|
|
|
onDidDestroyPane (fn) {
|
|
return this.emitter.on('did-destroy-pane', fn)
|
|
}
|
|
|
|
onWillDestroyPane (fn) {
|
|
return this.emitter.on('will-destroy-pane', fn)
|
|
}
|
|
|
|
onDidChangeActivePane (fn) {
|
|
return this.emitter.on('did-change-active-pane', fn)
|
|
}
|
|
|
|
onDidActivatePane (fn) {
|
|
return this.emitter.on('did-activate-pane', fn)
|
|
}
|
|
|
|
observeActivePane (fn) {
|
|
fn(this.getActivePane())
|
|
return this.onDidChangeActivePane(fn)
|
|
}
|
|
|
|
onDidAddPaneItem (fn) {
|
|
return this.emitter.on('did-add-pane-item', fn)
|
|
}
|
|
|
|
observePaneItems (fn) {
|
|
for (let item of this.getPaneItems()) { fn(item) }
|
|
return this.onDidAddPaneItem(({item}) => fn(item))
|
|
}
|
|
|
|
onDidChangeActivePaneItem (fn) {
|
|
return this.emitter.on('did-change-active-pane-item', fn)
|
|
}
|
|
|
|
onDidStopChangingActivePaneItem (fn) {
|
|
return this.emitter.on('did-stop-changing-active-pane-item', fn)
|
|
}
|
|
|
|
observeActivePaneItem (fn) {
|
|
fn(this.getActivePaneItem())
|
|
return this.onDidChangeActivePaneItem(fn)
|
|
}
|
|
|
|
onWillDestroyPaneItem (fn) {
|
|
return this.emitter.on('will-destroy-pane-item', fn)
|
|
}
|
|
|
|
onDidDestroyPaneItem (fn) {
|
|
return this.emitter.on('did-destroy-pane-item', fn)
|
|
}
|
|
|
|
getRoot () { return this.root }
|
|
|
|
setRoot (root) {
|
|
this.root = root
|
|
this.root.setParent(this)
|
|
this.root.setContainer(this)
|
|
this.emitter.emit('did-change-root', this.root)
|
|
if ((this.getActivePane() == null) && this.root instanceof Pane) {
|
|
this.didActivatePane(this.root)
|
|
}
|
|
}
|
|
|
|
replaceChild (oldChild, newChild) {
|
|
if (oldChild !== this.root) { throw new Error('Replacing non-existent child') }
|
|
this.setRoot(newChild)
|
|
}
|
|
|
|
getPanes () {
|
|
if (this.alive) {
|
|
return this.getRoot().getPanes()
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
|
|
getPaneItems () {
|
|
return this.getRoot().getItems()
|
|
}
|
|
|
|
getActivePane () {
|
|
return this.activePane
|
|
}
|
|
|
|
getActivePaneItem () {
|
|
return this.getActivePane().getActiveItem()
|
|
}
|
|
|
|
paneForURI (uri) {
|
|
return find(this.getPanes(), pane => pane.itemForURI(uri) != null)
|
|
}
|
|
|
|
paneForItem (item) {
|
|
return find(this.getPanes(), pane => pane.getItems().includes(item))
|
|
}
|
|
|
|
saveAll () {
|
|
for (let pane of this.getPanes()) { pane.saveItems() }
|
|
}
|
|
|
|
confirmClose (options) {
|
|
const promises = []
|
|
for (const pane of this.getPanes()) {
|
|
for (const item of pane.getItems()) {
|
|
promises.push(pane.promptToSaveItem(item, options))
|
|
}
|
|
}
|
|
return Promise.all(promises).then((results) => !results.includes(false))
|
|
}
|
|
|
|
activateNextPane () {
|
|
const panes = this.getPanes()
|
|
if (panes.length > 1) {
|
|
const currentIndex = panes.indexOf(this.activePane)
|
|
const nextIndex = (currentIndex + 1) % panes.length
|
|
panes[nextIndex].activate()
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
activatePreviousPane () {
|
|
const panes = this.getPanes()
|
|
if (panes.length > 1) {
|
|
const currentIndex = panes.indexOf(this.activePane)
|
|
let previousIndex = currentIndex - 1
|
|
if (previousIndex < 0) { previousIndex = panes.length - 1 }
|
|
panes[previousIndex].activate()
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
moveActiveItemToPane (destPane) {
|
|
const item = this.activePane.getActiveItem()
|
|
|
|
if (!destPane.isItemAllowed(item)) { return }
|
|
|
|
this.activePane.moveItemToPane(item, destPane)
|
|
destPane.setActiveItem(item)
|
|
}
|
|
|
|
copyActiveItemToPane (destPane) {
|
|
const item = this.activePane.copyActiveItem()
|
|
|
|
if (item && destPane.isItemAllowed(item)) {
|
|
destPane.activateItem(item)
|
|
}
|
|
}
|
|
|
|
destroyEmptyPanes () {
|
|
for (let pane of this.getPanes()) { if (pane.items.length === 0) { pane.destroy() } }
|
|
}
|
|
|
|
didAddPane (event) {
|
|
this.emitter.emit('did-add-pane', event)
|
|
const items = event.pane.getItems()
|
|
for (let i = 0, length = items.length; i < length; i++) {
|
|
const item = items[i]
|
|
this.didAddPaneItem(item, event.pane, i)
|
|
}
|
|
}
|
|
|
|
willDestroyPane (event) {
|
|
this.emitter.emit('will-destroy-pane', event)
|
|
}
|
|
|
|
didDestroyPane (event) {
|
|
this.emitter.emit('did-destroy-pane', event)
|
|
}
|
|
|
|
didActivatePane (activePane) {
|
|
if (activePane !== this.activePane) {
|
|
if (!this.getPanes().includes(activePane)) {
|
|
throw new Error('Setting active pane that is not present in pane container')
|
|
}
|
|
|
|
this.activePane = activePane
|
|
this.emitter.emit('did-change-active-pane', this.activePane)
|
|
this.didChangeActiveItemOnPane(this.activePane, this.activePane.getActiveItem())
|
|
}
|
|
this.emitter.emit('did-activate-pane', this.activePane)
|
|
return this.activePane
|
|
}
|
|
|
|
didAddPaneItem (item, pane, index) {
|
|
this.itemRegistry.addItem(item)
|
|
this.emitter.emit('did-add-pane-item', {item, pane, index})
|
|
}
|
|
|
|
willDestroyPaneItem (event) {
|
|
return this.emitter.emitAsync('will-destroy-pane-item', event)
|
|
}
|
|
|
|
didDestroyPaneItem (event) {
|
|
this.itemRegistry.removeItem(event.item)
|
|
this.emitter.emit('did-destroy-pane-item', event)
|
|
}
|
|
|
|
didChangeActiveItemOnPane (pane, activeItem) {
|
|
if (pane === this.getActivePane()) {
|
|
this.emitter.emit('did-change-active-pane-item', activeItem)
|
|
|
|
this.cancelStoppedChangingActivePaneItemTimeout()
|
|
// `setTimeout()` isn't available during the snapshotting phase, but that's okay.
|
|
if (typeof setTimeout === 'function') {
|
|
this.stoppedChangingActivePaneItemTimeout = setTimeout(() => {
|
|
this.stoppedChangingActivePaneItemTimeout = null
|
|
this.emitter.emit('did-stop-changing-active-pane-item', activeItem)
|
|
}, STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY)
|
|
}
|
|
}
|
|
}
|
|
|
|
cancelStoppedChangingActivePaneItemTimeout () {
|
|
if (this.stoppedChangingActivePaneItemTimeout != null) {
|
|
clearTimeout(this.stoppedChangingActivePaneItemTimeout)
|
|
}
|
|
}
|
|
}
|