/ src / pane-container.js
pane-container.js
  1  const { find } = require('underscore-plus');
  2  const { Emitter, CompositeDisposable } = require('event-kit');
  3  const Pane = require('./pane');
  4  const ItemRegistry = require('./item-registry');
  5  const PaneContainerElement = require('./pane-container-element');
  6  
  7  const SERIALIZATION_VERSION = 1;
  8  const STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY = 100;
  9  
 10  module.exports = class PaneContainer {
 11    constructor(params) {
 12      let applicationDelegate, deserializerManager, notificationManager;
 13      ({
 14        config: this.config,
 15        applicationDelegate,
 16        notificationManager,
 17        deserializerManager,
 18        viewRegistry: this.viewRegistry,
 19        location: this.location
 20      } = params);
 21      this.emitter = new Emitter();
 22      this.subscriptions = new CompositeDisposable();
 23      this.itemRegistry = new ItemRegistry();
 24      this.alive = true;
 25      this.stoppedChangingActivePaneItemTimeout = null;
 26  
 27      this.setRoot(
 28        new Pane({
 29          container: this,
 30          config: this.config,
 31          applicationDelegate,
 32          notificationManager,
 33          deserializerManager,
 34          viewRegistry: this.viewRegistry
 35        })
 36      );
 37      this.didActivatePane(this.getRoot());
 38    }
 39  
 40    getLocation() {
 41      return this.location;
 42    }
 43  
 44    getElement() {
 45      return this.element != null
 46        ? this.element
 47        : (this.element = new PaneContainerElement().initialize(this, {
 48            views: this.viewRegistry
 49          }));
 50    }
 51  
 52    destroy() {
 53      this.alive = false;
 54      for (let pane of this.getRoot().getPanes()) {
 55        pane.destroy();
 56      }
 57      this.cancelStoppedChangingActivePaneItemTimeout();
 58      this.subscriptions.dispose();
 59      this.emitter.dispose();
 60    }
 61  
 62    isAlive() {
 63      return this.alive;
 64    }
 65  
 66    isDestroyed() {
 67      return !this.isAlive();
 68    }
 69  
 70    serialize(params) {
 71      return {
 72        deserializer: 'PaneContainer',
 73        version: SERIALIZATION_VERSION,
 74        root: this.root ? this.root.serialize() : null,
 75        activePaneId: this.activePane.id
 76      };
 77    }
 78  
 79    deserialize(state, deserializerManager) {
 80      if (state.version !== SERIALIZATION_VERSION) return;
 81      this.itemRegistry = new ItemRegistry();
 82      this.setRoot(deserializerManager.deserialize(state.root));
 83      this.activePane =
 84        find(this.getRoot().getPanes(), pane => pane.id === state.activePaneId) ||
 85        this.getPanes()[0];
 86      if (this.config.get('core.destroyEmptyPanes')) this.destroyEmptyPanes();
 87    }
 88  
 89    onDidChangeRoot(fn) {
 90      return this.emitter.on('did-change-root', fn);
 91    }
 92  
 93    observeRoot(fn) {
 94      fn(this.getRoot());
 95      return this.onDidChangeRoot(fn);
 96    }
 97  
 98    onDidAddPane(fn) {
 99      return this.emitter.on('did-add-pane', fn);
100    }
101  
102    observePanes(fn) {
103      for (let pane of this.getPanes()) {
104        fn(pane);
105      }
106      return this.onDidAddPane(({ pane }) => fn(pane));
107    }
108  
109    onDidDestroyPane(fn) {
110      return this.emitter.on('did-destroy-pane', fn);
111    }
112  
113    onWillDestroyPane(fn) {
114      return this.emitter.on('will-destroy-pane', fn);
115    }
116  
117    onDidChangeActivePane(fn) {
118      return this.emitter.on('did-change-active-pane', fn);
119    }
120  
121    onDidActivatePane(fn) {
122      return this.emitter.on('did-activate-pane', fn);
123    }
124  
125    observeActivePane(fn) {
126      fn(this.getActivePane());
127      return this.onDidChangeActivePane(fn);
128    }
129  
130    onDidAddPaneItem(fn) {
131      return this.emitter.on('did-add-pane-item', fn);
132    }
133  
134    observePaneItems(fn) {
135      for (let item of this.getPaneItems()) {
136        fn(item);
137      }
138      return this.onDidAddPaneItem(({ item }) => fn(item));
139    }
140  
141    onDidChangeActivePaneItem(fn) {
142      return this.emitter.on('did-change-active-pane-item', fn);
143    }
144  
145    onDidStopChangingActivePaneItem(fn) {
146      return this.emitter.on('did-stop-changing-active-pane-item', fn);
147    }
148  
149    observeActivePaneItem(fn) {
150      fn(this.getActivePaneItem());
151      return this.onDidChangeActivePaneItem(fn);
152    }
153  
154    onWillDestroyPaneItem(fn) {
155      return this.emitter.on('will-destroy-pane-item', fn);
156    }
157  
158    onDidDestroyPaneItem(fn) {
159      return this.emitter.on('did-destroy-pane-item', fn);
160    }
161  
162    getRoot() {
163      return this.root;
164    }
165  
166    setRoot(root) {
167      this.root = root;
168      this.root.setParent(this);
169      this.root.setContainer(this);
170      this.emitter.emit('did-change-root', this.root);
171      if (this.getActivePane() == null && this.root instanceof Pane) {
172        this.didActivatePane(this.root);
173      }
174    }
175  
176    replaceChild(oldChild, newChild) {
177      if (oldChild !== this.root) {
178        throw new Error('Replacing non-existent child');
179      }
180      this.setRoot(newChild);
181    }
182  
183    getPanes() {
184      if (this.alive) {
185        return this.getRoot().getPanes();
186      } else {
187        return [];
188      }
189    }
190  
191    getPaneItems() {
192      return this.getRoot().getItems();
193    }
194  
195    getActivePane() {
196      return this.activePane;
197    }
198  
199    getActivePaneItem() {
200      return this.getActivePane().getActiveItem();
201    }
202  
203    paneForURI(uri) {
204      return find(this.getPanes(), pane => pane.itemForURI(uri) != null);
205    }
206  
207    paneForItem(item) {
208      return find(this.getPanes(), pane => pane.getItems().includes(item));
209    }
210  
211    saveAll() {
212      for (let pane of this.getPanes()) {
213        pane.saveItems();
214      }
215    }
216  
217    confirmClose(options) {
218      const promises = [];
219      for (const pane of this.getPanes()) {
220        for (const item of pane.getItems()) {
221          promises.push(pane.promptToSaveItem(item, options));
222        }
223      }
224      return Promise.all(promises).then(results => !results.includes(false));
225    }
226  
227    activateNextPane() {
228      const panes = this.getPanes();
229      if (panes.length > 1) {
230        const currentIndex = panes.indexOf(this.activePane);
231        const nextIndex = (currentIndex + 1) % panes.length;
232        panes[nextIndex].activate();
233        return true;
234      } else {
235        return false;
236      }
237    }
238  
239    activatePreviousPane() {
240      const panes = this.getPanes();
241      if (panes.length > 1) {
242        const currentIndex = panes.indexOf(this.activePane);
243        let previousIndex = currentIndex - 1;
244        if (previousIndex < 0) {
245          previousIndex = panes.length - 1;
246        }
247        panes[previousIndex].activate();
248        return true;
249      } else {
250        return false;
251      }
252    }
253  
254    moveActiveItemToPane(destPane) {
255      const item = this.activePane.getActiveItem();
256  
257      if (!destPane.isItemAllowed(item)) {
258        return;
259      }
260  
261      this.activePane.moveItemToPane(item, destPane);
262      destPane.setActiveItem(item);
263    }
264  
265    copyActiveItemToPane(destPane) {
266      const item = this.activePane.copyActiveItem();
267  
268      if (item && destPane.isItemAllowed(item)) {
269        destPane.activateItem(item);
270      }
271    }
272  
273    destroyEmptyPanes() {
274      for (let pane of this.getPanes()) {
275        if (pane.items.length === 0) {
276          pane.destroy();
277        }
278      }
279    }
280  
281    didAddPane(event) {
282      this.emitter.emit('did-add-pane', event);
283      const items = event.pane.getItems();
284      for (let i = 0, length = items.length; i < length; i++) {
285        const item = items[i];
286        this.didAddPaneItem(item, event.pane, i);
287      }
288    }
289  
290    willDestroyPane(event) {
291      this.emitter.emit('will-destroy-pane', event);
292    }
293  
294    didDestroyPane(event) {
295      this.emitter.emit('did-destroy-pane', event);
296    }
297  
298    didActivatePane(activePane) {
299      if (activePane !== this.activePane) {
300        if (!this.getPanes().includes(activePane)) {
301          throw new Error(
302            'Setting active pane that is not present in pane container'
303          );
304        }
305  
306        this.activePane = activePane;
307        this.emitter.emit('did-change-active-pane', this.activePane);
308        this.didChangeActiveItemOnPane(
309          this.activePane,
310          this.activePane.getActiveItem()
311        );
312      }
313      this.emitter.emit('did-activate-pane', this.activePane);
314      return this.activePane;
315    }
316  
317    didAddPaneItem(item, pane, index) {
318      this.itemRegistry.addItem(item);
319      this.emitter.emit('did-add-pane-item', { item, pane, index });
320    }
321  
322    willDestroyPaneItem(event) {
323      return this.emitter.emitAsync('will-destroy-pane-item', event);
324    }
325  
326    didDestroyPaneItem(event) {
327      this.itemRegistry.removeItem(event.item);
328      this.emitter.emit('did-destroy-pane-item', event);
329    }
330  
331    didChangeActiveItemOnPane(pane, activeItem) {
332      if (pane === this.getActivePane()) {
333        this.emitter.emit('did-change-active-pane-item', activeItem);
334  
335        this.cancelStoppedChangingActivePaneItemTimeout();
336        // `setTimeout()` isn't available during the snapshotting phase, but that's okay.
337        if (!global.isGeneratingSnapshot) {
338          this.stoppedChangingActivePaneItemTimeout = setTimeout(() => {
339            this.stoppedChangingActivePaneItemTimeout = null;
340            this.emitter.emit('did-stop-changing-active-pane-item', activeItem);
341          }, STOPPED_CHANGING_ACTIVE_PANE_ITEM_DELAY);
342        }
343      }
344    }
345  
346    cancelStoppedChangingActivePaneItemTimeout() {
347      if (this.stoppedChangingActivePaneItemTimeout != null) {
348        clearTimeout(this.stoppedChangingActivePaneItemTimeout);
349      }
350    }
351  };