From d9b73fa6454f1bd30ac03bbcbbf9d3725cdb5f07 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Wed, 22 Mar 2017 20:25:57 -0700 Subject: [PATCH] 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 --- spec/atom-environment-spec.coffee | 113 +++++++++++++++++++++--------- src/atom-environment.coffee | 33 ++++++--- src/project.coffee | 13 ++-- 3 files changed, 112 insertions(+), 47 deletions(-) diff --git a/spec/atom-environment-spec.coffee b/spec/atom-environment-spec.coffee index 6fc5ab1a8..52e4af91a 100644 --- a/spec/atom-environment-spec.coffee +++ b/spec/atom-environment-spec.coffee @@ -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 diff --git a/src/atom-environment.coffee b/src/atom-environment.coffee index 58d7ee431..ad4a2103d 100644 --- a/src/atom-environment.coffee +++ b/src/atom-environment.coffee @@ -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) -> diff --git a/src/project.coffee b/src/project.coffee index a02f27dac..b9d8be32d 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -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.