Merge branch 'master' into ns-mb-detangle-editor

This commit is contained in:
Max Brunsfeld
2016-08-15 09:32:39 -07:00
26 changed files with 1492 additions and 1242 deletions

View File

@@ -5,9 +5,8 @@ temp = require('temp').track()
_ = require 'underscore-plus'
async = require 'async'
# TODO: This should really be parallel on every platform, however:
# - On Windows, our fixtures step on each others toes.
if process.platform is 'win32'
# Run specs serially on CircleCI
if process.env.CIRCLECI
concurrency = 1
else
concurrency = 2
@@ -137,24 +136,13 @@ module.exports = (grunt) ->
else
async.parallel
specs =
if process.env.ATOM_SPECS_TASK is 'packages'
[runPackageSpecs]
else if process.env.ATOM_SPECS_TASK is 'core'
[runRendererProcessSpecs, runMainProcessSpecs]
else
[runRendererProcessSpecs, runMainProcessSpecs, runPackageSpecs]
specs = [runRendererProcessSpecs, runMainProcessSpecs, runPackageSpecs]
method specs, (error, results) ->
failedPackages = []
coreSpecFailed = null
if process.env.ATOM_SPECS_TASK is 'packages'
[failedPackages] = results
else if process.env.ATOM_SPECS_TASK is 'core'
[rendererProcessSpecsFailed, mainProcessSpecsFailed] = results
else
[rendererProcessSpecsFailed, mainProcessSpecsFailed, failedPackages] = results
[rendererProcessSpecsFailed, mainProcessSpecsFailed, failedPackages] = results
elapsedTime = Math.round((Date.now() - startTime) / 100) / 10
grunt.log.ok("Total spec time: #{elapsedTime}s using #{concurrency} cores")

View File

@@ -7,6 +7,9 @@ machine:
xcode:
version: 7.3
post:
- osascript -e 'tell application "System Events" to keystroke "x"' # clear screen saver
general:
artifacts:
- out/Atom.zip
@@ -29,10 +32,11 @@ dependencies:
- apm/node_modules
- build/node_modules
- node_modules
- ~/.atom/compile-cache
test:
override:
- script/grunt ci
- caffeinate -s script/grunt ci # Run with caffeinate to prevent screen saver
post:
- zip -r out/Atom.zip out/Atom.app

View File

