RootView constructor can be called with serialized view state data

Move the saving of serialized root view data to window.coffee. The window.startup method looks for window state on the atom object and instantiates the root view with that if it is present.
This commit is contained in:
Corey Johnson & Nathan Sobo
2012-04-12 14:23:01 -06:00
parent d1d0bd766b
commit 877b4dc336
6 changed files with 156 additions and 146 deletions

View File

@@ -11,55 +11,99 @@ describe "RootView", ->
beforeEach ->
path = require.resolve 'fixtures/dir/a'
rootView = new RootView({path})
rootView = new RootView(pathToOpen: path)
rootView.enableKeymap()
project = rootView.project
describe "initialize", ->
describe "when called with a path that references a file", ->
it "creates a project for the file's parent directory and opens it in the editor", ->
expect(rootView.project.path).toBe fs.directory(path)
expect(rootView.activeEditor().buffer.getPath()).toBe path
describe "initialize(viewState)", ->
describe "when called with a pathToOpen", ->
describe "when pathToOpen references a file", ->
it "creates a project for the file's parent directory and opens it in the editor", ->
expect(rootView.project.path).toBe fs.directory(path)
expect(rootView.editors().length).toBe 1
expect(rootView.editors()[0]).toHaveClass 'active'
expect(rootView.activeEditor().buffer.getPath()).toBe path
describe "when called with a path that references a directory", ->
it "creates a project for the directory and opens an empty buffer", ->
path = require.resolve 'fixtures/dir/'
rootView = new RootView({path})
describe "when pathToOpen references a directory", ->
it "creates a project for the directory does not open an editor", ->
path = require.resolve 'fixtures/dir/'
rootView = new RootView(pathToOpen: path)
expect(rootView.project.path).toBe path
expect(rootView.activeEditor().buffer.path).toBeUndefined()
expect(rootView.project.path).toBe path
expect(rootView.editors().length).toBe 0
describe "when not called with a path", ->
describe "when called with view state data returned from a previous call to RootView.prototype.serialize", ->
viewState = null
describe "when the serialized RootView does not have a project, only an unsaved buffer", ->
buffer = null
beforeEach ->
rootView = new RootView
editor1 = rootView.activeEditor()
buffer = editor1.buffer
editor1.splitRight()
viewState = rootView.serialize()
it "constructs the view with the same panes", ->
rootView = new RootView(viewState)
expect(rootView.project).toBeUndefined()
expect(rootView.editors().length).toBe 2
describe "when the serialized RootView has a project", ->
beforeEach ->
editor1 = rootView.activeEditor()
editor2 = editor1.splitRight()
editor3 = editor2.splitRight()
editor4 = editor2.splitDown()
editor2.setBuffer(new Buffer(require.resolve 'fixtures/dir/b'))
editor3.setBuffer(new Buffer(require.resolve 'fixtures/sample.js'))
editor3.setCursorScreenPosition([2, 3])
editor4.setBuffer(new Buffer(require.resolve 'fixtures/sample.txt'))
editor4.setCursorScreenPosition([0, 2])
rootView.attachToDom()
editor2.focus()
viewState = rootView.serialize()
rootView.remove()
it "constructs the view with the same project and panes", ->
rootView = new RootView(viewState)
rootView.attachToDom()
expect(rootView.editors().length).toBe 4
editor1 = rootView.panes.find('.row > .pane .editor:eq(0)').view()
editor3 = rootView.panes.find('.row > .pane .editor:eq(1)').view()
editor2 = rootView.panes.find('.row > .column > .pane .editor:eq(0)').view()
editor4 = rootView.panes.find('.row > .column > .pane .editor:eq(1)').view()
expect(editor1.buffer.path).toBe require.resolve('fixtures/dir/a')
expect(editor2.buffer.path).toBe require.resolve('fixtures/dir/b')
expect(editor3.buffer.path).toBe require.resolve('fixtures/sample.js')
expect(editor3.getCursorScreenPosition()).toEqual [2, 3]
expect(editor4.buffer.path).toBe require.resolve('fixtures/sample.txt')
expect(editor4.getCursorScreenPosition()).toEqual [0, 2]
# ensure adjustSplitPanes is called
expect(editor1.width()).toBeGreaterThan 0
expect(editor2.width()).toBeGreaterThan 0
expect(editor3.width()).toBeGreaterThan 0
expect(editor4.width()).toBeGreaterThan 0
# ensure correct editor is focused again
expect(editor2.isFocused).toBeTruthy()
expect(editor1.isFocused).toBeFalsy()
expect(editor3.isFocused).toBeFalsy()
expect(editor4.isFocused).toBeFalsy()
describe "when called with no state data", ->
it "opens an empty buffer", ->
rootView = new RootView
expect(rootView.editors().length).toBe 1
expect(rootView.activeEditor().buffer.path).toBeUndefined()
describe "when there is a window state for the current window stored on the atom object", ->
it "sets the window state on the root view", ->
spyOn(RootView.prototype, 'setWindowState')
describe "when the window is reloaded", ->
afterEach ->
delete atom.windowStatesByWindowNumber[$windowNumber]
it "stores its window state on the atom object by window number, then reassigns it next time the root view is constructed", ->
rootView.activeEditor().splitLeft()
expectedWindowState = rootView.getWindowState()
# simulate unload
$(window).trigger 'beforeunload'
expect(atom.windowStatesByWindowNumber[$windowNumber]).toEqual expectedWindowState
# simulate reload
newRootView = new RootView
expect(newRootView.getWindowState()).toEqual expectedWindowState
expect(newRootView.editors().length).toBe 2
describe "focus", ->
it "can receive focus if there is no active editor, but otherwise hands off focus to the active editor", ->
rootView = new RootView({path: require.resolve 'fixtures'})
rootView = new RootView(pathToOpen: require.resolve 'fixtures')
rootView.attachToDom()
expect(rootView).toMatchSelector(':focus')
@@ -71,59 +115,6 @@ describe "RootView", ->
expect(rootView).not.toMatchSelector(':focus')
expect(rootView.activeEditor().isFocused).toBeTruthy()
describe "windowState getter and setter", ->
[editor1, editor2, editor3, editor4, panesHtml] = []
beforeEach ->
editor1 = rootView.activeEditor()
editor2 = editor1.splitRight()
editor3 = editor2.splitRight()
editor4 = editor2.splitDown()
editor2.setBuffer(new Buffer(require.resolve 'fixtures/dir/b'))
editor3.setBuffer(new Buffer(require.resolve 'fixtures/sample.js'))
editor3.setCursorScreenPosition([2, 3])
editor4.setBuffer(new Buffer(require.resolve 'fixtures/sample.txt'))
editor4.setCursorScreenPosition([0, 2])
rootView.attachToDom()
editor2.focus()
panesHtml = rootView.panes.html()
it "can reconstruct the split pane arrangement from the window state hash returned by getWindowState", ->
windowState = rootView.getWindowState()
editor2.remove()
editor3.remove()
editor4.remove()
expect(rootView.panes.find('.editor').length).toBe 1
rootView.setWindowState(windowState)
expect(rootView.editors().length).toBe 4
editor1 = rootView.panes.find('.row > .pane .editor:eq(0)').view()
editor3 = rootView.panes.find('.row > .pane .editor:eq(1)').view()
editor2 = rootView.panes.find('.row > .column > .pane .editor:eq(0)').view()
editor4 = rootView.panes.find('.row > .column > .pane .editor:eq(1)').view()
expect(editor1.buffer.path).toBe require.resolve('fixtures/dir/a')
expect(editor2.buffer.path).toBe require.resolve('fixtures/dir/b')
expect(editor3.buffer.path).toBe require.resolve('fixtures/sample.js')
expect(editor3.getCursorScreenPosition()).toEqual [2, 3]
expect(editor4.buffer.path).toBe require.resolve('fixtures/sample.txt')
expect(editor4.getCursorScreenPosition()).toEqual [0, 2]
# ensure adjustSplitPanes is called
expect(editor1.width()).toBeGreaterThan 0
expect(editor2.width()).toBeGreaterThan 0
expect(editor3.width()).toBeGreaterThan 0
expect(editor4.width()).toBeGreaterThan 0
# ensure correct editor is focused again
expect(editor2.isFocused).toBeTruthy()
expect(editor1.isFocused).toBeFalsy()
expect(editor3.isFocused).toBeFalsy()
expect(editor4.isFocused).toBeFalsy()
describe "split editor panes", ->
editor1 = null

