diff --git a/.travis.yml b/.travis.yml index 7b6113aa0..9ac8cc72a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/Dockerfile b/Dockerfile index d792c30c5..22a101743 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 5b3ea42f4..d2ac45f05 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -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)? diff --git a/README.md b/README.md index 9f052ccff..a41ddeb64 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/apm/package.json b/apm/package.json index d4fcc851a..31162dcaf 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.10.0" + "atom-package-manager": "1.12.5" } } diff --git a/appveyor.yml b/appveyor.yml index 51e074a4c..316701aae 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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' diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index b248eda2e..5cf88175e 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -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) diff --git a/build/package.json b/build/package.json index 2f8d88d8a..533f49417 100644 --- a/build/package.json +++ b/build/package.json @@ -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", diff --git a/build/tasks/compile-packages-slug-task.coffee b/build/tasks/compile-packages-slug-task.coffee index 7317f158a..f6f297bec 100644 --- a/build/tasks/compile-packages-slug-task.coffee +++ b/build/tasks/compile-packages-slug-task.coffee @@ -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? diff --git a/build/tasks/publish-build-task.coffee b/build/tasks/publish-build-task.coffee index 2802f827d..ad31c0d8e 100644 --- a/build/tasks/publish-build-task.coffee +++ b/build/tasks/publish-build-task.coffee @@ -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' diff --git a/circle.yml b/circle.yml index a55900cca..95ebb024c 100644 --- a/circle.yml +++ b/circle.yml @@ -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 diff --git a/exports/atom.coffee b/exports/atom.coffee index 81d1726b8..bd8a1b62c 100644 --- a/exports/atom.coffee +++ b/exports/atom.coffee @@ -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 diff --git a/package.json b/package.json index f33c3fa0e..5609f48c8 100644 --- a/package.json +++ b/package.json @@ -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, diff --git a/script/bootstrap b/script/bootstrap index 3b9a35735..6fcf8fef0 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -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 diff --git a/script/cibuild b/script/cibuild index 627c2291a..6f16ac5cd 100755 --- a/script/cibuild +++ b/script/cibuild @@ -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 } } diff --git a/script/cibuild-atom-linux b/script/cibuild-atom-linux index 2c3395608..fe79c19fe 100755 --- a/script/cibuild-atom-linux +++ b/script/cibuild-atom-linux @@ -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 diff --git a/script/fingerprint-clean b/script/fingerprint-clean new file mode 100755 index 000000000..659395190 --- /dev/null +++ b/script/fingerprint-clean @@ -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) +} diff --git a/script/fingerprint-write b/script/fingerprint-write new file mode 100755 index 000000000..e5fad4d72 --- /dev/null +++ b/script/fingerprint-write @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./utils/fingerprint').writeFingerprint() diff --git a/script/utils/fingerprint.js b/script/utils/fingerprint.js index c419ba4fd..e1f6276d2 100644 --- a/script/utils/fingerprint.js +++ b/script/utils/fingerprint.js @@ -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') }, diff --git a/spec/squirrel-update-spec.coffee b/spec/squirrel-update-spec.coffee index a8a39eb54..2fe944e41 100644 --- a/spec/squirrel-update-spec.coffee +++ b/spec/squirrel-update-spec.coffee @@ -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') diff --git a/src/config.coffee b/src/config.coffee index 27ea1edef..1d314ada1 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -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) diff --git a/src/crash-reporter-start.coffee b/src/crash-reporter-start.coffee deleted file mode 100644 index 37c381473..000000000 --- a/src/crash-reporter-start.coffee +++ /dev/null @@ -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 - }) diff --git a/src/crash-reporter-start.js b/src/crash-reporter-start.js new file mode 100644 index 000000000..98b210d06 --- /dev/null +++ b/src/crash-reporter-start.js @@ -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 + }) +} diff --git a/src/main-process/atom-portable.coffee b/src/main-process/atom-portable.coffee deleted file mode 100644 index ae4bb67ec..000000000 --- a/src/main-process/atom-portable.coffee +++ /dev/null @@ -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 diff --git a/src/main-process/atom-portable.js b/src/main-process/atom-portable.js new file mode 100644 index 000000000..7d395c0e7 --- /dev/null +++ b/src/main-process/atom-portable.js @@ -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 + } +} diff --git a/src/main-process/main.coffee b/src/main-process/main.coffee deleted file mode 100644 index 5368710aa..000000000 --- a/src/main-process/main.coffee +++ /dev/null @@ -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() diff --git a/src/main-process/main.js b/src/main-process/main.js new file mode 100644 index 000000000..432f73832 --- /dev/null +++ b/src/main-process/main.js @@ -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() diff --git a/src/main-process/squirrel-update.coffee b/src/main-process/squirrel-update.coffee index a1bfc5359..acac81457 100644 --- a/src/main-process/squirrel-update.coffee +++ b/src/main-process/squirrel-update.coffee @@ -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() diff --git a/src/main-process/win-registry.coffee b/src/main-process/win-registry.coffee deleted file mode 100644 index f4b81b377..000000000 --- a/src/main-process/win-registry.coffee +++ /dev/null @@ -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) diff --git a/src/main-process/win-shell.coffee b/src/main-process/win-shell.coffee new file mode 100644 index 000000000..39e9f4ed0 --- /dev/null +++ b/src/main-process/win-shell.coffee @@ -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')) +) diff --git a/src/package.coffee b/src/package.coffee index b27e3ce0e..1bb8a939c 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -159,6 +159,7 @@ class Package # TODO: Remove. Settings view calls this method currently. activateConfig: -> + return if @configSchemaRegisteredOnLoad @requireMainModule() @registerConfigSchemaFromMainModule() diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index d91d3983e..a8ed9b6c1 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -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()