Merge pull request #18608 from atom/aw/single-file

When only a file is specified, don't open and index the parent directory
This commit is contained in:
Ash Wilson
2019-01-15 13:33:04 -05:00
committed by GitHub
8 changed files with 365 additions and 387 deletions

View File

@@ -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()[0]).toBe(__dirname)
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])
})
})
})

View File

@@ -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)

View File

@@ -1,3 +1,5 @@
/* globals assert */
const temp = require('temp').track()
const season = require('season')
const dedent = require('dedent')
@@ -44,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') {
@@ -114,95 +287,15 @@ 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")
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 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'))
// When opening *files* with --add, reuses an existing window and adds
// parent directory to the project
let [reusedWindow] = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add']))
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, dirCPath])
// 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])
})
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!')
@@ -213,7 +306,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 => {
@@ -223,95 +316,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 () => {
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)
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([]))
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 and loads its parent directory in the tree-view when launched with a new file path', async () => {
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), [path.dirname(newFilePath)])
})
it('adds a remote directory to the project when launched with a remote directory', async () => {
@@ -343,38 +347,11 @@ 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()]))
await focusWindow(app1Window1)
const [app1Window2] = await atomApplication1.launch(parseCommandLine([makeTempDir()]))
await focusWindow(app1Window2)
@@ -424,15 +401,16 @@ 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 [reusedWindow] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2]))
const [window] = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', projectDir]))
await focusWindow(window)
const [reusedWindow] = await atomApplication.launch(parseCommandLine(['--add', '--wait', '--pid', '102', filePath1, filePath2]))
assert.equal(reusedWindow, window)
const activeEditorPath = await evalInWebContents(window.browserWindow.webContents, send => {
@@ -471,8 +449,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, [])
@@ -498,7 +477,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
@@ -508,7 +487,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
@@ -524,24 +503,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])
})
})
@@ -562,11 +541,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')
@@ -594,12 +573,12 @@ 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([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)
@@ -662,7 +641,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
@@ -680,7 +659,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
}
@@ -710,12 +689,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 +710,8 @@ describe('AtomApplication', function () {
.from(treeView.element.querySelectorAll('.project-root > .header .name'))
.map(element => element.dataset.path)
)
} else {
sendBackToMainProcess([])
}
})
})

View File

@@ -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.
@@ -1359,42 +1362,56 @@ 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.
const locationStats = await Promise.all(
locations.map(async location => {
const stats = location.pathToOpen ? await stat(location.pathToOpen).catch(() => null) : null
return {location, stats}
}),
)
for (const location of locations) {
for (const {location, stats} of locationStats) {
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 (!pathToOpen) {
continue
}
if (!fs.isDirectorySync(pathToOpen)) {
fileLocationsToOpen.push(location)
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)
}
}
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) {

View File

@@ -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'
}
@@ -206,7 +208,6 @@ class AtomApplication extends EventEmitter {
openWithOptions (options) {
const {
initialPaths,
pathsToOpen,
executedFrom,
urlsToOpen,
@@ -216,7 +217,6 @@ class AtomApplication extends EventEmitter {
pidToKillWhenClosed,
devMode,
safeMode,
newWindow,
logFile,
profileStartup,
timeout,
@@ -250,11 +250,9 @@ class AtomApplication extends EventEmitter {
})
} else if (pathsToOpen.length > 0) {
return this.openPaths({
initialPaths,
pathsToOpen,
executedFrom,
pidToKillWhenClosed,
newWindow,
devMode,
safeMode,
profileStartup,
@@ -267,9 +265,7 @@ 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,
safeMode,
profileStartup,
@@ -777,17 +773,14 @@ 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.
// :window - {AtomWindow} to open file paths in.
// :addToLastWindow - Boolean of whether this should be opened in last focused window.
openPath ({
initialPaths,
pathToOpen,
pidToKillWhenClosed,
newWindow,
devMode,
safeMode,
profileStartup,
@@ -797,10 +790,8 @@ class AtomApplication extends EventEmitter {
env
} = {}) {
return this.openPaths({
initialPaths,
pathsToOpen: [pathToOpen],
pidToKillWhenClosed,
newWindow,
devMode,
safeMode,
profileStartup,
@@ -816,18 +807,15 @@ 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.
// :window - {AtomWindow} to open file paths in.
// :addToLastWindow - Boolean of whether this should be opened in last focused window.
openPaths ({
initialPaths,
pathsToOpen,
executedFrom,
pidToKillWhenClosed,
newWindow,
devMode,
safeMode,
windowDimensions,
@@ -843,26 +831,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 (addToLastWindow && normalizedPathsToOpen.length > 0) {
existingWindow = this.windowForPaths(normalizedPathsToOpen, devMode)
if (!existingWindow) {
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
}
}
}
@@ -895,7 +878,6 @@ class AtomApplication extends EventEmitter {
if (!windowDimensions) windowDimensions = this.getDimensionsForNewWindow()
openedWindow = new AtomWindow(this, this.fileRecoveryService, {
initialPaths,
locationsToOpen,
windowInitializationScript,
resourcePath,
@@ -916,7 +898,7 @@ class AtomApplication extends EventEmitter {
}
this.waitSessionsByWindow.get(openedWindow).push({
pid: pidToKillWhenClosed,
remainingPaths: new Set(pathsToOpen)
remainingPaths: new Set(normalizedPathsToOpen)
})
}
@@ -984,8 +966,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
@@ -1264,7 +1245,7 @@ class AtomApplication extends EventEmitter {
}
}
parsePathToOpen (pathToOpen, executedFrom = '') {
parsePathToOpen (pathToOpen, executedFrom, extra) {
let initialColumn, initialLine
if (!pathToOpen) {
return {pathToOpen}
@@ -1286,10 +1267,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 Object.assign({pathToOpen, initialLine, initialColumn}, extra)
}
// Opens a native dialog to prompt the user for a path.

View File

@@ -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

View File

@@ -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(

View File

@@ -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)
}