@@ -125,10 +125,10 @@
{
label: 'Panes'
submenu: [
{ label: 'Split Up', command: 'pane:split-up' }
{ label: 'Split Down', command: 'pane:split-down' }
{ label: 'Split Left', command: 'pane:split-left' }
{ label: 'Split Right', command: 'pane:split-right' }
{ label: 'Split Up', command: 'pane:split-up-and-copy-active-item' }
{ label: 'Split Down', command: 'pane:split-down-and-copy-active-item' }
{ label: 'Split Left', command: 'pane:split-left-and-copy-active-item' }
{ label: 'Split Right', command: 'pane:split-right-and-copy-active-item' }
{ type: 'separator' }
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }

View File

@@ -108,10 +108,10 @@
{
label: 'Panes'
submenu: [
{ label: 'Split Up', command: 'pane:split-up' }
{ label: 'Split Down', command: 'pane:split-down' }
{ label: 'Split Left', command: 'pane:split-left' }
{ label: 'Split Right', command: 'pane:split-right' }
{ label: 'Split Up', command: 'pane:split-up-and-copy-active-item' }
{ label: 'Split Down', command: 'pane:split-down-and-copy-active-item' }
{ label: 'Split Left', command: 'pane:split-left-and-copy-active-item' }
{ label: 'Split Right', command: 'pane:split-right-and-copy-active-item' }
{ type: 'separator' }
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }

View File

@@ -107,10 +107,10 @@
{
label: 'Panes'
submenu: [
{ label: 'Split Up', command: 'pane:split-up' }
{ label: 'Split Down', command: 'pane:split-down' }
{ label: 'Split Left', command: 'pane:split-left' }
{ label: 'Split Right', command: 'pane:split-right' }
{ label: 'Split Up', command: 'pane:split-up-and-copy-active-item' }
{ label: 'Split Down', command: 'pane:split-down-and-copy-active-item' }
{ label: 'Split Left', command: 'pane:split-left-and-copy-active-item' }
{ label: 'Split Right', command: 'pane:split-right-and-copy-active-item' }
{ type: 'separator' }
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }

View File

@@ -58,7 +58,7 @@
"sinon": "1.17.4",
"source-map-support": "^0.3.2",
"temp": "0.8.1",
"text-buffer": "9.2.9",
"text-buffer": "9.2.10",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"winreg": "^1.2.1",
@@ -99,7 +99,7 @@
"git-diff": "1.1.0",
"go-to-line": "0.31.0",
"grammar-selector": "0.48.2",
"image-view": "0.58.2",
"image-view": "0.58.3",
"incompatible-packages": "0.26.1",
"keybinding-resolver": "0.35.0",
"line-ending-selector": "0.5.0",
@@ -117,7 +117,7 @@
"symbols-view": "0.113.1",
"tabs": "0.100.2",
"timecop": "0.33.2",
"tree-view": "0.208.2",
"tree-view": "0.208.3",
"update-package-dependencies": "0.10.0",
"welcome": "0.34.0",
"whitespace": "0.33.0",

View File

@@ -1,30 +0,0 @@
exports.beforeEach = (fn) ->
global.beforeEach ->
result = fn()
if result instanceof Promise
waitsForPromise(-> result)
exports.afterEach = (fn) ->
global.afterEach ->
result = fn()
if result instanceof Promise
waitsForPromise(-> result)
['it', 'fit', 'ffit', 'fffit'].forEach (name) ->
exports[name] = (description, fn) ->
global[name] description, ->
result = fn()
if result instanceof Promise
waitsForPromise(-> result)
waitsForPromise = (fn) ->
promise = fn()
# This timeout is 3 minutes. We need to bump it back down once we fix backgrounding
# of the renderer process on CI. See https://github.com/atom/electron/issues/4317
waitsFor 'spec promise to resolve', 3 * 60 * 1000, (done) ->
promise.then(
done,
(error) ->
jasmine.getEnv().currentSpec.fail(error)
done()
)

View File

@@ -0,0 +1,65 @@
/** @babel */
export function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
export function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
module.exports[name] = function (description, fn) {
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
})
export function conditionPromise (condition) {
const timeoutError = new Error("Timed out waiting on condition")
Error.captureStackTrace(timeoutError, conditionPromise)
return new Promise(function (resolve, reject) {
const interval = global.setInterval(function () {
if (condition()) {
global.clearInterval(interval)
global.clearTimeout(timeout)
resolve()
}
}, 100)
const timeout = global.setTimeout(function () {
global.clearInterval(interval)
reject(timeoutError)
}, 5000)
})
}
export function timeoutPromise (timeout) {
return new Promise(function (resolve) {
global.setTimeout(resolve, timeout)
})
}
function waitsForPromise (fn) {
const promise = fn()
global.waitsFor('spec promise to resolve', function (done) {
promise.then(done, function (error) {
jasmine.getEnv().currentSpec.fail(error)
done()
})
})
}

View File

@@ -0,0 +1,26 @@
fs = require 'fs-plus'
path = require 'path'
temp = require('temp').track()
runAtom = require './helpers/start-atom'
describe "Smoke Test", ->
atomHome = temp.mkdirSync('atom-home')
beforeEach ->
jasmine.useRealClock()
fs.writeFileSync(path.join(atomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config.cson')))
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) ->
client
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath])
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
.waitForPaneItemCount(1, 1000)
.click("atom-text-editor")
.keys("Hello!")
.execute -> atom.workspace.getActiveTextEditor().getText()
.then ({value}) -> expect(value).toBe "Hello!"
.dispatchCommand("editor:delete-line")

View File

@@ -1,307 +0,0 @@
# These tests are excluded by default. To run them from the command line:
#
# ATOM_INTEGRATION_TESTS_ENABLED=true apm test
return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
# Integration tests require a fast machine and, for now, we cannot afford to
# run them on Travis.
return if process.env.CI
fs = require 'fs-plus'
path = require 'path'
temp = require('temp').track()
runAtom = require './helpers/start-atom'
CSON = require 'season'
describe "Starting Atom", ->
atomHome = temp.mkdirSync('atom-home')
[tempDirPath, otherTempDirPath] = []
beforeEach ->
jasmine.useRealClock()
fs.writeFileSync(path.join(atomHome, 'config.cson'), fs.readFileSync(path.join(__dirname, 'fixtures', 'atom-home', 'config.cson')))
fs.removeSync(path.join(atomHome, 'storage'))
tempDirPath = temp.mkdirSync("empty-dir")
otherTempDirPath = temp.mkdirSync("another-temp-dir")
describe "opening a new file", ->
it "opens the parent directory and creates an empty text editor", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath])
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
.waitForPaneItemCount(1, 1000)
.click("atom-text-editor")
.keys("Hello!")
.execute -> atom.workspace.getActiveTextEditor().getText()
.then ({value}) -> expect(value).toBe "Hello!"
.dispatchCommand("editor:delete-line")
it "opens the file to the specified line number", ->
filePath = path.join(fs.realpathSync(tempDirPath), "new-file")
fs.writeFileSync filePath, """
1
2
3
4
"""
runAtom ["#{filePath}:3"], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 1000)
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
.execute -> atom.workspace.getActiveTextEditor().getPath()
.then ({value}) -> expect(value).toBe filePath
.execute -> atom.workspace.getActiveTextEditor().getCursorBufferPosition()
.then ({value}) ->
expect(value.row).toBe 2
expect(value.column).toBe 0
it "opens the file to the specified line number and column number", ->
filePath = path.join(fs.realpathSync(tempDirPath), "new-file")
fs.writeFileSync filePath, """
1
2
3
4
"""
runAtom ["#{filePath}:2:2"], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 1000)
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
.execute -> atom.workspace.getActiveTextEditor().getPath()
.then ({value}) -> expect(value).toBe filePath
.execute -> atom.workspace.getActiveTextEditor().getCursorBufferPosition()
.then ({value}) ->
expect(value.row).toBe 1
expect(value.column).toBe 1
it "removes all trailing whitespace and colons from the specified path", ->
filePath = path.join(tempDirPath, "new-file")
runAtom ["#{filePath}: "], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 1000)
.waitForExist("atom-text-editor", 5000)
.then (exists) -> expect(exists).toBe true
.execute -> atom.workspace.getActiveTextEditor().getPath()
.then ({value}) -> expect(value).toBe filePath
describe "when there is already a window open", ->
it "reuses that window when opening files, but not when opening directories", ->
tempFilePath = path.join(temp.mkdirSync("a-third-dir"), "a-file")
fs.writeFileSync(tempFilePath, "This file was already here.")
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 5000)
# Opening another file reuses the same window and does not change the
# project paths.
.startAnotherAtom([tempFilePath], ATOM_HOME: atomHome)
.waitForPaneItemCount(2, 5000)
.waitForWindowCount(1, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath])
.execute -> atom.workspace.getActiveTextEditor().getText()
.then ({value: text}) -> expect(text).toBe "This file was already here."
# Opening another directory creates a second window.
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForPaneItemCount(0, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([otherTempDirPath])
describe "when using the -a, --add option", ->
it "reuses that window and add the folder to project paths", ->
fourthTempDir = temp.mkdirSync("a-fourth-dir")
fourthTempFilePath = path.join(fourthTempDir, "a-file")
fs.writeFileSync(fourthTempFilePath, "4 - This file was already here.")
fifthTempDir = temp.mkdirSync("a-fifth-dir")
fifthTempFilePath = path.join(fifthTempDir, "a-file")
fs.writeFileSync(fifthTempFilePath, "5 - This file was already here.")
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 5000)
# Opening another file reuses the same window and add parent dir to
# project paths.
.startAnotherAtom(['-a', fourthTempFilePath], ATOM_HOME: atomHome)
.waitForPaneItemCount(2, 5000)
.waitForWindowCount(1, 1000)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir])
.execute -> atom.workspace.getActiveTextEditor().getText()
.then ({value: text}) -> expect(text).toBe "4 - This file was already here."
# Opening another directory resuses the same window and add the folder to project paths.
.startAnotherAtom(['--add', fifthTempDir], ATOM_HOME: atomHome)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, fourthTempDir, fifthTempDir])
it "opens the new window offset from the other window", ->
runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) ->
win0Position = null
win1Position = null
client
.waitForWindowCount(1, 10000)
.execute -> atom.getPosition()
.then ({value}) -> win0Position = value
.waitForNewWindow(->
@startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome)
, 5000)
.waitForWindowCount(2, 10000)
.execute -> atom.getPosition()
.then ({value}) -> win1Position = value
.then ->
expect(win1Position.x).toBeGreaterThan(win0Position.x)
# Ideally we'd test the y coordinate too, but if the window's
# already as tall as it can be, then macOS won't move it down outside
# the screen.
# expect(win1Position.y).toBeGreaterThan(win0Position.y)
describe "reopening a directory that was previously opened", ->
it "remembers the state of the window", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(0, 3000)
.execute -> atom.workspace.open()
.waitForPaneItemCount(1, 3000)
.keys("Hello!")
.waitUntil((-> Promise.resolve(false)), 1100)
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 5000)
describe "opening multiple directories simultaneously", ->
it "shows them all in the tree-view", ->
nestedDir = path.join(otherTempDirPath, "nested-dir")
fs.mkdirSync(nestedDir)
runAtom [tempDirPath, otherTempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath])
# Opening one of those directories again reuses the same window and
# does not change the project paths.
.startAnotherAtom([nestedDir], ATOM_HOME: atomHome)
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([tempDirPath, otherTempDirPath])
describe "when there is an existing window with no project path", ->
it "reuses that window to open a directory", ->
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([])
.startAnotherAtom([tempDirPath], ATOM_HOME: atomHome)
.waitUntil(->
@treeViewRootDirectories()
.then ({value}) -> value[0] is tempDirPath
, 5000)
.then (result) -> expect(result).toBe(true)
.waitForWindowCount(1, 5000)
describe "launching with no path", ->
it "opens a new window with a single untitled buffer", ->
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(1, 5000)
# Opening with no file paths always creates a new window, even if
# existing windows have no project paths.
.waitForNewWindow(->
@startAnotherAtom([], ATOM_HOME: atomHome)
, 5000)
.waitForPaneItemCount(1, 5000)
it "doesn't open a new window if openEmptyEditorOnStart is disabled", ->
configPath = path.join(atomHome, 'config.cson')
config = CSON.readFileSync(configPath)
config['*'].core = {openEmptyEditorOnStart: false}
CSON.writeFileSync(configPath, config)
runAtom [], {ATOM_HOME: atomHome}, (client) ->
client
.waitForPaneItemCount(0, 5000)
it "reopens any previously opened windows", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
runAtom [], {ATOM_HOME: atomHome}, (client) ->
windowProjectPaths = []
client
.waitForWindowCount(2, 10000)
.then ({value: windowHandles}) ->
@window(windowHandles[0])
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
.window(windowHandles[1])
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
.call ->
expect(windowProjectPaths.sort()).toEqual [
[tempDirPath]
[otherTempDirPath]
].sort()
it "doesn't reopen any previously opened windows if restorePreviousWindowsOnStart is disabled", ->
runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) ->
client
.waitForExist("atom-workspace")
.waitForNewWindow(->
@startAnotherAtom([otherTempDirPath], ATOM_HOME: atomHome)
, 5000)
.waitForExist("atom-workspace")
configPath = path.join(atomHome, 'config.cson')
config = CSON.readFileSync(configPath)
config['*'].core = {restorePreviousWindowsOnStart: false}
CSON.writeFileSync(configPath, config)
runAtom [], {ATOM_HOME: atomHome}, (client) ->
windowProjectPaths = []
client
.waitForWindowCount(1, 10000)
.then ({value: windowHandles}) ->
@window(windowHandles[0])
.waitForExist("atom-workspace")
.treeViewRootDirectories()
.then ({value: directories}) -> windowProjectPaths.push(directories)
.call ->
expect(windowProjectPaths).toEqual [
[]
]
describe "opening a remote directory", ->
it "opens the parent directory and creates an empty text editor", ->
remoteDirectory = 'remote://server:3437/some/directory/path'
runAtom [remoteDirectory], {ATOM_HOME: atomHome}, (client) ->
client
.treeViewRootDirectories()
.then ({value}) -> expect(value).toEqual([remoteDirectory])

