From 01bd45ef4c13fa778683c1811dec474fec03c005 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 20 Dec 2018 15:59:41 -0500 Subject: [PATCH 01/17] When only a file is specified, don't open the parent directory --- spec/atom-environment-spec.js | 2 +- src/atom-environment.js | 64 +++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index aea5313e8..bb95bd49c 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -652,7 +652,7 @@ describe('AtomEnvironment', () => { it("adds it to the project's paths", async () => { const pathToOpen = __filename await atom.openLocations([{pathToOpen}]) - expect(atom.project.getPaths()[0]).toBe(__dirname) + expect(atom.project.getPaths()).toEqual([]) }) describe('then a second path is opened with forceAddToWindow', () => { diff --git a/src/atom-environment.js b/src/atom-environment.js index 915ff78f1..76abb7a5e 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -1,5 +1,6 @@ const crypto = require('crypto') const path = require('path') +const util = require('util') const {ipcRenderer} = require('electron') const _ = require('underscore-plus') @@ -44,6 +45,8 @@ const TextBuffer = require('text-buffer') const TextEditorRegistry = require('./text-editor-registry') const AutoUpdateManager = require('./auto-update-manager') +const stat = util.promisify(fs.stat); + let nextId = 0 // Essential: Atom global for dealing with packages, themes, menus, and the window. @@ -1358,42 +1361,61 @@ or use Pane::saveItemAs for programmatic saving.`) async openLocations (locations) { const needsProjectPaths = this.project && this.project.getPaths().length === 0 - const foldersToAddToProject = [] + const foldersToAddToProject = new Set() const fileLocationsToOpen = [] - function pushFolderToOpen (folder) { - if (!foldersToAddToProject.includes(folder)) { - foldersToAddToProject.push(folder) - } - } + // Asynchronously fetch stat information about each requested path to open. If the path does not + // exist, fetch stat information about its parent directory, too. + const locationStats = await Promise.all( + locations.map(async location => { + const payload = {location, stats: null, parentStats: null} - for (const location of locations) { - const {pathToOpen} = location - if (pathToOpen && (needsProjectPaths || location.forceAddToWindow)) { - if (fs.existsSync(pathToOpen)) { - pushFolderToOpen(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) - } else if (fs.existsSync(path.dirname(pathToOpen))) { - pushFolderToOpen(this.project.getDirectoryForProjectPath(path.dirname(pathToOpen)).getPath()) - } else { - pushFolderToOpen(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) + if (!location.pathToOpen) { + return payload } - } - if (!fs.isDirectorySync(pathToOpen)) { - fileLocationsToOpen.push(location) + payload.stats = await stat(location.pathToOpen).catch(() => null) + if (!payload.stats) { + payload.parentStats = await stat(path.dirname(location.pathToOpen)).catch(() => null) + } + + return payload; + }), + ); + + for (const {location, stats, parentStats} of locationStats) { + const {pathToOpen} = location + + if (pathToOpen && (needsProjectPaths || location.forceAddToWindow)) { + if (stats !== null) { + // Path exists + if (stats.isDirectory()) { + // Directory: add as a project folder + foldersToAddToProject.add(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) + } else if (stats.isFile()) { + // File: add as a file location + fileLocationsToOpen.push(location) + } + } else if (parentStats !== null && parentStats.isDirectory()) { + // Parent directory exists + foldersToAddToProject.add(this.project.getDirectoryForProjectPath(path.dirname(pathToOpen)).getPath()) + } else { + // Attempt to interpret as a URI from a different directory provider + foldersToAddToProject.add(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) + } } if (location.hasWaitSession) this.pathsWithWaitSessions.add(pathToOpen) } let restoredState = false - if (foldersToAddToProject.length > 0) { - const state = await this.loadState(this.getStateKey(foldersToAddToProject)) + if (foldersToAddToProject.size > 0) { + const state = await this.loadState(this.getStateKey(Array.from(foldersToAddToProject))) // only restore state if this is the first path added to the project if (state && needsProjectPaths) { const files = fileLocationsToOpen.map((location) => location.pathToOpen) - await this.attemptRestoreProjectStateForPaths(state, foldersToAddToProject, files) + await this.attemptRestoreProjectStateForPaths(state, Array.from(foldersToAddToProject), files) restoredState = true } else { for (let folder of foldersToAddToProject) { From b64dac8ea39e998b54973dd833645cb2c0455804 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 20 Dec 2018 20:51:56 -0500 Subject: [PATCH 02/17] :shirt: --- src/atom-environment.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/atom-environment.js b/src/atom-environment.js index 76abb7a5e..532f68326 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -45,7 +45,7 @@ const TextBuffer = require('text-buffer') const TextEditorRegistry = require('./text-editor-registry') const AutoUpdateManager = require('./auto-update-manager') -const stat = util.promisify(fs.stat); +const stat = util.promisify(fs.stat) let nextId = 0 @@ -1379,9 +1379,9 @@ or use Pane::saveItemAs for programmatic saving.`) payload.parentStats = await stat(path.dirname(location.pathToOpen)).catch(() => null) } - return payload; + return payload }), - ); + ) for (const {location, stats, parentStats} of locationStats) { const {pathToOpen} = location From e31a142a92e2c761976ee5f8383840b8756fe0d0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 11:28:49 -0500 Subject: [PATCH 03/17] Remove unused initialPaths --- src/main-process/atom-application.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index bd769eb2b..a4a661b0f 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -206,7 +206,6 @@ class AtomApplication extends EventEmitter { openWithOptions (options) { const { - initialPaths, pathsToOpen, executedFrom, urlsToOpen, @@ -250,7 +249,6 @@ class AtomApplication extends EventEmitter { }) } else if (pathsToOpen.length > 0) { return this.openPaths({ - initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, @@ -267,7 +265,6 @@ class AtomApplication extends EventEmitter { } else { // Always open a editor window if this is the first instance of Atom. return this.openPath({ - initialPaths, pidToKillWhenClosed, newWindow, devMode, @@ -784,7 +781,6 @@ class AtomApplication extends EventEmitter { // :window - {AtomWindow} to open file paths in. // :addToLastWindow - Boolean of whether this should be opened in last focused window. openPath ({ - initialPaths, pathToOpen, pidToKillWhenClosed, newWindow, @@ -797,7 +793,6 @@ class AtomApplication extends EventEmitter { env } = {}) { return this.openPaths({ - initialPaths, pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, @@ -823,7 +818,6 @@ class AtomApplication extends EventEmitter { // :window - {AtomWindow} to open file paths in. // :addToLastWindow - Boolean of whether this should be opened in last focused window. openPaths ({ - initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, @@ -895,7 +889,6 @@ class AtomApplication extends EventEmitter { if (!windowDimensions) windowDimensions = this.getDimensionsForNewWindow() openedWindow = new AtomWindow(this, this.fileRecoveryService, { - initialPaths, locationsToOpen, windowInitializationScript, resourcePath, @@ -984,8 +977,7 @@ class AtomApplication extends EventEmitter { const states = await this.storageFolder.load('application.json') if (states) { return states.map(state => ({ - initialPaths: state.initialPaths, - pathsToOpen: state.initialPaths.filter(p => fs.isDirectorySync(p)), + pathsToOpen: state.initialPaths, urlsToOpen: [], devMode: this.devMode, safeMode: this.safeMode From 6938a3132952414b6325c2385fa9b7335ac35bf4 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 16:39:09 -0500 Subject: [PATCH 04/17] Never open the parent directory of a file path --- spec/atom-environment-spec.js | 69 ++++++++++++++++++----------------- src/atom-environment.js | 55 +++++++++++++--------------- 2 files changed, 60 insertions(+), 64 deletions(-) diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index bb95bd49c..d90001205 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -639,7 +639,6 @@ describe('AtomEnvironment', () => { describe('::openLocations(locations) (called via IPC from browser process)', () => { beforeEach(() => { - spyOn(atom.workspace, 'open') atom.project.setPaths([]) }) @@ -649,48 +648,50 @@ describe('AtomEnvironment', () => { }) describe('when the opened path exists', () => { - it("adds it to the project's paths", async () => { + it('opens a file', async () => { const pathToOpen = __filename await atom.openLocations([{pathToOpen}]) expect(atom.project.getPaths()).toEqual([]) }) - describe('then a second path is opened with forceAddToWindow', () => { - it("adds the second path to the project's paths", async () => { - const firstPathToOpen = __dirname - const secondPathToOpen = path.resolve(__dirname, './fixtures') - await atom.openLocations([{pathToOpen: firstPathToOpen}]) - await atom.openLocations([{pathToOpen: secondPathToOpen, forceAddToWindow: true}]) - expect(atom.project.getPaths()).toEqual([firstPathToOpen, secondPathToOpen]) - }) - }) - }) - - describe('when the opened path does not exist but its parent directory does', () => { - it('adds the parent directory to the project paths', async () => { - const pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt') - await atom.openLocations([{pathToOpen}]) - expect(atom.project.getPaths()[0]).toBe(__dirname) - }) - }) - - describe('when the opened path is a file', () => { - it('opens it in the workspace', async () => { - const pathToOpen = __filename - await atom.openLocations([{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', async () => { + it('opens a directory as a project folder', async () => { const pathToOpen = __dirname await atom.openLocations([{pathToOpen}]) - expect(atom.workspace.open.callCount).toBe(0) + expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual([]) + expect(atom.project.getPaths()).toEqual([pathToOpen]) }) }) - describe('when the opened path is a uri', () => { + describe('when the opened path does not exist', () => { + it('opens it as a new file', async () => { + const pathToOpen = path.join(__dirname, 'this-path-does-not-exist.txt') + await atom.openLocations([{pathToOpen}]) + expect(atom.workspace.getTextEditors().map(e => e.getPath())).toEqual([pathToOpen]) + expect(atom.project.getPaths()).toEqual([]) + }) + }) + + describe('when the opened path is handled by a registered directory provider', () => { + let serviceDisposable + + beforeEach(() => { + serviceDisposable = atom.packages.serviceHub.provide('atom.directory-provider', '0.1.0', { + directoryForURISync (uri) { + if (uri.startsWith('remote://')) { + return { getPath() { return uri } } + } else { + return null + } + } + }) + + waitsFor(() => atom.project.directoryProviders.length > 0) + }) + + afterEach(() => { + serviceDisposable.dispose() + }) + it("adds it to the project's paths as is", async () => { const pathToOpen = 'remote://server:7644/some/dir/path' spyOn(atom.project, 'addPath') @@ -741,7 +742,7 @@ describe('AtomEnvironment', () => { const fileToOpen = path.join(pathToOpen, 'michelle-is-awesome.txt') await atom.openLocations([{pathToOpen}, {pathToOpen: fileToOpen}]) expect(atom.attemptRestoreProjectStateForPaths).not.toHaveBeenCalledWith(state, [pathToOpen], [fileToOpen]) - expect(atom.project.getPaths()).toEqual([__dirname]) + expect(atom.project.getPaths()).toEqual([__dirname, pathToOpen]) }) }) }) diff --git a/src/atom-environment.js b/src/atom-environment.js index 532f68326..a470aeb25 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -1364,44 +1364,39 @@ or use Pane::saveItemAs for programmatic saving.`) const foldersToAddToProject = new Set() const fileLocationsToOpen = [] - // Asynchronously fetch stat information about each requested path to open. If the path does not - // exist, fetch stat information about its parent directory, too. + // Asynchronously fetch stat information about each requested path to open. const locationStats = await Promise.all( locations.map(async location => { - const payload = {location, stats: null, parentStats: null} - - if (!location.pathToOpen) { - return payload - } - - payload.stats = await stat(location.pathToOpen).catch(() => null) - if (!payload.stats) { - payload.parentStats = await stat(path.dirname(location.pathToOpen)).catch(() => null) - } - - return payload + const stats = location.pathToOpen ? await stat(location.pathToOpen).catch(() => null) : null + return {location, stats} }), ) - for (const {location, stats, parentStats} of locationStats) { + for (const {location, stats} of locationStats) { const {pathToOpen} = location + if (!pathToOpen) { + continue; + } - if (pathToOpen && (needsProjectPaths || location.forceAddToWindow)) { - if (stats !== null) { - // Path exists - if (stats.isDirectory()) { - // Directory: add as a project folder - foldersToAddToProject.add(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) - } else if (stats.isFile()) { - // File: add as a file location - fileLocationsToOpen.push(location) - } - } else if (parentStats !== null && parentStats.isDirectory()) { - // Parent directory exists - foldersToAddToProject.add(this.project.getDirectoryForProjectPath(path.dirname(pathToOpen)).getPath()) - } else { - // Attempt to interpret as a URI from a different directory provider + if (stats !== null) { + // Path exists + if (stats.isDirectory()) { + // Directory: add as a project folder foldersToAddToProject.add(this.project.getDirectoryForProjectPath(pathToOpen).getPath()) + } else if (stats.isFile()) { + // File: add as a file location + fileLocationsToOpen.push(location) + } + } else { + // Path does not exist + // Attempt to interpret as a URI from a non-default directory provider + const directory = this.project.getProvidedDirectoryForProjectPath(pathToOpen) + if (directory) { + // Found: add as a project folder + foldersToAddToProject.add(directory.getPath()) + } else { + // Not found: open as a new file + fileLocationsToOpen.push(location) } } From e570f72d8660ace20168386e94cccf47ffd1dd87 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 16:40:35 -0500 Subject: [PATCH 05/17] Remove path normalization responsibilities from AtomWindow --- src/main-process/atom-window.js | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/main-process/atom-window.js b/src/main-process/atom-window.js index a56679143..d7f480f16 100644 --- a/src/main-process/atom-window.js +++ b/src/main-process/atom-window.js @@ -23,9 +23,7 @@ class AtomWindow extends EventEmitter { this.devMode = settings.devMode this.resourcePath = settings.resourcePath - let {pathToOpen, locationsToOpen} = settings - if (!locationsToOpen && pathToOpen) locationsToOpen = [{pathToOpen}] - if (!locationsToOpen) locationsToOpen = [] + const locationsToOpen = settings.locationsToOpen || [] this.loadedPromise = new Promise(resolve => { this.resolveLoadedPromise = resolve }) this.closedPromise = new Promise(resolve => { this.resolveClosedPromise = resolve }) @@ -73,23 +71,7 @@ class AtomWindow extends EventEmitter { if (this.loadSettings.safeMode == null) this.loadSettings.safeMode = false if (this.loadSettings.clearWindowState == null) this.loadSettings.clearWindowState = false - if (!this.loadSettings.initialPaths) { - this.loadSettings.initialPaths = [] - for (const {pathToOpen, stat} of locationsToOpen) { - if (!pathToOpen) continue - if (stat && stat.isDirectory()) { - this.loadSettings.initialPaths.push(pathToOpen) - } else { - const parentDirectory = path.dirname(pathToOpen) - if (stat && stat.isFile() || fs.existsSync(parentDirectory)) { - this.loadSettings.initialPaths.push(parentDirectory) - } else { - this.loadSettings.initialPaths.push(pathToOpen) - } - } - } - } - + this.loadSettings.initialPaths = locationsToOpen.map(location => location.pathToOpen).filter(Boolean) this.loadSettings.initialPaths.sort() // Only send to the first non-spec window created From 6d2e298de1d04326be8ab14c0846e14b4897e4f1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 16:40:47 -0500 Subject: [PATCH 06/17] Extract a utility method on Project --- src/project.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/project.js b/src/project.js index 8ccf60c0b..05a9e34c9 100644 --- a/src/project.js +++ b/src/project.js @@ -441,14 +441,20 @@ class Project extends Model { } } - getDirectoryForProjectPath (projectPath) { - let directory = null + getProvidedDirectoryForProjectPath (projectPath) { for (let provider of this.directoryProviders) { if (typeof provider.directoryForURISync === 'function') { - directory = provider.directoryForURISync(projectPath) - if (directory) break + const directory = provider.directoryForURISync(projectPath) + if (directory) { + return directory + } } } + return null + } + + getDirectoryForProjectPath (projectPath) { + let directory = this.getProvidedDirectoryForProjectPath(projectPath) if (directory == null) { directory = this.defaultDirectoryProvider.directoryForURISync(projectPath) } From 15594dd8c22d120fa9894bbc315258c30959113d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 3 Jan 2019 16:41:17 -0500 Subject: [PATCH 07/17] AtomApplication launch behavior shuffle --- spec/main-process/atom-application.test.js | 129 ++++++--------------- src/main-process/atom-application.js | 36 +++--- 2 files changed, 53 insertions(+), 112 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index c49e36b5d..11d291214 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -114,47 +114,6 @@ describe('AtomApplication', function () { }) } - it('reuses existing windows when opening paths, but not directories', async () => { - const dirAPath = makeTempDir("a") - const dirBPath = makeTempDir("b") - const dirCPath = makeTempDir("c") - const existingDirCFilePath = path.join(dirCPath, 'existing-file') - fs.writeFileSync(existingDirCFilePath, 'this is an existing file') - - const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) - await emitterEventPromise(window1, 'window:locations-opened') - await focusWindow(window1) - - let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { - atom.workspace.observeTextEditors(textEditor => { - sendBackToMainProcess(textEditor.getPath()) - }) - }) - assert.equal(activeEditorPath, path.join(dirAPath, 'new-file')) - - // Reuses the window when opening *files*, even if they're in a different directory - // Does not change the project paths when doing so. - const [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath])) - assert.equal(reusedWindow, window1) - assert.deepEqual(atomApplication.getAllWindows(), [window1]) - activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { - const subscription = atom.workspace.onDidChangeActivePaneItem(textEditor => { - sendBackToMainProcess(textEditor.getPath()) - subscription.dispose() - }) - }) - assert.equal(activeEditorPath, existingDirCFilePath) - assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) - - // Opens new windows when opening directories - const [window2] = await atomApplication.launch(parseCommandLine([dirCPath])) - await emitterEventPromise(window2, 'window:locations-opened') - assert.notEqual(window2, window1) - await focusWindow(window2) - assert.deepEqual(await getTreeViewRootDirectories(window2), [dirCPath]) - }) - it('adds folders to existing windows when the --add option is used', async () => { const dirAPath = makeTempDir("a") const dirBPath = makeTempDir("b") @@ -163,46 +122,43 @@ describe('AtomApplication', function () { fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) + const [window1] = await atomApplication.launch(parseCommandLine([dirAPath])) await focusWindow(window1) - let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { - atom.workspace.observeTextEditors(textEditor => { - sendBackToMainProcess(textEditor.getPath()) - }) - }) - assert.equal(activeEditorPath, path.join(dirAPath, 'new-file')) + await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 1) + assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) - // When opening *files* with --add, reuses an existing window and adds - // parent directory to the project + // When opening *files* with --add, reuses an existing window let [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) - activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { + let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { const subscription = atom.workspace.onDidChangeActivePaneItem(textEditor => { sendBackToMainProcess(textEditor.getPath()) subscription.dispose() }) }) assert.equal(activeEditorPath, existingDirCFilePath) - assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirCPath]) + assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) - // When opening *directories* with add reuses an existing window and adds - // the directory to the project + // When opening *directories* with --add, reuses an existing window and adds the directory to the project reusedWindow = (await atomApplication.launch(parseCommandLine([dirBPath, '-a'])))[0] assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) - await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length === 3) - assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirCPath, dirBPath]) + await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length === 2) + assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) }) it('persists window state based on the project directories', async () => { + // Choosing "Don't save" + mockElectronShowMessageBox({response: 2}) + const tempDirPath = makeTempDir() const atomApplication = buildAtomApplication() const nonExistentFilePath = path.join(tempDirPath, 'new-file') - const [window1] = await atomApplication.launch(parseCommandLine([nonExistentFilePath])) + const [window1] = await atomApplication.launch(parseCommandLine([tempDirPath, nonExistentFilePath])) await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { textEditor.insertText('Hello World!') @@ -223,17 +179,6 @@ describe('AtomApplication', function () { sendBackToMainProcess(textEditor.getText()) }) assert.equal(window2Text, 'Hello World! How are you?') - await window2.prepareToUnload() - window2.close() - await window2.closedPromise - - // Restore unsaved state when opening a path to a non-existent file in the directory - const [window3] = await atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')])) - await window3.loadedPromise - const window3Texts = await evalInWebContents(window3.browserWindow.webContents, (sendBackToMainProcess, nonExistentFilePath) => { - sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText())) - }) - assert.include(window3Texts, 'Hello World! How are you?') }) it('shows all directories in the tree view when multiple directory paths are passed to Atom', async () => { @@ -246,20 +191,10 @@ describe('AtomApplication', function () { const [window1] = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) await focusWindow(window1) + await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 2) assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) }) - it('reuses windows with no project paths to open directories', async () => { - const tempDirPath = makeTempDir() - const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([])) - await focusWindow(window1) - - const [reusedWindow] = await atomApplication.launch(parseCommandLine([tempDirPath])) - assert.equal(reusedWindow, window1) - await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length > 0) - }) - it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async () => { const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch(parseCommandLine([])) @@ -299,7 +234,10 @@ describe('AtomApplication', function () { assert.equal(itemCount, 0) }) - it('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path', async () => { + it('opens an empty text editor when launched with a new file path', async () => { + // Choosing "Don't save" + mockElectronShowMessageBox({response: 2}) + const atomApplication = buildAtomApplication() const newFilePath = path.join(makeTempDir(), 'new-file') const [window] = await atomApplication.launch(parseCommandLine([newFilePath])) @@ -311,7 +249,7 @@ describe('AtomApplication', function () { }) assert.equal(editorTitle, path.basename(newFilePath)) assert.equal(editorText, '') - assert.deepEqual(await getTreeViewRootDirectories(window), [path.dirname(newFilePath)]) + assert.deepEqual(await getTreeViewRootDirectories(window), []) }) it('adds a remote directory to the project when launched with a remote directory', async () => { @@ -352,6 +290,7 @@ describe('AtomApplication', function () { const atomApplication1 = buildAtomApplication() const [app1Window1] = await atomApplication1.launch(parseCommandLine([tempDirPath1])) await emitterEventPromise(app1Window1, 'window:locations-opened') + const [app1Window2] = await atomApplication1.launch(parseCommandLine([tempDirPath2])) await emitterEventPromise(app1Window2, 'window:locations-opened') @@ -375,6 +314,7 @@ describe('AtomApplication', function () { const atomApplication1 = buildAtomApplication() const [app1Window1] = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window1) + const [app1Window2] = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window2) @@ -424,14 +364,15 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened file in an existing window is closed', async () => { - const [window] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) - await focusWindow(window) - - const filePath1 = temp.openSync('test').path - const filePath2 = temp.openSync('test').path + const projectDir = makeTempDir('existing') + const filePath1 = path.join(projectDir, 'file-1') + const filePath2 = path.join(projectDir, 'file-2') fs.writeFileSync(filePath1, 'File 1') fs.writeFileSync(filePath2, 'File 2') + const [window] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', projectDir])) + await focusWindow(window) + const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) assert.equal(reusedWindow, window) @@ -471,8 +412,9 @@ describe('AtomApplication', function () { await focusWindow(window) const dirPath1 = makeTempDir() - const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1])) + const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--add', '--wait', '--pid', '101', dirPath1])) assert.equal(reusedWindow, window) + await conditionPromise(async () => (await getTreeViewRootDirectories(window)).length === 1) assert.deepEqual(await getTreeViewRootDirectories(window), [dirPath1]) assert.deepEqual(killedPids, []) @@ -597,9 +539,9 @@ describe('AtomApplication', function () { const dirAPath = makeTempDir("a") const dirBPath = makeTempDir("b") const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')])) + const [window1] = await atomApplication.launch(parseCommandLine([dirAPath])) await focusWindow(window1) - const [window2] = await atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')])) + const [window2] = await atomApplication.launch(parseCommandLine([dirBPath])) await focusWindow(window2) electron.app.quit() await new Promise(process.nextTick) @@ -710,12 +652,15 @@ describe('AtomApplication', function () { resolve(result) } - webContents.executeJavaScript(dedent` + const js = dedent` function sendBackToMainProcess (result) { require('electron').ipcRenderer.send('${channelId}', result) } (${source})(sendBackToMainProcess, ${args.map(JSON.stringify).join(', ')}) - `) + ` + // console.log(`about to execute:\n${js}`) + + webContents.executeJavaScript(js) }) } @@ -728,6 +673,8 @@ describe('AtomApplication', function () { .from(treeView.element.querySelectorAll('.project-root > .header .name')) .map(element => element.dataset.path) ) + } else { + sendBackToMainProcess([]) } }) }) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index a4a661b0f..3ccf17f21 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -837,26 +837,21 @@ class AtomApplication extends EventEmitter { safeMode = Boolean(safeMode) clearWindowState = Boolean(clearWindowState) - const locationsToOpen = [] - for (let i = 0; i < pathsToOpen.length; i++) { - const location = this.parsePathToOpen(pathsToOpen[i], executedFrom, addToLastWindow) - location.forceAddToWindow = addToLastWindow - location.hasWaitSession = pidToKillWhenClosed != null - locationsToOpen.push(location) - pathsToOpen[i] = location.pathToOpen - } + const locationsToOpen = pathsToOpen.map(pathToOpen => { + return this.parsePathToOpen(pathToOpen, executedFrom, { + forceAddToWindow: addToLastWindow, + hasWaitSession: pidToKillWhenClosed != null + }) + }) + const normalizedPathsToOpen = locationsToOpen.map(location => location.pathToOpen).filter(Boolean) let existingWindow - if (!newWindow) { - existingWindow = this.windowForPaths(pathsToOpen, devMode) - if (!existingWindow) { + if (!newWindow && normalizedPathsToOpen.length > 0) { + existingWindow = this.windowForPaths(normalizedPathsToOpen, devMode) + if (!existingWindow && addToLastWindow) { let lastWindow = window || this.getLastFocusedWindow() if (lastWindow && lastWindow.devMode === devMode) { - if (addToLastWindow || ( - locationsToOpen.every(({stat}) => stat && stat.isFile()) || - (locationsToOpen.some(({stat}) => stat && stat.isDirectory()) && !lastWindow.hasProjectPath()))) { - existingWindow = lastWindow - } + existingWindow = lastWindow } } } @@ -909,7 +904,7 @@ class AtomApplication extends EventEmitter { } this.waitSessionsByWindow.get(openedWindow).push({ pid: pidToKillWhenClosed, - remainingPaths: new Set(pathsToOpen) + remainingPaths: new Set(normalizedPathsToOpen) }) } @@ -1256,7 +1251,7 @@ class AtomApplication extends EventEmitter { } } - parsePathToOpen (pathToOpen, executedFrom = '') { + parsePathToOpen (pathToOpen, executedFrom, extra) { let initialColumn, initialLine if (!pathToOpen) { return {pathToOpen} @@ -1278,10 +1273,9 @@ class AtomApplication extends EventEmitter { } const normalizedPath = path.normalize(path.resolve(executedFrom, fs.normalize(pathToOpen))) - const stat = fs.statSyncNoException(normalizedPath) - if (stat || !url.parse(pathToOpen).protocol) pathToOpen = normalizedPath + if (!url.parse(pathToOpen).protocol) pathToOpen = normalizedPath - return {pathToOpen, stat, initialLine, initialColumn} + return {pathToOpen, initialLine, initialColumn, ...extra} } // Opens a native dialog to prompt the user for a path. From f1de652bac129819c2ee4881a51d95fe79046284 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 09:06:19 -0500 Subject: [PATCH 08/17] ... Right. Joanna can't handle spread properties in objects --- src/main-process/atom-application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 3ccf17f21..378fb5167 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -1275,7 +1275,7 @@ class AtomApplication extends EventEmitter { const normalizedPath = path.normalize(path.resolve(executedFrom, fs.normalize(pathToOpen))) if (!url.parse(pathToOpen).protocol) pathToOpen = normalizedPath - return {pathToOpen, initialLine, initialColumn, ...extra} + return Object.assign({pathToOpen, initialLine, initialColumn}, extra) } // Opens a native dialog to prompt the user for a path. From efc55d71a3174fe5e7d37943a6644100c3464fc6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 09:11:35 -0500 Subject: [PATCH 09/17] :shirt: Make standard happy with the test source --- spec/main-process/atom-application.test.js | 34 ++++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index 11d291214..f9ce18257 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -1,3 +1,5 @@ +/* globals assert */ + const temp = require('temp').track() const season = require('season') const dedent = require('dedent') @@ -115,9 +117,9 @@ describe('AtomApplication', function () { } it('adds folders to existing windows when the --add option is used', async () => { - const dirAPath = makeTempDir("a") - const dirBPath = makeTempDir("b") - const dirCPath = makeTempDir("c") + const dirAPath = makeTempDir('a') + const dirBPath = makeTempDir('b') + const dirCPath = makeTempDir('c') const existingDirCFilePath = path.join(dirCPath, 'existing-file') fs.writeFileSync(existingDirCFilePath, 'this is an existing file') @@ -169,7 +171,7 @@ describe('AtomApplication', function () { window1.close() await window1.closedPromise - // Restore unsaved state when opening the directory itself + // Restore unsaved state when opening the same project directory const [window2] = await atomApplication.launch(parseCommandLine([tempDirPath])) await window2.loadedPromise const window2Text = await evalInWebContents(window2.browserWindow.webContents, sendBackToMainProcess => { @@ -182,8 +184,8 @@ describe('AtomApplication', function () { }) it('shows all directories in the tree view when multiple directory paths are passed to Atom', async () => { - const dirAPath = makeTempDir("a") - const dirBPath = makeTempDir("b") + const dirAPath = makeTempDir('a') + const dirBPath = makeTempDir('b') const dirBSubdirPath = path.join(dirBPath, 'c') fs.mkdirSync(dirBSubdirPath) @@ -282,7 +284,7 @@ describe('AtomApplication', function () { }) it('reopens any previously opened windows when launched with no path', async () => { - if (process.platform === 'win32') return; // Test is too flakey on Windows + if (process.platform === 'win32') return // Test is too flakey on Windows const tempDirPath1 = makeTempDir() const tempDirPath2 = makeTempDir() @@ -440,7 +442,7 @@ describe('AtomApplication', function () { if (process.platform === 'linux' || process.platform === 'win32') { it('quits the application', async () => { const atomApplication = buildAtomApplication() - const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir('a'), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -450,7 +452,7 @@ describe('AtomApplication', function () { } else if (process.platform === 'darwin') { it('leaves the application open', async () => { const atomApplication = buildAtomApplication() - const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const [window] = await atomApplication.launch(parseCommandLine([path.join(makeTempDir('a'), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -504,11 +506,11 @@ describe('AtomApplication', function () { let reached = await evalInWebContents(windows[0].browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess(global.reachedUrlMain) }) - assert.equal(reached, true); - windows[0].close(); + assert.isTrue(reached) + windows[0].close() }) - it('triggers /core/open/file in the correct window', async function() { + it('triggers /core/open/file in the correct window', async function () { const dirAPath = makeTempDir('a') const dirBPath = makeTempDir('b') @@ -536,8 +538,8 @@ describe('AtomApplication', function () { }) it('waits until all the windows have saved their state before quitting', async () => { - const dirAPath = makeTempDir("a") - const dirBPath = makeTempDir("b") + const dirAPath = makeTempDir('a') + const dirBPath = makeTempDir('b') const atomApplication = buildAtomApplication() const [window1] = await atomApplication.launch(parseCommandLine([dirAPath])) await focusWindow(window1) @@ -604,7 +606,7 @@ describe('AtomApplication', function () { function buildAtomApplication (params = {}) { const atomApplication = new AtomApplication(Object.assign({ resourcePath: ATOM_RESOURCE_PATH, - atomHomeDirPath: process.env.ATOM_HOME, + atomHomeDirPath: process.env.ATOM_HOME }, params)) atomApplicationsToDestroy.push(atomApplication) return atomApplication @@ -622,7 +624,7 @@ describe('AtomApplication', function () { electron.app.quit = function () { this.quit.callCount++ let defaultPrevented = false - this.emit('before-quit', {preventDefault() { defaultPrevented = true }}) + this.emit('before-quit', {preventDefault () { defaultPrevented = true }}) if (!defaultPrevented) didQuit = true } From d725f5e42b9cd61869425c413732cf051594fe5f Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 09:54:36 -0500 Subject: [PATCH 10/17] :shirt: Now that I actually got my linter working :eyes: --- src/atom-environment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom-environment.js b/src/atom-environment.js index a470aeb25..00192c16c 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -1375,7 +1375,7 @@ or use Pane::saveItemAs for programmatic saving.`) for (const {location, stats} of locationStats) { const {pathToOpen} = location if (!pathToOpen) { - continue; + continue } if (stats !== null) { From d3671f705528e246ac22ecbc2c234d47ed6292d7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 11:06:17 -0500 Subject: [PATCH 11/17] Require --add to open locations in existing windows --- src/main-process/atom-application.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 378fb5167..415d0b4e1 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -846,9 +846,9 @@ class AtomApplication extends EventEmitter { const normalizedPathsToOpen = locationsToOpen.map(location => location.pathToOpen).filter(Boolean) let existingWindow - if (!newWindow && normalizedPathsToOpen.length > 0) { + if (addToLastWindow && normalizedPathsToOpen.length > 0) { existingWindow = this.windowForPaths(normalizedPathsToOpen, devMode) - if (!existingWindow && addToLastWindow) { + if (!existingWindow) { let lastWindow = window || this.getLastFocusedWindow() if (lastWindow && lastWindow.devMode === devMode) { existingWindow = lastWindow From 34167d426fec22c66461b0824fb060c68106a7ac Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 11:07:06 -0500 Subject: [PATCH 12/17] Adjust tests to use --add when necessary --- spec/main-process/atom-application.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index f9ce18257..aae7f4d71 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -375,7 +375,7 @@ describe('AtomApplication', function () { const [window] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', projectDir])) await focusWindow(window) - const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) + const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--add', '--wait', '--pid', '102', filePath1, filePath2])) assert.equal(reusedWindow, window) const activeEditorPath = await evalInWebContents(window.browserWindow.webContents, send => { From bac378654e145d32d0e12516412d27eee1d0cef5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 11:07:56 -0500 Subject: [PATCH 13/17] Deflake test that depends on tree-view loading project folders --- spec/main-process/atom-application.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index aae7f4d71..977ec1bf7 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -468,24 +468,24 @@ describe('AtomApplication', function () { const dirB = makeTempDir() const atomApplication = buildAtomApplication() - const [window] = await atomApplication.launch(parseCommandLine([dirA, dirB])) - await emitterEventPromise(window, 'window:locations-opened') - await focusWindow(window) - assert.deepEqual(await getTreeViewRootDirectories(window), [dirA, dirB]) + const [window0] = await atomApplication.launch(parseCommandLine([dirA, dirB])) + await focusWindow(window0) + await conditionPromise(async () => (await getTreeViewRootDirectories(window0)).length === 2) + assert.deepEqual(await getTreeViewRootDirectories(window0), [dirA, dirB]) const saveStatePromise = emitterEventPromise(atomApplication, 'application:did-save-state') - await evalInWebContents(window.browserWindow.webContents, (sendBackToMainProcess) => { + await evalInWebContents(window0.browserWindow.webContents, (sendBackToMainProcess) => { atom.project.removePath(atom.project.getPaths()[0]) sendBackToMainProcess(null) }) - assert.deepEqual(await getTreeViewRootDirectories(window), [dirB]) + assert.deepEqual(await getTreeViewRootDirectories(window0), [dirB]) await saveStatePromise // Window state should be saved when the project folder is removed const atomApplication2 = buildAtomApplication() const [window2] = await atomApplication2.launch(parseCommandLine([])) - await emitterEventPromise(window2, 'window:locations-opened') await focusWindow(window2) + await conditionPromise(async () => (await getTreeViewRootDirectories(window2)).length === 1) assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB]) }) }) From ae57abe70a0b207cff5b876fdfccf339032691ba Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 4 Jan 2019 13:25:42 -0500 Subject: [PATCH 14/17] Update smoke test to match new open behavior --- spec/integration/smoke-spec.coffee | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/integration/smoke-spec.coffee b/spec/integration/smoke-spec.coffee index e147cf5c0..dd6c9776f 100644 --- a/spec/integration/smoke-spec.coffee +++ b/spec/integration/smoke-spec.coffee @@ -23,10 +23,14 @@ describe "Smoke Test", -> it "can open a file in Atom and perform basic operations on it", -> tempDirPath = temp.mkdirSync("empty-dir") - runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> + filePath = path.join(tempDirPath, "new-file") + + fs.writeFileSync filePath, "", {encoding: "utf8"} + + runAtom [filePath], {ATOM_HOME: atomHome}, (client) -> client .treeViewRootDirectories() - .then ({value}) -> expect(value).toEqual([tempDirPath]) + .then ({value}) -> expect(value).toEqual([]) .waitForExist("atom-text-editor", 5000) .then (exists) -> expect(exists).toBe true .waitForPaneItemCount(1, 1000) From cc0b982c5abbe8799fba2b49d15bc6761fd929a7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 14 Jan 2019 10:49:21 -0500 Subject: [PATCH 15/17] Re-word help text --- src/main-process/parse-command-line.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main-process/parse-command-line.js b/src/main-process/parse-command-line.js index 5d7849eac..3aa019565 100644 --- a/src/main-process/parse-command-line.js +++ b/src/main-process/parse-command-line.js @@ -11,16 +11,20 @@ module.exports = function parseCommandLine (processArgs) { dedent`Atom Editor v${version} Usage: + atom atom [options] [path ...] atom file[:line[:column]] - One or more paths to files or folders may be specified. If there is an - existing Atom window that contains all of the given folders, the paths - will be opened in that window. Otherwise, they will be opened in a new - window. + If no arguments are given and no Atom windows are already open, restore all windows + from the previous editing session. Use "atom --new-window" to open a single empty + Atom window instead. - A file may be opened at the desired line (and optionally column) by - appending the numbers right after the file name, e.g. \`atom file:5:8\`. + If no arguments are given and at least one Atom window is open, open a new, empty + Atom window. + + One or more paths to files or folders may be specified. All paths will be opened + in a new Atom window. Each file may be opened at the desired line (and optionally + column) by appending the numbers after the file name, e.g. \`atom file:5:8\`. Paths that start with \`atom://\` will be interpreted as URLs. @@ -39,7 +43,7 @@ module.exports = function parseCommandLine (processArgs) { options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the main process in the foreground.') options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.') options.alias('l', 'log-file').string('l').describe('l', 'Log all output to file.') - options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.') + options.alias('n', 'new-window').boolean('n').describe('n', 'Launch an empty Atom window instead of restoring previous session.') options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.') options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.') options.boolean('safe').describe( From 2ab0e8db24cfc1d2b1a326b82c0e24f698fbbeef Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 14 Jan 2019 11:46:18 -0500 Subject: [PATCH 16/17] --new-window locks shouldReopenPreviousWindows to false --- src/main-process/atom-application.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 415d0b4e1..04f7fba5c 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -187,6 +187,8 @@ class AtomApplication extends EventEmitter { (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' } @@ -215,7 +217,6 @@ class AtomApplication extends EventEmitter { pidToKillWhenClosed, devMode, safeMode, - newWindow, logFile, profileStartup, timeout, @@ -252,7 +253,6 @@ class AtomApplication extends EventEmitter { pathsToOpen, executedFrom, pidToKillWhenClosed, - newWindow, devMode, safeMode, profileStartup, @@ -266,7 +266,6 @@ class AtomApplication extends EventEmitter { // Always open a editor window if this is the first instance of Atom. return this.openPath({ pidToKillWhenClosed, - newWindow, devMode, safeMode, profileStartup, @@ -774,7 +773,6 @@ class AtomApplication extends EventEmitter { // options - // :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. // :profileStartup - Boolean to control creating a profile of the startup time. @@ -783,7 +781,6 @@ class AtomApplication extends EventEmitter { openPath ({ pathToOpen, pidToKillWhenClosed, - newWindow, devMode, safeMode, profileStartup, @@ -795,7 +792,6 @@ class AtomApplication extends EventEmitter { return this.openPaths({ pathsToOpen: [pathToOpen], pidToKillWhenClosed, - newWindow, devMode, safeMode, profileStartup, @@ -811,7 +807,6 @@ class AtomApplication extends EventEmitter { // options - // :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. @@ -821,7 +816,6 @@ class AtomApplication extends EventEmitter { pathsToOpen, executedFrom, pidToKillWhenClosed, - newWindow, devMode, safeMode, windowDimensions, From f20aa038bd0261b74798af7e6965fca54a1772ca Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Mon, 14 Jan 2019 11:46:30 -0500 Subject: [PATCH 17/17] Reorganize AtomApplication tests --- spec/main-process/atom-application.test.js | 373 +++++++++++---------- 1 file changed, 204 insertions(+), 169 deletions(-) diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index 977ec1bf7..c271608c4 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -46,55 +46,226 @@ describe('AtomApplication', function () { }) describe('launch', () => { - it('can open to a specific line number of a file', async () => { - const filePath = path.join(makeTempDir(), 'new-file') - fs.writeFileSync(filePath, '1\n2\n3\n4\n') - const atomApplication = buildAtomApplication() + describe('with no paths', () => { + it('reopens any previously opened windows', async () => { + if (process.platform === 'win32') return // Test is too flakey on Windows - const [window] = await atomApplication.launch(parseCommandLine([filePath + ':3'])) - await focusWindow(window) + const tempDirPath1 = makeTempDir() + const tempDirPath2 = makeTempDir() - const cursorRow = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { - atom.workspace.observeTextEditors(textEditor => { - sendBackToMainProcess(textEditor.getCursorBufferPosition().row) - }) + const atomApplication1 = buildAtomApplication() + const [app1Window1] = await atomApplication1.launch(parseCommandLine([tempDirPath1])) + await emitterEventPromise(app1Window1, 'window:locations-opened') + + const [app1Window2] = await atomApplication1.launch(parseCommandLine([tempDirPath2])) + await emitterEventPromise(app1Window2, 'window:locations-opened') + + await Promise.all([ + app1Window1.prepareToUnload(), + app1Window2.prepareToUnload() + ]) + + const atomApplication2 = buildAtomApplication() + const [app2Window1, app2Window2] = await atomApplication2.launch(parseCommandLine([])) + await Promise.all([ + emitterEventPromise(app2Window1, 'window:locations-opened'), + emitterEventPromise(app2Window2, 'window:locations-opened') + ]) + + assert.deepEqual(await getTreeViewRootDirectories(app2Window1), [tempDirPath1]) + assert.deepEqual(await getTreeViewRootDirectories(app2Window2), [tempDirPath2]) }) - assert.equal(cursorRow, 2) + it('when windows already exist, opens a new window with a single untitled buffer', async () => { + const atomApplication = buildAtomApplication() + const [window1] = await atomApplication.launch(parseCommandLine([])) + await focusWindow(window1) + const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { + sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) + }) + assert.equal(window1EditorTitle, 'untitled') + + const window2 = atomApplication.openWithOptions(parseCommandLine([])) + await window2.loadedPromise + const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { + sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) + }) + assert.equal(window2EditorTitle, 'untitled') + + assert.deepEqual(atomApplication.getAllWindows(), [window2, window1]) + }) + + it('when no windows are open but --new-window is passed, opens a new window with a single untitled buffer', async () => { + // Populate some saved state + const tempDirPath1 = makeTempDir() + const tempDirPath2 = makeTempDir() + + const atomApplication1 = buildAtomApplication() + const [app1Window1] = await atomApplication1.launch(parseCommandLine([tempDirPath1])) + await emitterEventPromise(app1Window1, 'window:locations-opened') + + const [app1Window2] = await atomApplication1.launch(parseCommandLine([tempDirPath2])) + await emitterEventPromise(app1Window2, 'window:locations-opened') + + await Promise.all([ + app1Window1.prepareToUnload(), + app1Window2.prepareToUnload() + ]) + + // Launch with --new-window + const atomApplication2 = buildAtomApplication() + const appWindows2 = await atomApplication2.launch(parseCommandLine(['--new-window'])) + assert.lengthOf(appWindows2, 1) + const [appWindow2] = appWindows2 + await appWindow2.loadedPromise + const window2EditorTitle = await evalInWebContents(appWindow2.browserWindow.webContents, sendBackToMainProcess => { + sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) + }) + assert.equal(window2EditorTitle, 'untitled') + }) + + it('does not open an empty editor if core.openEmptyEditorOnStart is false', async () => { + const configPath = path.join(process.env.ATOM_HOME, 'config.cson') + const config = season.readFileSync(configPath) + if (!config['*'].core) config['*'].core = {} + config['*'].core.openEmptyEditorOnStart = false + season.writeFileSync(configPath, config) + + const atomApplication = buildAtomApplication() + const [window1] = await atomApplication.launch(parseCommandLine([])) + await focusWindow(window1) + + // wait a bit just to make sure we don't pass due to querying the render process before it loads + await timeoutPromise(1000) + + const itemCount = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { + sendBackToMainProcess(atom.workspace.getActivePane().getItems().length) + }) + assert.equal(itemCount, 0) + }) }) - it('can open to a specific line and column of a file', async () => { - const filePath = path.join(makeTempDir(), 'new-file') - fs.writeFileSync(filePath, '1\n2\n3\n4\n') - const atomApplication = buildAtomApplication() + describe('with file or folder paths', () => { + it('shows all directories in the tree view when multiple directory paths are passed to Atom', async () => { + const dirAPath = makeTempDir('a') + const dirBPath = makeTempDir('b') + const dirBSubdirPath = path.join(dirBPath, 'c') + fs.mkdirSync(dirBSubdirPath) - const [window] = await atomApplication.launch(parseCommandLine([filePath + ':2:2'])) - await focusWindow(window) + const atomApplication = buildAtomApplication() + const [window1] = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) + await focusWindow(window1) - const cursorPosition = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { - atom.workspace.observeTextEditors(textEditor => { - sendBackToMainProcess(textEditor.getCursorBufferPosition()) - }) + await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 2) + assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) }) - assert.deepEqual(cursorPosition, {row: 1, column: 1}) + it('can open to a specific line number of a file', async () => { + const filePath = path.join(makeTempDir(), 'new-file') + fs.writeFileSync(filePath, '1\n2\n3\n4\n') + const atomApplication = buildAtomApplication() + + const [window] = await atomApplication.launch(parseCommandLine([filePath + ':3'])) + await focusWindow(window) + + const cursorRow = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { + atom.workspace.observeTextEditors(textEditor => { + sendBackToMainProcess(textEditor.getCursorBufferPosition().row) + }) + }) + + assert.equal(cursorRow, 2) + }) + + it('can open to a specific line and column of a file', async () => { + const filePath = path.join(makeTempDir(), 'new-file') + fs.writeFileSync(filePath, '1\n2\n3\n4\n') + const atomApplication = buildAtomApplication() + + const [window] = await atomApplication.launch(parseCommandLine([filePath + ':2:2'])) + await focusWindow(window) + + const cursorPosition = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { + atom.workspace.observeTextEditors(textEditor => { + sendBackToMainProcess(textEditor.getCursorBufferPosition()) + }) + }) + + assert.deepEqual(cursorPosition, {row: 1, column: 1}) + }) + + it('removes all trailing whitespace and colons from the specified path', async () => { + let filePath = path.join(makeTempDir(), 'new-file') + fs.writeFileSync(filePath, '1\n2\n3\n4\n') + const atomApplication = buildAtomApplication() + + const [window] = await atomApplication.launch(parseCommandLine([filePath + ':: '])) + await focusWindow(window) + + const openedPath = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { + atom.workspace.observeTextEditors(textEditor => { + sendBackToMainProcess(textEditor.getPath()) + }) + }) + + assert.equal(openedPath, filePath) + }) + + it('opens an empty text editor when launched with a new file path', async () => { + // Choosing "Don't save" + mockElectronShowMessageBox({response: 2}) + + const atomApplication = buildAtomApplication() + const newFilePath = path.join(makeTempDir(), 'new-file') + const [window] = await atomApplication.launch(parseCommandLine([newFilePath])) + await focusWindow(window) + const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { + atom.workspace.observeTextEditors(editor => { + sendBackToMainProcess({editorTitle: editor.getTitle(), editorText: editor.getText()}) + }) + }) + assert.equal(editorTitle, path.basename(newFilePath)) + assert.equal(editorText, '') + assert.deepEqual(await getTreeViewRootDirectories(window), []) + }) }) - it('removes all trailing whitespace and colons from the specified path', async () => { - let filePath = path.join(makeTempDir(), 'new-file') - fs.writeFileSync(filePath, '1\n2\n3\n4\n') - const atomApplication = buildAtomApplication() + describe('when the --add option is specified', () => { + it('adds folders to existing windows when the --add option is used', async () => { + const dirAPath = makeTempDir('a') + const dirBPath = makeTempDir('b') + const dirCPath = makeTempDir('c') + const existingDirCFilePath = path.join(dirCPath, 'existing-file') + fs.writeFileSync(existingDirCFilePath, 'this is an existing file') - const [window] = await atomApplication.launch(parseCommandLine([filePath + ':: '])) - await focusWindow(window) + const atomApplication = buildAtomApplication() + const [window1] = await atomApplication.launch(parseCommandLine([dirAPath])) + await focusWindow(window1) - const openedPath = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { - atom.workspace.observeTextEditors(textEditor => { - sendBackToMainProcess(textEditor.getPath()) + await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 1) + assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) + + // When opening *files* with --add, reuses an existing window + let [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) + assert.equal(reusedWindow, window1) + assert.deepEqual(atomApplication.getAllWindows(), [window1]) + let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { + const subscription = atom.workspace.onDidChangeActivePaneItem(textEditor => { + sendBackToMainProcess(textEditor.getPath()) + subscription.dispose() + }) }) - }) + assert.equal(activeEditorPath, existingDirCFilePath) + assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) - assert.equal(openedPath, filePath) + // When opening *directories* with --add, reuses an existing window and adds the directory to the project + reusedWindow = (await atomApplication.launch(parseCommandLine([dirBPath, '-a'])))[0] + assert.equal(reusedWindow, window1) + assert.deepEqual(atomApplication.getAllWindows(), [window1]) + + await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length === 2) + assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) + }) }) if (process.platform === 'darwin' || process.platform === 'win32') { @@ -116,42 +287,6 @@ describe('AtomApplication', function () { }) } - it('adds folders to existing windows when the --add option is used', async () => { - const dirAPath = makeTempDir('a') - const dirBPath = makeTempDir('b') - const dirCPath = makeTempDir('c') - const existingDirCFilePath = path.join(dirCPath, 'existing-file') - fs.writeFileSync(existingDirCFilePath, 'this is an existing file') - - const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([dirAPath])) - await focusWindow(window1) - - await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 1) - assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) - - // When opening *files* with --add, reuses an existing window - let [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) - assert.equal(reusedWindow, window1) - assert.deepEqual(atomApplication.getAllWindows(), [window1]) - let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { - const subscription = atom.workspace.onDidChangeActivePaneItem(textEditor => { - sendBackToMainProcess(textEditor.getPath()) - subscription.dispose() - }) - }) - assert.equal(activeEditorPath, existingDirCFilePath) - assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) - - // When opening *directories* with --add, reuses an existing window and adds the directory to the project - reusedWindow = (await atomApplication.launch(parseCommandLine([dirBPath, '-a'])))[0] - assert.equal(reusedWindow, window1) - assert.deepEqual(atomApplication.getAllWindows(), [window1]) - - await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length === 2) - assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) - }) - it('persists window state based on the project directories', async () => { // Choosing "Don't save" mockElectronShowMessageBox({response: 2}) @@ -183,77 +318,6 @@ describe('AtomApplication', function () { assert.equal(window2Text, 'Hello World! How are you?') }) - it('shows all directories in the tree view when multiple directory paths are passed to Atom', async () => { - const dirAPath = makeTempDir('a') - const dirBPath = makeTempDir('b') - const dirBSubdirPath = path.join(dirBPath, 'c') - fs.mkdirSync(dirBSubdirPath) - - const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) - await focusWindow(window1) - - await conditionPromise(async () => (await getTreeViewRootDirectories(window1)).length === 2) - assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) - }) - - it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async () => { - const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([])) - await focusWindow(window1) - const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { - sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) - }) - assert.equal(window1EditorTitle, 'untitled') - - const window2 = atomApplication.openWithOptions(parseCommandLine([])) - await focusWindow(window2) - const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { - sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) - }) - assert.equal(window2EditorTitle, 'untitled') - - assert.deepEqual(atomApplication.getAllWindows(), [window2, window1]) - }) - - it('does not open an empty editor when opened with no path if the core.openEmptyEditorOnStart config setting is false', async () => { - const configPath = path.join(process.env.ATOM_HOME, 'config.cson') - const config = season.readFileSync(configPath) - if (!config['*'].core) config['*'].core = {} - config['*'].core.openEmptyEditorOnStart = false - season.writeFileSync(configPath, config) - - const atomApplication = buildAtomApplication() - const [window1] = await atomApplication.launch(parseCommandLine([])) - await focusWindow(window1) - - // wait a bit just to make sure we don't pass due to querying the render process before it loads - await timeoutPromise(1000) - - const itemCount = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { - sendBackToMainProcess(atom.workspace.getActivePane().getItems().length) - }) - assert.equal(itemCount, 0) - }) - - it('opens an empty text editor when launched with a new file path', async () => { - // Choosing "Don't save" - mockElectronShowMessageBox({response: 2}) - - const atomApplication = buildAtomApplication() - const newFilePath = path.join(makeTempDir(), 'new-file') - const [window] = await atomApplication.launch(parseCommandLine([newFilePath])) - await focusWindow(window) - const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { - atom.workspace.observeTextEditors(editor => { - sendBackToMainProcess({editorTitle: editor.getTitle(), editorText: editor.getText()}) - }) - }) - assert.equal(editorTitle, path.basename(newFilePath)) - assert.equal(editorText, '') - assert.deepEqual(await getTreeViewRootDirectories(window), []) - }) - it('adds a remote directory to the project when launched with a remote directory', async () => { const packagePath = path.join(__dirname, '..', 'fixtures', 'packages', 'package-with-directory-provider') const packagesDirPath = path.join(process.env.ATOM_HOME, 'packages') @@ -283,35 +347,6 @@ describe('AtomApplication', function () { } }) - it('reopens any previously opened windows when launched with no path', async () => { - if (process.platform === 'win32') return // Test is too flakey on Windows - - const tempDirPath1 = makeTempDir() - const tempDirPath2 = makeTempDir() - - const atomApplication1 = buildAtomApplication() - const [app1Window1] = await atomApplication1.launch(parseCommandLine([tempDirPath1])) - await emitterEventPromise(app1Window1, 'window:locations-opened') - - const [app1Window2] = await atomApplication1.launch(parseCommandLine([tempDirPath2])) - await emitterEventPromise(app1Window2, 'window:locations-opened') - - await Promise.all([ - app1Window1.prepareToUnload(), - app1Window2.prepareToUnload() - ]) - - const atomApplication2 = buildAtomApplication() - const [app2Window1, app2Window2] = await atomApplication2.launch(parseCommandLine([])) - await Promise.all([ - emitterEventPromise(app2Window1, 'window:locations-opened'), - emitterEventPromise(app2Window2, 'window:locations-opened') - ]) - - assert.deepEqual(await getTreeViewRootDirectories(app2Window1), [tempDirPath1]) - assert.deepEqual(await getTreeViewRootDirectories(app2Window2), [tempDirPath2]) - }) - it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async () => { const atomApplication1 = buildAtomApplication() const [app1Window1] = await atomApplication1.launch(parseCommandLine([makeTempDir()]))