View File

@@ -30,3 +30,14 @@ describe "Window", ->
requireStylesheet('atom.css')
expect($('head style').length).toBe 1
describe "before the window is unloaded", ->
afterEach ->
delete atom.rootViewStates[$windowNumber]
it "saves the serialized state of the root view to the atom object so it can be rehydrated after reload", ->
expect(atom.rootViewStates[$windowNumber]).toBeUndefined()
$(window).trigger 'beforeunload'
expect(atom.rootViewStates[$windowNumber]).toEqual window.rootView.serialize()

View File

@@ -9,11 +9,9 @@ window.showConsole()
beforeEach ->
window.resetTimeouts()
delete atom.windowStatesByWindowNumber[$windowNumber]
afterEach ->
$('#jasmine-content').empty()
$(window).off 'beforeunload'
window.keymap.bindKeys '*', 'meta-w': 'close'
$(document).on 'close', -> window.close()

View File

@@ -10,12 +10,13 @@ class Atom
keymap: null
windows: null
userConfigurationPath: null
rootViewStates: null
constructor: (@loadPath, nativeMethods)->
@windows = []
@setUpKeymap()
@userConfigurationPath = fs.absolute "~/.atom/atom.coffee"
@windowStatesByWindowNumber = {}
@rootViewStates = {}
setUpKeymap: ->
@keymap = new Keymap()

