mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge branch 'master' into ns-mb-detangle-editor
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
65
spec/async-spec-helpers.js
Normal file
65
spec/async-spec-helpers.js
Normal 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()
|
||||
})
|
||||
})
|
||||
}
|
||||
26
spec/integration/smoke-spec.coffee
Normal file
26
spec/integration/smoke-spec.coffee
Normal 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")
|
||||
@@ -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])
|
||||
427
spec/main-process/atom-application.test.js
Normal file
427
spec/main-process/atom-application.test.js
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
'use babel'
|
||||
/** @babel */
|
||||
|
||||
import {dialog} from 'electron'
|
||||
import FileRecoveryService from '../../src/main-process/file-recovery-service'
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
148
src/main-process/parse-command-line.js
Normal file
148
src/main-process/parse-command-line.js
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -791,7 +791,6 @@ class TextEditorComponent
|
||||
|
||||
sampleBackgroundColors: (suppressUpdate) ->
|
||||
{backgroundColor} = getComputedStyle(@hostElement)
|
||||
|
||||
@presenter.setBackgroundColor(backgroundColor)
|
||||
|
||||
lineNumberGutter = @gutterContainerComponent?.getLineNumberGutterComponent()
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -12,6 +12,7 @@ class WindowEventHandler
|
||||
|
||||
@previousOnbeforeunloadHandler = @window.onbeforeunload
|
||||
@window.onbeforeunload = @handleWindowBeforeunload
|
||||
@addEventListener(@window, 'unload', @handleWindowUnload)
|
||||
@addEventListener(@window, 'focus', @handleWindowFocus)
|
||||
@addEventListener(@window, 'blur', @handleWindowBlur)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user