mirror of
https://github.com/atom/atom.git
synced 2026-02-12 15:45:23 -05:00
Merge branch 'master' into wl-rm-safe-clipboard
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}\"`
|
||||
|
||||
Reference in New Issue
Block a user