View File

@@ -18,10 +18,7 @@ class RootView extends View
@div id: 'root-view', tabindex: -1, =>
@div id: 'panes', outlet: 'panes'
initialize: (params) ->
{path} = params
@createProject(path)
initialize: ({ pathToOpen, projectPath, panesViewState }) ->
@on 'toggle-file-finder', => @toggleFileFinder()
@on 'show-console', => window.showConsole()
@on 'find-in-file', => @commandPanel.show("/")
@@ -34,18 +31,53 @@ class RootView extends View
@commandPanel = new CommandPanel({rootView: this})
$(window).on 'beforeunload', =>
atom.windowStatesByWindowNumber[$windowNumber] = @getWindowState()
if projectPath?
@project = new Project(projectPath)
else if pathToOpen?
@project = new Project(fs.directory(pathToOpen))
@open(pathToOpen) if fs.isFile(pathToOpen)
else if not panesViewState
@activeEditor().setBuffer(new Buffer)
if windowState = atom.windowStatesByWindowNumber[$windowNumber]
@setWindowState(windowState)
@deserializePanes(panesViewState) if panesViewState
createProject: (path) ->
if path
@project = new Project(fs.directory(path))
@open(path) if fs.isFile(path)
else
@activeEditor().setBuffer(new Buffer())
serialize: ->
projectPath: @project?.path
panesViewState: @serializePanes()
serializePanes: (element = @panes.children(':eq(0)')) ->
if element.hasClass('pane')
['editor', element.view().content.getEditorState()]
else if element.hasClass('row')
['row'].concat element.children().toArray().map (elt) =>
@serializePanes($(elt))
else if element.hasClass('column')
['column'].concat element.children().toArray().map (elt) =>
@serializePanes($(elt))
deserializePanes: (panesViewState, parent) ->
adjustSplitPanes = false
unless parent
@panes.empty()
adjustSplitPanes = true
parent = @panes
switch panesViewState.shift()
when 'editor'
editor = new Editor(panesViewState...)
parent.append(new Pane(editor))
when 'row'
row = $$ -> @div class: 'row'
parent.append row
for child in panesViewState
@deserializePanes(child, row)
when 'column'
column = $$ -> @div class: 'column'
parent.append column
for child in panesViewState
@deserializePanes(child, column)
@adjustSplitPanes() if adjustSplitPanes
open: (path) ->
@activeEditor().setBuffer(@project.open(path))
@@ -80,45 +112,16 @@ class RootView extends View
if editor.length
editor.view()
else
editor = new Editor
pane = new Pane(editor)
@panes.append(pane)
editor.focus()
editor
editor = @panes.find('.editor:first')
if editor.length
editor.view()
else
editor = new Editor
pane = new Pane(editor)
@panes.append(pane)
editor.focus()
editor
getWindowState: (element = @panes.children(':eq(0)')) ->
if element.hasClass('pane')
['editor', element.view().content.getEditorState()]
else if element.hasClass('row')
['row'].concat element.children().toArray().map (elt) =>
@getWindowState($(elt))
else if element.hasClass('column')
['column'].concat element.children().toArray().map (elt) =>
@getWindowState($(elt))
setWindowState: (windowState, parent) ->
adjustSplitPanes = false
unless parent
@panes.empty()
adjustSplitPanes = true
parent = @panes
switch windowState.shift()
when 'editor'
editor = new Editor(windowState...)
parent.append(new Pane(editor))
when 'row'
row = $$ -> @div class: 'row'
parent.append row
for child in windowState
@setWindowState(child, row)
when 'column'
column = $$ -> @div class: 'column'
parent.append column
for child in windowState
@setWindowState(child, column)
@adjustSplitPanes() if adjustSplitPanes
addPane: (view, sibling, axis, side) ->
unless sibling.parent().hasClass(axis)

View File

@@ -24,6 +24,7 @@ windowAdditions =
@attachRootView(path)
@loadUserConfiguration()
$(window).on 'close', => @close()
$(window).on 'beforeunload', => @saveRootViewState()
$(window).focus()
atom.windowOpened this
@@ -31,12 +32,17 @@ windowAdditions =
@rootView.remove()
$(window).unbind('focus')
$(window).unbind('blur')
$(window).off('before')
atom.windowClosed this
attachRootView: (path) ->
@rootView = new RootView {path}
attachRootView: (pathToOpen) ->
rootViewState = atom.rootViewStates[$windowNumber] or { pathToOpen }
@rootView = new RootView(rootViewState)
$(@rootViewParentSelector).append @rootView
saveRootViewState: ->
atom.rootViewStates[$windowNumber] = @rootView.serialize()
loadUserConfiguration: ->
try
require atom.userConfigurationPath if fs.exists(atom.userConfigurationPath)