Set multiple project paths for multiple cmd-line paths

Signed-off-by: Jessica Lord <jlord@github.com>
This commit is contained in:
Max Brunsfeld
2015-02-12 21:18:18 -08:00
committed by Jessica Lord
parent 81d07e2804
commit f7e1629cfc
8 changed files with 137 additions and 84 deletions

View File

@@ -59,3 +59,45 @@ describe "Starting Atom", ->
.waitForExist("atom-workspace", 5000)
.then((exists) -> expect(exists).toBe true)
.waitForPaneItemCount(0, 1000)
it "saves the state of closed windows", ->
runAtom [otherTempDirPath], {ATOM_HOME: AtomHome}, (client) ->
client
# Opening a file in another window creates another window with a tab
# open for that file.
.waitForExist("atom-workspace", 5000)
.startAnotherWindow([tempFilePath], ATOM_HOME: AtomHome)
.waitForWindowCount(2, 5000)
.then(({value}) -> @window(value[1]))
.waitForExist("atom-text-editor", 5000)
.click("atom-text-editor")
.execute(-> atom.workspace.getActiveTextEditor().getText())
.then(({value}) -> expect(value).toBe "This file was already here.")
# Closing that window and reopening that directory shows the
# previously-opened file.
.execute(-> atom.unloadEditorWindow())
.close()
.waitForWindowCount(1, 5000)
.startAnotherWindow([tempDirPath], ATOM_HOME: AtomHome)
.waitForWindowCount(2, 5000)
.then(({value}) -> @window(value[1]))
.waitForExist("atom-text-editor", 5000)
.execute(-> atom.workspace.getActiveTextEditor().getText())
.then(({value}) -> expect(value).toBe "This file was already here.")
it "allows multiple project directories to be passed as separate arguments", ->
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) ->
client
.waitForExist("atom-workspace", 5000)
.then((exists) -> expect(exists).toBe true)
.execute(-> atom.project.getPaths())
.then(({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath]))
# Opening a file in one of the directories reuses the same window
# and does not change the project paths.
.startAnotherWindow([tempFilePath], ATOM_HOME: AtomHome)
.waitForPaneItemCount(1, 5000)
.execute(-> atom.project.getPaths())
.then(({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath]))

View File

@@ -293,6 +293,21 @@ describe "Project", ->
expect(onDidChangePathsSpy.callCount).toBe 1
expect(onDidChangePathsSpy.mostRecentCall.args[0]).toEqual([oldPath, newPath])
describe "when the project already has the path or one of its descendants", ->
it "doesn't add it again", ->
onDidChangePathsSpy = jasmine.createSpy('onDidChangePaths spy')
atom.project.onDidChangePaths(onDidChangePathsSpy)
[oldPath] = atom.project.getPaths()
atom.project.addPath(oldPath)
atom.project.addPath(path.join(oldPath, "some-file.txt"))
atom.project.addPath(path.join(oldPath, "a-dir"))
atom.project.addPath(path.join(oldPath, "a-dir", "oh-git"))
expect(atom.project.getPaths()).toEqual([oldPath])
expect(onDidChangePathsSpy).not.toHaveBeenCalled()
describe ".relativize(path)", ->
it "returns the path, relative to whichever root directory it is inside of", ->
rootPath = atom.project.getPaths()[0]

View File

@@ -276,33 +276,31 @@ describe "Window", ->
elements.trigger "core:focus-previous"
expect(elements.find("[tabindex=1]:focus")).toExist()
describe "the window:open-path event", ->
describe "the window:open-locations event", ->
beforeEach ->
spyOn(atom.workspace, 'open')
atom.project.setPaths([])
describe "when the project does not have a path", ->
beforeEach ->
atom.project.setPaths([])
describe "when the opened path exists", ->
it "adds it to the project's paths", ->
pathToOpen = __filename
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path exists", ->
it "sets the project path to the opened path", ->
atom.getCurrentWindow().send 'message', 'open-path', pathToOpen: __filename
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path does not exist but its parent directory does", ->
it "sets the project path to the opened path's parent directory", ->
pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt')
atom.getCurrentWindow().send 'message', 'open-path', {pathToOpen}
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.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
expect(atom.project.getPaths()[0]).toBe __dirname
describe "when the opened path is a file", ->
it "opens it in the workspace", ->
atom.getCurrentWindow().send 'message', 'open-path', pathToOpen: __filename
pathToOpen = __filename
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
expect(atom.workspace.open.mostRecentCall.args[0]).toBe __filename
describe "when the opened path is a directory", ->
it "does not open it in the workspace", ->
atom.getCurrentWindow().send 'message', 'open-path', pathToOpen: __dirname
pathToOpen = __dirname
atom.getCurrentWindow().send 'message', 'open-locations', [{pathToOpen}]
expect(atom.workspace.open.callCount).toBe 0

