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

This commit is contained in:
Max Brunsfeld
2016-07-28 10:33:23 -07:00
32 changed files with 577 additions and 398 deletions

View File

@@ -13,14 +13,8 @@ compiler: clang
matrix:
include:
- os: linux
env: NODE_VERSION=0.12
- os: linux
env: NODE_VERSION=4
- os: osx
env: ATOM_SPECS_TASK=core NODE_VERSION=0.12
- os: osx
env: ATOM_SPECS_TASK=packages NODE_VERSION=0.12
sudo: false
@@ -28,16 +22,15 @@ install:
- git clone https://github.com/creationix/nvm.git /tmp/.nvm
- source /tmp/.nvm/nvm.sh
- nvm install $NODE_VERSION
- nvm use $NODE_VERSION
- nvm use --delete-prefix $NODE_VERSION
script: script/cibuild
cache:
directories:
- node_modules
- build/node_modules
- apm/node_modules
- $HOME/.atom/compile-cache
- build/node_modules
notifications:
email:
@@ -46,8 +39,11 @@ notifications:
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- build-essential
- git
- libgnome-keyring-dev
- fakeroot
- gcc-multilib

View File

@@ -2,7 +2,7 @@
# DESCRIPTION: Image to build Atom and create a .rpm file
# Base docker image
FROM fedora:21
FROM nodesource/fedora21:4.2.6
# Install dependencies
RUN yum install -y \
@@ -12,11 +12,9 @@ RUN yum install -y \
glibc-devel \
git-core \
libgnome-keyring-devel \
rpmdevtools \
nodejs \
npm
rpmdevtools
RUN npm install -g npm@1.4.28 --loglevel error
RUN npm install -g npm --loglevel error
ADD . /atom
WORKDIR /atom

View File