View File

@@ -0,0 +1,427 @@
/** @babel */
import season from 'season'
import dedent from 'dedent'
import electron from 'electron'
import fs from 'fs-plus'
import path from 'path'
import AtomApplication from '../../src/main-process/atom-application'
import parseCommandLine from '../../src/main-process/parse-command-line'
import {timeoutPromise, conditionPromise} from '../async-spec-helpers'
const ATOM_RESOURCE_PATH = path.resolve(__dirname, '..', '..')
describe('AtomApplication', function () {
this.timeout(60 * 1000)
let originalAtomHome, atomApplicationsToDestroy
beforeEach(function () {
originalAtomHome = process.env.ATOM_HOME
process.env.ATOM_HOME = makeTempDir('atom-home')
// Symlinking the compile cache into the temporary home dir makes the windows load much faster
fs.symlinkSync(path.join(originalAtomHome, 'compile-cache'), path.join(process.env.ATOM_HOME, 'compile-cache'))
season.writeFileSync(path.join(process.env.ATOM_HOME, 'config.cson'), {
'*': {welcome: {showOnStartup: false}}
})
atomApplicationsToDestroy = []
})
afterEach(async function () {
process.env.ATOM_HOME = originalAtomHome
for (let atomApplication of atomApplicationsToDestroy) {
await atomApplication.destroy()
}
await clearElectronSession()
})
describe('launch', function () {
it('can open to a specific line number of a file', async function () {
const filePath = path.join(makeTempDir(), 'new-file')
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
const atomApplication = buildAtomApplication()
const window = atomApplication.launch(parseCommandLine([filePath + ':3']))
await focusWindow(window)
const cursorRow = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) sendBackToMainProcess(textEditor.getCursorBufferPosition().row)
})
})
assert.equal(cursorRow, 2)
})
it('can open to a specific line and column of a file', async function () {
const filePath = path.join(makeTempDir(), 'new-file')
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
const atomApplication = buildAtomApplication()
const window = atomApplication.launch(parseCommandLine([filePath + ':2:2']))
await focusWindow(window)
const cursorPosition = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) sendBackToMainProcess(textEditor.getCursorBufferPosition())
})
})
assert.deepEqual(cursorPosition, {row: 1, column: 1})
})
it('removes all trailing whitespace and colons from the specified path', async function () {
let filePath = path.join(makeTempDir(), 'new-file')
fs.writeFileSync(filePath, '1\n2\n3\n4\n')
const atomApplication = buildAtomApplication()
const window = atomApplication.launch(parseCommandLine([filePath + ':: ']))
await focusWindow(window)
const openedPath = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) sendBackToMainProcess(textEditor.getPath())
})
})
assert.equal(openedPath, filePath)
})
it('positions new windows at an offset distance from the previous window', async function () {
const atomApplication = buildAtomApplication()
const window1 = atomApplication.launch(parseCommandLine([makeTempDir()]))
await focusWindow(window1)
window1.browserWindow.setBounds({width: 400, height: 400, x: 0, y: 0})
const window2 = atomApplication.launch(parseCommandLine([makeTempDir()]))
await focusWindow(window2)
assert.notEqual(window1, window2)
window1Dimensions = window1.getDimensions()
window2Dimensions = window2.getDimensions()
assert.isAbove(window2Dimensions.x, window1Dimensions.x)
assert.isAbove(window2Dimensions.y, window1Dimensions.y)
})
it('reuses existing windows when opening paths, but not directories', async function () {
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 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')]))
await focusWindow(window1)
let activeEditorPath
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (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 = atomApplication.launch(parseCommandLine([existingDirCFilePath]))
assert.equal(reusedWindow, window1)
assert.deepEqual(atomApplication.windows, [window1])
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.onDidChangeActivePaneItem(function (textEditor) {
sendBackToMainProcess(textEditor.getPath())
})
})
assert.equal(activeEditorPath, existingDirCFilePath)
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath])
// Opens new windows when opening directories
const window2 = atomApplication.launch(parseCommandLine([dirCPath]))
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 function () {
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 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')]))
await focusWindow(window1)
let activeEditorPath
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (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 = atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add']))
assert.equal(reusedWindow, window1)
assert.deepEqual(atomApplication.windows, [window1])
activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.onDidChangeActivePaneItem(function (textEditor) {
sendBackToMainProcess(textEditor.getPath())
})
})
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 = atomApplication.launch(parseCommandLine([dirBPath, '-a']))
assert.equal(reusedWindow, window1)
assert.deepEqual(atomApplication.windows, [window1])
assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirCPath, dirBPath])
})
it('persists window state based on the project directories', async function () {
const tempDirPath = makeTempDir()
const atomApplication = buildAtomApplication()
const window1 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'new-file')]))
await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) {
textEditor.insertText('Hello World!')
sendBackToMainProcess(null)
}
})
})
window1.close()
await window1.closedPromise
const window2 = atomApplication.launch(parseCommandLine([path.join(tempDirPath)]))
const window2Text = await evalInWebContents(window2.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (textEditor) {
if (textEditor) sendBackToMainProcess(textEditor.getText())
})
})
assert.equal(window2Text, 'Hello World!')
})
it('shows all directories in the tree view when multiple directory paths are passed to Atom', async function () {
const dirAPath = makeTempDir("a")
const dirBPath = makeTempDir("b")
const dirBSubdirPath = path.join(dirBPath, 'c')
fs.mkdirSync(dirBSubdirPath)
const atomApplication = buildAtomApplication()
const window1 = atomApplication.launch(parseCommandLine([dirAPath, dirBPath]))
await focusWindow(window1)
await new Promise(function (resolve) {
setTimeout(resolve, 1000)
})
let treeViewPaths = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
sendBackToMainProcess(
Array
.from(document.querySelectorAll('.tree-view .project-root > .header .name'))
.map(element => element.dataset.path)
)
})
assert.deepEqual(treeViewPaths, [dirAPath, dirBPath])
})
it('reuses windows with no project paths to open directories', async function () {
const tempDirPath = makeTempDir()
const atomApplication = buildAtomApplication()
const window1 = atomApplication.launch(parseCommandLine([]))
await focusWindow(window1)
const reusedWindow = atomApplication.launch(parseCommandLine([tempDirPath]))
assert.equal(reusedWindow, window1)
assert.deepEqual(await getTreeViewRootDirectories(window1), [tempDirPath])
})
it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async function () {
const atomApplication = buildAtomApplication()
const window1 = atomApplication.launch(parseCommandLine([]))
await focusWindow(window1)
const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
})
assert.equal(window1EditorTitle, 'untitled')
const window2 = atomApplication.launch(parseCommandLine([]))
await focusWindow(window2)
const window2EditorTitle = await evalInWebContents(window1.browserWindow.webContents, function (sendBackToMainProcess) {
sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle())
})
assert.equal(window2EditorTitle, 'untitled')
assert.deepEqual(atomApplication.windows, [window1, window2])
})
it('does not open an empty editor when opened with no path if the core.openEmptyEditorOnStart config setting is false', async function () {
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 = 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, function (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 function () {
const atomApplication = buildAtomApplication()
const newFilePath = path.join(makeTempDir(), 'new-file')
const window = atomApplication.launch(parseCommandLine([newFilePath]))
await focusWindow(window)
const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (editor) {
if (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('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path in a remote directory', async function () {
// Disable the tree-view because it will try to enumerate the contents of
// the remote directory and, since it doesn't exist, throw an error.
const configPath = path.join(process.env.ATOM_HOME, 'config.cson')
const config = season.readFileSync(configPath)
if (!config['*'].core) config['*'].core = {}
config['*'].core.disabledPackages = ['tree-view']
season.writeFileSync(configPath, config)
const atomApplication = buildAtomApplication()
const newRemoteFilePath = 'remote://server:3437/some/directory/path'
const window = atomApplication.launch(parseCommandLine([newRemoteFilePath]))
await focusWindow(window)
const {projectPaths, editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, function (sendBackToMainProcess) {
atom.workspace.observeActivePaneItem(function (editor) {
if (editor) {
sendBackToMainProcess({
projectPaths: atom.project.getPaths(),
editorTitle: editor.getTitle(),
editorText: editor.getText()
})
}
})
})
assert.deepEqual(projectPaths, [newRemoteFilePath])
assert.equal(editorTitle, path.basename(newRemoteFilePath))
assert.equal(editorText, '')
})
it('reopens any previously opened windows when launched with no path', async function () {
const tempDirPath1 = makeTempDir()
const tempDirPath2 = makeTempDir()
const atomApplication1 = buildAtomApplication()
const app1Window1 = atomApplication1.launch(parseCommandLine([tempDirPath1]))
await app1Window1.loadedPromise
const app1Window2 = atomApplication1.launch(parseCommandLine([tempDirPath2]))
await app1Window2.loadedPromise
const atomApplication2 = buildAtomApplication()
const [app2Window1, app2Window2] = atomApplication2.launch(parseCommandLine([]))
await app2Window1.loadedPromise
await app2Window2.loadedPromise
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 false', async function () {
const atomApplication1 = buildAtomApplication()
const app1Window1 = atomApplication1.launch(parseCommandLine([makeTempDir()]))
await focusWindow(app1Window1)
const app1Window2 = atomApplication1.launch(parseCommandLine([makeTempDir()]))
await focusWindow(app1Window2)
const configPath = path.join(process.env.ATOM_HOME, 'config.cson')
const config = season.readFileSync(configPath)
if (!config['*'].core) config['*'].core = {}
config['*'].core.restorePreviousWindowsOnStart = false
season.writeFileSync(configPath, config)
const atomApplication2 = buildAtomApplication()
const app2Window = atomApplication2.launch(parseCommandLine([]))
await focusWindow(app2Window)
assert.deepEqual(await getTreeViewRootDirectories(app2Window), [])
})
})
function buildAtomApplication () {
const atomApplication = new AtomApplication({
resourcePath: ATOM_RESOURCE_PATH,
atomHomeDirPath: process.env.ATOM_HOME
})
atomApplicationsToDestroy.push(atomApplication)
return atomApplication
}
async function focusWindow (window) {
window.focus()
await window.loadedPromise
await conditionPromise(() => window.atomApplication.lastFocusedWindow === window)
}
function makeTempDir (name) {
return fs.realpathSync(require('temp').mkdirSync(name))
}
let channelIdCounter = 0
function evalInWebContents (webContents, source) {
const channelId = 'eval-result-' + channelIdCounter++
return new Promise(function (resolve) {
electron.ipcMain.on(channelId, receiveResult)
function receiveResult (event, result) {
electron.ipcMain.removeListener('eval-result', receiveResult)
resolve(result)
}
webContents.executeJavaScript(dedent`
function sendBackToMainProcess (result) {
require('electron').ipcRenderer.send('${channelId}', result)
}
(${source})(sendBackToMainProcess)
`)
})
}
function getTreeViewRootDirectories (atomWindow) {
return evalInWebContents(atomWindow.browserWindow.webContents, function (sendBackToMainProcess) {
sendBackToMainProcess(
Array
.from(document.querySelectorAll('.tree-view .project-root > .header .name'))
.map(element => element.dataset.path)
)
})
}
function clearElectronSession () {
return new Promise(function (resolve) {
electron.session.defaultSession.clearStorageData(function () {
// Resolve promise on next tick, otherwise the process stalls. This
// might be a bug in Electron, but it's probably fixed on the newer
// versions.
process.nextTick(resolve)
})
})
}
})

View File

@@ -1,4 +1,4 @@
'use babel'
/** @babel */
import {dialog} from 'electron'
import FileRecoveryService from '../../src/main-process/file-recovery-service'

View File

@@ -7,11 +7,11 @@ import {assert} from 'chai'
export default function (testPaths) {
global.assert = assert
const mocha = new Mocha({reporter: 'dot'})
const mocha = new Mocha({reporter: 'spec'})
for (let testPath of testPaths) {
if (fs.isDirectorySync(testPath)) {
for (let testFilePath of fs.listTreeSync(testPath)) {
if (/\.spec\.(coffee|js)$/.test(testFilePath)) {
if (/\.test\.(coffee|js)$/.test(testFilePath)) {
mocha.addFile(testFilePath)
}
}

View File

@@ -168,6 +168,13 @@ jasmine.useRealClock = ->
jasmine.unspy(window, 'clearTimeout')
jasmine.unspy(_._, 'now')
# The clock is halfway mocked now in a sad and terrible way... only setTimeout
# and clearTimeout are included. This method will also include setInterval. We
# would do this everywhere if didn't cause us to break a bunch of package tests.
jasmine.useMockClock = ->
spyOn(window, 'setInterval').andCallFake(fakeSetInterval)
spyOn(window, 'clearInterval').andCallFake(fakeClearInterval)
addCustomMatchers = (spec) ->
spec.addMatchers
toBeInstanceOf: (expected) ->
@@ -241,7 +248,7 @@ window.resetTimeouts = ->
window.timeouts = []
window.intervalTimeouts = {}
window.fakeSetTimeout = (callback, ms) ->
window.fakeSetTimeout = (callback, ms=0) ->
id = ++window.timeoutCount
window.timeouts.push([id, window.now + ms, callback])
id

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
/* eslint-env jasmine */
import child_process from 'child_process'
import updateProcessEnv from '../src/update-process-env'
import {updateProcessEnv, shouldGetEnvFromShell} from '../src/update-process-env'
import dedent from 'dedent'
describe('updateProcessEnv(launchEnv)', function () {
@@ -84,5 +84,33 @@ describe('updateProcessEnv(launchEnv)', function () {
expect(process.env).toEqual({FOO: 'bar'})
})
})
describe('shouldGetEnvFromShell()', function () {
it('returns the shell when the shell should be patched', function () {
process.platform = 'darwin'
expect(shouldGetEnvFromShell('/bin/sh')).toBe(true)
expect(shouldGetEnvFromShell('/usr/local/bin/sh')).toBe(true)
expect(shouldGetEnvFromShell('/bin/bash')).toBe(true)
expect(shouldGetEnvFromShell('/usr/local/bin/bash')).toBe(true)
expect(shouldGetEnvFromShell('/bin/zsh')).toBe(true)
expect(shouldGetEnvFromShell('/usr/local/bin/zsh')).toBe(true)
expect(shouldGetEnvFromShell('/bin/fish')).toBe(true)
expect(shouldGetEnvFromShell('/usr/local/bin/fish')).toBe(true)
})
it('returns false when the shell should not be patched', function () {
process.platform = 'darwin'
expect(shouldGetEnvFromShell('/bin/unsupported')).toBe(false)
expect(shouldGetEnvFromShell('/bin/shh')).toBe(false)
expect(shouldGetEnvFromShell('/bin/tcsh')).toBe(false)
expect(shouldGetEnvFromShell('/usr/csh')).toBe(false)
})
it('returns false when the shell is undefined or empty', function () {
process.platform = 'darwin'
expect(shouldGetEnvFromShell(undefined)).toBe(false)
expect(shouldGetEnvFromShell('')).toBe(false)
})
})
})
})

View File

@@ -1,6 +1,6 @@
# Like sands through the hourglass, so are the days of our lives.
module.exports = ({blobStore}) ->
updateProcessEnv = require('./update-process-env')
{updateProcessEnv} = require('./update-process-env')
path = require 'path'
require './window'
{getWindowLoadSettings} = require './window-load-settings-helpers'

View File

@@ -1,6 +1,16 @@
var ipcRenderer = null
var ipcMain = null
var BrowserWindow = null
'use strict'
const Disposable = require('event-kit').Disposable
let ipcRenderer = null
let ipcMain = null
let BrowserWindow = null
exports.on = function (emitter, eventName, callback) {
emitter.on(eventName, callback)
return new Disposable(function () {
emitter.removeListener(eventName, callback)
})
}
exports.call = function (methodName, ...args) {
if (!ipcRenderer) {
@@ -28,7 +38,7 @@ exports.respondTo = function (methodName, callback) {
var responseChannel = getResponseChannel(methodName)
ipcMain.on(methodName, function (event, ...args) {
return exports.on(ipcMain, methodName, function (event, ...args) {
var browserWindow = BrowserWindow.fromWebContents(event.sender)
var result = callback(browserWindow, ...args)
event.sender.send(responseChannel, result)

View File

@@ -7,6 +7,7 @@ Config = require '../config'
FileRecoveryService = require './file-recovery-service'
ipcHelpers = require '../ipc-helpers'
{BrowserWindow, Menu, app, dialog, ipcMain, shell} = require 'electron'
{CompositeDisposable} = require 'event-kit'
fs = require 'fs-plus'
path = require 'path'
os = require 'os'
@@ -36,14 +37,12 @@ class AtomApplication
else
options.socketPath = path.join(os.tmpdir(), "atom-#{options.version}-#{process.env.USER}.sock")
createAtomApplication = -> new AtomApplication(options)
# FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
# take a few seconds to trigger 'error' event, it could be a bug of node
# or atom-shell, before it's fixed we check the existence of socketPath to
# speedup startup.
if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test
createAtomApplication()
new AtomApplication(options).initialize(options)
return
client = net.connect {path: options.socketPath}, ->
@@ -51,7 +50,7 @@ class AtomApplication
client.end()
app.quit()
client.on 'error', createAtomApplication
client.on 'error', -> new AtomApplication(options).initialize(options)
windows: null
applicationMenu: null
@@ -64,32 +63,46 @@ class AtomApplication
constructor: (options) ->
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, timeout, clearWindowState} = options
@socketPath = null if options.test
global.atomApplication = this
@pidsToOpenWindows = {}
@windows = []
@config = new Config({configDirPath: process.env.ATOM_HOME, @resourcePath, enablePersistence: true})
@config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))}
@config.load()
@fileRecoveryService = new FileRecoveryService(path.join(process.env.ATOM_HOME, "recovery"))
@storageFolder = new StorageFolder(process.env.ATOM_HOME)
@disposable = new CompositeDisposable
@handleEvents()
# This stuff was previously done in the constructor, but we want to be able to construct this object
# for testing purposes without booting up the world. As you add tests, feel free to move instantiation
# of these various sub-objects into the constructor, but you'll need to remove the side-effects they
# perform during their construction, adding an initialize method that you call here.
initialize: (options) ->
global.atomApplication = this
@config.onDidChange 'core.useCustomTitleBar', @promptForRelaunch
@autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath, @config)
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
@fileRecoveryService = new FileRecoveryService(path.join(process.env.ATOM_HOME, "recovery"))
@listenForArgumentsFromNewProcess()
@setupJavaScriptArguments()
@handleEvents()
@setupDockMenu()
@storageFolder = new StorageFolder(process.env.ATOM_HOME)
@launch(options)
destroy: ->
@disposable.dispose()
windowsClosePromises = @windows.map (window) ->
window.close()
window.closedPromise
Promise.all(windowsClosePromises)
launch: (options) ->
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
@openWithOptions(options)
else
@@ -121,7 +134,7 @@ class AtomApplication
@windows.push window
@applicationMenu?.addWindow(window.browserWindow)
window.once 'window:loaded', =>
@autoUpdateManager.emitUpdateAvailableEvent(window)
@autoUpdateManager?.emitUpdateAvailableEvent(window)
unless window.isSpec
focusHandler = => @lastFocusedWindow = window
@@ -218,27 +231,27 @@ class AtomApplication
@openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
@openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md'))
app.on 'before-quit', =>
@disposable.add ipcHelpers.on app, 'before-quit', =>
@quitting = true
app.on 'will-quit', =>
@disposable.add ipcHelpers.on app, 'will-quit', =>
@killAllProcesses()
@deleteSocketFile()
app.on 'open-file', (event, pathToOpen) =>
@disposable.add ipcHelpers.on app, 'open-file', (event, pathToOpen) =>
event.preventDefault()
@openPath({pathToOpen})
app.on 'open-url', (event, urlToOpen) =>
@disposable.add ipcHelpers.on app, 'open-url', (event, urlToOpen) =>
event.preventDefault()
@openUrl({urlToOpen, @devMode, @safeMode})
app.on 'activate-with-no-open-windows', (event) =>
@disposable.add ipcHelpers.on app, 'activate-with-no-open-windows', (event) =>
event?.preventDefault()
@emit('application:new-window')
# A request from the associated render process to open a new render process.
ipcMain.on 'open', (event, options) =>
@disposable.add ipcHelpers.on ipcMain, 'open', (event, options) =>
window = @windowForEvent(event)
if options?
if typeof options.pathsToOpen is 'string'
@@ -247,21 +260,21 @@ class AtomApplication
options.window = window
@openPaths(options)
else
new AtomWindow(@fileRecoveryService, options)
new AtomWindow(this, @fileRecoveryService, options)
else
@promptForPathToOpen('all', {window})
ipcMain.on 'update-application-menu', (event, template, keystrokesByCommand) =>
@disposable.add ipcHelpers.on ipcMain, 'update-application-menu', (event, template, keystrokesByCommand) =>
win = BrowserWindow.fromWebContents(event.sender)
@applicationMenu.update(win, template, keystrokesByCommand)
@applicationMenu?.update(win, template, keystrokesByCommand)
ipcMain.on 'run-package-specs', (event, packageSpecPath) =>
@disposable.add ipcHelpers.on ipcMain, 'run-package-specs', (event, packageSpecPath) =>
@runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false})
ipcMain.on 'command', (event, command) =>
@disposable.add ipcHelpers.on ipcMain, 'command', (event, command) =>
@emit(command)
ipcMain.on 'open-command', (event, command, args...) =>
@disposable.add ipcHelpers.on ipcMain, 'open-command', (event, command, args...) =>
defaultPath = args[0] if args.length > 0
switch command
when 'application:open' then @promptForPathToOpen('all', getLoadSettings(), defaultPath)
@@ -269,72 +282,72 @@ class AtomApplication
when 'application:open-folder' then @promptForPathToOpen('folder', getLoadSettings(), defaultPath)
else console.log "Invalid open-command received: " + command
ipcMain.on 'window-command', (event, command, args...) ->
@disposable.add ipcHelpers.on ipcMain, 'window-command', (event, command, args...) ->
win = BrowserWindow.fromWebContents(event.sender)
win.emit(command, args...)
ipcMain.on 'call-window-method', (event, method, args...) ->
@disposable.add ipcHelpers.on ipcMain, 'call-window-method', (event, method, args...) ->
win = BrowserWindow.fromWebContents(event.sender)
win[method](args...)
ipcMain.on 'pick-folder', (event, responseChannel) =>
@disposable.add ipcHelpers.on ipcMain, 'pick-folder', (event, responseChannel) =>
@promptForPath "folder", (selectedPaths) ->
event.sender.send(responseChannel, selectedPaths)
ipcHelpers.respondTo 'set-window-size', (win, width, height) ->
@disposable.add ipcHelpers.respondTo 'set-window-size', (win, width, height) ->
win.setSize(width, height)
ipcHelpers.respondTo 'set-window-position', (win, x, y) ->
@disposable.add ipcHelpers.respondTo 'set-window-position', (win, x, y) ->
win.setPosition(x, y)
ipcHelpers.respondTo 'center-window', (win) ->
@disposable.add ipcHelpers.respondTo 'center-window', (win) ->
win.center()
ipcHelpers.respondTo 'focus-window', (win) ->
@disposable.add ipcHelpers.respondTo 'focus-window', (win) ->
win.focus()
ipcHelpers.respondTo 'show-window', (win) ->
@disposable.add ipcHelpers.respondTo 'show-window', (win) ->
win.show()
ipcHelpers.respondTo 'hide-window', (win) ->
@disposable.add ipcHelpers.respondTo 'hide-window', (win) ->
win.hide()
ipcHelpers.respondTo 'get-temporary-window-state', (win) ->
@disposable.add ipcHelpers.respondTo 'get-temporary-window-state', (win) ->
win.temporaryState
ipcHelpers.respondTo 'set-temporary-window-state', (win, state) ->
@disposable.add ipcHelpers.respondTo 'set-temporary-window-state', (win, state) ->
win.temporaryState = state
ipcMain.on 'did-cancel-window-unload', =>
@disposable.add ipcHelpers.on ipcMain, 'did-cancel-window-unload', =>
@quitting = false
clipboard = require '../safe-clipboard'
ipcMain.on 'write-text-to-selection-clipboard', (event, selectedText) ->
@disposable.add ipcHelpers.on ipcMain, 'write-text-to-selection-clipboard', (event, selectedText) ->
clipboard.writeText(selectedText, 'selection')
ipcMain.on 'write-to-stdout', (event, output) ->
@disposable.add ipcHelpers.on ipcMain, 'write-to-stdout', (event, output) ->
process.stdout.write(output)
ipcMain.on 'write-to-stderr', (event, output) ->
@disposable.add ipcHelpers.on ipcMain, 'write-to-stderr', (event, output) ->
process.stderr.write(output)
ipcMain.on 'add-recent-document', (event, filename) ->
@disposable.add ipcHelpers.on ipcMain, 'add-recent-document', (event, filename) ->
app.addRecentDocument(filename)
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
@disposable.add ipcHelpers.on ipcMain, 'execute-javascript-in-dev-tools', (event, code) ->
event.sender.devToolsWebContents?.executeJavaScript(code)
ipcMain.on 'get-auto-update-manager-state', (event) =>
@disposable.add ipcHelpers.on ipcMain, 'get-auto-update-manager-state', (event) =>
event.returnValue = @autoUpdateManager.getState()
ipcMain.on 'get-auto-update-manager-error', (event) =>
@disposable.add ipcHelpers.on ipcMain, 'get-auto-update-manager-error', (event) =>
event.returnValue = @autoUpdateManager.getErrorMessage()
ipcMain.on 'will-save-path', (event, path) =>
@disposable.add ipcHelpers.on ipcMain, 'will-save-path', (event, path) =>
@fileRecoveryService.willSavePath(@windowForEvent(event), path)
event.returnValue = true
ipcMain.on 'did-save-path', (event, path) =>
@disposable.add ipcHelpers.on ipcMain, 'did-save-path', (event, path) =>
@fileRecoveryService.didSavePath(@windowForEvent(event), path)
event.returnValue = true
@@ -498,7 +511,8 @@ class AtomApplication
windowInitializationScript ?= require.resolve('../initialize-application-window')
resourcePath ?= @resourcePath
windowDimensions ?= @getDimensionsForNewWindow()
openedWindow = new AtomWindow(@fileRecoveryService, {initialPaths, locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState, env})
openedWindow = new AtomWindow(this, @fileRecoveryService, {initialPaths, locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState, env})
openedWindow.focus()
if pidToKillWhenClosed?
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
@@ -506,6 +520,8 @@ class AtomApplication
openedWindow.browserWindow.once 'closed', =>
@killProcessForWindow(openedWindow)
openedWindow
# Kill all processes associated with opened windows.
killAllProcesses: ->
@killProcess(pid) for pid of @pidsToOpenWindows
@@ -548,9 +564,8 @@ class AtomApplication
devMode: @devMode
safeMode: @safeMode
}))
true
else
false
null
# Open an atom:// url.
#
@@ -577,7 +592,7 @@ class AtomApplication
packagePath = @packages.resolvePackagePath(packageName)
windowInitializationScript = path.resolve(packagePath, pack.urlMain)
windowDimensions = @getDimensionsForNewWindow()
new AtomWindow(@fileRecoveryService, {windowInitializationScript, @resourcePath, devMode, safeMode, urlToOpen, windowDimensions, env})
new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, @resourcePath, devMode, safeMode, urlToOpen, windowDimensions, env})
else
console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
else
@@ -622,7 +637,7 @@ class AtomApplication
devMode = true
isSpec = true
safeMode ?= false
new AtomWindow(@fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode, env})
new AtomWindow(this, @fileRecoveryService, {windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode, env})
resolveTestRunnerPath: (testPath) ->
FindParentDir ?= require 'find-parent-dir'
@@ -720,4 +735,3 @@ class AtomApplication
# once we're using electron v.1.2.2
# app.relaunch()
app.quit()

