diff --git a/build-old/Gruntfile.coffee b/build-old/Gruntfile.coffee index 5cf88175e..451752829 100644 --- a/build-old/Gruntfile.coffee +++ b/build-old/Gruntfile.coffee @@ -320,7 +320,7 @@ getDefaultChannelAndReleaseBranch = (version) -> else channel = 'stable' - minorVersion = version.match(/^\d\.\d/)[0] + minorVersion = version.match(/^\d+\.\d+/)[0] releaseBranch = "#{minorVersion}-releases" [channel, releaseBranch] diff --git a/package.json b/package.json index 886216133..3372814c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.10.0-dev", + "version": "1.11.0-dev", "description": "A hackable text editor for the 21st Century.", "main": "./src/main-process/main.js", "repository": { @@ -23,6 +23,7 @@ "clear-cut": "^2.0.1", "coffee-script": "1.8.0", "color": "^0.7.3", + "dedent": "0.6.0", "devtron": "1.1.0", "event-kit": "^1.5.0", "find-parent-dir": "^0.3.0", @@ -108,7 +109,7 @@ "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", @@ -116,7 +117,7 @@ "symbols-view": "0.113.0", "tabs": "0.100.0", "timecop": "0.33.2", - "tree-view": "0.208.1", + "tree-view": "0.208.2", "update-package-dependencies": "0.10.0", "welcome": "0.34.0", "whitespace": "0.33.0", @@ -129,7 +130,7 @@ "language-gfm": "0.88.0", "language-git": "0.15.0", "language-go": "0.42.1", - "language-html": "0.45.0", + "language-html": "0.45.1", "language-hyperlink": "0.16.0", "language-java": "0.23.0", "language-javascript": "0.119.0", diff --git a/src/crash-reporter-start.coffee b/src/crash-reporter-start.js similarity index 66% rename from src/crash-reporter-start.coffee rename to src/crash-reporter-start.js index 47aae2469..98b210d06 100644 --- a/src/crash-reporter-start.coffee +++ b/src/crash-reporter-start.js @@ -1,6 +1,5 @@ -module.exports = (extra) -> - {crashReporter} = require 'electron' - +module.exports = function (extra) { + const {crashReporter} = require('electron') crashReporter.start({ productName: 'Atom', companyName: 'GitHub', @@ -8,3 +7,4 @@ module.exports = (extra) -> 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/text-editor-component.coffee b/src/text-editor-component.coffee index e6fc0dc15..3949f18e5 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -365,7 +365,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()