@@ -1,6 +1,6 @@
### Prerequisites
* [ ] Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#check-if-the-problem-shows-up-in-safe-mode)?
* [ ] Can you reproduce the problem in [safe mode](http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode)?
* [ ] Are you running the [latest version of Atom](http://flight-manual.atom.io/hacking-atom/sections/debugging/#update-to-the-latest-version)?
* [ ] Did you check the [debugging guide](http://flight-manual.atom.io/hacking-atom/sections/debugging/)?
* [ ] Did you check the [FAQs on Discuss](https://discuss.atom.io/c/faq)?

View File

@@ -1,6 +1,6 @@
![Atom](https://cloud.githubusercontent.com/assets/72919/2874231/3af1db48-d3dd-11e3-98dc-6066f8bc766f.png)
[![macOS Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1tkktwh654w07eim?svg=true)](https://ci.appveyor.com/project/Atom/atom)
[![macOS Build Status](https://circleci.com/gh/atom/atom.svg?style=svg)](https://circleci.com/gh/atom/atom) [![Linux Build Status](https://travis-ci.org/atom/atom.svg?branch=master)](https://travis-ci.org/atom/atom) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/1tkktwh654w07eim?svg=true)](https://ci.appveyor.com/project/Atom/atom)
[![Dependency Status](https://david-dm.org/atom/atom.svg)](https://david-dm.org/atom/atom)
[![Join the Atom Community on Slack](http://atom-slack.herokuapp.com/badge.svg)](http://atom-slack.herokuapp.com/)

View File

@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "1.10.0"
"atom-package-manager": "1.12.5"
}
}

View File

@@ -1,9 +1,5 @@
version: "{build}"
branches:
only:
- master
skip_tags: true
clone_folder: c:\projects\atom
clone_depth: 10
@@ -16,9 +12,10 @@ environment:
ATOM_DEV_RESOURCE_PATH: c:\projects\atom
ATOM_ACCESS_TOKEN:
secure: Q7vxmSq0bVCLTTRPzXw5ZhPTe7XYhWxX0tQV6neEkddTH6pZkOYNmSCG6VnMX2f+
ATOM_NOISY_BUILD: 1
matrix:
- NODE_VERSION: 0.10.35
- NODE_VERSION: 4.4.5
install:
- SET PATH=C:\Program Files\Atom\resources\cli;%PATH%
@@ -37,6 +34,9 @@ artifacts:
name: AtomSetup.msi
cache:
- '%APPVEYOR_BUILD_FOLDER%\build\node_modules'
- '%APPVEYOR_BUILD_FOLDER%\apm\node_modules'
- '%APPVEYOR_BUILD_FOLDER%\node_modules'
- '%USERPROFILE%\.atom\.apm'
- '%USERPROFILE%\.atom\.node-gyp\.atom'
- '%USERPROFILE%\.atom\.npm'

View File

@@ -14,6 +14,8 @@ _ = require 'underscore-plus'
packageJson = require '../package.json'
module.exports = (grunt) ->
process.env.ATOM_RESOURCE_PATH ?= path.resolve(__dirname, '..')
require('time-grunt')(grunt)
grunt.loadNpmTasks('grunt-babel')
@@ -296,7 +298,9 @@ module.exports = (grunt) ->
ciTasks.push('create-windows-installer:installer')
ciTasks.push('codesign:installer') if process.env.JANKY_SIGNTOOL
ciTasks.push('codesign:cleanup')
ciTasks.push('publish-build') unless process.env.CI
if process.env.ATOM_PUBLISH_REPO or not process.env.CI
ciTasks.push('publish-build')
grunt.registerTask('ci', ciTasks)

View File

@@ -32,7 +32,7 @@
"grunt-standard": "^2.0.0",
"legal-eagle": "~0.13.0",
"minidump": "~0.9",
"npm": "2.13.3",
"npm": "3.10.5",
"rcedit": "~0.3.0",
"request": "~2.27.0",
"rimraf": "~2.2.2",

View File

@@ -40,6 +40,8 @@ module.exports = (grunt) ->
continue if path.basename(moduleDirectory) is '.bin'
metadataPath = path.join(moduleDirectory, 'package.json')
continue unless fs.existsSync(metadataPath)
metadata = grunt.file.readJSON(metadataPath)
continue unless metadata?.engines?.atom?

View File

@@ -11,7 +11,7 @@ AWS = require 'aws-sdk'
grunt = null
token = process.env.ATOM_ACCESS_TOKEN
repo = process.env.ATOM_REPO ? 'atom/atom'
repo = process.env.ATOM_PUBLISH_REPO ? 'atom/atom'
defaultHeaders =
Authorization: "token #{token}"
'User-Agent': 'Atom'
@@ -34,7 +34,12 @@ module.exports = (gruntObject) ->
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
releaseBranch = grunt.config.get('atom.releaseBranch')
isPrerelease = grunt.config.get('atom.channel') is 'beta'
return unless releaseBranch?
unless releaseBranch?
grunt.log.ok("Skipping upload-assets to #{repo} repo because this is not a release branch")
return
grunt.log.ok("Starting upload-assets to #{repo} repo")
doneCallback = @async()
startTime = Date.now()
@@ -119,6 +124,7 @@ logError = (message, error, details) ->
zipAssets = (buildDir, assets, callback) ->
zip = (directory, sourcePath, assetName, callback) ->
grunt.log.ok("Zipping #{sourcePath} into #{assetName}")
if process.platform is 'win32'
sevenZipPath = if process.env.JANKY_SHA1? then "C:/psmodules/" else ""
zipCommand = "#{sevenZipPath}7z.exe a -r \"#{assetName}\" \"#{sourcePath}\""
@@ -136,6 +142,7 @@ zipAssets = (buildDir, assets, callback) ->
async.parallel(tasks, callback)
getAtomDraftRelease = (isPrerelease, branchName, callback) ->
grunt.log.ok("Obtaining GitHub draft release for #{branchName}")
atomRepo = new GitHub({repo: repo, token})
atomRepo.getReleases {prerelease: isPrerelease}, (error, releases=[]) ->
if error?
@@ -154,12 +161,14 @@ getAtomDraftRelease = (isPrerelease, branchName, callback) ->
logError('Fetching draft release assets failed', error, assets)
callback(error ? new Error(response.statusCode))
else
grunt.log.ok("Using GitHub draft release #{firstDraft.name}")
firstDraft.assets = assets
callback(null, firstDraft)
else
createAtomDraftRelease(isPrerelease, branchName, callback)
createAtomDraftRelease = (isPrerelease, branchName, callback) ->
grunt.log.ok("Creating GitHub draft release #{branchName}")
{version} = require('../../package.json')
options =
uri: "https://api.github.com/repos/#{repo}/releases"
@@ -185,6 +194,7 @@ createAtomDraftRelease = (isPrerelease, branchName, callback) ->
callback(null, body)
deleteRelease = (release) ->
grunt.log.ok("Deleting GitHub release #{release.tag_name}")
options =
uri: release.url
method: 'DELETE'
@@ -195,6 +205,7 @@ deleteRelease = (release) ->
logError('Deleting release failed', error, body)
deleteExistingAssets = (release, assetNames, callback) ->
grunt.log.ok("Deleting #{assetNames.join(',')} from GitHub release #{release.tag_name}")
[callback, assetNames] = [assetNames, callback] if not callback?
deleteAsset = (url, callback) ->
@@ -216,6 +227,7 @@ deleteExistingAssets = (release, assetNames, callback) ->
uploadAssets = (release, buildDir, assets, callback) ->
uploadToReleases = (release, assetName, assetPath, callback) ->
grunt.log.ok("Uploading #{assetName} to GitHub release #{release.tag_name}")
options =
uri: release.upload_url.replace(/\{.*$/, "?name=#{assetName}")
method: 'POST'
@@ -248,6 +260,7 @@ uploadAssets = (release, buildDir, assets, callback) ->
s3 = new AWS.S3 s3Info
key = "releases/#{release.tag_name}/#{assetName}"
grunt.log.ok("Uploading to S3 #{key}")
uploadParams =
Bucket: s3Bucket
ACL: 'public-read'

View File

@@ -1,4 +1,38 @@
machine:
environment:
XCODE_SCHEME: test
XCODE_WORKSPACE: test
XCODE_PROJECT: test
xcode:
version: 7.3
general:
branches:
only:
- io-circle-ci
artifacts:
- out/Atom.zip
dependencies:
pre:
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.3/install.sh | bash
- nvm install 4.4.7
- nvm use 4.4.7
- npm install -g npm
- script/fingerprint-clean
override:
- script/bootstrap
post:
- script/fingerprint-write
cache_directories:
- apm/node_modules
- build/node_modules
- node_modules
test:
override:
- script/grunt ci
post:
- zip -r out/Atom.zip out/Atom.app

View File

@@ -18,6 +18,10 @@ module.exports =
Disposable: Disposable
CompositeDisposable: CompositeDisposable
# Shell integration is required by both Squirrel and Settings-View
if process.platform is 'win32'
module.exports.WinShell = require '../src/main-process/win-shell'
# The following classes can't be used from a Task handler and should therefore
# only be exported when not running as a child node process
unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE

View File

@@ -61,6 +61,7 @@
"text-buffer": "9.2.2",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
"winreg": "^1.2.1",
"yargs": "^3.23.0"
},
"packageDependencies": {
@@ -70,8 +71,8 @@
"atom-light-ui": "0.44.0",
"base16-tomorrow-dark-theme": "1.1.0",
"base16-tomorrow-light-theme": "1.1.1",
"one-dark-ui": "1.4.0",
"one-light-ui": "1.4.0",
"one-dark-ui": "1.5.0",
"one-light-ui": "1.5.0",
"one-dark-syntax": "1.3.0",
"one-light-syntax": "1.3.0",
"solarized-dark-syntax": "1.0.2",
@@ -108,50 +109,50 @@
"notifications": "0.65.0",
"open-on-github": "1.2.0",
"package-generator": "1.0.0",
"settings-view": "0.240.1",
"settings-view": "0.241.0",
"snippets": "1.0.2",
"spell-check": "0.67.1",
"status-bar": "1.4.0",
"styleguide": "0.47.0",
"symbols-view": "0.113.0",
"tabs": "0.99.0",
"timecop": "0.33.1",
"tree-view": "0.208.1",
"tabs": "0.100.0",
"timecop": "0.33.2",
"tree-view": "0.208.2",
"update-package-dependencies": "0.10.0",
"welcome": "0.34.0",
"whitespace": "0.32.2",
"whitespace": "0.33.0",
"wrap-guide": "0.38.1",
"language-c": "0.52.1",
"language-clojure": "0.21.0",
"language-coffee-script": "0.47.1",
"language-coffee-script": "0.47.2",
"language-csharp": "0.12.1",
"language-css": "0.37.0",
"language-css": "0.37.1",
"language-gfm": "0.88.0",
"language-git": "0.14.0",
"language-go": "0.42.0",
"language-html": "0.44.1",
"language-git": "0.15.0",
"language-go": "0.42.1",
"language-html": "0.45.1",
"language-hyperlink": "0.16.0",
"language-java": "0.23.0",
"language-javascript": "0.119.0",
"language-json": "0.18.1",
"language-less": "0.29.4",
"language-json": "0.18.2",
"language-less": "0.29.5",
"language-make": "0.22.2",
"language-mustache": "0.13.0",
"language-objective-c": "0.15.1",
"language-perl": "0.35.0",
"language-php": "0.37.1",
"language-php": "0.37.2",
"language-property-list": "0.8.0",
"language-python": "0.45.0",
"language-ruby": "0.68.6",
"language-ruby": "0.69.0",
"language-ruby-on-rails": "0.25.0",
"language-sass": "0.55.0",
"language-sass": "0.56.0",
"language-shellscript": "0.22.4",
"language-source": "0.9.0",
"language-sql": "0.22.0",
"language-sql": "0.23.0",
"language-text": "0.7.1",
"language-todo": "0.28.0",
"language-toml": "0.18.0",
"language-xml": "0.34.8",
"language-xml": "0.34.9",
"language-yaml": "0.26.0"
},
"private": true,

View File

@@ -75,7 +75,7 @@ function bootstrap() {
var buildInstallCommand = initialNpmCommand + npmFlags + 'install';
var buildInstallOptions = {cwd: path.resolve(__dirname, '..', 'build')};
var apmInstallCommand = npmPath + npmFlags + '--target=0.10.40 ' + 'install';
var apmInstallCommand = npmPath + npmFlags + '--target=4.4.5 --global-style ' + 'install';
var apmInstallOptions = {cwd: apmInstallPath};
var moduleInstallCommand = apmPath + ' install' + apmFlags;
var dedupeApmCommand = apmPath + ' dedupe' + apmFlags;
@@ -97,7 +97,7 @@ function bootstrap() {
}
var moduleInstallOptions = {env: moduleInstallEnv};
if (process.argv.indexOf('--no-quiet') === -1) {
if (process.argv.indexOf('--no-quiet') === -1 || process.env.ATOM_NOISY_BUILD) {
buildInstallCommand += ' --loglevel error';
apmInstallCommand += ' --loglevel error';
moduleInstallCommand += ' --loglevel error';
@@ -122,11 +122,6 @@ function bootstrap() {
message: 'Installing apm...',
options: apmInstallOptions
},
{
command: apmPath + ' clean' + apmFlags,
message: 'Deleting old packages...',
options: moduleInstallOptions
},
{
command: moduleInstallCommand,
options: moduleInstallOptions

View File

@@ -40,10 +40,6 @@ function setEnvironmentVariables() {
process.env.CC = 'clang';
process.env.CXX = 'clang++';
process.env.npm_config_clang = '1';
} else if (process.platform === 'win32') {
process.env.BUILD_ATOM_RELEASES_S3_KEY = process.env.BUILD_ATOM_WIN_RELEASES_S3_KEY
process.env.BUILD_ATOM_RELEASES_S3_SECRET = process.env.BUILD_ATOM_WIN_RELEASES_S3_SECRET
process.env.BUILD_ATOM_RELEASES_S3_BUCKET = process.env.BUILD_ATOM_WIN_RELEASES_S3_BUCKET
}
}

View File

@@ -7,10 +7,11 @@ export BUILD_ATOM_RELEASES_S3_KEY=$BUILD_ATOM_LINUX_RELEASES_S3_KEY
export BUILD_ATOM_RELEASES_S3_SECRET=$BUILD_ATOM_LINUX_RELEASES_S3_SECRET
export BUILD_ATOM_RELEASES_S3_BUCKET=$BUILD_ATOM_LINUX_RELEASES_S3_BUCKET
if [ -d /usr/local/share/nodenv ]; then
export NODENV_ROOT=/usr/local/share/nodenv
export PATH=/usr/local/share/nodenv/bin:/usr/local/share/nodenv/shims:$PATH
export NODENV_VERSION="v0.10.21"
fi
rm -rf /tmp/.atom-nvm
git clone https://github.com/creationix/nvm.git /tmp/.atom-nvm
source /tmp/.atom-nvm/nvm.sh
nvm install 4.4.7
nvm use 4.4.7
npm install -g npm
script/cibuild

29
script/fingerprint-clean Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
var fingerprint = require('./utils/fingerprint')
var fs = require('fs')
var path = require('path')
if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules', '.atom-ci-fingerprint'))) {
return
}
if (fingerprint.fingerprintMatches()) {
console.log('node_modules matches current fingerprint ' + fingerprint.fingerprint() + ' - not removing')
return
}
var fsPlus
try {
fsPlus = require('fs-plus')
} catch (error) {
console.log(error.message)
return
}
try {
fsPlus.removeSync(path.resolve(__dirname, '..', 'node_modules'))
fsPlus.removeSync(path.resolve(__dirname, '..', 'apm', 'node_modules'))
} catch (error) {
console.error(error.message)
process.exit(1)
}

2
script/fingerprint-write Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('./utils/fingerprint').writeFingerprint()

View File

@@ -6,12 +6,14 @@ var fingerprintPath = path.resolve(__dirname, '..', '..', 'node_modules', '.atom
module.exports = {
fingerprint: function () {
var packageJson = fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'))
var atomPackageJson = fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'))
var apmPackageJson = fs.readFileSync(path.resolve(__dirname, '..', '..', 'apm', 'package.json'))
//Include the electron minor version in the fingerprint since that changing requires a re-install
var electronVersion = JSON.parse(packageJson).electronVersion.replace(/\.\d+$/, '')
var electronVersion = JSON.parse(atomPackageJson).electronVersion.replace(/\.\d+$/, '')
var apmVersion = JSON.parse(apmPackageJson).dependencies['atom-package-manager']
var body = electronVersion + process.platform + process.version
var body = electronVersion + apmVersion + process.platform + process.version
return crypto.createHash('sha1').update(body).digest('hex')
},

View File

@@ -5,7 +5,7 @@ temp = require 'temp'
SquirrelUpdate = require '../src/main-process/squirrel-update'
Spawner = require '../src/main-process/spawner'
WinPowerShell = require '../src/main-process/win-powershell'
WinRegistry = require '../src/main-process/win-registry'
WinShell = require '../src/main-process/win-shell'
# Run passed callback as Spawner.spawn() would do
invokeCallback = (callback) ->
@@ -26,12 +26,16 @@ describe "Windows Squirrel Update", ->
# do nothing on command, just run passed callback
invokeCallback callback
# Prevent any actual change to Windows registry
for own method of WinRegistry
# all WinRegistry APIs share the same signature
spyOn(WinRegistry, method).andCallFake (callback) ->
# do nothing on registry, just run passed callback
invokeCallback callback
# Prevent any actual change to Windows Shell
class FakeShellOption
isRegistered: (callback) -> callback true
register: (callback) -> callback null
deregister: (callback) -> callback null, true
update: (callback) -> callback null
WinShell.fileHandler = new FakeShellOption()
WinShell.fileContextMenu = new FakeShellOption()
WinShell.folderContextMenu = new FakeShellOption()
WinShell.folderBackgroundContextMenu = new FakeShellOption()
it "quits the app on all squirrel events", ->
app = quit: jasmine.createSpy('quit')

View File

@@ -786,9 +786,10 @@ class Config
rootSchema = properties[key]
Object.assign rootSchema, schema
@setDefaults(keyPath, @extractDefaultsFromSchema(schema))
@setScopedDefaultsFromSchema(keyPath, schema)
@resetSettingsForSchemaChange()
@transact =>
@setDefaults(keyPath, @extractDefaultsFromSchema(schema))
@setScopedDefaultsFromSchema(keyPath, schema)
@resetSettingsForSchemaChange()
load: ->
@initializeConfigDirectory()
@@ -958,9 +959,10 @@ class Config
setDefaults: (keyPath, defaults) ->
if defaults? and isPlainObject(defaults)
keys = splitKeyPath(keyPath)
for key, childValue of defaults
continue unless defaults.hasOwnProperty(key)
@setDefaults(keys.concat([key]).join('.'), childValue)
@transact =>
for key, childValue of defaults
continue unless defaults.hasOwnProperty(key)
@setDefaults(keys.concat([key]).join('.'), childValue)
else
try
defaults = @makeValueConformToSchema(keyPath, defaults)

View File

@@ -1,13 +0,0 @@
module.exports = (extra) ->
# Breakpad on Mac OS X must be running on UI and non-UI processes
# Crashpad on Windows and Linux should only be running on non-UI process
return if process.type is 'renderer' and process.platform isnt 'darwin'
{crashReporter} = require 'electron'
crashReporter.start({
productName: 'Atom',
companyName: 'GitHub',
submitURL: 'http://54.249.141.255:1127/post'
extra: extra
})

View File

@@ -0,0 +1,10 @@
module.exports = function (extra) {
const {crashReporter} = require('electron')
crashReporter.start({
productName: 'Atom',
companyName: 'GitHub',
submitURL: 'https://crashreporter.atom.io',
autoSubmit: false,
extra: extra
})
}

View File

@@ -1,35 +0,0 @@
fs = require 'fs-plus'
path = require 'path'
{ipcMain} = require 'electron'
module.exports =
class AtomPortable
@getPortableAtomHomePath: ->
execDirectoryPath = path.dirname(process.execPath)
path.join(execDirectoryPath, '..', '.atom')
@setPortable: (existingAtomHome) ->
fs.copySync(existingAtomHome, @getPortableAtomHomePath())
@isPortableInstall: (platform, environmentAtomHome, defaultHome) ->
return false unless platform in ['linux', 'win32']
return false if environmentAtomHome
return false if not fs.existsSync(@getPortableAtomHomePath())
# currently checking only that the directory exists and is writable,
# probably want to do some integrity checks on contents in future
@isPortableAtomHomePathWritable(defaultHome)
@isPortableAtomHomePathWritable: (defaultHome) ->
writable = false
message = ""
try
writePermissionTestFile = path.join(@getPortableAtomHomePath(), "write.test")
fs.writeFileSync(writePermissionTestFile, "test") if not fs.existsSync(writePermissionTestFile)
fs.removeSync(writePermissionTestFile)
writable = true
catch error
message = "Failed to use portable Atom home directory (#{@getPortableAtomHomePath()}). Using the default instead (#{defaultHome}). #{error.message}"
ipcMain.on 'check-portable-home-writable', (event) ->
event.sender.send 'check-portable-home-writable-response', {writable, message}
writable

View File

@@ -0,0 +1,58 @@
const fs = require('fs-plus')
const path = require('path')
const {ipcMain} = require('electron')
module.exports = class AtomPortable {
static getPortableAtomHomePath () {
const execDirectoryPath = path.dirname(process.execPath)
return path.join(execDirectoryPath, '..', '.atom')
}
static setPortable (existingAtomHome) {
fs.copySync(existingAtomHome, this.getPortableAtomHomePath())
}
static isPortableInstall (platform, environmentAtomHome, defaultHome) {
if (!['linux', 'win32'].includes(platform)) {
return false
}
if (environmentAtomHome) {
return false
}
if (!fs.existsSync(this.getPortableAtomHomePath())) {
return false
}
// Currently checking only that the directory exists and is writable,
// probably want to do some integrity checks on contents in future.
return this.isPortableAtomHomePathWritable(defaultHome)
}
static isPortableAtomHomePathWritable (defaultHome) {
let writable = false
let message = ''
try {
const writePermissionTestFile = path.join(this.getPortableAtomHomePath(), 'write.test')
if (!fs.existsSync(writePermissionTestFile)) {
fs.writeFileSync(writePermissionTestFile, 'test')
}
fs.removeSync(writePermissionTestFile)
writable = true
} catch (error) {
message = `Failed to use portable Atom home directory (${this.getPortableAtomHomePath()}). Using the default instead (${defaultHome}). ${error.message}.`
}
ipcMain.on('check-portable-home-writable', function (event) {
event.sender.send('check-portable-home-writable-response', {
writable: writable,
message: message
})
})
return writable
}
}

View File

@@ -1,198 +0,0 @@
global.shellStartTime = Date.now()
process.on 'uncaughtException', (error={}) ->
console.log(error.message) if error.message?
console.log(error.stack) if error.stack?
{app} = require 'electron'
fs = require 'fs-plus'
path = require 'path'
temp = require 'temp'
yargs = require 'yargs'
previousConsoleLog = console.log
startCrashReporter = require('../crash-reporter-start')
console.log = require 'nslog'
start = ->
args = parseCommandLine()
args.env = process.env
setupAtomHome(args)
setupCompileCache()
if handleStartupEventWithSquirrel()
return
else if args.test and args.mainProcess
console.log = previousConsoleLog
testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner'))
app.on 'ready', -> testRunner(args.pathsToOpen)
return
# NB: This prevents Win10 from showing dupe items in the taskbar
app.setAppUserModelId('com.squirrel.atom.atom')
addPathToOpen = (event, pathToOpen) ->
event.preventDefault()
args.pathsToOpen.push(pathToOpen)
addUrlToOpen = (event, urlToOpen) ->
event.preventDefault()
args.urlsToOpen.push(urlToOpen)
app.on 'open-file', addPathToOpen
app.on 'open-url', addUrlToOpen
app.on 'will-finish-launching', startCrashReporter
if args.userDataDir?
app.setPath('userData', args.userDataDir)
else if args.test
app.setPath('userData', temp.mkdirSync('atom-test-data'))
app.on 'ready', ->
app.removeListener 'open-file', addPathToOpen
app.removeListener 'open-url', addUrlToOpen
AtomApplication = require path.join(args.resourcePath, 'src', 'main-process', 'atom-application')
AtomApplication.open(args)
console.log("App load time: #{Date.now() - global.shellStartTime}ms") unless args.test
normalizeDriveLetterName = (filePath) ->
if process.platform is 'win32'
filePath.replace /^([a-z]):/, ([driveLetter]) -> driveLetter.toUpperCase() + ":"
else
filePath
handleStartupEventWithSquirrel = ->
return false unless process.platform is 'win32'
SquirrelUpdate = require './squirrel-update'
squirrelCommand = process.argv[1]
SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
setupAtomHome = ({setPortable}) ->
return if process.env.ATOM_HOME
atomHome = path.join(app.getPath('home'), '.atom')
AtomPortable = require './atom-portable'
if setPortable and not AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)
try
AtomPortable.setPortable(atomHome)
catch error
console.log("Failed copying portable directory '#{atomHome}' to '#{AtomPortable.getPortableAtomHomePath()}'")
console.log("#{error.message} #{error.stack}")
if AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)
atomHome = AtomPortable.getPortableAtomHomePath()
try
atomHome = fs.realpathSync(atomHome)
process.env.ATOM_HOME = atomHome
setupCompileCache = ->
compileCache = require('../compile-cache')
compileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
writeFullVersion = ->
process.stdout.write """
Atom : #{app.getVersion()}
Electron: #{process.versions.electron}
Chrome : #{process.versions.chrome}
Node : #{process.versions.node}
"""
parseCommandLine = ->
version = app.getVersion()
options = yargs(process.argv[1..]).wrap(100)
options.usage """
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.')
args = options.argv
if args.help
process.stdout.write(options.help())
process.exit(0)
if args.version
writeFullVersion()
process.exit(0)
addToLastWindow = args['add']
executedFrom = args['executed-from']?.toString() ? process.cwd()
devMode = args['dev']
safeMode = args['safe']
pathsToOpen = args._
test = args['test']
mainProcess = args['main-process']
timeout = args['timeout']
newWindow = args['new-window']
pidToKillWhenClosed = args['pid'] if args['wait']
logFile = args['log-file']
socketPath = args['socket-path']
userDataDir = args['user-data-dir']
profileStartup = args['profile-startup']
clearWindowState = args['clear-window-state']
urlsToOpen = []
devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getPath('home'), 'github', 'atom')
setPortable = args.portable
if args['resource-path']
devMode = true
resourcePath = args['resource-path']
devMode = true if test
resourcePath ?= devResourcePath if devMode
unless fs.statSyncNoException(resourcePath)
resourcePath = path.dirname(path.dirname(__dirname))
# 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'] if args['path-environment']
resourcePath = normalizeDriveLetterName(resourcePath)
devResourcePath = normalizeDriveLetterName(devResourcePath)
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow,
logFile, socketPath, userDataDir, profileStartup, timeout, setPortable,
clearWindowState, addToLastWindow, mainProcess}
start()

263
src/main-process/main.js Normal file
View File

@@ -0,0 +1,263 @@
global.shellStartTime = Date.now()
process.on('uncaughtException', function (error = {}) {
if (error.message != null) {
console.log(error.message)
}
if (error.stack != null) {
console.log(error.stack)
}
})
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 startCrashReporter = require('../crash-reporter-start')
const previousConsoleLog = console.log
console.log = require('nslog')
function start () {
const args = parseCommandLine()
args.env = process.env
setupAtomHome(args)
setupCompileCache()
if (handleStartupEventWithSquirrel()) {
return
} else if (args.test && args.mainProcess) {
console.log = previousConsoleLog
app.on('ready', function () {
const testRunner = require(path.join(args.resourcePath, 'spec/main-process/mocha-test-runner'))
testRunner(args.pathsToOpen)
})
return
}
// NB: This prevents Win10 from showing dupe items in the taskbar
app.setAppUserModelId('com.squirrel.atom.atom')
function addPathToOpen (event, pathToOpen) {
event.preventDefault()
args.pathsToOpen.push(pathToOpen)
}
function addUrlToOpen (event, urlToOpen) {
event.preventDefault()
args.urlsToOpen.push(urlToOpen)
}
app.on('open-file', addPathToOpen)
app.on('open-url', addUrlToOpen)
app.on('will-finish-launching', startCrashReporter)
if (args.userDataDir != null) {
app.setPath('userData', args.userDataDir)
} else if (args.test) {
app.setPath('userData', temp.mkdirSync('atom-test-data'))
}
app.on('ready', function () {
app.removeListener('open-file', addPathToOpen)
app.removeListener('open-url', addUrlToOpen)
const AtomApplication = require(path.join(args.resourcePath, 'src', 'main-process', 'atom-application'))
AtomApplication.open(args)
if (!args.test) {
console.log(`App load time: ${Date.now() - global.shellStartTime}ms`)
}
})
}
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
}
const SquirrelUpdate = require('./squirrel-update')
const squirrelCommand = process.argv[1]
return SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
}
function setupAtomHome ({setPortable}) {
if (process.env.ATOM_HOME) {
return
}
let atomHome = path.join(app.getPath('home'), '.atom')
const AtomPortable = require('./atom-portable')
if (setPortable && !AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) {
try {
AtomPortable.setPortable(atomHome)
} catch (error) {
console.log(`Failed copying portable directory '${atomHome}' to '${AtomPortable.getPortableAtomHomePath()}'`)
console.log(`${error.message} ${error.stack}`)
}
}
if (AtomPortable.isPortableInstall(process.platform, process.env.ATOM_HOME, atomHome)) {
atomHome = AtomPortable.getPortableAtomHomePath()
}
try {
atomHome = fs.realpathSync(atomHome)
} finally {
process.env.ATOM_HOME = atomHome
}
}
function setupCompileCache () {
const CompileCache = require('../compile-cache')
CompileCache.setAtomHomeDirectory(process.env.ATOM_HOME)
}
function writeFullVersion () {
process.stdout.write(
`Atom : ${app.getVersion()}\n` +
`Electron: ${process.versions.electron}\n` +
`Chrome : ${process.versions.chrome}\n` +
`Node : ${process.versions.node}\n`
)
}
function parseCommandLine () {
const options = yargs(process.argv.slice(1)).wrap(100)
const version = app.getVersion()
options.usage(
dedent`Atom Editor v${version}
Usage: atom [options] [path ...]
One or more paths to files or folders may be specified. If there is an
existing Atom window that contains all of the given folders, the paths
will be opened in that window. Otherwise, they will be opened in a new
window.
Environment Variables:
ATOM_DEV_RESOURCE_PATH The path from which Atom loads source code in dev mode.
Defaults to \`~/github/atom\`.
ATOM_HOME The root path for all configuration files and folders.
Defaults to \`~/.atom\`.`
)
// Deprecated 1.0 API preview flag
options.alias('1', 'one').boolean('1').describe('1', 'This option is no longer supported.')
options.boolean('include-deprecated-apis').describe('include-deprecated-apis', 'This option is not currently supported.')
options.alias('d', 'dev').boolean('d').describe('d', 'Run in development mode.')
options.alias('f', 'foreground').boolean('f').describe('f', 'Keep the main process in the foreground.')
options.alias('h', 'help').boolean('h').describe('h', 'Print this usage message.')
options.alias('l', 'log-file').string('l').describe('l', 'Log all output to file.')
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.')
options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.')
options.boolean('safe').describe(
'safe',
'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.'
)
options.boolean('portable').describe(
'portable',
'Set portable mode. Copies the ~/.atom folder to be a sibling of the installed Atom location if a .atom folder is not already there.'
)
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.alias('m', 'main-process').boolean('m').describe('m', 'Run the specified specs in the main process.')
options.string('timeout').describe(
'timeout',
'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).'
)
options.alias('v', 'version').boolean('v').describe('v', 'Print the version information.')
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
options.string('socket-path')
options.string('user-data-dir')
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
const args = options.argv
if (args.help) {
process.stdout.write(options.help())
process.exit(0)
}
if (args.version) {
writeFullVersion()
process.exit(0)
}
const addToLastWindow = args['add']
const safeMode = args['safe']
const pathsToOpen = args._
const test = args['test']
const mainProcess = args['main-process']
const timeout = args['timeout']
const newWindow = args['new-window']
let executedFrom = null
if (args['executed-from'] && args['executed-from'].toString()) {
executedFrom = args['executed-from'].toString()
} else {
executedFrom = process.cwd()
}
let pidToKillWhenClosed = null
if (args['wait']) {
pidToKillWhenClosed = args['pid']
}
const logFile = args['log-file']
const socketPath = args['socket-path']
const userDataDir = args['user-data-dir']
const profileStartup = args['profile-startup']
const clearWindowState = args['clear-window-state']
const urlsToOpen = []
const setPortable = args.portable
let devMode = args['dev']
let devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH || path.join(app.getPath('home'), 'github', 'atom')
let resourcePath = null
if (args['resource-path']) {
devMode = true
resourcePath = args['resource-path']
}
if (test) {
devMode = true
}
if (devMode && !resourcePath) {
resourcePath = devResourcePath
}
if (!fs.statSyncNoException(resourcePath)) {
resourcePath = path.dirname(path.dirname(__dirname))
}
if (args['path-environment']) {
// On Yosemite the $PATH is not inherited by the "open" command, so we have to
// explicitly pass it by command line, see http://git.io/YC8_Ew.
process.env.PATH = args['path-environment']
}
resourcePath = normalizeDriveLetterName(resourcePath)
devResourcePath = normalizeDriveLetterName(devResourcePath)
return {
resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, socketPath,
userDataDir, profileStartup, timeout, setPortable, clearWindowState,
addToLastWindow, mainProcess
}
}
start()

View File

@@ -1,7 +1,7 @@
fs = require 'fs-plus'
path = require 'path'
Spawner = require './spawner'
WinRegistry = require './win-registry'
WinShell = require './win-shell'
WinPowerShell = require './win-powershell'
appFolder = path.resolve(process.execPath, '..')
@@ -125,26 +125,36 @@ exports.restartAtom = (app) ->
app.once 'will-quit', -> Spawner.spawn(path.join(binFolder, 'atom.cmd'), args)
app.quit()
updateContextMenus = (callback) ->
WinShell.fileContextMenu.update ->
WinShell.folderContextMenu.update ->
WinShell.folderBackgroundContextMenu.update ->
callback()
# Handle squirrel events denoted by --squirrel-* command line arguments.
exports.handleStartupEvent = (app, squirrelCommand) ->
switch squirrelCommand
when '--squirrel-install'
createShortcuts ->
WinRegistry.installContextMenu ->
addCommandsToPath ->
app.quit()
addCommandsToPath ->
WinShell.fileHandler.register ->
updateContextMenus ->
app.quit()
true
when '--squirrel-updated'
updateShortcuts ->
WinRegistry.installContextMenu ->
addCommandsToPath ->
addCommandsToPath ->
updateContextMenus ->
app.quit()
true
when '--squirrel-uninstall'
removeShortcuts ->
WinRegistry.uninstallContextMenu ->
removeCommandsFromPath ->
app.quit()
removeCommandsFromPath ->
WinShell.fileHandler.deregister ->
WinShell.fileContextMenu.deregister ->
WinShell.folderContextMenu.deregister ->
WinShell.folderBackgroundContextMenu.deregister ->
app.quit()
true
when '--squirrel-obsolete'
app.quit()

View File

@@ -1,62 +0,0 @@
path = require 'path'
Spawner = require './spawner'
if process.env.SystemRoot
system32Path = path.join(process.env.SystemRoot, 'System32')
regPath = path.join(system32Path, 'reg.exe')
else
regPath = 'reg.exe'
# Registry keys used for context menu
fileKeyPath = 'HKCU\\Software\\Classes\\*\\shell\\Atom'
directoryKeyPath = 'HKCU\\Software\\Classes\\directory\\shell\\Atom'
backgroundKeyPath = 'HKCU\\Software\\Classes\\directory\\background\\shell\\Atom'
applicationsKeyPath = 'HKCU\\Software\\Classes\\Applications\\atom.exe'
# Spawn reg.exe and callback when it completes
spawnReg = (args, callback) ->
Spawner.spawn(regPath, args, callback)
# Install the Open with Atom explorer context menu items via the registry.
#
# * `callback` The {Function} to call after registry operation is done.
# It will be invoked with the same arguments provided by {Spawner.spawn}.
#
# Returns `undefined`.
exports.installContextMenu = (callback) ->
addToRegistry = (args, callback) ->
args.unshift('add')
args.push('/f')
spawnReg(args, callback)
installFileHandler = (callback) ->
args = ["#{applicationsKeyPath}\\shell\\open\\command", '/ve', '/d', "\"#{process.execPath}\" \"%1\""]
addToRegistry(args, callback)
installMenu = (keyPath, arg, callback) ->
args = [keyPath, '/ve', '/d', 'Open with Atom']
addToRegistry args, ->
args = [keyPath, '/v', 'Icon', '/d', "\"#{process.execPath}\""]
addToRegistry args, ->
args = ["#{keyPath}\\command", '/ve', '/d', "\"#{process.execPath}\" \"#{arg}\""]
addToRegistry(args, callback)
installMenu fileKeyPath, '%1', ->
installMenu directoryKeyPath, '%1', ->
installMenu backgroundKeyPath, '%V', ->
installFileHandler(callback)
# Uninstall the Open with Atom explorer context menu items via the registry.
#
# * `callback` The {Function} to call after registry operation is done.
# It will be invoked with the same arguments provided by {Spawner.spawn}.
#
# Returns `undefined`.
exports.uninstallContextMenu = (callback) ->
deleteFromRegistry = (keyPath, callback) ->
spawnReg(['delete', keyPath, '/f'], callback)
deleteFromRegistry fileKeyPath, ->
deleteFromRegistry directoryKeyPath, ->
deleteFromRegistry backgroundKeyPath, ->
deleteFromRegistry(applicationsKeyPath, callback)

View File

@@ -0,0 +1,57 @@
Registry = require 'winreg'
Path = require 'path'
exeName = Path.basename(process.execPath)
appPath = "\"#{process.execPath}\""
appName = exeName.replace('atom', 'Atom').replace('beta', 'Beta').replace('.exe', '')
class ShellOption
constructor: (key, parts) ->
@key = key
@parts = parts
isRegistered: (callback) =>
new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
.get @parts[0].name, (err, val) =>
callback(not err? and val.value is @parts[0].value)
register: (callback) =>
doneCount = @parts.length
for part in @parts
reg = new Registry({hive: 'HKCU', key: if part.key? then "#{@key}\\#{part.key}" else @key})
reg.create( -> reg.set part.name, Registry.REG_SZ, part.value, -> callback() if --doneCount is 0)
deregister: (callback) =>
@isRegistered (isRegistered) =>
if isRegistered
new Registry({hive: 'HKCU', key: @key}).destroy -> callback null, true
else
callback null, false
update: (callback) =>
new Registry({hive: 'HKCU', key: "#{@key}\\#{@parts[0].key}"})
.get @parts[0].name, (err, val) =>
if err? or not val.value.includes '\\' + exeName
callback(err)
else
@register callback
exports.appName = appName
exports.fileHandler = new ShellOption("\\Software\\Classes\\Applications\\#{exeName}",
[{key: 'shell\\open\\command', name: '', value: "#{appPath} \"%1\""}]
)
contextParts = [
{key: 'command', name: '', value: "#{appPath} \"%1\""},
{name: '', value: "Open with #{appName}"},
{name: 'Icon', value: "#{appPath}"}
]
exports.fileContextMenu = new ShellOption("\\Software\\Classes\\*\\shell\\#{appName}", contextParts)
exports.folderContextMenu = new ShellOption("\\Software\\Classes\\Directory\\shell\\#{appName}", contextParts)
exports.folderBackgroundContextMenu = new ShellOption("\\Software\\Classes\\Directory\\background\\shell\\#{appName}",
JSON.parse(JSON.stringify(contextParts).replace('%1', '%V'))
)

View File

@@ -159,6 +159,7 @@ class Package
# TODO: Remove. Settings view calls this method currently.
activateConfig: ->
return if @configSchemaRegisteredOnLoad
@requireMainModule()
@registerConfigSchemaFromMainModule()

View File

@@ -344,7 +344,12 @@ class TextEditorComponent
onTextInput: (event) =>
event.stopPropagation()
event.preventDefault()
# WARNING: If we call preventDefault on the input of a space character,
# then the browser interprets the spacebar keypress as a page-down command,
# causing spaces to scroll elements containing editors. This is impossible
# to test.
event.preventDefault() if event.data isnt ' '
return unless @isInputEnabled()