From 5ad53bb32ca17b9e1e337befc036d4a7dec337ba Mon Sep 17 00:00:00 2001 From: Corey Johnson & Nathan Sobo Date: Wed, 6 Mar 2013 11:57:17 -0800 Subject: [PATCH] Add restoreItem to Pane container --- spec/app/pane-container-spec.coffee | 60 +++++++++++++++++++++++++++-- spec/app/pane-spec.coffee | 38 +++++++++--------- src/app/edit-session.coffee | 7 ++++ src/app/pane-container.coffee | 22 +++++++++++ src/app/pane.coffee | 13 ++++--- src/app/root-view.coffee | 2 +- src/app/window.coffee | 5 ++- 7 files changed, 117 insertions(+), 30 deletions(-) diff --git a/spec/app/pane-container-spec.coffee b/spec/app/pane-container-spec.coffee index 6c1d8179a..ae9cb619f 100644 --- a/spec/app/pane-container-spec.coffee +++ b/spec/app/pane-container-spec.coffee @@ -1,6 +1,7 @@ PaneContainer = require 'pane-container' Pane = require 'pane' {View, $$} = require 'space-pen' +_ = require 'underscore' $ = require 'jquery' describe "PaneContainer", -> @@ -9,12 +10,13 @@ describe "PaneContainer", -> beforeEach -> class TestView extends View registerDeserializer(this) - @deserialize: ({myText}) -> new TestView(myText) + @deserialize: ({name}) -> new TestView(name) @content: -> @div tabindex: -1 - initialize: (@myText) -> @text(@myText) - serialize: -> deserializer: 'TestView', myText: @myText - getPath: -> "/tmp/hi" + initialize: (@name) -> @text(@name) + serialize: -> { deserializer: 'TestView', @name } + getUri: -> "/tmp/#{@name}" save: -> @saved = true + isEqual: (other) -> @name is other.name container = new PaneContainer pane1 = new Pane(new TestView('1')) @@ -74,6 +76,56 @@ describe "PaneContainer", -> pane4.splitDown() expect(panes).toEqual [] + describe ".restoreItem()", -> + describe "when there is an active pane", -> + it "reconstructs and shows the last-closed pane item", -> + expect(container.getActivePane()).toBe pane3 + item3 = pane3.activeItem + item4 = new TestView('4') + pane3.showItem(item4) + + pane3.destroyItem(item3) + pane3.destroyItem(item4) + expect(container.getActivePane()).toBe pane1 + + expect(container.restoreItem()).toBeTruthy() + expect(pane1.activeItem).toEqual item4 + + expect(container.restoreItem()).toBeTruthy() + expect(pane1.activeItem).toEqual item3 + + expect(container.restoreItem()).toBeFalsy() + expect(pane1.activeItem).toEqual item3 + + describe "when there is no active pane", -> + it "attaches a new pane with the reconstructed last pane item", -> + pane1.remove() + pane2.remove() + item3 = pane3.activeItem + pane3.destroyItem(item3) + expect(container.getActivePane()).toBeUndefined() + + container.restoreItem() + + expect(container.getActivePane().activeItem).toEqual item3 + + it "does not reopen an item that is already open", -> + item3 = pane3.activeItem + item4 = new TestView('4') + pane3.showItem(item4) + pane3.destroyItem(item3) + pane3.destroyItem(item4) + + expect(container.getActivePane()).toBe pane1 + pane1.showItem(new TestView('4')) + + expect(container.restoreItem()).toBeTruthy() + expect(_.pluck(pane1.getItems(), 'name')).toEqual ['1', '4', '3'] + expect(pane1.activeItem).toEqual item3 + + expect(container.restoreItem()).toBeFalsy() + expect(pane1.activeItem).toEqual item3 + describe ".saveAll()", -> it "saves all open pane items", -> pane1.showItem(new TestView('4')) diff --git a/spec/app/pane-spec.coffee b/spec/app/pane-spec.coffee index 887050f7f..b25db976d 100644 --- a/spec/app/pane-spec.coffee +++ b/spec/app/pane-spec.coffee @@ -125,7 +125,7 @@ describe "Pane", -> expect(editSession2.destroyed).toBeFalsy() describe "if the [Save] option is selected", -> - describe "when the item has a path", -> + describe "when the item has a uri", -> it "saves the item before removing and destroying it", -> atom.confirm.selectOption('Save') @@ -133,8 +133,8 @@ describe "Pane", -> expect(pane.getItems().indexOf(editSession2)).toBe -1 expect(editSession2.destroyed).toBeTruthy() - describe "when the item has no path", -> - it "presents a save-as dialog, then saves the item with the given path before removing and destroying it", -> + describe "when the item has no uri", -> + it "presents a save-as dialog, then saves the item with the given uri before removing and destroying it", -> editSession2.buffer.setPath(undefined) atom.confirm.selectOption('Save') @@ -286,7 +286,7 @@ describe "Pane", -> expect(pane.getItems()).toEqual [editSession1] describe "core:save", -> - describe "when the current item has a path", -> + describe "when the current item has a uri", -> describe "when the current item has a save method", -> it "saves the current item", -> spyOn(editSession2, 'save') @@ -299,7 +299,7 @@ describe "Pane", -> expect(pane.activeItem.save).toBeUndefined() pane.trigger 'core:save' - describe "when the current item has no path", -> + describe "when the current item has no uri", -> beforeEach -> spyOn(atom, 'showSaveDialog') @@ -576,17 +576,17 @@ describe "Pane", -> expect(pane1.outerWidth()).toBe container.width() describe "autosave", -> - [initialActiveItem, initialActiveItemPath] = [] + [initialActiveItem, initialActiveItemUri] = [] beforeEach -> initialActiveItem = pane.activeItem - initialActiveItemPath = null - pane.activeItem.getPath = -> initialActiveItemPath + initialActiveItemUri = null + pane.activeItem.getUri = -> initialActiveItemUri pane.activeItem.save = jasmine.createSpy("activeItem.save") spyOn(pane, 'saveItem').andCallThrough() describe "when the active view loses focus", -> - it "saves the item if core.autosave is true and the item has a path", -> + it "saves the item if core.autosave is true and the item has a uri", -> pane.activeView.trigger 'focusout' expect(pane.saveItem).not.toHaveBeenCalled() expect(pane.activeItem.save).not.toHaveBeenCalled() @@ -596,12 +596,12 @@ describe "Pane", -> expect(pane.saveItem).not.toHaveBeenCalled() expect(pane.activeItem.save).not.toHaveBeenCalled() - initialActiveItemPath = '/tmp/hi' + initialActiveItemUri = '/tmp/hi' pane.activeView.trigger 'focusout' expect(pane.activeItem.save).toHaveBeenCalled() describe "when an item becomes inactive", -> - it "saves the item if core.autosave is true and the item has a path", -> + it "saves the item if core.autosave is true and the item has a uri", -> expect(view2).not.toBe pane.activeItem expect(pane.saveItem).not.toHaveBeenCalled() expect(initialActiveItem.save).not.toHaveBeenCalled() @@ -614,12 +614,12 @@ describe "Pane", -> expect(initialActiveItem.save).not.toHaveBeenCalled() pane.showItem(initialActiveItem) - initialActiveItemPath = '/tmp/hi' + initialActiveItemUri = '/tmp/hi' pane.showItem(view2) expect(initialActiveItem.save).toHaveBeenCalled() describe "when an item is destroyed", -> - it "saves the item if core.autosave is true and the item has a path", -> + it "saves the item if core.autosave is true and the item has a uri", -> # doesn't have to be the active item expect(view2).not.toBe pane.activeItem pane.showItem(view2) @@ -628,19 +628,19 @@ describe "Pane", -> expect(pane.saveItem).not.toHaveBeenCalled() config.set("core.autosave", true) - view2.getPath = -> undefined + view2.getUri = -> undefined view2.save = -> pane.destroyItem(view2) expect(pane.saveItem).not.toHaveBeenCalled() - initialActiveItemPath = '/tmp/hi' + initialActiveItemUri = '/tmp/hi' pane.destroyItem(initialActiveItem) expect(initialActiveItem.save).toHaveBeenCalled() - describe ".itemForPath(path)", -> - it "returns the item for which a call to .getPath() returns the given path", -> - expect(pane.itemForPath(editSession1.getPath())).toBe editSession1 - expect(pane.itemForPath(editSession2.getPath())).toBe editSession2 + describe ".itemForUri(uri)", -> + it "returns the item for which a call to .getUri() returns the given uri", -> + expect(pane.itemForUri(editSession1.getUri())).toBe editSession1 + expect(pane.itemForUri(editSession2.getUri())).toBe editSession2 describe "serialization", -> it "can serialize and deserialize the pane and all its serializable items", -> diff --git a/src/app/edit-session.coffee b/src/app/edit-session.coffee index 46c9094f3..ba6ba0401 100644 --- a/src/app/edit-session.coffee +++ b/src/app/edit-session.coffee @@ -25,6 +25,12 @@ class EditSession session.setCursorScreenPosition(state.cursorScreenPosition) session + @identifiedBy: 'path' + + @deserializesToSameObject: (state, editSession) -> + state.path + + scrollTop: 0 scrollLeft: 0 languageMode: null @@ -151,6 +157,7 @@ class EditSession saveAs: (path) -> @buffer.saveAs(path) getFileExtension: -> @buffer.getExtension() getPath: -> @buffer.getPath() + getUri: -> @getPath() isBufferRowBlank: (bufferRow) -> @buffer.isRowBlank(bufferRow) nextNonBlankBufferRow: (bufferRow) -> @buffer.nextNonBlankRow(bufferRow) getEofBufferPosition: -> @buffer.getEofPosition() diff --git a/src/app/pane-container.coffee b/src/app/pane-container.coffee index 423ebab29..2db350200 100644 --- a/src/app/pane-container.coffee +++ b/src/app/pane-container.coffee @@ -1,4 +1,5 @@ {View} = require 'space-pen' +Pane = require 'pane' $ = require 'jquery' module.exports = @@ -13,6 +14,9 @@ class PaneContainer extends View @content: -> @div id: 'panes' + initialize: -> + @destroyedItemStates = [] + serialize: -> deserializer: 'PaneContainer' root: @getRoot()?.serialize() @@ -33,6 +37,24 @@ class PaneContainer extends View nextIndex = (currentIndex + 1) % panes.length panes[nextIndex].makeActive() + restoreItem: -> + if lastItemState = @destroyedItemStates.pop() + if activePane = @getActivePane() + activePane.showItem(deserialize(lastItemState)) + true + else + @append(new Pane(deserialize(lastItemState))) + + itemDestroyed: (item) -> + state = item.serialize?() + state.uri ?= item.getUri?() + @destroyedItemStates.push(state) if state? + + itemAdded: (item) -> + itemUri = item.getUri?() + @destroyedItemStates = @destroyedItemStates.filter (itemState) -> + itemState.uri isnt itemUri + getRoot: -> @children().first().view() diff --git a/src/app/pane.coffee b/src/app/pane.coffee index ba61be3eb..f061212a7 100644 --- a/src/app/pane.coffee +++ b/src/app/pane.coffee @@ -111,6 +111,7 @@ class Pane extends View return if _.include(@items, item) index = @getActiveItemIndex() + 1 @items.splice(index, 0, item) + @getContainer().itemAdded(item) @trigger 'pane:item-added', [item, index] item @@ -119,8 +120,10 @@ class Pane extends View false destroyItem: (item) -> + container = @getContainer() reallyDestroyItem = => @removeItem(item) + container.itemDestroyed(item) item.destroy?() @autosaveItem(item) @@ -137,7 +140,7 @@ class Pane extends View @destroyItem(item) for item in @getItems() when item isnt @activeItem promptToSaveItem: (item, nextAction) -> - path = item.getPath() + uri = item.getUri() atom.confirm( "'#{item.getTitle()}' has changes, do you want to save them?" "Your changes will be lost if close this item without saving." @@ -153,7 +156,7 @@ class Pane extends View @saveItemAs(@activeItem) saveItem: (item, nextAction) -> - if item.getPath?() + if item.getUri?() item.save() nextAction?() else @@ -173,7 +176,7 @@ class Pane extends View @autosaveItem(@activeItem) autosaveItem: (item) -> - @saveItem(item) if config.get('core.autosave') and item.getPath?() + @saveItem(item) if config.get('core.autosave') and item.getUri?() removeItem: (item) -> index = @items.indexOf(item) @@ -194,8 +197,8 @@ class Pane extends View @removeItem(item) pane.addItem(item, index) - itemForPath: (path) -> - _.detect @items, (item) -> item.getPath?() is path + itemForUri: (uri) -> + _.detect @items, (item) -> item.getUri?() is uri cleanupItemView: (item) -> if item instanceof $ diff --git a/src/app/root-view.coffee b/src/app/root-view.coffee index 0ce0b04ba..1e3eaf3d9 100644 --- a/src/app/root-view.coffee +++ b/src/app/root-view.coffee @@ -92,7 +92,7 @@ class RootView extends View changeFocus = options.changeFocus ? true path = project.resolve(path) if path? if activePane = @getActivePane() - if editSession = activePane.itemForPath(path) + if editSession = activePane.itemForUri(path) activePane.showItem(editSession) else editSession = project.buildEditSession(path) diff --git a/src/app/window.coffee b/src/app/window.coffee index 2ff52830c..109a5096d 100644 --- a/src/app/window.coffee +++ b/src/app/window.coffee @@ -155,7 +155,10 @@ window.unregisterDeserializer = (klass) -> delete deserializers[klass.name] window.deserialize = (state) -> - deserializers[state?.deserializer]?.deserialize(state) + getDeserializer(state)?.deserialize(state) + +window.getDeserializer = (state) -> + deserializers[state?.deserializer] window.measure = (description, fn) -> start = new Date().getTime()