mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge pull request #6380 from atom/mb-reopen-windows
Reopen windows when starting app w/ no arguments
This commit is contained in:
@@ -139,7 +139,7 @@ describe "the `atom` global", ->
|
||||
windowState: null
|
||||
|
||||
spyOn(Atom, 'getLoadSettings').andCallFake -> loadSettings
|
||||
spyOn(Atom, 'getStorageDirPath').andReturn(temp.mkdirSync("storage-dir-"))
|
||||
spyOn(Atom.getStorageFolder(), 'getPath').andReturn(temp.mkdirSync("storage-dir-"))
|
||||
|
||||
atom.mode = "editor"
|
||||
atom.state.stuff = "cool"
|
||||
|
||||
@@ -94,6 +94,15 @@ buildAtomClient = (args, env) ->
|
||||
]), env: extend({}, process.env, env))
|
||||
done()
|
||||
|
||||
.addCommand "dispatchCommand", (command, done) ->
|
||||
@execute "atom.commands.dispatch(document.activeElement, '#{command}')"
|
||||
.call(done)
|
||||
|
||||
.addCommand "simulateQuit", (done) ->
|
||||
@execute -> atom.unloadEditorWindow()
|
||||
.execute -> require("remote").require("app").emit("before-quit")
|
||||
.call(done)
|
||||
|
||||
module.exports = (args, env, fn) ->
|
||||
[chromedriver, chromedriverLogs, chromedriverExit] = []
|
||||
|
||||
@@ -119,6 +128,7 @@ module.exports = (args, env, fn) ->
|
||||
waitsFor("webdriver to finish", (done) ->
|
||||
finish = once ->
|
||||
client
|
||||
.simulateQuit()
|
||||
.end()
|
||||
.then(-> chromedriver.kill())
|
||||
.then(chromedriverExit.then(
|
||||
@@ -133,7 +143,7 @@ module.exports = (args, env, fn) ->
|
||||
client = buildAtomClient(args, env)
|
||||
|
||||
client.on "error", (err) ->
|
||||
jasmine.getEnv().currentSpec.fail(JSON.stringify(err))
|
||||
jasmine.getEnv().currentSpec.fail(new Error(err.response?.body?.value?.message))
|
||||
finish()
|
||||
|
||||
fn(client.init()).then(finish)
|
||||
|
||||
@@ -6,22 +6,22 @@ return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
|
||||
fs = require "fs"
|
||||
path = require "path"
|
||||
temp = require("temp").track()
|
||||
AtomHome = temp.mkdirSync('atom-home')
|
||||
fs.writeFileSync(path.join(AtomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config.cson')))
|
||||
runAtom = require("./helpers/start-atom")
|
||||
|
||||
describe "Starting Atom", ->
|
||||
[tempDirPath, otherTempDirPath] = []
|
||||
[tempDirPath, otherTempDirPath, atomHome] = []
|
||||
|
||||
beforeEach ->
|
||||
jasmine.useRealClock()
|
||||
|
||||
atomHome = temp.mkdirSync('atom-home')
|
||||
fs.writeFileSync(path.join(atomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config.cson')))
|
||||
tempDirPath = temp.mkdirSync("empty-dir")
|
||||
otherTempDirPath = temp.mkdirSync("another-temp-dir")
|
||||
|
||||
describe "opening a new file", ->
|
||||
it "opens the parent directory and creates an empty text editor", ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: AtomHome}, (client) ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForWindowCount(1, 1000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
@@ -36,13 +36,14 @@ describe "Starting Atom", ->
|
||||
.keys("Hello!")
|
||||
.execute -> atom.workspace.getActiveTextEditor().getText()
|
||||
.then ({value}) -> expect(value).toBe "Hello!"
|
||||
.dispatchCommand("editor:delete-line")
|
||||
|
||||
describe "when there is already a window open", ->
|
||||
it "reuses that window when opening files, but not when opening directories", ->
|
||||
tempFilePath = path.join(temp.mkdirSync("a-third-dir"), "a-file")
|
||||
fs.writeFileSync(tempFilePath, "This file was already here.")
|
||||
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: AtomHome}, (client) ->
|
||||
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForWindowCount(1, 1000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
@@ -50,7 +51,7 @@ describe "Starting Atom", ->
|
||||
|
||||
# Opening another file reuses the same window and does not change the
|
||||
# project paths.
|
||||
.startAnotherAtom([tempFilePath], ATOM_HOME: AtomHome)
|
||||
.startAnotherAtom([tempFilePath], ATOM_HOME: atomHome)
|
||||
.waitForPaneItemCount(2, 5000)
|
||||
.waitForWindowCount(1, 1000)
|
||||
.treeViewRootDirectories()
|
||||
@@ -60,7 +61,7 @@ describe "Starting Atom", ->
|
||||
|
||||
# Opening another directory creates a second window.
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([otherTempDirPath], ATOM_HOME: AtomHome)
|
||||
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
|
||||
, 5000)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(0, 1000)
|
||||
@@ -69,15 +70,14 @@ describe "Starting Atom", ->
|
||||
|
||||
describe "reopening a directory that was previously opened", ->
|
||||
it "remembers the state of the window", ->
|
||||
runAtom [tempDirPath], {ATOM_HOME: AtomHome}, (client) ->
|
||||
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(0, 3000)
|
||||
.execute -> atom.workspace.open()
|
||||
.waitForPaneItemCount(1, 3000)
|
||||
.execute -> atom.unloadEditorWindow()
|
||||
|
||||
runAtom [tempDirPath], {ATOM_HOME: AtomHome}, (client) ->
|
||||
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
@@ -87,7 +87,7 @@ describe "Starting Atom", ->
|
||||
nestedDir = path.join(otherTempDirPath, "nested-dir")
|
||||
fs.mkdirSync(nestedDir)
|
||||
|
||||
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: AtomHome}, (client) ->
|
||||
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.treeViewRootDirectories()
|
||||
@@ -95,42 +95,67 @@ describe "Starting Atom", ->
|
||||
|
||||
# Opening one of those directories again reuses the same window and
|
||||
# does not change the project paths.
|
||||
.startAnotherAtom([nestedDir], ATOM_HOME: AtomHome)
|
||||
.startAnotherAtom([nestedDir], ATOM_HOME: atomHome)
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath])
|
||||
|
||||
describe "when there is an existing window with no project path", ->
|
||||
describe "opening a directory", ->
|
||||
it "opens the directory in the existing window", ->
|
||||
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace")
|
||||
it "reuses that window to open a directory", ->
|
||||
runAtom [], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace")
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([])
|
||||
|
||||
.startAnotherAtom([tempDirPath], ATOM_HOME: atomHome)
|
||||
.waitUntil(->
|
||||
@treeViewRootDirectories()
|
||||
.then ({value}) -> value[0] is tempDirPath
|
||||
, 5000)
|
||||
.then (result) -> expect(result).toBe(true)
|
||||
.waitForWindowCount(1, 5000)
|
||||
|
||||
describe "launching with no path", ->
|
||||
it "opens a new window with a single untitled buffer", ->
|
||||
runAtom [], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace")
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
|
||||
# Opening with no file paths always creates a new window, even if
|
||||
# existing windows have no project paths.
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([], ATOM_HOME: atomHome)
|
||||
, 5000)
|
||||
.waitForExist("atom-workspace")
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
|
||||
it "reopens any previously opened windows", ->
|
||||
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace")
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
|
||||
, 5000)
|
||||
.waitForExist("atom-workspace")
|
||||
|
||||
runAtom [], {ATOM_HOME: atomHome}, (client) ->
|
||||
windowProjectPaths = []
|
||||
|
||||
client
|
||||
.waitForWindowCount(2, 10000)
|
||||
.then ({value: windowHandles}) ->
|
||||
@window(windowHandles[0])
|
||||
.treeViewRootDirectories()
|
||||
.then ({value}) -> expect(value).toEqual([])
|
||||
.then ({value: directories}) -> windowProjectPaths.push(directories)
|
||||
|
||||
.startAnotherAtom([tempDirPath], ATOM_HOME: AtomHome)
|
||||
.waitUntil(->
|
||||
@treeViewRootDirectories()
|
||||
.then ({value}) -> value[0] is tempDirPath
|
||||
, 5000)
|
||||
.then (result) -> expect(result).toBe(true)
|
||||
.waitForWindowCount(1, 5000)
|
||||
.window(windowHandles[1])
|
||||
.treeViewRootDirectories()
|
||||
.then ({value: directories}) -> windowProjectPaths.push(directories)
|
||||
|
||||
describe "launching with no path", ->
|
||||
it "always opens a new window with a single untitled buffer", ->
|
||||
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace")
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
|
||||
runAtom [], {ATOM_HOME: AtomHome}, (client) ->
|
||||
client
|
||||
.waitForExist("atom-workspace")
|
||||
.waitForPaneItemCount(1, 5000)
|
||||
|
||||
# Opening with no file paths always creates a new window, even if
|
||||
# existing windows have no project paths.
|
||||
.waitForNewWindow(->
|
||||
@startAnotherAtom([], ATOM_HOME: AtomHome)
|
||||
, 5000)
|
||||
.call ->
|
||||
expect(windowProjectPaths.sort()).toEqual [
|
||||
[tempDirPath]
|
||||
[otherTempDirPath]
|
||||
].sort()
|
||||
|
||||
@@ -14,6 +14,7 @@ Model = require './model'
|
||||
{$} = require './space-pen-extensions'
|
||||
WindowEventHandler = require './window-event-handler'
|
||||
StylesElement = require './styles-element'
|
||||
StorageFolder = require './storage-folder'
|
||||
|
||||
# Essential: Atom global for dealing with packages, themes, menus, and the window.
|
||||
#
|
||||
@@ -73,34 +74,24 @@ class Atom extends Model
|
||||
# Loads and returns the serialized state corresponding to this window
|
||||
# if it exists; otherwise returns undefined.
|
||||
@loadState: (mode) ->
|
||||
statePath = @getStatePath(@getLoadSettings().initialPaths, mode)
|
||||
if stateKey = @getStateKey(@getLoadSettings().initialPaths, mode)
|
||||
if state = @getStorageFolder().load(stateKey)
|
||||
return state
|
||||
|
||||
if fs.existsSync(statePath)
|
||||
if windowState = @getLoadSettings().windowState
|
||||
try
|
||||
stateString = fs.readFileSync(statePath, 'utf8')
|
||||
JSON.parse(@getLoadSettings().windowState)
|
||||
catch error
|
||||
console.warn "Error reading window state: #{statePath}", error.stack, error
|
||||
else
|
||||
stateString = @getLoadSettings().windowState
|
||||
|
||||
try
|
||||
JSON.parse(stateString) if stateString?
|
||||
catch error
|
||||
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
|
||||
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
|
||||
|
||||
# Returns the path where the state for the current window will be
|
||||
# located if it exists.
|
||||
@getStatePath: (paths, mode) ->
|
||||
switch mode
|
||||
when 'spec'
|
||||
filename = 'spec'
|
||||
when 'editor'
|
||||
if paths?.length > 0
|
||||
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
|
||||
filename = "editor-#{sha1}"
|
||||
|
||||
if filename
|
||||
path.join(@getStorageDirPath(), filename)
|
||||
@getStateKey: (paths, mode) ->
|
||||
if mode is 'spec'
|
||||
'spec'
|
||||
else if mode is 'editor' and paths?.length > 0
|
||||
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
|
||||
"editor-#{sha1}"
|
||||
else
|
||||
null
|
||||
|
||||
@@ -110,11 +101,8 @@ class Atom extends Model
|
||||
@getConfigDirPath: ->
|
||||
@configDirPath ?= process.env.ATOM_HOME
|
||||
|
||||
# Get the path to Atom's storage directory.
|
||||
#
|
||||
# Returns the absolute path to ~/.atom/storage
|
||||
@getStorageDirPath: ->
|
||||
@storageDirPath ?= path.join(@getConfigDirPath(), 'storage')
|
||||
@getStorageFolder: ->
|
||||
@storageFolder ?= new StorageFolder(@getConfigDirPath())
|
||||
|
||||
# Returns the load settings hash associated with the current window.
|
||||
@getLoadSettings: ->
|
||||
@@ -791,11 +779,10 @@ class Atom extends Model
|
||||
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
|
||||
|
||||
saveSync: ->
|
||||
stateString = JSON.stringify(@state)
|
||||
if statePath = @constructor.getStatePath(@project?.getPaths(), @mode)
|
||||
fs.writeFileSync(statePath, stateString, 'utf8')
|
||||
if storageKey = @constructor.getStateKey(@project?.getPaths(), @mode)
|
||||
@constructor.getStorageFolder().store(storageKey, @state)
|
||||
else
|
||||
@getCurrentWindow().loadSettings.windowState = stateString
|
||||
@getCurrentWindow().loadSettings.windowState = JSON.stringify(@state)
|
||||
|
||||
crashMainProcess: ->
|
||||
remote.process.crash()
|
||||
|
||||
@@ -3,6 +3,7 @@ ApplicationMenu = require './application-menu'
|
||||
AtomProtocolHandler = require './atom-protocol-handler'
|
||||
AutoUpdateManager = require './auto-update-manager'
|
||||
BrowserWindow = require 'browser-window'
|
||||
StorageFolder = require '../storage-folder'
|
||||
Menu = require 'menu'
|
||||
app = require 'app'
|
||||
fs = require 'fs-plus'
|
||||
@@ -43,7 +44,6 @@ class AtomApplication
|
||||
createAtomApplication()
|
||||
return
|
||||
|
||||
|
||||
client = net.connect {path: options.socketPath}, ->
|
||||
client.write JSON.stringify(options), ->
|
||||
client.end()
|
||||
@@ -78,10 +78,13 @@ class AtomApplication
|
||||
@listenForArgumentsFromNewProcess()
|
||||
@setupJavaScriptArguments()
|
||||
@handleEvents()
|
||||
@storageFolder = new StorageFolder(process.env.ATOM_HOME)
|
||||
|
||||
@openWithOptions(options)
|
||||
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
|
||||
@openWithOptions(options)
|
||||
else
|
||||
@loadState() or @openPath(options)
|
||||
|
||||
# Opens a new window based on the options provided.
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, apiPreviewMode, newWindow, specDirectory, logFile}) ->
|
||||
if test
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
|
||||
@@ -198,6 +201,9 @@ class AtomApplication
|
||||
app.on 'window-all-closed', ->
|
||||
app.quit() if process.platform in ['win32', 'linux']
|
||||
|
||||
app.on 'before-quit', =>
|
||||
@saveState()
|
||||
|
||||
app.on 'will-quit', =>
|
||||
@killAllProcesses()
|
||||
@deleteSocketFile()
|
||||
@@ -328,7 +334,7 @@ class AtomApplication
|
||||
focusedWindow: ->
|
||||
_.find @windows, (atomWindow) -> atomWindow.isFocused()
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
# Public: Opens a single path, in an existing window if possible.
|
||||
#
|
||||
# options -
|
||||
# :pathToOpen - The file path to open
|
||||
@@ -341,7 +347,7 @@ class AtomApplication
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, apiPreviewMode, window})
|
||||
|
||||
# Public: Opens a single path, in an existing window if possible.
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
# options -
|
||||
# :pathsToOpen - The array of file paths to open
|
||||
@@ -411,6 +417,33 @@ class AtomApplication
|
||||
console.log("Killing process #{pid} failed: #{error.code ? error.message}")
|
||||
delete @pidsToOpenWindows[pid]
|
||||
|
||||
saveState: ->
|
||||
states = []
|
||||
for window in @windows
|
||||
if loadSettings = window.getLoadSettings()
|
||||
unless loadSettings.isSpec
|
||||
states.push(_.pick(loadSettings,
|
||||
'initialPaths'
|
||||
'devMode'
|
||||
'safeMode'
|
||||
'apiPreviewMode'
|
||||
))
|
||||
@storageFolder.store('application.json', states)
|
||||
|
||||
loadState: ->
|
||||
if (states = @storageFolder.load('application.json'))?.length > 0
|
||||
for state in states
|
||||
@openWithOptions({
|
||||
pathsToOpen: state.initialPaths
|
||||
urlsToOpen: []
|
||||
devMode: state.devMode
|
||||
safeMode: state.safeMode
|
||||
apiPreviewMode: state.apiPreviewMode
|
||||
})
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
# Open an atom:// url.
|
||||
#
|
||||
# The host of the URL being opened is assumed to be the package name
|
||||
|
||||
@@ -87,8 +87,9 @@ class AtomWindow
|
||||
hash: encodeURIComponent(JSON.stringify(loadSettings))
|
||||
|
||||
getLoadSettings: ->
|
||||
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
|
||||
JSON.parse(decodeURIComponent(hash))
|
||||
if @browserWindow.webContents.loaded
|
||||
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
|
||||
JSON.parse(decodeURIComponent(hash))
|
||||
|
||||
hasProjectPath: -> @getLoadSettings().initialPaths?.length > 0
|
||||
|
||||
@@ -105,7 +106,7 @@ class AtomWindow
|
||||
true
|
||||
|
||||
containsPath: (pathToCheck) ->
|
||||
@getLoadSettings().initialPaths?.some (projectPath) ->
|
||||
@getLoadSettings()?.initialPaths?.some (projectPath) ->
|
||||
if not projectPath
|
||||
false
|
||||
else if not pathToCheck
|
||||
|
||||
27
src/storage-folder.coffee
Normal file
27
src/storage-folder.coffee
Normal file
@@ -0,0 +1,27 @@
|
||||
path = require "path"
|
||||
fs = require "fs-plus"
|
||||
|
||||
module.exports =
|
||||
class StorageFolder
|
||||
constructor: (containingPath) ->
|
||||
@path = path.join(containingPath, "storage")
|
||||
|
||||
store: (name, object) ->
|
||||
fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8')
|
||||
|
||||
load: (name) ->
|
||||
try
|
||||
stateString = fs.readFileSync(@pathForKey(name), 'utf8')
|
||||
catch error
|
||||
if error.code is 'ENOENT'
|
||||
return undefined
|
||||
else
|
||||
throw error
|
||||
|
||||
try
|
||||
JSON.parse(stateString)
|
||||
catch error
|
||||
console.warn "Error reading state file: #{statePath}", error.stack, error
|
||||
|
||||
pathForKey: (name) -> path.join(@getPath(), name)
|
||||
getPath: -> @path
|
||||
Reference in New Issue
Block a user