View File

@@ -95,9 +95,9 @@ class Atom extends Model
when 'spec'
filename = 'spec'
when 'editor'
{initialPath} = @getLoadSettings()
if initialPath
sha1 = crypto.createHash('sha1').update(initialPath).digest('hex')
{initialPaths} = @getLoadSettings()
if initialPaths
sha1 = crypto.createHash('sha1').update(initialPaths.join("\n")).digest('hex')
filename = "editor-#{sha1}"
if filename
@@ -704,7 +704,7 @@ class Atom extends Model
Project = require './project'
startTime = Date.now()
@project ?= @deserializers.deserialize(@state.project) ? new Project(paths: [@getLoadSettings().initialPath])
@project ?= @deserializers.deserialize(@state.project) ? new Project()
@deserializeTimings.project = Date.now() - startTime
deserializeWorkspaceView: ->
@@ -747,7 +747,7 @@ class Atom extends Model
# Notify the browser project of the window's current project path
watchProjectPath: ->
onProjectPathChanged = =>
ipc.send('window-command', 'project-path-changed', @project.getPaths()[0])
ipc.send('window-command', 'project-path-changed', @project.getPaths())
@subscribe @project.onDidChangePaths(onProjectPathChanged)
onProjectPathChanged()

View File

@@ -311,9 +311,9 @@ class AtomApplication
else
@openPath({pathToOpen})
# Returns the {AtomWindow} for the given path.
windowForPath: (pathToOpen) ->
_.find @windows, (atomWindow) -> atomWindow.containsPath(pathToOpen)
# Returns the {AtomWindow} for the given paths.
windowForPaths: (pathsToOpen) ->
_.find @windows, (atomWindow) -> atomWindow.containsPaths(pathsToOpen)
# Returns the {AtomWindow} for the given ipc event.
windowForEvent: ({sender}) ->
@@ -327,49 +327,35 @@ class AtomApplication
# Public: Opens multiple paths, in existing windows if possible.
#
# options -
# :pathsToOpen - The array of file paths to open
# :pathToOpen - The file path to open
# :pidToKillWhenClosed - The integer of the pid to kill
# :newWindow - Boolean of whether this should be opened in a new window.
# :devMode - Boolean to control the opened window's dev mode.
# :safeMode - Boolean to control the opened window's safe mode.
# :window - {AtomWindow} to open file paths in.
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window}) ->
for pathToOpen in pathsToOpen ? []
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window})
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, window}) ->
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, window})
# Public: Opens a single path, in an existing window if possible.
#
# options -
# :pathToOpen - The file path to open
# :pathsToOpen - The array of file paths to open
# :pidToKillWhenClosed - The integer of the pid to kill
# :newWindow - Boolean of whether this should be opened in a new window.
# :devMode - Boolean to control the opened window's dev mode.
# :safeMode - Boolean to control the opened window's safe mode.
# :windowDimensions - Object with height and width keys.
# :window - {AtomWindow} to open file paths in.
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) ->
{pathToOpen, initialLine, initialColumn} = @locationForPathToOpen(pathToOpen)
pathToOpen = fs.normalize(pathToOpen)
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, window}={}) ->
pathsToOpen = (fs.normalize(pathToOpen) for pathToOpen in pathsToOpen)
locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen)
unless pidToKillWhenClosed or newWindow
pathToOpenStat = fs.statSyncNoException(pathToOpen)
# Default to using the specified window or the last focused window
currentWindow = window ? @lastFocusedWindow
if pathToOpenStat.isFile?()
# Open the file in the current window
existingWindow = currentWindow
else if pathToOpenStat.isDirectory?()
# Open the folder in the current window if it doesn't have a path
existingWindow = currentWindow unless currentWindow?.hasProjectPath()
# Don't reuse windows in dev mode
existingWindow ?= @windowForPath(pathToOpen) unless devMode
unless pidToKillWhenClosed or newWindow # or devMode
existingWindow = @windowForPaths(pathsToOpen)
if existingWindow?
openedWindow = existingWindow
openedWindow.openPath(pathToOpen, initialLine, initialColumn)
openedWindow.openLocations(locationsToOpen)
if openedWindow.isMinimized()
openedWindow.restore()
else
@@ -382,7 +368,7 @@ class AtomApplication
bootstrapScript ?= require.resolve('../window-bootstrap')
resourcePath ?= @resourcePath
openedWindow = new AtomWindow({pathToOpen, initialLine, initialColumn, bootstrapScript, resourcePath, devMode, safeMode, windowDimensions})
openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, windowDimensions})
if pidToKillWhenClosed?
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow

View File

