Merge branch 'master' into wl-rm-safe-clipboard

This commit is contained in:
Max Brunsfeld
2018-08-24 11:57:36 -07:00
committed by GitHub
257 changed files with 40837 additions and 6029 deletions

View File

@@ -201,7 +201,7 @@ class ApplicationMenu {
if (item.command) {
item.accelerator = this.acceleratorForCommand(item.command, keystrokesByCommand)
item.click = () => global.atomApplication.sendCommand(item.command, item.commandDetail)
if (!/^application:/.test(item.command, item.commandDetail)) {
if (!/^application:/.test(item.command)) {
item.metadata.windowSpecific = true
}
}

View File

@@ -4,6 +4,7 @@ const AtomProtocolHandler = require('./atom-protocol-handler')
const AutoUpdateManager = require('./auto-update-manager')
const StorageFolder = require('../storage-folder')
const Config = require('../config')
const ConfigFile = require('../config-file')
const FileRecoveryService = require('./file-recovery-service')
const ipcHelpers = require('../ipc-helpers')
const {BrowserWindow, Menu, app, clipboard, dialog, ipcMain, shell, screen} = require('electron')
@@ -32,7 +33,7 @@ class AtomApplication extends EventEmitter {
// Public: The entry point into the Atom application.
static open (options) {
if (!options.socketPath) {
const username = process.platform === 'win32' ? process.env.USERNAME : process.env.USER
const {username} = os.userInfo()
// Lowercasing the ATOM_HOME to make sure that we don't get multiple sockets
// on case-insensitive filesystems due to arbitrary case differences in paths.
@@ -43,7 +44,7 @@ class AtomApplication extends EventEmitter {
.update('|')
.update(process.arch)
.update('|')
.update(username)
.update(username || '')
.update('|')
.update(atomHomeUnique)
@@ -92,7 +93,6 @@ class AtomApplication extends EventEmitter {
this.quitting = false
this.getAllWindows = this.getAllWindows.bind(this)
this.getLastFocusedWindow = this.getLastFocusedWindow.bind(this)
this.resourcePath = options.resourcePath
this.devResourcePath = options.devResourcePath
this.version = options.version
@@ -107,20 +107,21 @@ class AtomApplication extends EventEmitter {
this.waitSessionsByWindow = new Map()
this.windowStack = new WindowStack()
this.config = new Config({enablePersistence: true})
this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)})
ConfigSchema.projectHome = {
type: 'string',
default: path.join(fs.getHomeDirectory(), 'github'),
description:
'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.'
}
this.config.initialize({
configDirPath: process.env.ATOM_HOME,
resourcePath: this.resourcePath,
projectHomeSchema: ConfigSchema.projectHome
this.initializeAtomHome(process.env.ATOM_HOME)
const configFilePath = fs.existsSync(path.join(process.env.ATOM_HOME, 'config.json'))
? path.join(process.env.ATOM_HOME, 'config.json')
: path.join(process.env.ATOM_HOME, 'config.cson')
this.configFile = ConfigFile.at(configFilePath)
this.config = new Config({
saveCallback: settings => {
if (!this.quitting) {
return this.configFile.update(settings)
}
}
})
this.config.load()
this.config.setSchema(null, {type: 'object', properties: _.clone(ConfigSchema)})
this.fileRecoveryService = new FileRecoveryService(path.join(process.env.ATOM_HOME, 'recovery'))
this.storageFolder = new StorageFolder(process.env.ATOM_HOME)
@@ -138,7 +139,7 @@ class AtomApplication extends EventEmitter {
// 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) {
async initialize (options) {
global.atomApplication = this
// DEPRECATED: This can be removed at some point (added in 1.13)
@@ -148,16 +149,15 @@ class AtomApplication extends EventEmitter {
this.config.set('core.titleBar', 'custom')
}
this.config.onDidChange('core.titleBar', this.promptForRestart.bind(this))
process.nextTick(() => this.autoUpdateManager.initialize())
this.applicationMenu = new ApplicationMenu(this.version, this.autoUpdateManager)
this.atomProtocolHandler = new AtomProtocolHandler(this.resourcePath, this.safeMode)
this.listenForArgumentsFromNewProcess()
this.setupDockMenu()
return this.launch(options)
const result = await this.launch(options)
this.autoUpdateManager.initialize()
return result
}
async destroy () {
@@ -169,18 +169,39 @@ class AtomApplication extends EventEmitter {
this.disposable.dispose()
}
launch (options) {
async launch (options) {
if (!this.configFilePromise) {
this.configFilePromise = this.configFile.watch()
this.disposable.add(await this.configFilePromise)
this.config.onDidChange('core.titleBar', () => this.promptForRestart())
this.config.onDidChange('core.colorProfile', () => this.promptForRestart())
}
const optionsForWindowsToOpen = []
let shouldReopenPreviousWindows = false
if (options.test || options.benchmark || options.benchmarkTest) {
return this.openWithOptions(options)
optionsForWindowsToOpen.push(options)
} else if ((options.pathsToOpen && options.pathsToOpen.length > 0) ||
(options.urlsToOpen && options.urlsToOpen.length > 0)) {
if (this.config.get('core.restorePreviousWindowsOnStart') === 'always') {
this.loadState(_.deepClone(options))
}
return this.openWithOptions(options)
optionsForWindowsToOpen.push(options)
shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') === 'always'
} else {
return this.loadState(options) || this.openPath(options)
shouldReopenPreviousWindows = this.config.get('core.restorePreviousWindowsOnStart') !== 'no'
}
if (shouldReopenPreviousWindows) {
for (const previousOptions of await this.loadPreviousWindowOptions()) {
optionsForWindowsToOpen.push(Object.assign({}, options, previousOptions))
}
}
if (optionsForWindowsToOpen.length === 0) {
optionsForWindowsToOpen.push(options)
}
return optionsForWindowsToOpen.map(options => this.openWithOptions(options))
}
openWithOptions (options) {
@@ -271,7 +292,7 @@ class AtomApplication extends EventEmitter {
return
}
}
if (!window.isSpec) this.saveState(true)
if (!window.isSpec) this.saveCurrentWindowOptions(true)
}
// Public: Adds the {AtomWindow} to the global window list.
@@ -285,7 +306,7 @@ class AtomApplication extends EventEmitter {
if (!window.isSpec) {
const focusHandler = () => this.windowStack.touch(window)
const blurHandler = () => this.saveState(false)
const blurHandler = () => this.saveCurrentWindowOptions(false)
window.browserWindow.on('focus', focusHandler)
window.browserWindow.on('blur', blurHandler)
window.browserWindow.once('closed', () => {
@@ -397,6 +418,18 @@ class AtomApplication extends EventEmitter {
this.openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
this.openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md'))
this.configFile.onDidChange(settings => {
for (let window of this.getAllWindows()) {
window.didChangeUserSettings(settings)
}
this.config.resetUserSettings(settings)
})
this.configFile.onDidError(message => {
const window = this.focusedWindow() || this.getLastFocusedWindow()
if (window) window.didFailToReadUserSettings(message)
})
this.disposable.add(ipcHelpers.on(app, 'before-quit', async event => {
let resolveBeforeQuitPromise
this.lastBeforeQuitPromise = new Promise(resolve => { resolveBeforeQuitPromise = resolve })
@@ -406,7 +439,11 @@ class AtomApplication extends EventEmitter {
event.preventDefault()
const windowUnloadPromises = this.getAllWindows().map(window => window.prepareToUnload())
const windowUnloadedResults = await Promise.all(windowUnloadPromises)
if (windowUnloadedResults.every(Boolean)) app.quit()
if (windowUnloadedResults.every(Boolean)) {
app.quit()
} else {
this.quitting = false
}
}
resolveBeforeQuitPromise()
@@ -474,12 +511,12 @@ class AtomApplication extends EventEmitter {
if (this.applicationMenu) this.applicationMenu.update(window, template, menu)
}))
this.disposable.add(ipcHelpers.on(ipcMain, 'run-package-specs', (event, packageSpecPath) => {
this.runTests({
this.disposable.add(ipcHelpers.on(ipcMain, 'run-package-specs', (event, packageSpecPath, options = {}) => {
this.runTests(Object.assign({
resourcePath: this.devResourcePath,
pathsToOpen: [packageSpecPath],
headless: false
})
}, options))
}))
this.disposable.add(ipcHelpers.on(ipcMain, 'run-benchmarks', (event, benchmarksPath) => {
@@ -530,6 +567,12 @@ class AtomApplication extends EventEmitter {
window.setPosition(x, y)
}))
this.disposable.add(ipcHelpers.respondTo('set-user-settings', (window, settings, filePath) => {
if (!this.quitting) {
ConfigFile.at(filePath || this.configFilePath).update(JSON.parse(settings))
}
}))
this.disposable.add(ipcHelpers.respondTo('center-window', window => window.center()))
this.disposable.add(ipcHelpers.respondTo('focus-window', window => window.focus()))
this.disposable.add(ipcHelpers.respondTo('show-window', window => window.show()))
@@ -568,18 +611,16 @@ class AtomApplication extends EventEmitter {
event.returnValue = this.autoUpdateManager.getErrorMessage()
}))
this.disposable.add(ipcHelpers.on(ipcMain, 'will-save-path', (event, path) => {
this.fileRecoveryService.willSavePath(this.atomWindowForEvent(event), path)
event.returnValue = true
}))
this.disposable.add(ipcHelpers.respondTo('will-save-path', (window, path) =>
this.fileRecoveryService.willSavePath(window, path)
))
this.disposable.add(ipcHelpers.on(ipcMain, 'did-save-path', (event, path) => {
this.fileRecoveryService.didSavePath(this.atomWindowForEvent(event), path)
event.returnValue = true
}))
this.disposable.add(ipcHelpers.respondTo('did-save-path', (window, path) =>
this.fileRecoveryService.didSavePath(window, path)
))
this.disposable.add(ipcHelpers.on(ipcMain, 'did-change-paths', () =>
this.saveState(false)
this.saveCurrentWindowOptions(false)
))
this.disposable.add(this.disableZoomOnDisplayChange())
@@ -593,6 +634,13 @@ class AtomApplication extends EventEmitter {
}
}
initializeAtomHome (configDirPath) {
if (!fs.existsSync(configDirPath)) {
const templateConfigDirPath = fs.resolve(this.resourcePath, 'dot-atom')
fs.copySync(templateConfigDirPath, configDirPath)
}
}
// Public: Executes the given command.
//
// If it isn't handled globally, delegate to the currently focused window.
@@ -800,13 +848,12 @@ class AtomApplication extends EventEmitter {
let existingWindow
if (!newWindow) {
existingWindow = this.windowForPaths(pathsToOpen, devMode)
const stats = pathsToOpen.map(pathToOpen => fs.statSyncNoException(pathToOpen))
if (!existingWindow) {
let lastWindow = window || this.getLastFocusedWindow()
if (lastWindow && lastWindow.devMode === devMode) {
if (addToLastWindow || (
stats.every(s => s.isFile && s.isFile()) ||
(stats.some(s => s.isDirectory && s.isDirectory()) && !lastWindow.hasProjectPath()))) {
locationsToOpen.every(({stat}) => stat && stat.isFile()) ||
(locationsToOpen.some(({stat}) => stat && stat.isDirectory()) && !lastWindow.hasProjectPath()))) {
existingWindow = lastWindow
}
}
@@ -839,6 +886,7 @@ class AtomApplication extends EventEmitter {
}
if (!resourcePath) resourcePath = this.resourcePath
if (!windowDimensions) windowDimensions = this.getDimensionsForNewWindow()
openedWindow = new AtomWindow(this, this.fileRecoveryService, {
initialPaths,
locationsToOpen,
@@ -910,7 +958,7 @@ class AtomApplication extends EventEmitter {
}
}
saveState (allowEmpty = false) {
async saveCurrentWindowOptions (allowEmpty = false) {
if (this.quitting) return
const states = []
@@ -920,28 +968,23 @@ class AtomApplication extends EventEmitter {
states.reverse()
if (states.length > 0 || allowEmpty) {
this.storageFolder.storeSync('application.json', states)
await this.storageFolder.store('application.json', states)
this.emit('application:did-save-state')
}
}
loadState (options) {
const states = this.storageFolder.load('application.json')
if (
['yes', 'always'].includes(this.config.get('core.restorePreviousWindowsOnStart')) &&
states && states.length > 0
) {
return states.map(state =>
this.openWithOptions(Object.assign(options, {
initialPaths: state.initialPaths,
pathsToOpen: state.initialPaths.filter(p => fs.isDirectorySync(p)),
urlsToOpen: [],
devMode: this.devMode,
safeMode: this.safeMode
}))
)
async loadPreviousWindowOptions () {
const states = await this.storageFolder.load('application.json')
if (states) {
return states.map(state => ({
initialPaths: state.initialPaths,
pathsToOpen: state.initialPaths.filter(p => fs.isDirectorySync(p)),
urlsToOpen: [],
devMode: this.devMode,
safeMode: this.safeMode
}))
} else {
return null
return []
}
}
@@ -1124,6 +1167,7 @@ class AtomApplication extends EventEmitter {
env
})
this.addWindow(window)
if (env) window.replaceEnvironment(env)
return window
}
@@ -1234,11 +1278,11 @@ class AtomApplication extends EventEmitter {
initialLine = initialColumn = null
}
if (url.parse(pathToOpen).protocol == null) {
pathToOpen = path.resolve(executedFrom, fs.normalize(pathToOpen))
}
const normalizedPath = path.normalize(path.resolve(executedFrom, fs.normalize(pathToOpen)))
const stat = fs.statSyncNoException(normalizedPath)
if (stat || !url.parse(pathToOpen).protocol) pathToOpen = normalizedPath
return {pathToOpen, initialLine, initialColumn}
return {pathToOpen, stat, initialLine, initialColumn}
}
// Opens a native dialog to prompt the user for a path.
@@ -1292,17 +1336,16 @@ class AtomApplication extends EventEmitter {
// File dialog defaults to project directory of currently active editor
if (path) openOptions.defaultPath = path
return dialog.showOpenDialog(parentWindow, openOptions, callback)
dialog.showOpenDialog(parentWindow, openOptions, callback)
}
promptForRestart () {
const chosen = dialog.showMessageBox(BrowserWindow.getFocusedWindow(), {
dialog.showMessageBox(BrowserWindow.getFocusedWindow(), {
type: 'warning',
title: 'Restart required',
message: 'You will need to restart Atom for this change to take effect.',
buttons: ['Restart Atom', 'Cancel']
})
if (chosen === 0) return this.restart()
}, response => { if (response === 0) this.restart() })
}
restart () {

View File

@@ -20,6 +20,7 @@ class AtomProtocolHandler {
if (!safeMode) {
this.loadPaths.push(path.join(process.env.ATOM_HOME, 'dev', 'packages'))
this.loadPaths.push(path.join(resourcePath, 'packages'))
}
this.loadPaths.push(path.join(process.env.ATOM_HOME, 'packages'))

View File

@@ -51,10 +51,18 @@ class AtomWindow extends EventEmitter {
// taskbar's icon. See https://github.com/atom/atom/issues/4811 for more.
if (process.platform === 'linux') options.icon = ICON_PATH
if (this.shouldAddCustomTitleBar()) options.titleBarStyle = 'hidden'
if (this.shouldAddCustomInsetTitleBar()) options.titleBarStyle = 'hidden-inset'
if (this.shouldAddCustomInsetTitleBar()) options.titleBarStyle = 'hiddenInset'
if (this.shouldHideTitleBar()) options.frame = false
this.browserWindow = new BrowserWindow(options)
Object.defineProperty(this.browserWindow, 'loadSettingsJSON', {
get: () => JSON.stringify(Object.assign({
userSettings: !this.isSpec
? this.atomApplication.configFile.get()
: null
}, this.loadSettings))
})
this.handleEvents()
this.loadSettings = Object.assign({}, settings)
@@ -67,14 +75,13 @@ class AtomWindow extends EventEmitter {
if (!this.loadSettings.initialPaths) {
this.loadSettings.initialPaths = []
for (const {pathToOpen} of locationsToOpen) {
for (const {pathToOpen, stat} of locationsToOpen) {
if (!pathToOpen) continue
const stat = fs.statSyncNoException(pathToOpen) || null
if (stat && stat.isDirectory()) {
this.loadSettings.initialPaths.push(pathToOpen)
} else {
const parentDirectory = path.dirname(pathToOpen)
if ((stat && stat.isFile()) || fs.existsSync(parentDirectory)) {
if (stat && stat.isFile() || fs.existsSync(parentDirectory)) {
this.loadSettings.initialPaths.push(parentDirectory)
} else {
this.loadSettings.initialPaths.push(pathToOpen)
@@ -96,8 +103,6 @@ class AtomWindow extends EventEmitter {
this.representedDirectoryPaths = this.loadSettings.initialPaths
if (!this.loadSettings.env) this.env = this.loadSettings.env
this.browserWindow.loadSettingsJSON = JSON.stringify(this.loadSettings)
this.browserWindow.on('window:loaded', () => {
this.disableZoom()
this.emit('window:loaded')
@@ -150,12 +155,13 @@ class AtomWindow extends EventEmitter {
containsPath (pathToCheck) {
if (!pathToCheck) return false
const stat = fs.statSyncNoException(pathToCheck)
if (stat && stat.isDirectory()) return false
return this.representedDirectoryPaths.some(projectPath =>
pathToCheck === projectPath || pathToCheck.startsWith(path.join(projectPath, path.sep))
)
let stat
return this.representedDirectoryPaths.some(projectPath => {
if (pathToCheck === projectPath) return true
if (!pathToCheck.startsWith(path.join(projectPath, path.sep))) return false
if (stat === undefined) stat = fs.statSyncNoException(pathToCheck)
return !stat || !stat.isDirectory()
})
}
handleEvents () {
@@ -163,7 +169,7 @@ class AtomWindow extends EventEmitter {
if (!this.atomApplication.quitting && !this.unloading) {
event.preventDefault()
this.unloading = true
this.atomApplication.saveState(false)
this.atomApplication.saveCurrentWindowOptions(false)
if (await this.prepareToUnload()) this.close()
}
})
@@ -176,34 +182,36 @@ class AtomWindow extends EventEmitter {
this.browserWindow.on('unresponsive', () => {
if (this.isSpec) return
const chosen = dialog.showMessageBox(this.browserWindow, {
dialog.showMessageBox(this.browserWindow, {
type: 'warning',
buttons: ['Force Close', 'Keep Waiting'],
cancelId: 1, // Canceling should be the least destructive action
message: 'Editor is not responding',
detail:
'The editor is not responding. Would you like to force close it or just keep waiting?'
})
if (chosen === 0) this.browserWindow.destroy()
}, response => { if (response === 0) this.browserWindow.destroy() })
})
this.browserWindow.webContents.on('crashed', () => {
this.browserWindow.webContents.on('crashed', async () => {
if (this.headless) {
console.log('Renderer process crashed, exiting')
this.atomApplication.exit(100)
return
}
this.fileRecoveryService.didCrashWindow(this)
const chosen = dialog.showMessageBox(this.browserWindow, {
await this.fileRecoveryService.didCrashWindow(this)
dialog.showMessageBox(this.browserWindow, {
type: 'warning',
buttons: ['Close Window', 'Reload', 'Keep It Open'],
cancelId: 2, // Canceling should be the least destructive action
message: 'The editor has crashed',
detail: 'Please report this issue to https://github.com/atom/atom'
}, response => {
switch (response) {
case 0: return this.browserWindow.destroy()
case 1: return this.browserWindow.reload()
}
})
switch (chosen) {
case 0: return this.browserWindow.destroy()
case 1: return this.browserWindow.reload()
}
})
this.browserWindow.webContents.on('will-navigate', (event, url) => {
@@ -246,6 +254,14 @@ class AtomWindow extends EventEmitter {
this.sendMessage('open-locations', locationsToOpen)
}
didChangeUserSettings (settings) {
this.sendMessage('did-change-user-settings', settings)
}
didFailToReadUserSettings (message) {
this.sendMessage('did-fail-to-read-user-settings', message)
}
replaceEnvironment (env) {
this.browserWindow.webContents.send('environment', env)
}
@@ -414,8 +430,7 @@ class AtomWindow extends EventEmitter {
this.representedDirectoryPaths = representedDirectoryPaths
this.representedDirectoryPaths.sort()
this.loadSettings.initialPaths = this.representedDirectoryPaths
this.browserWindow.loadSettingsJSON = JSON.stringify(this.loadSettings)
return this.atomApplication.saveState()
return this.atomApplication.saveCurrentWindowOptions()
}
didClosePathWithWaitSession (path) {

View File

@@ -94,7 +94,7 @@ class AutoUpdateManager
scheduleUpdateCheck: ->
# Only schedule update check periodically if running in release version and
# and there is no existing scheduled update check.
unless /\w{7}/.test(@version) or @checkForUpdatesIntervalID
unless /-dev/.test(@version) or @checkForUpdatesIntervalID
checkForUpdates = => @check(hidePopups: true)
fourHours = 1000 * 60 * 60 * 4
@checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours)
@@ -118,24 +118,26 @@ class AutoUpdateManager
onUpdateNotAvailable: =>
autoUpdater.removeListener 'error', @onUpdateError
{dialog} = require 'electron'
dialog.showMessageBox
dialog.showMessageBox {
type: 'info'
buttons: ['OK']
icon: @iconPath
message: 'No update available.'
title: 'No Update Available'
detail: "Version #{@version} is the latest version."
}, -> # noop callback to get async behavior
onUpdateError: (event, message) =>
autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable
{dialog} = require 'electron'
dialog.showMessageBox
dialog.showMessageBox {
type: 'warning'
buttons: ['OK']
icon: @iconPath
message: 'There was an error checking for updates.'
title: 'Update Error'
detail: message
}, -> # noop callback to get async behavior
getWindows: ->
global.atomApplication.getAllWindows()

View File

@@ -1,11 +1,11 @@
'use babel'
const {dialog} = require('electron')
const crypto = require('crypto')
const Path = require('path')
const fs = require('fs-plus')
const mkdirp = require('mkdirp')
import {dialog} from 'electron'
import crypto from 'crypto'
import Path from 'path'
import fs from 'fs-plus'
export default class FileRecoveryService {
module.exports =
class FileRecoveryService {
constructor (recoveryDirectory) {
this.recoveryDirectory = recoveryDirectory
this.recoveryFilesByFilePath = new Map()
@@ -13,15 +13,16 @@ export default class FileRecoveryService {
this.windowsByRecoveryFile = new Map()
}
willSavePath (window, path) {
if (!fs.existsSync(path)) return
async willSavePath (window, path) {
const stats = await tryStatFile(path)
if (!stats) return
const recoveryPath = Path.join(this.recoveryDirectory, RecoveryFile.fileNameForPath(path))
const recoveryFile =
this.recoveryFilesByFilePath.get(path) || new RecoveryFile(path, recoveryPath)
this.recoveryFilesByFilePath.get(path) || new RecoveryFile(path, stats.mode, recoveryPath)
try {
recoveryFile.retain()
await recoveryFile.retain()
} catch (err) {
console.log(`Couldn't retain ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`)
return
@@ -39,11 +40,11 @@ export default class FileRecoveryService {
this.recoveryFilesByFilePath.set(path, recoveryFile)
}
didSavePath (window, path) {
async didSavePath (window, path) {
const recoveryFile = this.recoveryFilesByFilePath.get(path)
if (recoveryFile != null) {
try {
recoveryFile.release()
await recoveryFile.release()
} catch (err) {
console.log(`Couldn't release ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`)
}
@@ -53,27 +54,31 @@ export default class FileRecoveryService {
}
}
didCrashWindow (window) {
async didCrashWindow (window) {
if (!this.recoveryFilesByWindow.has(window)) return
const promises = []
for (const recoveryFile of this.recoveryFilesByWindow.get(window)) {
try {
recoveryFile.recoverSync()
} catch (error) {
const message = 'A file that Atom was saving could be corrupted'
const detail =
`Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` +
`Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".`
console.log(detail)
dialog.showMessageBox(window.browserWindow, {type: 'info', buttons: ['OK'], message, detail})
} finally {
for (let window of this.windowsByRecoveryFile.get(recoveryFile)) {
this.recoveryFilesByWindow.get(window).delete(recoveryFile)
}
this.windowsByRecoveryFile.delete(recoveryFile)
this.recoveryFilesByFilePath.delete(recoveryFile.originalPath)
}
promises.push(recoveryFile.recover()
.catch(error => {
const message = 'A file that Atom was saving could be corrupted'
const detail =
`Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` +
`Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".`
console.log(detail)
dialog.showMessageBox(window, {type: 'info', buttons: ['OK'], message, detail}, () => { /* noop callback to get async behavior */ })
})
.then(() => {
for (let window of this.windowsByRecoveryFile.get(recoveryFile)) {
this.recoveryFilesByWindow.get(window).delete(recoveryFile)
}
this.windowsByRecoveryFile.delete(recoveryFile)
this.recoveryFilesByFilePath.delete(recoveryFile.originalPath)
})
)
}
await Promise.all(promises)
}
didCloseWindow (window) {
@@ -94,36 +99,67 @@ class RecoveryFile {
return `${basename}-${randomSuffix}${extension}`
}
constructor (originalPath, recoveryPath) {
constructor (originalPath, fileMode, recoveryPath) {
this.originalPath = originalPath
this.fileMode = fileMode
this.recoveryPath = recoveryPath
this.refCount = 0
}
storeSync () {
fs.copyFileSync(this.originalPath, this.recoveryPath)
async store () {
await copyFile(this.originalPath, this.recoveryPath, this.fileMode)
}
recoverSync () {
fs.copyFileSync(this.recoveryPath, this.originalPath)
this.removeSync()
async recover () {
await copyFile(this.recoveryPath, this.originalPath, this.fileMode)
await this.remove()
}
removeSync () {
fs.unlinkSync(this.recoveryPath)
async remove () {
return new Promise((resolve, reject) =>
fs.unlink(this.recoveryPath, error =>
error && error.code !== 'ENOENT' ? reject(error) : resolve()
)
)
}
retain () {
if (this.isReleased()) this.storeSync()
async retain () {
if (this.isReleased()) await this.store()
this.refCount++
}
release () {
async release () {
this.refCount--
if (this.isReleased()) this.removeSync()
if (this.isReleased()) await this.remove()
}
isReleased () {
return this.refCount === 0
}
}
async function tryStatFile (path) {
return new Promise((resolve, reject) =>
fs.stat(path, (error, result) =>
resolve(error == null && result)
)
)
}
async function copyFile (source, destination, mode) {
return new Promise((resolve, reject) => {
mkdirp(Path.dirname(destination), (error) => {
if (error) return reject(error)
const readStream = fs.createReadStream(source)
readStream
.on('error', reject)
.once('open', () => {
const writeStream = fs.createWriteStream(destination, {mode})
writeStream
.on('error', reject)
.on('open', () => readStream.pipe(writeStream))
.once('close', () => resolve())
})
})
})
}

View File

@@ -4,37 +4,55 @@ if (typeof snapshotResult !== 'undefined') {
const startTime = Date.now()
const electron = require('electron')
const fs = require('fs')
const path = require('path')
const fs = require('fs-plus')
const CSON = require('season')
const yargs = require('yargs')
const electron = require('electron')
const args =
yargs(process.argv)
.alias('d', 'dev')
.alias('t', 'test')
.alias('r', 'resource-path')
.argv
function isAtomRepoPath (repoPath) {
let packageJsonPath = path.join(repoPath, 'package.json')
if (fs.statSyncNoException(packageJsonPath)) {
let packageJson = CSON.readFileSync(packageJsonPath)
return packageJson.name === 'atom'
}
return false
}
let resourcePath
let devResourcePath
if (args.resourcePath) {
resourcePath = args.resourcePath
devResourcePath = resourcePath
} else {
const stableResourcePath = path.dirname(path.dirname(__dirname))
const defaultRepositoryPath = path.join(electron.app.getPath('home'), 'github', 'atom')
if (process.env.ATOM_DEV_RESOURCE_PATH) {
devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH
} else if (isAtomRepoPath(process.cwd())) {
devResourcePath = process.cwd()
} else if (fs.statSyncNoException(defaultRepositoryPath)) {
devResourcePath = defaultRepositoryPath
} else {
devResourcePath = stableResourcePath
}
if (args.dev || args.test || args.benchmark || args.benchmarkTest) {
if (process.env.ATOM_DEV_RESOURCE_PATH) {
resourcePath = process.env.ATOM_DEV_RESOURCE_PATH
} else if (fs.statSyncNoException(defaultRepositoryPath)) {
resourcePath = defaultRepositoryPath
} else {
resourcePath = stableResourcePath
}
resourcePath = devResourcePath
} else {
resourcePath = stableResourcePath
}
}
const start = require(path.join(resourcePath, 'src', 'main-process', 'start'))
start(resourcePath, startTime)
start(resourcePath, devResourcePath, startTime)

View File

@@ -3,8 +3,6 @@
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(yargs.terminalWidth())
@@ -12,13 +10,18 @@ module.exports = function parseCommandLine (processArgs) {
options.usage(
dedent`Atom Editor v${version}
Usage: atom [options] [path ...]
Usage:
atom [options] [path ...]
atom file[:line[:column]]
One or more paths to files or folders may be specified. If there is an
existing Atom window that contains all of the given folders, the paths
will be opened in that window. Otherwise, they will be opened in a new
window.
A file may be opened at the desired line (and optionally column) by
appending the numbers right after the file name, e.g. \`atom file:5:8\`.
Paths that start with \`atom://\` will be interpreted as URLs.
Environment Variables:
@@ -44,7 +47,7 @@ module.exports = function parseCommandLine (processArgs) {
'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.'
)
options.boolean('benchmark').describe('benchmark', 'Open a new window that runs the specified benchmarks.')
options.boolean('benchmark-test').describe('benchmark--test', 'Run a faster version of the benchmarks in headless mode.')
options.boolean('benchmark-test').describe('benchmark-test', 'Run a faster version of the benchmarks in headless mode.')
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(
@@ -114,8 +117,6 @@ module.exports = function parseCommandLine (processArgs) {
let pathsToOpen = []
let urlsToOpen = []
let devMode = args['dev']
let devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH || path.join(app.getPath('home'), 'github', 'atom')
let resourcePath = null
for (const path of args._) {
if (path.startsWith('atom://')) {
@@ -125,21 +126,8 @@ module.exports = function parseCommandLine (processArgs) {
}
}
if (args['resource-path']) {
if (args.resourcePath || test) {
devMode = true
devResourcePath = args['resource-path']
}
if (test) {
devMode = true
}
if (devMode) {
resourcePath = devResourcePath
}
if (!fs.statSyncNoException(resourcePath)) {
resourcePath = path.dirname(path.dirname(__dirname))
}
if (args['path-environment']) {
@@ -148,12 +136,7 @@ module.exports = function parseCommandLine (processArgs) {
process.env.PATH = args['path-environment']
}
resourcePath = normalizeDriveLetterName(resourcePath)
devResourcePath = normalizeDriveLetterName(devResourcePath)
return {
resourcePath,
devResourcePath,
pathsToOpen,
urlsToOpen,
executedFrom,
@@ -176,11 +159,3 @@ module.exports = function parseCommandLine (processArgs) {
env: process.env
}
}
function normalizeDriveLetterName (filePath) {
if (process.platform === 'win32') {
return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':')
} else {
return filePath
}
}

View File

@@ -5,8 +5,11 @@ const temp = require('temp').track()
const parseCommandLine = require('./parse-command-line')
const startCrashReporter = require('../crash-reporter-start')
const atomPaths = require('../atom-paths')
const fs = require('fs')
const CSON = require('season')
const Config = require('../config')
module.exports = function start (resourcePath, startTime) {
module.exports = function start (resourcePath, devResourcePath, startTime) {
global.shellStartTime = startTime
process.on('uncaughtException', function (error = {}) {
@@ -19,16 +22,35 @@ module.exports = function start (resourcePath, startTime) {
}
})
process.on('unhandledRejection', function (error = {}) {
if (error.message != null) {
console.log(error.message)
}
if (error.stack != null) {
console.log(error.stack)
}
})
const previousConsoleLog = console.log
console.log = nslog
app.commandLine.appendSwitch('enable-experimental-web-platform-features')
const args = parseCommandLine(process.argv.slice(1))
args.resourcePath = normalizeDriveLetterName(resourcePath)
args.devResourcePath = normalizeDriveLetterName(devResourcePath)
atomPaths.setAtomHome(app.getPath('home'))
atomPaths.setUserData(app)
setupCompileCache()
const config = getConfig()
const colorProfile = config.get('core.colorProfile')
if (colorProfile && colorProfile !== 'default') {
app.commandLine.appendSwitch('force-color-profile', colorProfile)
}
if (handleStartupEventWithSquirrel()) {
return
} else if (args.test && args.mainProcess) {
@@ -87,3 +109,29 @@ function setupCompileCache () {
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
CompileCache.install(process.resourcesPath, require)
}
function getConfig () {
const config = new Config()
let configFilePath
if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.json'))) {
configFilePath = path.join(process.env.ATOM_HOME, 'config.json')
} else if (fs.existsSync(path.join(process.env.ATOM_HOME, 'config.cson'))) {
configFilePath = path.join(process.env.ATOM_HOME, 'config.cson')
}
if (configFilePath) {
const configFileData = CSON.readFileSync(configFilePath)
config.resetUserSettings(configFileData)
}
return config
}
function normalizeDriveLetterName (filePath) {
if (process.platform === 'win32' && filePath) {
return filePath.replace(/^([a-z]):/, ([driveLetter]) => driveLetter.toUpperCase() + ':')
} else {
return filePath
}
}

View File

@@ -1,7 +1,5 @@
'use babel'
import Registry from 'winreg'
import Path from 'path'
const Registry = require('winreg')
const Path = require('path')
let exeName = Path.basename(process.execPath)
let appPath = `\"${process.execPath}\"`