mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge pull request #19169 from atom/aw/launch-it
Improve launch behavior
This commit is contained in:
@@ -206,7 +206,7 @@ describe('AtomEnvironment', () => {
|
||||
const [dir1, dir2] = [temp.mkdirSync('dir1-'), temp.mkdirSync('dir2-')]
|
||||
|
||||
const loadSettings = Object.assign(atom.getLoadSettings(), {
|
||||
initialPaths: [dir1],
|
||||
initialProjectRoots: [dir1],
|
||||
windowState: null
|
||||
})
|
||||
|
||||
@@ -221,7 +221,7 @@ describe('AtomEnvironment', () => {
|
||||
await atom.saveState()
|
||||
expect(await atom.loadState()).toBeFalsy()
|
||||
|
||||
loadSettings.initialPaths = [dir2, dir1]
|
||||
loadSettings.initialProjectRoots = [dir2, dir1]
|
||||
expect(await atom.loadState()).toEqual({ stuff: 'cool' })
|
||||
})
|
||||
|
||||
@@ -393,9 +393,7 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
describe('openInitialEmptyEditorIfNecessary', () => {
|
||||
describe('when there are no paths set', () => {
|
||||
beforeEach(() =>
|
||||
spyOn(atom, 'getLoadSettings').andReturn({ initialPaths: [] })
|
||||
)
|
||||
beforeEach(() => spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: false }))
|
||||
|
||||
it('opens an empty buffer', () => {
|
||||
spyOn(atom.workspace, 'open')
|
||||
@@ -403,24 +401,24 @@ describe('AtomEnvironment', () => {
|
||||
expect(atom.workspace.open).toHaveBeenCalledWith(null)
|
||||
})
|
||||
|
||||
describe('when there is already a buffer open', () => {
|
||||
beforeEach(async () => {
|
||||
await atom.workspace.open()
|
||||
})
|
||||
it('does not open an empty buffer when a buffer is already open', async () => {
|
||||
await atom.workspace.open()
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not open an empty buffer', () => {
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
})
|
||||
it('does not open an empty buffer when core.openEmptyEditorOnStart is false', async () => {
|
||||
atom.config.set('core.openEmptyEditorOnStart', false)
|
||||
spyOn(atom.workspace, 'open')
|
||||
atom.openInitialEmptyEditorIfNecessary()
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the project has a path', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(atom, 'getLoadSettings').andReturn({
|
||||
initialPaths: ['something']
|
||||
})
|
||||
spyOn(atom, 'getLoadSettings').andReturn({ hasOpenFiles: true })
|
||||
spyOn(atom.workspace, 'open')
|
||||
})
|
||||
|
||||
@@ -433,10 +431,10 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
describe('adding a project folder', () => {
|
||||
it('does nothing if the user dismisses the file picker', () => {
|
||||
const initialPaths = atom.project.getPaths()
|
||||
const projectRoots = atom.project.getPaths()
|
||||
spyOn(atom, 'pickFolder').andCallFake(callback => callback(null))
|
||||
atom.addProjectFolder()
|
||||
expect(atom.project.getPaths()).toEqual(initialPaths)
|
||||
expect(atom.project.getPaths()).toEqual(projectRoots)
|
||||
})
|
||||
|
||||
describe('when there is no saved state for the added folders', () => {
|
||||
@@ -708,7 +706,7 @@ describe('AtomEnvironment', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('::openLocations(locations) (called via IPC from browser process)', () => {
|
||||
describe('::openLocations(locations)', () => {
|
||||
beforeEach(() => {
|
||||
atom.project.setPaths([])
|
||||
})
|
||||
@@ -721,13 +719,13 @@ describe('AtomEnvironment', () => {
|
||||
describe('when the opened path exists', () => {
|
||||
it('opens a file', async () => {
|
||||
const pathToOpen = __filename
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
await atom.openLocations([{ pathToOpen, exists: true, isFile: true }])
|
||||
expect(atom.project.getPaths()).toEqual([])
|
||||
})
|
||||
|
||||
it('opens a directory as a project folder', async () => {
|
||||
const pathToOpen = __dirname
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
await atom.openLocations([{ pathToOpen, exists: true, isDirectory: true }])
|
||||
expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual(
|
||||
[]
|
||||
)
|
||||
@@ -741,7 +739,7 @@ describe('AtomEnvironment', () => {
|
||||
__dirname,
|
||||
'this-path-does-not-exist.txt'
|
||||
)
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
await atom.openLocations([{ pathToOpen, exists: false }])
|
||||
expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual(
|
||||
[pathToOpen]
|
||||
)
|
||||
@@ -756,9 +754,9 @@ describe('AtomEnvironment', () => {
|
||||
const existingDir = path.join(__dirname, 'fixtures')
|
||||
|
||||
await atom.openLocations([
|
||||
{ pathToOpen: nonExistent, mustBeDirectory: true },
|
||||
{ pathToOpen: existingFile, mustBeDirectory: true },
|
||||
{ pathToOpen: existingDir, mustBeDirectory: true }
|
||||
{ pathToOpen: nonExistent, isDirectory: true },
|
||||
{ pathToOpen: existingFile, isDirectory: true },
|
||||
{ pathToOpen: existingDir, isDirectory: true }
|
||||
])
|
||||
|
||||
expect(atom.workspace.getTextEditors()).toEqual([])
|
||||
@@ -829,7 +827,7 @@ describe('AtomEnvironment', () => {
|
||||
describe('when there are no project folders', () => {
|
||||
it('attempts to restore the project state', async () => {
|
||||
const pathToOpen = __dirname
|
||||
await atom.openLocations([{ pathToOpen }])
|
||||
await atom.openLocations([{ pathToOpen, isDirectory: true }])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
|
||||
state,
|
||||
[pathToOpen],
|
||||
@@ -852,7 +850,7 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
await atom.openLocations([
|
||||
{ pathToOpen: existingDir },
|
||||
{ pathToOpen: missingDir, mustBeDirectory: true }
|
||||
{ pathToOpen: missingDir, isDirectory: true }
|
||||
])
|
||||
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
|
||||
@@ -865,7 +863,7 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
it('opens the specified files', async () => {
|
||||
await atom.openLocations([
|
||||
{ pathToOpen: __dirname },
|
||||
{ pathToOpen: __dirname, isDirectory: true },
|
||||
{ pathToOpen: __filename }
|
||||
])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).toHaveBeenCalledWith(
|
||||
@@ -882,7 +880,7 @@ describe('AtomEnvironment', () => {
|
||||
|
||||
it('does not attempt to restore the project state, instead adding the project paths', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'fixtures')
|
||||
await atom.openLocations([{ pathToOpen, forceAddToWindow: true }])
|
||||
await atom.openLocations([{ pathToOpen, exists: true, isDirectory: true }])
|
||||
expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalled()
|
||||
expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen])
|
||||
})
|
||||
@@ -890,7 +888,10 @@ describe('AtomEnvironment', () => {
|
||||
it('opens the specified files', async () => {
|
||||
const pathToOpen = path.join(__dirname, 'fixtures')
|
||||
const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt')
|
||||
await atom.openLocations([{ pathToOpen }, { pathToOpen: fileToOpen }])
|
||||
await atom.openLocations([
|
||||
{ pathToOpen, exists: true, isDirectory: true },
|
||||
{ pathToOpen: fileToOpen, exists: true, isFile: true }
|
||||
])
|
||||
expect(
|
||||
atom.attemptRestoreProjectStateForPaths
|
||||
).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen])
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
405
spec/main-process/atom-window.test.js
Normal file
405
spec/main-process/atom-window.test.js
Normal file
@@ -0,0 +1,405 @@
|
||||
/* globals assert */
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
const url = require('url')
|
||||
const {EventEmitter} = require('events')
|
||||
const temp = require('temp').track()
|
||||
const {sandbox} = require('sinon')
|
||||
const dedent = require('dedent')
|
||||
|
||||
const AtomWindow = require('../../src/main-process/atom-window')
|
||||
const {emitterEventPromise} = require('../async-spec-helpers')
|
||||
|
||||
describe('AtomWindow', function () {
|
||||
let sinon, app, service
|
||||
|
||||
beforeEach(function () {
|
||||
sinon = sandbox.create()
|
||||
app = new StubApplication(sinon)
|
||||
service = new StubRecoveryService(sinon)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
describe('creating a real window', function () {
|
||||
let resourcePath, windowInitializationScript, atomHome
|
||||
let original
|
||||
|
||||
this.timeout(10 * 1000)
|
||||
|
||||
beforeEach(async function () {
|
||||
original = {
|
||||
ATOM_HOME: process.env.ATOM_HOME,
|
||||
ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT: process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT
|
||||
}
|
||||
|
||||
resourcePath = path.resolve(__dirname, '../..')
|
||||
|
||||
windowInitializationScript = require.resolve(
|
||||
path.join(resourcePath, 'src/initialize-application-window')
|
||||
)
|
||||
|
||||
atomHome = await new Promise((resolve, reject) => {
|
||||
temp.mkdir('launch-', (err, rootPath) => {
|
||||
if (err) { reject(err) } else { resolve(rootPath) }
|
||||
})
|
||||
})
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const config = dedent`
|
||||
'*':
|
||||
core:
|
||||
automaticallyUpdate: false
|
||||
telemetryConsent: "no"
|
||||
welcome:
|
||||
showOnStartup: false
|
||||
`
|
||||
|
||||
fs.writeFile(path.join(atomHome, 'config.cson'), config, {encoding: 'utf8'}, err => {
|
||||
if (err) { reject(err) } else { resolve() }
|
||||
})
|
||||
})
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
fs.symlink(
|
||||
path.join(original.ATOM_HOME, 'compile-cache'),
|
||||
path.join(atomHome, 'compile-cache'),
|
||||
'junction',
|
||||
err => { if (err) { reject(err) } else { resolve() } }
|
||||
)
|
||||
})
|
||||
|
||||
process.env.ATOM_HOME = atomHome
|
||||
process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = 'true'
|
||||
})
|
||||
|
||||
afterEach(async function () {
|
||||
process.env.ATOM_HOME = original.ATOM_HOME
|
||||
process.env.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT = original.ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT
|
||||
})
|
||||
|
||||
it('creates a real, properly configured BrowserWindow', async function () {
|
||||
const w = new AtomWindow(app, service, {
|
||||
resourcePath,
|
||||
windowInitializationScript,
|
||||
headless: true,
|
||||
extra: 'extra-load-setting'
|
||||
})
|
||||
const {browserWindow} = w
|
||||
|
||||
assert.isFalse(browserWindow.isVisible())
|
||||
assert.strictEqual(browserWindow.getTitle(), 'Atom')
|
||||
|
||||
const settings = JSON.parse(browserWindow.loadSettingsJSON)
|
||||
assert.strictEqual(settings.userSettings, 'stub-config')
|
||||
assert.strictEqual(settings.extra, 'extra-load-setting')
|
||||
assert.strictEqual(settings.resourcePath, resourcePath)
|
||||
assert.strictEqual(settings.atomHome, atomHome)
|
||||
assert.isFalse(settings.devMode)
|
||||
assert.isFalse(settings.safeMode)
|
||||
assert.isFalse(settings.clearWindowState)
|
||||
|
||||
await emitterEventPromise(browserWindow, 'ready-to-show')
|
||||
|
||||
assert.strictEqual(browserWindow.webContents.getURL(), url.format({
|
||||
protocol: 'file',
|
||||
pathname: `${resourcePath.replace(/\\/g, '/')}/static/index.html`,
|
||||
slashes: true
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('launch behavior', function () {
|
||||
if (process.platform === 'darwin') {
|
||||
it('sets titleBarStyle to "hidden" for a custom title bar on non-spec windows', function () {
|
||||
app.config['core.titleBar'] = 'custom'
|
||||
|
||||
const {browserWindow: w0} = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow
|
||||
})
|
||||
assert.strictEqual(w0.options.titleBarStyle, 'hidden')
|
||||
|
||||
const {browserWindow: w1} = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow,
|
||||
isSpec: true
|
||||
})
|
||||
assert.isUndefined(w1.options.titleBarStyle)
|
||||
})
|
||||
|
||||
it('sets titleBarStyle to "hiddenInset" for a custom inset title bar on non-spec windows', function () {
|
||||
app.config['core.titleBar'] = 'custom-inset'
|
||||
|
||||
const {browserWindow: w0} = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow
|
||||
})
|
||||
assert.strictEqual(w0.options.titleBarStyle, 'hiddenInset')
|
||||
|
||||
const {browserWindow: w1} = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow,
|
||||
isSpec: true
|
||||
})
|
||||
assert.isUndefined(w1.options.titleBarStyle)
|
||||
})
|
||||
|
||||
it('sets frame to "false" for a hidden title bar on non-spec windows', function () {
|
||||
app.config['core.titleBar'] = 'hidden'
|
||||
|
||||
const {browserWindow: w0} = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow
|
||||
})
|
||||
assert.isFalse(w0.options.frame)
|
||||
|
||||
const {browserWindow: w1} = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow,
|
||||
isSpec: true
|
||||
})
|
||||
assert.isUndefined(w1.options.frame)
|
||||
})
|
||||
} else {
|
||||
it('ignores title bar style settings', function () {
|
||||
for (const value of ['custom', 'custom-inset', 'hidden']) {
|
||||
app.config['core.titleBar'] = value
|
||||
const {browserWindow} = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow
|
||||
})
|
||||
assert.isUndefined(browserWindow.options.titleBarStyle)
|
||||
assert.isUndefined(browserWindow.options.frame)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('opens initial locations', async function () {
|
||||
const locationsToOpen = [
|
||||
{
|
||||
pathToOpen: 'file.txt',
|
||||
initialLine: 1,
|
||||
initialColumn: 2,
|
||||
isDirectory: false,
|
||||
hasWaitSession: false
|
||||
},
|
||||
{
|
||||
pathToOpen: '/directory',
|
||||
initialLine: null,
|
||||
initialColumn: null,
|
||||
isDirectory: true,
|
||||
hasWaitSession: false
|
||||
}
|
||||
]
|
||||
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
|
||||
const loadPromise = emitterEventPromise(w, 'window:loaded')
|
||||
w.browserWindow.emit('window:loaded')
|
||||
await loadPromise
|
||||
|
||||
assert.deepEqual(w.browserWindow.sent, [['message', 'open-locations', locationsToOpen]])
|
||||
})
|
||||
|
||||
it('does not open an initial null location', async function () {
|
||||
const w = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow,
|
||||
locationsToOpen: [{ pathToOpen: null }]
|
||||
})
|
||||
|
||||
const loadPromise = emitterEventPromise(w, 'window:loaded')
|
||||
w.browserWindow.emit('window:loaded')
|
||||
await loadPromise
|
||||
|
||||
assert.lengthOf(w.browserWindow.sent, 0)
|
||||
})
|
||||
|
||||
it('does not open initial locations in spec mode', async function () {
|
||||
const w = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow,
|
||||
locationsToOpen: [{ pathToOpen: 'file.txt' }],
|
||||
isSpec: true
|
||||
})
|
||||
|
||||
const loadPromise = emitterEventPromise(w, 'window:loaded')
|
||||
w.browserWindow.emit('window:loaded')
|
||||
await loadPromise
|
||||
|
||||
assert.lengthOf(w.browserWindow.sent, 0)
|
||||
})
|
||||
|
||||
it('focuses the webView for specs', function () {
|
||||
const w = new AtomWindow(app, service, {
|
||||
browserWindowConstructor: StubBrowserWindow,
|
||||
isSpec: true
|
||||
})
|
||||
|
||||
assert.isTrue(w.browserWindow.behavior.focusOnWebView)
|
||||
})
|
||||
})
|
||||
|
||||
describe('project root tracking', function () {
|
||||
it('knows when it has no roots', function () {
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow })
|
||||
assert.isFalse(w.hasProjectPaths())
|
||||
})
|
||||
|
||||
it('is initialized from directories in the initial locationsToOpen', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'file.txt', exists: true, isFile: true },
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'directory1', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'new-file.txt' },
|
||||
{ pathToOpen: null }
|
||||
]
|
||||
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
|
||||
assert.deepEqual(w.projectRoots, ['directory0', 'directory1'])
|
||||
assert.isTrue(w.loadSettings.hasOpenFiles)
|
||||
assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0', 'directory1'])
|
||||
assert.isTrue(w.hasProjectPaths())
|
||||
})
|
||||
|
||||
it('is updated by setProjectRoots', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true }
|
||||
]
|
||||
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
assert.deepEqual(w.projectRoots, ['directory0'])
|
||||
assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0'])
|
||||
|
||||
w.setProjectRoots(['directory1', 'directory0', 'directory2'])
|
||||
assert.deepEqual(w.projectRoots, ['directory0', 'directory1', 'directory2'])
|
||||
assert.deepEqual(w.loadSettings.initialProjectRoots, ['directory0', 'directory1', 'directory2'])
|
||||
})
|
||||
|
||||
it('never reports that it owns the empty path', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'directory1', exists: true, isDirectory: true },
|
||||
{ pathToOpen: null }
|
||||
]
|
||||
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
assert.isFalse(w.containsLocation({pathToOpen: null}))
|
||||
})
|
||||
|
||||
it('discovers an exact path match', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'directory1', exists: true, isDirectory: true }
|
||||
]
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
|
||||
assert.isTrue(w.containsLocation({pathToOpen: 'directory0'}))
|
||||
assert.isFalse(w.containsLocation({pathToOpen: 'directory2'}))
|
||||
})
|
||||
|
||||
it('discovers the path of a file within any project root', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'directory1', exists: true, isDirectory: true }
|
||||
]
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
|
||||
assert.isTrue(w.containsLocation({
|
||||
pathToOpen: path.join('directory0/file-0.txt'), exists: true, isFile: true
|
||||
}))
|
||||
assert.isTrue(w.containsLocation({
|
||||
pathToOpen: path.join('directory0/deep/file-0.txt'), exists: true, isFile: true
|
||||
}))
|
||||
assert.isFalse(w.containsLocation({
|
||||
pathToOpen: path.join('directory2/file-9.txt'), exists: true, isFile: true
|
||||
}))
|
||||
assert.isFalse(w.containsLocation({
|
||||
pathToOpen: path.join('directory2/deep/file-9.txt'), exists: true, isFile: true
|
||||
}))
|
||||
})
|
||||
|
||||
it('reports that it owns nonexistent paths within a project root', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'directory1', exists: true, isDirectory: true }
|
||||
]
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
|
||||
assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory0/file-1.txt'), exists: false }))
|
||||
assert.isTrue(w.containsLocation({ pathToOpen: path.join('directory1/subdir/file-0.txt'), exists: false }))
|
||||
})
|
||||
|
||||
it('never reports that it owns directories within a project root', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'directory1', exists: true, isDirectory: true }
|
||||
]
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
|
||||
assert.isFalse(w.containsLocation({
|
||||
pathToOpen: path.join('directory0/subdir-0'), exists: true, isDirectory: true
|
||||
}))
|
||||
})
|
||||
|
||||
it('checks a full list of paths and reports if it owns all of them', function () {
|
||||
const locationsToOpen = [
|
||||
{ pathToOpen: 'directory0', exists: true, isDirectory: true },
|
||||
{ pathToOpen: 'directory1', exists: true, isDirectory: true }
|
||||
]
|
||||
const w = new AtomWindow(app, service, { browserWindowConstructor: StubBrowserWindow, locationsToOpen })
|
||||
|
||||
assert.isTrue(w.containsLocations([
|
||||
{ pathToOpen: 'directory0' },
|
||||
{ pathToOpen: path.join('directory1/file-0.txt'), exists: true, isFile: true }
|
||||
]))
|
||||
assert.isFalse(w.containsLocations([
|
||||
{ pathToOpen: 'directory2' },
|
||||
{ pathToOpen: 'directory0' }
|
||||
]))
|
||||
assert.isFalse(w.containsLocations([
|
||||
{ pathToOpen: 'directory2' },
|
||||
{ pathToOpen: 'directory1' }
|
||||
]))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
class StubApplication {
|
||||
constructor (sinon) {
|
||||
this.config = {
|
||||
'core.titleBar': 'native',
|
||||
get: key => this.config[key] || null
|
||||
}
|
||||
this.configFile = {
|
||||
get () { return 'stub-config' }
|
||||
}
|
||||
|
||||
this.removeWindow = sinon.spy()
|
||||
this.saveCurrentWindowOptions = sinon.spy()
|
||||
}
|
||||
}
|
||||
|
||||
class StubRecoveryService {
|
||||
constructor (sinon) {
|
||||
this.didCloseWindow = sinon.spy()
|
||||
this.didCrashWindow = sinon.spy()
|
||||
}
|
||||
}
|
||||
|
||||
class StubBrowserWindow extends EventEmitter {
|
||||
constructor (options) {
|
||||
super()
|
||||
this.options = options
|
||||
this.sent = []
|
||||
this.behavior = {
|
||||
focusOnWebView: false
|
||||
}
|
||||
|
||||
this.webContents = new EventEmitter()
|
||||
this.webContents.send = (...args) => { this.sent.push(args) }
|
||||
this.webContents.setVisualZoomLevelLimits = () => {}
|
||||
}
|
||||
|
||||
loadURL () {}
|
||||
|
||||
focusOnWebView () {
|
||||
this.behavior.focusOnWebView = true
|
||||
}
|
||||
}
|
||||
@@ -170,8 +170,8 @@ class ApplicationDelegate {
|
||||
return ipcRenderer.send('add-recent-document', filename)
|
||||
}
|
||||
|
||||
setRepresentedDirectoryPaths (paths) {
|
||||
return ipcHelpers.call('window-method', 'setRepresentedDirectoryPaths', paths)
|
||||
setProjectRoots (paths) {
|
||||
return ipcHelpers.call('window-method', 'setProjectRoots', paths)
|
||||
}
|
||||
|
||||
setAutoHideWindowMenuBar (autoHide) {
|
||||
|
||||
@@ -784,7 +784,9 @@ class AtomEnvironment {
|
||||
|
||||
const loadStatePromise = this.loadState().then(async state => {
|
||||
this.windowDimensions = state && state.windowDimensions
|
||||
await this.displayWindow()
|
||||
if (!this.getLoadSettings().headless) {
|
||||
await this.displayWindow()
|
||||
}
|
||||
this.commandInstaller.installAtomCommand(false, (error) => {
|
||||
if (error) console.warn(error.message)
|
||||
})
|
||||
@@ -838,7 +840,7 @@ class AtomEnvironment {
|
||||
}
|
||||
}
|
||||
previousProjectPaths = newPaths
|
||||
this.applicationDelegate.setRepresentedDirectoryPaths(newPaths)
|
||||
this.applicationDelegate.setProjectRoots(newPaths)
|
||||
}))
|
||||
this.disposables.add(this.workspace.onDidDestroyPaneItem(({item}) => {
|
||||
const path = item.getPath && item.getPath()
|
||||
@@ -916,8 +918,8 @@ class AtomEnvironment {
|
||||
|
||||
openInitialEmptyEditorIfNecessary () {
|
||||
if (!this.config.get('core.openEmptyEditorOnStart')) return
|
||||
const {initialPaths} = this.getLoadSettings()
|
||||
if (initialPaths && initialPaths.length === 0 && this.workspace.getPaneItems().length === 0) {
|
||||
const {hasOpenFiles} = this.getLoadSettings()
|
||||
if (!hasOpenFiles && this.workspace.getPaneItems().length === 0) {
|
||||
return this.workspace.open(null)
|
||||
}
|
||||
}
|
||||
@@ -1213,7 +1215,7 @@ or use Pane::saveItemAs for programmatic saving.`)
|
||||
|
||||
loadState (stateKey) {
|
||||
if (this.enablePersistence) {
|
||||
if (!stateKey) stateKey = this.getStateKey(this.getLoadSettings().initialPaths)
|
||||
if (!stateKey) stateKey = this.getStateKey(this.getLoadSettings().initialProjectRoots)
|
||||
if (stateKey) {
|
||||
return this.stateStore.load(stateKey)
|
||||
} else {
|
||||
@@ -1388,7 +1390,7 @@ or use Pane::saveItemAs for programmatic saving.`)
|
||||
// Directory: add as a project folder
|
||||
foldersToAddToProject.add(this.project.getDirectoryForProjectPath(pathToOpen).getPath())
|
||||
} else if (stats.isFile()) {
|
||||
if (location.mustBeDirectory) {
|
||||
if (location.isDirectory) {
|
||||
// File: no longer a directory
|
||||
missingFolders.push(location)
|
||||
} else {
|
||||
@@ -1403,7 +1405,7 @@ or use Pane::saveItemAs for programmatic saving.`)
|
||||
if (directory) {
|
||||
// Found: add as a project folder
|
||||
foldersToAddToProject.add(directory.getPath())
|
||||
} else if (location.mustBeDirectory) {
|
||||
} else if (location.isDirectory) {
|
||||
// Not found and must be a directory: add to missing list and use to derive state key
|
||||
missingFolders.push(location)
|
||||
} else {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const fs = require('fs-plus')
|
||||
|
||||
// Converts a query string parameter for a line or column number
|
||||
// to a zero-based line or column number for the Atom API.
|
||||
function getLineColNumber (numStr) {
|
||||
@@ -17,7 +19,14 @@ function openFile (atom, {query}) {
|
||||
|
||||
function windowShouldOpenFile ({query}) {
|
||||
const {filename} = query
|
||||
return (win) => win.containsPath(filename)
|
||||
const stat = fs.statSyncNoException(filename)
|
||||
|
||||
return win => win.containsLocation({
|
||||
pathToOpen: filename,
|
||||
exists: Boolean(stat),
|
||||
isFile: stat.isFile(),
|
||||
isDirectory: stat.isDirectory()
|
||||
})
|
||||
}
|
||||
|
||||
const ROUTER = {
|
||||
@@ -39,7 +48,7 @@ module.exports = {
|
||||
if (config && config.getWindowPredicate) {
|
||||
return config.getWindowPredicate(parsed)
|
||||
} else {
|
||||
return (win) => true
|
||||
return () => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ exports.respondTo = function (channel, callback) {
|
||||
return exports.on(ipcMain, channel, async (event, responseChannel, ...args) => {
|
||||
const browserWindow = BrowserWindow.fromWebContents(event.sender)
|
||||
const result = await callback(browserWindow, ...args)
|
||||
event.sender.send(responseChannel, result)
|
||||
if (!event.sender.isDestroyed()) {
|
||||
event.sender.send(responseChannel, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,6 +120,11 @@ class AtomApplication extends EventEmitter {
|
||||
static open (options) {
|
||||
const socketSecret = getExistingSocketSecret(options.version)
|
||||
const socketPath = getSocketPath(socketSecret)
|
||||
const createApplication = options.createApplication || (async () => {
|
||||
const app = new AtomApplication(options)
|
||||
await app.initialize(options)
|
||||
return app
|
||||
})
|
||||
|
||||
// FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
|
||||
// take a few seconds to trigger 'error' event, it could be a bug of node
|
||||
@@ -129,18 +134,20 @@ class AtomApplication extends EventEmitter {
|
||||
!socketPath || options.test || options.benchmark || options.benchmarkTest ||
|
||||
(process.platform !== 'win32' && !fs.existsSync(socketPath))
|
||||
) {
|
||||
new AtomApplication(options).initialize(options)
|
||||
return
|
||||
return createApplication(options)
|
||||
}
|
||||
|
||||
const client = net.connect({path: socketPath}, () => {
|
||||
client.write(encryptOptions(options, socketSecret), () => {
|
||||
client.end()
|
||||
app.quit()
|
||||
return new Promise(resolve => {
|
||||
const client = net.connect({path: socketPath}, () => {
|
||||
client.write(encryptOptions(options, socketSecret), () => {
|
||||
client.end()
|
||||
app.quit()
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
client.on('error', () => new AtomApplication(options).initialize(options))
|
||||
client.on('error', () => resolve(createApplication(options)))
|
||||
})
|
||||
}
|
||||
|
||||
exit (status) {
|
||||
@@ -246,19 +253,19 @@ class AtomApplication extends EventEmitter {
|
||||
|
||||
if (options.test || options.benchmark || options.benchmarkTest) {
|
||||
optionsForWindowsToOpen.push(options)
|
||||
} else if (options.newWindow) {
|
||||
shouldReopenPreviousWindows = false
|
||||
} else if ((options.pathsToOpen && options.pathsToOpen.length > 0) ||
|
||||
(options.urlsToOpen && options.urlsToOpen.length > 0)) {
|
||||
optionsForWindowsToOpen.push(options)
|
||||
shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') === 'always'
|
||||
} else if (options.newWindow) {
|
||||
shouldReopenPreviousWindows = false
|
||||
} else {
|
||||
shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') !== 'no'
|
||||
}
|
||||
|
||||
if (shouldReopenPreviousWindows) {
|
||||
for (const previousOptions of await this.loadPreviousWindowOptions()) {
|
||||
optionsForWindowsToOpen.push(Object.assign({}, options, previousOptions))
|
||||
optionsForWindowsToOpen.push(previousOptions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +273,12 @@ class AtomApplication extends EventEmitter {
|
||||
optionsForWindowsToOpen.push(options)
|
||||
}
|
||||
|
||||
return optionsForWindowsToOpen.map(options => this.openWithOptions(options))
|
||||
// Preserve window opening order
|
||||
const windows = []
|
||||
for (const options of optionsForWindowsToOpen) {
|
||||
windows.push(await this.openWithOptions(options))
|
||||
}
|
||||
return windows
|
||||
}
|
||||
|
||||
openWithOptions (options) {
|
||||
@@ -287,10 +299,13 @@ class AtomApplication extends EventEmitter {
|
||||
timeout,
|
||||
clearWindowState,
|
||||
addToLastWindow,
|
||||
preserveFocus,
|
||||
env
|
||||
} = options
|
||||
|
||||
app.focus()
|
||||
if (!preserveFocus) {
|
||||
app.focus()
|
||||
}
|
||||
|
||||
if (test) {
|
||||
return this.runTests({
|
||||
@@ -327,11 +342,14 @@ class AtomApplication extends EventEmitter {
|
||||
addToLastWindow,
|
||||
env
|
||||
})
|
||||
} else if (urlsToOpen.length > 0) {
|
||||
return urlsToOpen.map(urlToOpen => this.openUrl({urlToOpen, devMode, safeMode, env}))
|
||||
} else if (urlsToOpen && urlsToOpen.length > 0) {
|
||||
return Promise.all(
|
||||
urlsToOpen.map(urlToOpen => this.openUrl({urlToOpen, devMode, safeMode, env}))
|
||||
)
|
||||
} else {
|
||||
// Always open a editor window if this is the first instance of Atom.
|
||||
// Always open an editor window if this is the first instance of Atom.
|
||||
return this.openPath({
|
||||
pathToOpen: null,
|
||||
pidToKillWhenClosed,
|
||||
newWindow,
|
||||
devMode,
|
||||
@@ -344,6 +362,11 @@ class AtomApplication extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// Public: Create a new {AtomWindow} bound to this application.
|
||||
createWindow (settings) {
|
||||
return new AtomWindow(this, this.fileRecoveryService, settings)
|
||||
}
|
||||
|
||||
// Public: Removes the {AtomWindow} from the global window list.
|
||||
removeWindow (window) {
|
||||
this.windowStack.removeWindow(window)
|
||||
@@ -379,6 +402,7 @@ class AtomApplication extends EventEmitter {
|
||||
window.browserWindow.removeListener('blur', blurHandler)
|
||||
})
|
||||
window.browserWindow.webContents.once('did-finish-load', blurHandler)
|
||||
this.saveCurrentWindowOptions(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,8 +437,10 @@ class AtomApplication extends EventEmitter {
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(this.socketPath)
|
||||
server.on('error', error => console.error('Application server failed', error))
|
||||
return new Promise(resolve => {
|
||||
server.listen(this.socketPath, resolve)
|
||||
server.on('error', error => console.error('Application server failed', error))
|
||||
})
|
||||
}
|
||||
|
||||
deleteSocketFile () {
|
||||
@@ -455,9 +481,9 @@ class AtomApplication extends EventEmitter {
|
||||
const getLoadSettings = includeWindow => {
|
||||
const window = this.focusedWindow()
|
||||
return {
|
||||
devMode: window && window.devMode,
|
||||
safeMode: window && window.safeMode,
|
||||
window: includeWindow && window
|
||||
devMode: window ? window.devMode : false,
|
||||
safeMode: window ? window.safeMode : false,
|
||||
window: includeWindow && window ? window : null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,7 +635,7 @@ class AtomApplication extends EventEmitter {
|
||||
options.window = window
|
||||
this.openPaths(options)
|
||||
} else {
|
||||
this.addWindow(new AtomWindow(this, this.fileRecoveryService, options))
|
||||
this.addWindow(this.createWindow(options))
|
||||
}
|
||||
} else {
|
||||
this.promptForPathToOpen('all', {window})
|
||||
@@ -657,7 +683,7 @@ class AtomApplication extends EventEmitter {
|
||||
|
||||
this.disposable.add(ipcHelpers.on(ipcMain, 'window-command', (event, command, ...args) => {
|
||||
const window = BrowserWindow.fromWebContents(event.sender)
|
||||
return window.emit(command, ...args)
|
||||
return window && window.emit(command, ...args)
|
||||
}))
|
||||
|
||||
this.disposable.add(ipcHelpers.respondTo('window-method', (browserWindow, method, ...args) => {
|
||||
@@ -729,10 +755,6 @@ class AtomApplication extends EventEmitter {
|
||||
this.fileRecoveryService.didSavePath(window, path)
|
||||
))
|
||||
|
||||
this.disposable.add(ipcHelpers.on(ipcMain, 'did-change-paths', () =>
|
||||
this.saveCurrentWindowOptions(false)
|
||||
))
|
||||
|
||||
this.disposable.add(this.disableZoomOnDisplayChange())
|
||||
}
|
||||
|
||||
@@ -831,10 +853,12 @@ class AtomApplication extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
// Returns the {AtomWindow} for the given paths.
|
||||
windowForPaths (pathsToOpen, devMode) {
|
||||
return this.getAllWindows().find(window =>
|
||||
window.devMode === devMode && window.containsPaths(pathsToOpen)
|
||||
// Returns the {AtomWindow} for the given locations.
|
||||
windowForLocations (locationsToOpen, devMode, safeMode) {
|
||||
return this.getLastFocusedWindow(window =>
|
||||
window.devMode === devMode &&
|
||||
window.safeMode === safeMode &&
|
||||
window.containsLocations(locationsToOpen)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -924,7 +948,7 @@ class AtomApplication extends EventEmitter {
|
||||
// :windowDimensions - Object with height and width keys.
|
||||
// :window - {AtomWindow} to open file paths in.
|
||||
// :addToLastWindow - Boolean of whether this should be opened in last focused window.
|
||||
openPaths ({
|
||||
async openPaths ({
|
||||
pathsToOpen,
|
||||
foldersToOpen,
|
||||
executedFrom,
|
||||
@@ -947,20 +971,19 @@ class AtomApplication extends EventEmitter {
|
||||
safeMode = Boolean(safeMode)
|
||||
clearWindowState = Boolean(clearWindowState)
|
||||
|
||||
const locationsToOpen = pathsToOpen.map(pathToOpen => {
|
||||
return this.parsePathToOpen(pathToOpen, executedFrom, {
|
||||
forceAddToWindow: addToLastWindow,
|
||||
const locationsToOpen = await Promise.all(
|
||||
pathsToOpen.map(pathToOpen => this.parsePathToOpen(pathToOpen, executedFrom, {
|
||||
hasWaitSession: pidToKillWhenClosed != null
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
|
||||
for (const folderToOpen of foldersToOpen) {
|
||||
locationsToOpen.push({
|
||||
pathToOpen: folderToOpen,
|
||||
initialLine: null,
|
||||
initialColumn: null,
|
||||
mustBeDirectory: true,
|
||||
forceAddToWindow: addToLastWindow,
|
||||
exists: true,
|
||||
isDirectory: true,
|
||||
hasWaitSession: pidToKillWhenClosed != null
|
||||
})
|
||||
}
|
||||
@@ -969,27 +992,42 @@ class AtomApplication extends EventEmitter {
|
||||
return
|
||||
}
|
||||
|
||||
const normalizedPathsToOpen = locationsToOpen.map(location => location.pathToOpen).filter(Boolean)
|
||||
const hasNonEmptyPath = locationsToOpen.some(location => location.pathToOpen)
|
||||
const createNewWindow = newWindow || !hasNonEmptyPath
|
||||
|
||||
let existingWindow
|
||||
|
||||
// Explicitly provided AtomWindow has precedence unless a new window is forced.
|
||||
if (!newWindow) {
|
||||
if (!createNewWindow) {
|
||||
// An explicitly provided AtomWindow has precedence.
|
||||
existingWindow = window
|
||||
}
|
||||
|
||||
// If no window is specified, a new window is not forced, and at least one path is provided, locate
|
||||
// an existing window that contains all paths.
|
||||
if (!existingWindow && !newWindow && normalizedPathsToOpen.length > 0) {
|
||||
existingWindow = this.windowForPaths(normalizedPathsToOpen, devMode)
|
||||
}
|
||||
// If no window is specified and at least one path is provided, locate an existing window that contains all
|
||||
// provided paths.
|
||||
if (!existingWindow && hasNonEmptyPath) {
|
||||
existingWindow = this.windowForLocations(locationsToOpen, devMode, safeMode)
|
||||
}
|
||||
|
||||
// No window specified, new window not forced, no existing window found, and addition to the last window
|
||||
// requested. Find the last focused window.
|
||||
if (!existingWindow && !newWindow && addToLastWindow) {
|
||||
let lastWindow = window || this.getLastFocusedWindow()
|
||||
if (lastWindow && lastWindow.devMode === devMode) {
|
||||
existingWindow = lastWindow
|
||||
// No window specified, no existing window found, and addition to the last window requested. Find the last
|
||||
// focused window that matches the requested dev and safe modes.
|
||||
if (!existingWindow && addToLastWindow) {
|
||||
existingWindow = this.getLastFocusedWindow(win => {
|
||||
return win.devMode === devMode && win.safeMode === safeMode
|
||||
})
|
||||
}
|
||||
|
||||
// Fall back to the last focused window that has no project roots.
|
||||
if (!existingWindow) {
|
||||
existingWindow = this.getLastFocusedWindow(win => !win.hasProjectPaths())
|
||||
}
|
||||
|
||||
// One last case: if *no* paths are directories, add to the last focused window.
|
||||
if (!existingWindow) {
|
||||
const noDirectories = locationsToOpen.every(location => !location.isDirectory)
|
||||
if (noDirectories) {
|
||||
existingWindow = this.getLastFocusedWindow(win => {
|
||||
return win.devMode === devMode && win.safeMode === safeMode
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1020,7 +1058,7 @@ class AtomApplication extends EventEmitter {
|
||||
if (!resourcePath) resourcePath = this.resourcePath
|
||||
if (!windowDimensions) windowDimensions = this.getDimensionsForNewWindow()
|
||||
|
||||
openedWindow = new AtomWindow(this, this.fileRecoveryService, {
|
||||
openedWindow = this.createWindow({
|
||||
locationsToOpen,
|
||||
windowInitializationScript,
|
||||
resourcePath,
|
||||
@@ -1041,7 +1079,7 @@ class AtomApplication extends EventEmitter {
|
||||
}
|
||||
this.waitSessionsByWindow.get(openedWindow).push({
|
||||
pid: pidToKillWhenClosed,
|
||||
remainingPaths: new Set(normalizedPathsToOpen)
|
||||
remainingPaths: new Set(locationsToOpen.map(location => location.pathToOpen).filter(Boolean))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1095,7 +1133,7 @@ class AtomApplication extends EventEmitter {
|
||||
|
||||
const states = []
|
||||
for (let window of this.getAllWindows()) {
|
||||
if (!window.isSpec) states.push({initialPaths: window.representedDirectoryPaths})
|
||||
if (!window.isSpec) states.push({initialPaths: window.projectRoots})
|
||||
}
|
||||
states.reverse()
|
||||
|
||||
@@ -1110,7 +1148,6 @@ class AtomApplication extends EventEmitter {
|
||||
if (states) {
|
||||
return states.map(state => ({
|
||||
foldersToOpen: state.initialPaths,
|
||||
urlsToOpen: [],
|
||||
devMode: this.devMode,
|
||||
safeMode: this.safeMode
|
||||
}))
|
||||
@@ -1161,6 +1198,7 @@ class AtomApplication extends EventEmitter {
|
||||
if (bestWindow) {
|
||||
bestWindow.sendURIMessage(url)
|
||||
bestWindow.focus()
|
||||
return bestWindow
|
||||
} else {
|
||||
let windowInitializationScript
|
||||
let {resourcePath} = this
|
||||
@@ -1178,7 +1216,7 @@ class AtomApplication extends EventEmitter {
|
||||
}
|
||||
|
||||
const windowDimensions = this.getDimensionsForNewWindow()
|
||||
const window = new AtomWindow(this, this.fileRecoveryService, {
|
||||
const window = this.createWindow({
|
||||
resourcePath,
|
||||
windowInitializationScript,
|
||||
devMode,
|
||||
@@ -1202,7 +1240,7 @@ class AtomApplication extends EventEmitter {
|
||||
const packagePath = this.getPackageManager(devMode).resolvePackagePath(packageName)
|
||||
const windowInitializationScript = path.resolve(packagePath, packageUrlMain)
|
||||
const windowDimensions = this.getDimensionsForNewWindow()
|
||||
const window = new AtomWindow(this, this.fileRecoveryService, {
|
||||
const window = this.createWindow({
|
||||
windowInitializationScript,
|
||||
resourcePath: this.resourcePath,
|
||||
devMode,
|
||||
@@ -1284,7 +1322,7 @@ class AtomApplication extends EventEmitter {
|
||||
if (safeMode == null) {
|
||||
safeMode = false
|
||||
}
|
||||
const window = new AtomWindow(this, this.fileRecoveryService, {
|
||||
const window = this.createWindow({
|
||||
windowInitializationScript,
|
||||
resourcePath,
|
||||
headless,
|
||||
@@ -1333,7 +1371,7 @@ class AtomApplication extends EventEmitter {
|
||||
const devMode = true
|
||||
const isSpec = true
|
||||
const safeMode = false
|
||||
const window = new AtomWindow(this, this.fileRecoveryService, {
|
||||
const window = this.createWindow({
|
||||
windowInitializationScript,
|
||||
resourcePath,
|
||||
headless,
|
||||
@@ -1388,31 +1426,58 @@ class AtomApplication extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
parsePathToOpen (pathToOpen, executedFrom, extra) {
|
||||
let initialColumn, initialLine
|
||||
async parsePathToOpen (pathToOpen, executedFrom, extra) {
|
||||
const result = Object.assign({
|
||||
pathToOpen,
|
||||
initialColumn: null,
|
||||
initialLine: null,
|
||||
exists: false,
|
||||
isDirectory: false,
|
||||
isFile: false
|
||||
}, extra)
|
||||
|
||||
if (!pathToOpen) {
|
||||
return {pathToOpen}
|
||||
return result
|
||||
}
|
||||
|
||||
pathToOpen = pathToOpen.replace(/[:\s]+$/, '')
|
||||
const match = pathToOpen.match(LocationSuffixRegExp)
|
||||
result.pathToOpen = result.pathToOpen.replace(/[:\s]+$/, '')
|
||||
const match = result.pathToOpen.match(LocationSuffixRegExp)
|
||||
|
||||
if (match != null) {
|
||||
pathToOpen = pathToOpen.slice(0, -match[0].length)
|
||||
result.pathToOpen = result.pathToOpen.slice(0, -match[0].length)
|
||||
if (match[1]) {
|
||||
initialLine = Math.max(0, parseInt(match[1].slice(1)) - 1)
|
||||
result.initialLine = Math.max(0, parseInt(match[1].slice(1), 10) - 1)
|
||||
}
|
||||
if (match[2]) {
|
||||
initialColumn = Math.max(0, parseInt(match[2].slice(1)) - 1)
|
||||
result.initialColumn = Math.max(0, parseInt(match[2].slice(1), 10) - 1)
|
||||
}
|
||||
} else {
|
||||
initialLine = initialColumn = null
|
||||
}
|
||||
|
||||
const normalizedPath = path.normalize(path.resolve(executedFrom, fs.normalize(pathToOpen)))
|
||||
if (!url.parse(pathToOpen).protocol) pathToOpen = normalizedPath
|
||||
const normalizedPath = path.normalize(path.resolve(executedFrom, fs.normalize(result.pathToOpen)))
|
||||
if (!url.parse(pathToOpen).protocol) {
|
||||
result.pathToOpen = normalizedPath
|
||||
}
|
||||
|
||||
return Object.assign({pathToOpen, initialLine, initialColumn}, extra)
|
||||
await new Promise((resolve, reject) => {
|
||||
fs.stat(result.pathToOpen, (err, st) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT' || err.code === 'EACCES') {
|
||||
result.exists = false
|
||||
resolve()
|
||||
} else {
|
||||
reject(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
result.exists = true
|
||||
result.isFile = st.isFile()
|
||||
result.isDirectory = st.isDirectory()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Opens a native dialog to prompt the user for a path.
|
||||
@@ -1467,8 +1532,8 @@ class AtomApplication extends EventEmitter {
|
||||
}
|
||||
})()
|
||||
|
||||
// Show the open dialog as child window on Windows and Linux, and as
|
||||
// independent dialog on macOS. This matches most native apps.
|
||||
// Show the open dialog as child window on Windows and Linux, and as an independent dialog on macOS. This matches
|
||||
// most native apps.
|
||||
const parentWindow = process.platform === 'darwin' ? null : BrowserWindow.getFocusedWindow()
|
||||
|
||||
const openOptions = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const {BrowserWindow, app, dialog, ipcMain} = require('electron')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const url = require('url')
|
||||
const {EventEmitter} = require('events')
|
||||
|
||||
@@ -51,7 +50,9 @@ class AtomWindow extends EventEmitter {
|
||||
if (this.shouldAddCustomTitleBar()) options.titleBarStyle = 'hidden'
|
||||
if (this.shouldAddCustomInsetTitleBar()) options.titleBarStyle = 'hiddenInset'
|
||||
if (this.shouldHideTitleBar()) options.frame = false
|
||||
this.browserWindow = new BrowserWindow(options)
|
||||
|
||||
const BrowserWindowConstructor = settings.browserWindowConstructor || BrowserWindow
|
||||
this.browserWindow = new BrowserWindowConstructor(options)
|
||||
|
||||
Object.defineProperty(this.browserWindow, 'loadSettingsJSON', {
|
||||
get: () => JSON.stringify(Object.assign({
|
||||
@@ -71,8 +72,14 @@ class AtomWindow extends EventEmitter {
|
||||
if (this.loadSettings.safeMode == null) this.loadSettings.safeMode = false
|
||||
if (this.loadSettings.clearWindowState == null) this.loadSettings.clearWindowState = false
|
||||
|
||||
this.loadSettings.initialPaths = locationsToOpen.map(location => location.pathToOpen).filter(Boolean)
|
||||
this.loadSettings.initialPaths.sort()
|
||||
this.projectRoots = locationsToOpen
|
||||
.filter(location => location.pathToOpen && location.exists && location.isDirectory)
|
||||
.map(location => location.pathToOpen)
|
||||
this.projectRoots.sort()
|
||||
|
||||
this.loadSettings.hasOpenFiles = locationsToOpen
|
||||
.some(location => location.pathToOpen && !location.isDirectory)
|
||||
this.loadSettings.initialProjectRoots = this.projectRoots
|
||||
|
||||
// Only send to the first non-spec window created
|
||||
if (includeShellLoadTime && !this.isSpec) {
|
||||
@@ -82,7 +89,6 @@ class AtomWindow extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
this.representedDirectoryPaths = this.loadSettings.initialPaths
|
||||
if (!this.loadSettings.env) this.env = this.loadSettings.env
|
||||
|
||||
this.browserWindow.on('window:loaded', () => {
|
||||
@@ -119,8 +125,8 @@ class AtomWindow extends EventEmitter {
|
||||
if (hasPathToOpen && !this.isSpecWindow()) this.openLocations(locationsToOpen)
|
||||
}
|
||||
|
||||
hasProjectPath () {
|
||||
return this.representedDirectoryPaths.length > 0
|
||||
hasProjectPaths () {
|
||||
return this.projectRoots.length > 0
|
||||
}
|
||||
|
||||
setupContextMenu () {
|
||||
@@ -131,18 +137,20 @@ class AtomWindow extends EventEmitter {
|
||||
})
|
||||
}
|
||||
|
||||
containsPaths (paths) {
|
||||
return paths.every(p => this.containsPath(p))
|
||||
containsLocations (locations) {
|
||||
return locations.every(location => this.containsLocation(location))
|
||||
}
|
||||
|
||||
containsPath (pathToCheck) {
|
||||
if (!pathToCheck) return false
|
||||
let stat
|
||||
return this.representedDirectoryPaths.some(projectPath => {
|
||||
if (pathToCheck === projectPath) return true
|
||||
if (!pathToCheck.startsWith(path.join(projectPath, path.sep))) return false
|
||||
if (stat === undefined) stat = fs.statSyncNoException(pathToCheck)
|
||||
return !stat || !stat.isDirectory()
|
||||
containsLocation (location) {
|
||||
if (!location.pathToOpen) return false
|
||||
|
||||
return this.projectRoots.some(projectPath => {
|
||||
if (location.pathToOpen === projectPath) return true
|
||||
if (location.pathToOpen.startsWith(path.join(projectPath, path.sep))) {
|
||||
if (!location.exists) return true
|
||||
if (!location.isDirectory) return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -376,7 +384,7 @@ class AtomWindow extends EventEmitter {
|
||||
showSaveDialog (options, callback) {
|
||||
options = Object.assign({
|
||||
title: 'Save File',
|
||||
defaultPath: this.representedDirectoryPaths[0]
|
||||
defaultPath: this.projectRoots[0]
|
||||
}, options)
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
@@ -408,10 +416,10 @@ class AtomWindow extends EventEmitter {
|
||||
return this.browserWindow.setRepresentedFilename(representedFilename)
|
||||
}
|
||||
|
||||
setRepresentedDirectoryPaths (representedDirectoryPaths) {
|
||||
this.representedDirectoryPaths = representedDirectoryPaths
|
||||
this.representedDirectoryPaths.sort()
|
||||
this.loadSettings.initialPaths = this.representedDirectoryPaths
|
||||
setProjectRoots (projectRootPaths) {
|
||||
this.projectRoots = projectRootPaths
|
||||
this.projectRoots.sort()
|
||||
this.loadSettings.initialProjectRoots = this.projectRoots
|
||||
return this.atomApplication.saveCurrentWindowOptions()
|
||||
}
|
||||
|
||||
@@ -426,4 +434,8 @@ class AtomWindow extends EventEmitter {
|
||||
disableZoom () {
|
||||
return this.browserWindow.webContents.setVisualZoomLevelLimits(1, 1)
|
||||
}
|
||||
|
||||
getLoadedPromise () {
|
||||
return this.loadedPromise
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user