Restore state when opening folders to applicable windows

Note: "clean window" is defined as 1) having an empty project and 2)
having no pane items or only empty unnamed buffers

When project is empty and there is saved state associated with the
opened/added folders...
* Open a file or folder (from command line or Open menu)
  * If we have a clean window, restore project state in window
  * If window is dirty, restore saved state in new window
This commit is contained in:
Michelle Tilley
2017-03-22 20:25:57 -07:00
committed by Katrina Uychaco
parent 910fef97a0
commit d9b73fa645
3 changed files with 112 additions and 47 deletions

View File

@@ -461,44 +461,91 @@ describe "AtomEnvironment", ->
spyOn(atom.workspace, 'open')
atom.project.setPaths([])
describe "when the opened path exists", ->
it "adds it to the project's paths", ->
pathToOpen = __filename
atom.openLocations([{pathToOpen}])
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when there is no saved state", ->
beforeEach ->
spyOn(atom, "loadState").andReturn(Promise.resolve(null))
describe "then a second path is opened with forceAddToWindow", ->
it "adds the second path to the project's paths", ->
firstPathToOpen = __dirname
secondPathToOpen = path.resolve(__dirname, './fixtures')
atom.openLocations([{pathToOpen: firstPathToOpen}])
atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])
expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])
describe "when the opened path exists", ->
it "adds it to the project's paths", ->
pathToOpen = __filename
waitsForPromise -> atom.openLocations([{pathToOpen}])
runs -> expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path does not exist but its parent directory does", ->
it "adds the parent directory to the project paths", ->
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
atom.openLocations([{pathToOpen}])
expect(atom.project.getPaths()[0]).toBe __dirname
describe "then a second path is opened with forceAddToWindow", ->
it "adds the second path to the project's paths", ->
firstPathToOpen = __dirname
secondPathToOpen = path.resolve(__dirname, './fixtures')
waitsForPromise -> atom.openLocations([{pathToOpen: firstPathToOpen}])
waitsForPromise -> atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}])
runs -> expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen])
describe "when the opened path is a file", ->
it "opens it in the workspace", ->
pathToOpen = __filename
atom.openLocations([{pathToOpen}])
expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
describe "when the opened path does not exist but its parent directory does", ->
it "adds the parent directory to the project paths", ->
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
waitsForPromise -> atom.openLocations([{pathToOpen}])
runs -> expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path is a directory", ->
it "does not open it in the workspace", ->
pathToOpen = __dirname
atom.openLocations([{pathToOpen}])
expect(atom.workspace.open.callCount).toBe 0
describe "when the opened path is a file", ->
it "opens it in the workspace", ->
pathToOpen = __filename
waitsForPromise -> atom.openLocations([{pathToOpen}])
runs -> expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
describe "when the opened path is a uri", ->
it "adds it to the project's paths as is", ->
pathToOpen = 'remote://server:7644/some/dir/path'
spyOn(atom.project, 'addPath')
atom.openLocations([{pathToOpen}])
expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen)
describe "when the opened path is a directory", ->
it "does not open it in the workspace", ->
pathToOpen = __dirname
waitsForPromise -> atom.openLocations([{pathToOpen}])
runs -> expect(atom.workspace.open.callCount).toBe 0
describe "when the opened path is a uri", ->
it "adds it to the project's paths as is", ->
pathToOpen = 'remote://server:7644/some/dir/path'
spyOn(atom.project, 'addPath')
waitsForPromise -> atom.openLocations([{pathToOpen}])
runs -> expect(atom.project.addPath).toHaveBeenCalledWith(pathToOpen)
describe "when there is saved state for the relevant directories", ->
state = Symbol('savedState')
beforeEach ->
spyOn(atom, "getStateKey").andCallFake (dirs) -> dirs.join(':')
spyOn(atom, "loadState").andCallFake (key) ->
if key == __dirname then Promise.resolve(state) else Promise.resolve(null)
spyOn(atom, "attemptRestoreProjectStateForPaths")
describe "when there are no project folders", ->
it "attempts to restore the project state", ->
pathToOpen = __dirname
waitsForPromise -> atom.openLocations([{pathToOpen}])
runs ->
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [pathToOpen], [])
expect(atom.project.getPaths()).toEqual([])
it "opens the specified files", ->
waitsForPromise -> atom.openLocations([{pathToOpen: __dirname}, {pathToOpen: __filename}])
runs ->
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(state, [__dirname], [__filename])
expect(atom.project.getPaths()).toEqual([])
describe "when there are already project folders", ->
beforeEach ->
atom.project.setPaths([__dirname])
it "does not attempt to restore the project state, instead adding the project paths", ->
pathToOpen = path.join(__dirname, 'fixtures')
waitsForPromise -> atom.openLocations([{pathToOpen, forceAddToWindow: true}])
runs ->
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen])
it "opens the specified files", ->
pathToOpen = path.join(__dirname, 'fixtures')
fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt')
waitsForPromise -> atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}])
runs ->
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen])
expect(atom.project.getPaths()).toEqual([__dirname])
describe "::updateAvailable(info) (called via IPC from browser process)", ->
subscription = null