View File

@@ -15,11 +15,14 @@ class AtomWindow
loaded: null
isSpec: null
constructor: (@fileRecoveryService, settings={}) ->
constructor: (@atomApplication, @fileRecoveryService, settings={}) ->
{@resourcePath, initialPaths, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
locationsToOpen ?= [{pathToOpen}] if pathToOpen
locationsToOpen ?= []
@loadedPromise = new Promise((@resolveLoadedPromise) =>)
@closedPromise = new Promise((@resolveClosedPromise) =>)
options =
show: false
title: 'Atom'
@@ -44,7 +47,7 @@ class AtomWindow
options.titleBarStyle = 'hidden'
@browserWindow = new BrowserWindow options
global.atomApplication.addWindow(this)
@atomApplication.addWindow(this)
@handleEvents()
@@ -72,8 +75,9 @@ class AtomWindow
@browserWindow.loadSettings = loadSettings
@browserWindow.once 'window:loaded', =>
@emit 'window:loaded'
@loaded = true
@emit 'window:loaded'
@resolveLoadedPromise()
@setLoadSettings(loadSettings)
@env = loadSettings.env if loadSettings.env?
@@ -124,12 +128,13 @@ class AtomWindow
false
handleEvents: ->
@browserWindow.on 'close', ->
global.atomApplication.saveState(false)
@browserWindow.on 'close', =>
@atomApplication.saveState(false)
@browserWindow.on 'closed', =>
@fileRecoveryService.didCloseWindow(this)
global.atomApplication.removeWindow(this)
@atomApplication.removeWindow(this)
@resolveClosedPromise()
@browserWindow.on 'unresponsive', =>
return if @isSpec
@@ -142,7 +147,7 @@ class AtomWindow
@browserWindow.destroy() if chosen is 0
@browserWindow.webContents.on 'crashed', =>
global.atomApplication.exit(100) if @headless
@atomApplication.exit(100) if @headless
@fileRecoveryService.didCrashWindow(this)
chosen = dialog.showMessageBox @browserWindow,
@@ -182,7 +187,7 @@ class AtomWindow
sendCommand: (command, args...) ->
if @isSpecWindow()
unless global.atomApplication.sendCommandToFirstResponder(command)
unless @atomApplication.sendCommandToFirstResponder(command)
switch command
when 'window:reload' then @reload()
when 'window:toggle-dev-tools' then @toggleDevTools()
@@ -190,7 +195,7 @@ class AtomWindow
else if @isWebViewFocused()
@sendCommandToBrowserWindow(command, args...)
else
unless global.atomApplication.sendCommandToFirstResponder(command)
unless @atomApplication.sendCommandToFirstResponder(command)
@sendCommandToBrowserWindow(command, args...)
sendCommandToBrowserWindow: (command, args...) ->
@@ -205,7 +210,7 @@ class AtomWindow
shouldHideTitleBar: ->
not @isSpec and
process.platform is 'darwin' and
global.atomApplication.config.get('core.useCustomTitleBar')
@atomApplication.config.get('core.useCustomTitleBar')
close: -> @browserWindow.close()

View File

@@ -14,21 +14,20 @@ const {app} = require('electron')
const fs = require('fs-plus')
const path = require('path')
const temp = require('temp')
const yargs = require('yargs')
const dedent = require('dedent')
const parseCommandLine = require('./parse-command-line')
const startCrashReporter = require('../crash-reporter-start')
const previousConsoleLog = console.log
console.log = require('nslog')
function start () {
const args = parseCommandLine()
args.env = process.env
const args = parseCommandLine(process.argv.slice(1))
setupAtomHome(args)
setupCompileCache()
if (handleStartupEventWithSquirrel()) {
return
} else if (args.test && args.mainProcess) {
app.setPath('userData', temp.mkdirSync('atom-user-data-dir-for-main-process-tests'))
console.log = previousConsoleLog
app.on('ready', function () {
const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner'))
@@ -72,14 +71,6 @@ function start () {
})
}
function normalizeDriveLetterName (filePath) {
if (process.platform === 'win32') {
return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':')
} else {
return filePath
}
}
function handleStartupEventWithSquirrel () {
if (process.platform !== 'win32') {
return false
@@ -125,141 +116,4 @@ function setupCompileCache () {
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
}
function writeFullVersion () {
process.stdout.write(
`Atom : ${app.getVersion()}\n` +
`Electron: ${process.versions.electron}\n` +
`Chrome : ${process.versions.chrome}\n` +
`Node : ${process.versions.node}\n`
)
}
function parseCommandLine () {
const options = yargs(process.argv.slice(1)).wrap(100)
const version = app.getVersion()
options.usage(
dedent`Atom Editor v${version}
Usage: atom [options] [path ...]
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.
Environment Variables:
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
Defaults to \`~/github/atom\`.
ATOM_HOME The root path for all configuration files and folders.
Defaults to \`~/.atom\`.`
)
// Deprecated 1.0 API preview flag
options.alias('1', 'one').boolean('1').describe('1', 'This option is no longer supported.')
options.boolean('include-deprecated-apis').describe('include-deprecated-apis', 'This option is not currently supported.')
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
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.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(
'safe',
'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.'
)
options.boolean('portable').describe(
'portable',
'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.'
)
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.')
options.string('timeout').describe(
'timeout',
'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).'
)
options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.')
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
options.string('socket-path')
options.string('user-data-dir')
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
const args = options.argv
if (args.help) {
process.stdout.write(options.help())
process.exit(0)
}
if (args.version) {
writeFullVersion()
process.exit(0)
}
const addToLastWindow = args['add']
const safeMode = args['safe']
const pathsToOpen = args._
const test = args['test']
const mainProcess = args['main-process']
const timeout = args['timeout']
const newWindow = args['new-window']
let executedFrom = null
if (args['executed-from'] && args['executed-from'].toString()) {
executedFrom = args['executed-from'].toString()
} else {
executedFrom = process.cwd()
}
let pidToKillWhenClosed = null
if (args['wait']) {
pidToKillWhenClosed = args['pid']
}
const logFile = args['log-file']
const socketPath = args['socket-path']
const userDataDir = args['user-data-dir']
const profileStartup = args['profile-startup']
const clearWindowState = args['clear-window-state']
const urlsToOpen = []
const setPortable = args.portable
let devMode = args['dev']
let devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH || path.join(app.getPath('home'), 'github', 'atom')
let resourcePath = null
if (args['resource-path']) {
devMode = true
resourcePath = args['resource-path']
}
if (test) {
devMode = true
}
if (devMode && !resourcePath) {
resourcePath = devResourcePath
}
if (!fs.statSyncNoException(resourcePath)) {
resourcePath = path.dirname(path.dirname(__dirname))
}
if (args['path-environment']) {
// On Yosemite the $PATH is not inherited by the "open" command, so we have to
// explicitly pass it by command line, see http://git.io/YC8_Ew.
process.env.PATH = args['path-environment']
}
resourcePath = normalizeDriveLetterName(resourcePath)
devResourcePath = normalizeDriveLetterName(devResourcePath)
return {
resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath,
userDataDir, profileStartup, timeout, setPortable, clearWindowState,
addToLastWindow, mainProcess
}
}
start()

View File

@@ -0,0 +1,148 @@
'use strict'
const dedent = require('dedent')
const yargs = require('yargs')
const {app} = require('electron')
const path = require('path')
const fs = require('fs-plus')
module.exports = function parseCommandLine (processArgs) {
const options = yargs(processArgs).wrap(100)
const version = app.getVersion()
options.usage(
dedent`Atom Editor v${version}
Usage: atom [options] [path ...]
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.
Environment Variables:
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
Defaults to \`~/github/atom\`.
ATOM_HOME The root path for all configuration files and folders.
Defaults to \`~/.atom\`.`
)
// Deprecated 1.0 API preview flag
options.alias('1', 'one').boolean('1').describe('1', 'This option is no longer supported.')
options.boolean('include-deprecated-apis').describe('include-deprecated-apis', 'This option is not currently supported.')
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
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.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(
'safe',
'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.'
)
options.boolean('portable').describe(
'portable',
'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.'
)
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.')
options.string('timeout').describe(
'timeout',
'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).'
)
options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.')
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
options.string('socket-path')
options.string('user-data-dir')
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
const args = options.argv
if (args.help) {
process.stdout.write(options.help())
process.exit(0)
}
if (args.version) {
process.stdout.write(
`Atom : ${app.getVersion()}\n` +
`Electron: ${process.versions.electron}\n` +
`Chrome : ${process.versions.chrome}\n` +
`Node : ${process.versions.node}\n`
)
process.exit(0)
}
const addToLastWindow = args['add']
const safeMode = args['safe']
const pathsToOpen = args._
const test = args['test']
const mainProcess = args['main-process']
const timeout = args['timeout']
const newWindow = args['new-window']
let executedFrom = null
if (args['executed-from'] && args['executed-from'].toString()) {
executedFrom = args['executed-from'].toString()
} else {
executedFrom = process.cwd()
}
let pidToKillWhenClosed = null
if (args['wait']) {
pidToKillWhenClosed = args['pid']
}
const logFile = args['log-file']
const socketPath = args['socket-path']
const userDataDir = args['user-data-dir']
const profileStartup = args['profile-startup']
const clearWindowState = args['clear-window-state']
const urlsToOpen = []
const setPortable = args.portable
let devMode = args['dev']
let devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH || path.join(app.getPath('home'), 'github', 'atom')
let resourcePath = null
if (args['resource-path']) {
devMode = true
resourcePath = args['resource-path']
}
if (test) {
devMode = true
}
if (devMode && !resourcePath) {
resourcePath = devResourcePath
}
if (!fs.statSyncNoException(resourcePath)) {
resourcePath = path.dirname(path.dirname(__dirname))
}
if (args['path-environment']) {
// On Yosemite the $PATH is not inherited by the "open" command, so we have to
// explicitly pass it by command line, see http://git.io/YC8_Ew.
process.env.PATH = args['path-environment']
}
resourcePath = normalizeDriveLetterName(resourcePath)
devResourcePath = normalizeDriveLetterName(devResourcePath)
return {
resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath,
userDataDir, profileStartup, timeout, setPortable, clearWindowState,
addToLastWindow, mainProcess, env: process.env
}
}
function normalizeDriveLetterName (filePath) {
if (process.platform === 'win32') {
return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':')
} else {
return filePath
}
}

View File

@@ -26,7 +26,7 @@ class StateStore {
save (key, value) {
return new Promise((resolve, reject) => {
this.dbPromise.then(db => {
if (db == null) resolve()
if (db == null) return resolve()
var request = db.transaction(['states'], 'readwrite')
.objectStore('states')

View File

@@ -791,7 +791,6 @@ class TextEditorComponent
sampleBackgroundColors: (suppressUpdate) ->
{backgroundColor} = getComputedStyle(@hostElement)
@presenter.setBackgroundColor(backgroundColor)
lineNumberGutter = @gutterContainerComponent?.getLineNumberGutterComponent()

View File

@@ -8,7 +8,14 @@ const ENVIRONMENT_VARIABLES_TO_PRESERVE = new Set([
'ATOM_HOME'
])
export default function updateProcessEnv (launchEnv) {
const OSX_SHELLS = new Set([
'/sh',
'/bash',
'/zsh',
'/fish'
])
function updateProcessEnv (launchEnv) {
let envToAssign
if (launchEnv && launchEnv.PWD) {
envToAssign = launchEnv
@@ -33,21 +40,38 @@ export default function updateProcessEnv (launchEnv) {
}
}
function getEnvFromShell () {
let shell = process.env.SHELL
if (shell && (shell.endsWith('/bash') || shell.endsWith('/sh'))) {
let {stdout} = spawnSync(shell, ['-ilc', 'command env'], {encoding: 'utf8'})
if (stdout) {
let result = {}
for (let line of stdout.split('\n')) {
if (line.includes('=')) {
let components = line.split('=')
let key = components.shift()
let value = components.join('=')
result[key] = value
}
}
return result
function shouldGetEnvFromShell (shell) {
if (!shell || shell.trim() === '') {
return false
}
for (let s of OSX_SHELLS) {
if (shell.endsWith(s)) {
return true
}
}
return false
}
function getEnvFromShell () {
let shell = process.env.SHELL
if (!shouldGetEnvFromShell(shell)) {
return
}
let {stdout} = spawnSync(shell, ['-ilc', 'command env'], {encoding: 'utf8'})
if (stdout) {
let result = {}
for (let line of stdout.split('\n')) {
if (line.includes('=')) {
let components = line.split('=')
let key = components.shift()
let value = components.join('=')
result[key] = value
}
}
return result
}
}
export default { updateProcessEnv, shouldGetEnvFromShell }

View File

@@ -12,6 +12,7 @@ class WindowEventHandler
@previousOnbeforeunloadHandler = @window.onbeforeunload
@window.onbeforeunload = @handleWindowBeforeunload
@addEventListener(@window, 'unload', @handleWindowUnload)
@addEventListener(@window, 'focus', @handleWindowFocus)
@addEventListener(@window, 'blur', @handleWindowBlur)