Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Reopen projects" in a new window #19203

Merged
merged 5 commits into from Apr 23, 2019
Merged
Changes from 4 commits
Commits
File filter...
Filter file types
Jump to鈥
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -745,6 +745,106 @@ describe('AtomApplication', function () {
})
})

describe('IPC handling', function () {
let w0, w1, w2, app

beforeEach(async function () {
w0 = (await scenario.launch(parseCommandLine(['a'])))[0]
w1 = await scenario.open(parseCommandLine(['--new-window']))
w2 = await scenario.open(parseCommandLine(['--new-window', 'b']))

app = scenario.getApplication(0)
sinon.spy(app, 'openPaths')
sinon.stub(app, 'promptForPath', (_type, callback, defaultPath) => callback([defaultPath]))
})

// This is the IPC message used to handle:
// * application:reopen-project
// * choosing "open in new window" when adding a folder that has previously saved state
// * deprecated call links in deprecation-cop
// * other direct callers of `atom.open()`
it('"open" opens a fixed path by the standard opening rules', async function () {
sinon.stub(app, 'atomWindowForEvent', () => w1)

electron.ipcMain.emit('open', {}, {pathsToOpen: scenario.convertEditorPath('a/1.md')})
await app.openPaths.lastCall.returnValue
await scenario.assert('[a 1.md] [_ _] [b _]')

electron.ipcMain.emit('open', {}, {pathsToOpen: scenario.convertRootPath('c')})
await app.openPaths.lastCall.returnValue
await scenario.assert('[a 1.md] [c _] [b _]')

electron.ipcMain.emit('open', {}, {pathsToOpen: scenario.convertRootPath('d')})
await app.openPaths.lastCall.returnValue
await scenario.assert('[a 1.md] [c _] [b _] [d _]')
})

it('"open-chosen-any" opens a file in the sending window', async function () {
sinon.stub(app, 'atomWindowForEvent', () => w2)

electron.ipcMain.emit('open-chosen-any', {}, scenario.convertEditorPath('a/1.md'))
await conditionPromise(() => app.openPaths.called)
await app.openPaths.lastCall.returnValue
await scenario.assert('[a _] [_ _] [b 1.md]')

assert.isTrue(app.promptForPath.called)
assert.strictEqual(app.promptForPath.lastCall.args[0], 'all')
})

it('"open-chosen-any" opens a directory by the standard opening rules', async function () {
sinon.stub(app, 'atomWindowForEvent', () => w1)

// Open unrecognized directory in empty window
electron.ipcMain.emit('open-chosen-any', {}, scenario.convertRootPath('c'))
await conditionPromise(() => app.openPaths.callCount > 0)
await app.openPaths.lastCall.returnValue
await scenario.assert('[a _] [c _] [b _]')

assert.strictEqual(app.promptForPath.callCount, 1)
assert.strictEqual(app.promptForPath.lastCall.args[0], 'all')

// Open unrecognized directory in new window
electron.ipcMain.emit('open-chosen-any', {}, scenario.convertRootPath('d'))
await conditionPromise(() => app.openPaths.callCount > 1)
await app.openPaths.lastCall.returnValue
await scenario.assert('[a _] [c _] [b _] [d _]')

assert.strictEqual(app.promptForPath.callCount, 2)
assert.strictEqual(app.promptForPath.lastCall.args[0], 'all')

// Open recognized directory in existing window
electron.ipcMain.emit('open-chosen-any', {}, scenario.convertRootPath('a'))
await conditionPromise(() => app.openPaths.callCount > 2)
await app.openPaths.lastCall.returnValue
await scenario.assert('[a _] [c _] [b _] [d _]')

assert.strictEqual(app.promptForPath.callCount, 3)
assert.strictEqual(app.promptForPath.lastCall.args[0], 'all')
})

it('"open-chosen-file" opens a file chooser and opens the chosen file in the sending window', async function () {
sinon.stub(app, 'atomWindowForEvent', () => w0)

electron.ipcMain.emit('open-chosen-file', {}, scenario.convertEditorPath('b/2.md'))
await app.openPaths.lastCall.returnValue
await scenario.assert('[a 2.md] [_ _] [b _]')

assert.isTrue(app.promptForPath.called)
assert.strictEqual(app.promptForPath.lastCall.args[0], 'file')
})

it('"open-chosen-folder" opens a directory chooser and opens the chosen directory', async function () {
sinon.stub(app, 'atomWindowForEvent', () => w0)

electron.ipcMain.emit('open-chosen-folder', {}, scenario.convertRootPath('c'))
await app.openPaths.lastCall.returnValue
await scenario.assert('[a _] [c _] [b _]')

assert.isTrue(app.promptForPath.called)
assert.strictEqual(app.promptForPath.lastCall.args[0], 'folder')
})
})