@@ -18,7 +18,8 @@ class AtomWindow
isSpec: null
constructor: (settings={}) ->
{@resourcePath, pathToOpen, initialLine, initialColumn, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
{@resourcePath, pathToOpen, @locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
@locationsToOpen ?= [{pathToOpen}] if pathToOpen
# Normalize to make sure drive letter case is consistent on Windows
@resourcePath = path.normalize(@resourcePath) if @resourcePath
@@ -51,21 +52,24 @@ class AtomWindow
@constructor.includeShellLoadTime = false
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
loadSettings.initialPath = pathToOpen
if fs.statSyncNoException(pathToOpen).isFile?()
loadSettings.initialPath = path.dirname(pathToOpen)
loadSettings.initialPaths = for {pathToOpen} in (@locationsToOpen ? [])
if fs.statSyncNoException(pathToOpen).isFile?()
path.dirname(pathToOpen)
else
pathToOpen
loadSettings.initialPaths.sort()
@browserWindow.loadSettings = loadSettings
@browserWindow.once 'window:loaded', =>
@emit 'window:loaded'
@loaded = true
@browserWindow.on 'project-path-changed', (@projectPath) =>
@browserWindow.on 'project-path-changed', (@projectPaths) =>
@browserWindow.loadUrl @getUrl(loadSettings)
@browserWindow.focusOnWebView() if @isSpec
@openPath(pathToOpen, initialLine, initialColumn) unless @isSpecWindow()
@openLocations(@locationsToOpen) unless @isSpecWindow()
getUrl: (loadSettingsObj) ->
# Ignore the windowState when passing loadSettings via URL, since it could
@@ -79,10 +83,7 @@ class AtomWindow
slashes: true
query: {loadSettings: JSON.stringify(loadSettings)}
hasProjectPath: -> @projectPath?.length > 0
getInitialPath: ->
@browserWindow.loadSettings.initialPath
hasProjectPath: -> @projectPaths?.length > 0
setupContextMenu: ->
ContextMenu = null
@@ -91,20 +92,25 @@ class AtomWindow
ContextMenu ?= require './context-menu'
new ContextMenu(menuTemplate, this)
containsPaths: (paths) ->
for pathToCheck in paths
return false unless @containsPath(pathToCheck)
true
containsPath: (pathToCheck) ->
initialPath = @getInitialPath()
if not initialPath
false
else if not pathToCheck
false
else if pathToCheck is initialPath
true
else if fs.statSyncNoException(pathToCheck).isDirectory?()
false
else if pathToCheck.indexOf(path.join(initialPath, path.sep)) is 0
true
else
false
@projectPaths.some (projectPath) ->
if not projectPath
false
else if not pathToCheck
false
else if pathToCheck is projectPath
true
else if fs.statSyncNoException(pathToCheck).isDirectory?()
false
else if pathToCheck.indexOf(path.join(projectPath, path.sep)) is 0
true
else
false
handleEvents: ->
@browserWindow.on 'closed', =>
@@ -148,11 +154,14 @@ class AtomWindow
@browserWindow.focusOnWebView() unless @isWindowClosing
openPath: (pathToOpen, initialLine, initialColumn) ->
@openLocations([{pathToOpen, initialLine, initialColumn}])
openLocations: (locationsToOpen) ->
if @loaded
@focus()
@sendMessage 'open-path', {pathToOpen, initialLine, initialColumn}
@sendMessage 'open-locations', locationsToOpen
else
@browserWindow.once 'window:loaded', => @openPath(pathToOpen, initialLine, initialColumn)
@browserWindow.once 'window:loaded', => @openLocations(locationsToOpen)
sendMessage: (message, detail) ->
@browserWindow.webContents.send 'message', message, detail

View File

@@ -184,6 +184,11 @@ class Project extends Model
projectPath
else
path.dirname(projectPath)
return if @getPaths().some (existingPath) ->
(directoryPath is existingPath) or
(directoryPath.indexOf(path.join(existingPath, path.sep)) is 0)
directory = new Directory(directoryPath)
@rootDirectories.push(directory)

View File

@@ -17,15 +17,13 @@ class WindowEventHandler
@subscribe ipc, 'message', (message, detail) ->
switch message
when 'open-path'
{pathToOpen, initialLine, initialColumn} = detail
when 'open-locations'
for {pathToOpen, initialLine, initialColumn} in detail
if pathToOpen and (fs.existsSync(pathToOpen) or fs.existsSync(path.dirname(pathToOpen)))
atom.project?.addPath(pathToOpen)
unless atom.project?.getPaths().length
if fs.existsSync(pathToOpen) or fs.existsSync(path.dirname(pathToOpen))
atom.project?.setPaths([pathToOpen])
unless fs.isDirectorySync(pathToOpen)
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
unless fs.isDirectorySync(pathToOpen)
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
when 'update-available'
atom.updateAvailable(detail)