View File

@@ -1019,23 +1019,38 @@ class AtomEnvironment extends Model
openLocations: (locations) ->
needsProjectPaths = @project?.getPaths().length is 0
foldersToAddToProject = []
fileLocationsToOpen = []
pushFolderToOpen = (folder) ->
if folder not in foldersToAddToProject
foldersToAddToProject.push(folder)
for {pathToOpen, initialLine, initialColumn, forceAddToWindow} in locations
if pathToOpen? and (needsProjectPaths or forceAddToWindow)
if fs.existsSync(pathToOpen)
@project.addPath(pathToOpen)
pushFolderToOpen @project.getDirectoryForProjectPath(pathToOpen).getPath()
else if fs.existsSync(path.dirname(pathToOpen))
@project.addPath(path.dirname(pathToOpen))
pushFolderToOpen @project.getDirectoryForProjectPath(path.dirname(pathToOpen)).getPath()
else
@project.addPath(pathToOpen)
pushFolderToOpen @project.getDirectoryForProjectPath(pathToOpen).getPath()
unless fs.isDirectorySync(pathToOpen)
fileLocationsToOpen.push({pathToOpen, initialLine, initialColumn})
if foldersToAddToProject.length > 0
@loadState(@getStateKey(foldersToAddToProject)).then (state) =>
if state and needsProjectPaths # only load state if this is the first path added to the project
files = (location.pathToOpen for location in fileLocationsToOpen)
@attemptRestoreProjectStateForPaths(state, foldersToAddToProject, files)
else
@project.addPath(folder) for folder in foldersToAddToProject
for {pathToOpen, initialLine, initialColumn} in fileLocationsToOpen
@workspace?.open(pathToOpen, {initialLine, initialColumn})
else
for {pathToOpen, initialLine, initialColumn} in fileLocationsToOpen
@workspace?.open(pathToOpen, {initialLine, initialColumn})
if needsProjectPaths
@loadState(@getStateKey(@project.getPaths())).then (state) =>
@restoreStateIntoEnvironment(state) if state
return
Promise.resolve(null)
# Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner.
Promise.prototype.done = (callback) ->

View File

@@ -184,11 +184,7 @@ class Project extends Model
#
# * `projectPath` {String} The path to the directory to add.
addPath: (projectPath, options) ->
directory = null
for provider in @directoryProviders
break if directory = provider.directoryForURISync?(projectPath)
directory ?= @defaultDirectoryProvider.directoryForURISync(projectPath)
directory = @getDirectoryForProjectPath(projectPath)
return unless directory.existsSync()
for existingDirectory in @getDirectories()
return if existingDirectory.getPath() is directory.getPath()
@@ -203,6 +199,13 @@ class Project extends Model
unless options?.emitEvent is false
@emitter.emit 'did-change-paths', @getPaths()
getDirectoryForProjectPath: (projectPath) ->
directory = null
for provider in @directoryProviders
break if directory = provider.directoryForURISync?(projectPath)
directory ?= @defaultDirectoryProvider.directoryForURISync(projectPath)
directory
# Public: remove a path from the project's list of root paths.
#
# * `projectPath` {String} The path to remove.