describe('window state serialization', function () {
it('occurs immediately when adding a window', async function () {
await scenario.launch(parseCommandLine(['a']))
@@ -1027,7 +1127,7 @@ class LaunchScenario {
process.env.ATOM_HOME = this.atomHome

await Promise.all(
['a', 'b', 'c'].map(dirPath => new Promise((resolve, reject) => {
['a', 'b', 'c', 'd'].map(dirPath => new Promise((resolve, reject) => {
const fullDirPath = path.join(this.root, dirPath)
fs.makeTree(fullDirPath, err => {
if (err) {
@@ -306,7 +306,14 @@ class AtomEnvironment {
}

registerDefaultCommands () {
registerDefaultCommands({commandRegistry: this.commands, config: this.config, commandInstaller: this.commandInstaller, notificationManager: this.notifications, project: this.project, clipboard: this.clipboard})
registerDefaultCommands({
commandRegistry: this.commands,
config: this.config,
commandInstaller: this.commandInstaller,
notificationManager: this.notifications,
project: this.project,
clipboard: this.clipboard
})
}

registerDefaultOpeners () {
@@ -478,17 +478,17 @@ class AtomApplication extends EventEmitter {

// Registers basic application commands, non-idempotent.
handleEvents () {
const getLoadSettings = includeWindow => {
const window = this.focusedWindow()
const createOpenSettings = ({event, sameWindow}) => {
const targetWindow = event ? this.atomWindowForEvent(event) : this.focusedWindow()
return {
devMode: window ? window.devMode : false,
safeMode: window ? window.safeMode : false,
window: includeWindow && window ? window : null
devMode: targetWindow ? targetWindow.devMode : false,
safeMode: targetWindow ? targetWindow.safeMode : false,
window: sameWindow && targetWindow ? targetWindow : null
}
}

this.on('application:quit', () => app.quit())
this.on('application:new-window', () => this.openPath(getLoadSettings(false)))
this.on('application:new-window', () => this.openPath(createOpenSettings({})))
this.on('application:new-file', () => (this.focusedWindow() || this).openPath())
this.on('application:open-dev', () => this.promptForPathToOpen('all', {devMode: true}))
this.on('application:open-safe', () => this.promptForPathToOpen('all', {safeMode: true}))
@@ -517,9 +517,16 @@ class AtomApplication extends EventEmitter {
this.openPaths({ pathsToOpen: paths })
})

this.on('application:open', () => this.promptForPathToOpen('all', getLoadSettings(true), getDefaultPath()))
this.on('application:open-file', () => this.promptForPathToOpen('file', getLoadSettings(true), getDefaultPath()))
this.on('application:open-folder', () => this.promptForPathToOpen('folder', getLoadSettings(true), getDefaultPath()))
this.on('application:open', () => {
this.promptForPathToOpen('all', createOpenSettings({sameWindow: true}), getDefaultPath())
})
this.on('application:open-file', () => {
this.promptForPathToOpen('file', createOpenSettings({sameWindow: true}), getDefaultPath())
})
this.on('application:open-folder', () => {
this.promptForPathToOpen('folder', createOpenSettings({sameWindow: true}), getDefaultPath())
})

this.on('application:bring-all-windows-to-front', () => Menu.sendActionToFirstResponder('arrangeInFront:'))
this.on('application:hide', () => Menu.sendActionToFirstResponder('hide:'))
this.on('application:hide-other-applications', () => Menu.sendActionToFirstResponder('hideOtherApplications:'))
@@ -590,6 +597,9 @@ class AtomApplication extends EventEmitter {
this.deleteSocketSecretFile()
}))

// Triggered by the 'open-file' event from Electron:
// https://electronjs.org/docs/api/app#event-open-file-macos
// For example, this is fired when a file is dragged and dropped onto the dock.
This conversation was marked as resolved by smashwilson

This comment has been minimized.

Copy link
@jasonrudolph

jasonrudolph Apr 23, 2019

Member

Would the following change be an accurate clarification?

Suggested change
// For example, this is fired when a file is dragged and dropped onto the dock.
// For example, this is fired when a file is dragged and dropped onto the Atom application icon in the dock.
this.disposable.add(ipcHelpers.on(app, 'open-file', (event, pathToOpen) => {
event.preventDefault()
this.openPath({pathToOpen})
@@ -623,16 +633,15 @@ class AtomApplication extends EventEmitter {
}
}))

// A request from the associated render process to open a new render process.
this.disposable.add(ipcHelpers.on(ipcMain, 'open', (event, options) => {
const window = this.atomWindowForEvent(event)
// A request from the associated render process to open a set of paths using the standard window location logic.
// Used for application:reopen-project.
this.disposable.add(ipcHelpers.on(ipcMain, 'open', (_event, options) => {
if (options) {
if (typeof options.pathsToOpen === 'string') {
options.pathsToOpen = [options.pathsToOpen]
}

if (options.pathsToOpen && options.pathsToOpen.length > 0) {
options.window = window
this.openPaths(options)
} else {
this.addWindow(this.createWindow(options))
@@ -642,6 +651,18 @@ class AtomApplication extends EventEmitter {
}
}))

// Prompt for a file, folder, or either, then open the chosen paths. Files will be opened in the originating
// window; folders will be opened in a new window unless an existing window exactly contains all of them.
this.disposable.add(ipcHelpers.on(ipcMain, 'open-chosen-any', (event, defaultPath) => {
this.promptForPathToOpen('all', createOpenSettings({event, sameWindow: true}), defaultPath)
}))
this.disposable.add(ipcHelpers.on(ipcMain, 'open-chosen-file', (event, defaultPath) => {
this.promptForPathToOpen('file', createOpenSettings({event, sameWindow: true}), defaultPath)
}))
this.disposable.add(ipcHelpers.on(ipcMain, 'open-chosen-folder', (event, defaultPath) => {
this.promptForPathToOpen('folder', createOpenSettings({event}), defaultPath)
}))

this.disposable.add(ipcHelpers.on(ipcMain, 'update-application-menu', (event, template, menu) => {
const window = BrowserWindow.fromWebContents(event.sender)
if (this.applicationMenu) this.applicationMenu.update(window, template, menu)
@@ -668,19 +689,6 @@ class AtomApplication extends EventEmitter {
this.emit(command)
}))

this.disposable.add(ipcHelpers.on(ipcMain, 'open-command', (event, command, defaultPath) => {
switch (command) {
case 'application:open':
return this.promptForPathToOpen('all', getLoadSettings(true), defaultPath)
case 'application:open-file':
return this.promptForPathToOpen('file', getLoadSettings(true), defaultPath)
case 'application:open-folder':
return this.promptForPathToOpen('folder', getLoadSettings(true), defaultPath)
default:
return console.log(`Invalid open-command received: ${command}`)
}
}))

this.disposable.add(ipcHelpers.on(ipcMain, 'window-command', (event, command, ...args) => {
const window = BrowserWindow.fromWebContents(event.sender)
return window && window.emit(command, ...args)
@@ -36,13 +36,13 @@ module.exports = ({commandRegistry, commandInstaller, config, notificationManage
'application:new-file': -> ipcRenderer.send('command', 'application:new-file')
'application:open': ->
defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0]
ipcRenderer.send('open-command', 'application:open', defaultPath)
ipcRenderer.send('open-chosen-any', defaultPath)
'application:open-file': ->
defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0]
ipcRenderer.send('open-command', 'application:open-file', defaultPath)
ipcRenderer.send('open-chosen-file', defaultPath)
'application:open-folder': ->
defaultPath = atom.workspace.getActiveTextEditor()?.getPath() ? atom.project.getPaths()?[0]
ipcRenderer.send('open-command', 'application:open-folder', defaultPath)
ipcRenderer.send('open-chosen-folder', defaultPath)
'application:open-dev': -> ipcRenderer.send('command', 'application:open-dev')
'application:open-safe': -> ipcRenderer.send('command', 'application:open-safe')
'application:add-project-folder': -> atom.addProjectFolder()
ProTip! Use n and p to navigate between commits in a pull request.
You can鈥檛 perform that action at this time.