Merge branch 'master' into as-continuous-reflow

This commit is contained in:
Antonio Scandurra 2015-09-17 16:58:37 +02:00
commit 91bb8f518d
47 changed files with 741 additions and 358 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ npm-debug.log
debug.log
/tags
/atom-shell/
/electron/
docs/output
docs/includes
spec/fixtures/evil-files/

View File

@ -10,6 +10,8 @@ env:
- ATOM_ACCESS_TOKEN=da809a6077bb1b0aa7c5623f7b2d5f1fec2faae4
- NODE_VERSION=0.12
compiler: clang
matrix:
include:
- os: linux

View File

@ -234,6 +234,7 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
| `installer` | [search][search-atom-repo-label-installer] | [search][search-atom-org-label-installer] | Related to the Atom installers for different OSes. |
| `auto-updater` | [search][search-atom-repo-label-auto-updater] | [search][search-atom-org-label-auto-updater] | Related to the auto-updater for different OSes. |
| `deprecation-help` | [search][search-atom-repo-label-deprecation-help] | [search][search-atom-org-label-deprecation-help] | Issues for helping package authors remove usage of deprecated APIs in packages. |
| `electron` | [search][search-atom-repo-label-electron] | [search][search-atom-org-label-electron] | Issues that require changes to [Electron](https://electron.atom.io) to fix or implement. |
#### Core Team Project Management
@ -329,6 +330,8 @@ Please open an issue on `atom/atom` if you have suggestions for new labels, and
[search-atom-org-label-auto-updater]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aauto-updater
[search-atom-repo-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Adeprecation-help
[search-atom-org-label-deprecation-help]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Adeprecation-help
[search-atom-repo-label-electron]: https://github.com/issues?q=is%3Aissue+repo%3Aatom%2Fatom+is%3Aopen+label%3Aelectron
[search-atom-org-label-electron]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aelectron
[search-atom-repo-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Aon-deck
[search-atom-org-label-on-deck]: https://github.com/issues?q=is%3Aopen+is%3Aissue+user%3Aatom+label%3Aon-deck
[search-atom-repo-label-in-progress]: https://github.com/issues?q=is%3Aopen+is%3Aissue+repo%3Aatom%2Fatom+label%3Ain-progress

View File

@ -23,7 +23,7 @@ module.exports = (grunt) ->
grunt.loadNpmTasks('grunt-contrib-coffee')
grunt.loadNpmTasks('grunt-contrib-less')
grunt.loadNpmTasks('grunt-shell')
grunt.loadNpmTasks('grunt-download-atom-shell')
grunt.loadNpmTasks('grunt-download-electron')
grunt.loadNpmTasks('grunt-electron-installer')
grunt.loadNpmTasks('grunt-peg')
grunt.loadTasks('tasks')
@ -31,10 +31,6 @@ module.exports = (grunt) ->
# This allows all subsequent paths to the relative to the root of the repo
grunt.file.setBase(path.resolve('..'))
if not grunt.option('verbose')
grunt.log.writeln = (args...) -> grunt.log
grunt.log.write = (args...) -> grunt.log
[major, minor, patch] = packageJson.version.split('.')
tmpDir = os.tmpdir()
appName = if process.platform is 'darwin' then 'Atom.app' else 'Atom'
@ -43,7 +39,7 @@ module.exports = (grunt) ->
installDir = grunt.option('install-dir')
home = if process.platform is 'win32' then process.env.USERPROFILE else process.env.HOME
atomShellDownloadDir = path.join(home, '.atom', 'atom-shell')
electronDownloadDir = path.join(home, '.atom', 'electron')
symbolsDir = path.join(buildDir, 'Atom.breakpad.syms')
shellAppDir = path.join(buildDir, appName)
@ -225,11 +221,11 @@ module.exports = (grunt) ->
'static/**/*.less'
]
'download-atom-shell':
version: packageJson.atomShellVersion
outputDir: 'atom-shell'
downloadDir: atomShellDownloadDir
rebuild: true # rebuild native modules after atom-shell is updated
'download-electron':
version: packageJson.electronVersion
outputDir: 'electron'
downloadDir: electronDownloadDir
rebuild: true # rebuild native modules after electron is updated
token: process.env.ATOM_ACCESS_TOKEN
'create-windows-installer':
@ -254,7 +250,7 @@ module.exports = (grunt) ->
grunt.registerTask('lint', ['standard', 'coffeelint', 'csslint', 'lesslint'])
grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
ciTasks = ['output-disk-space', 'download-atom-shell', 'download-atom-shell-chromedriver', 'build']
ciTasks = ['output-disk-space', 'download-electron', 'download-electron-chromedriver', 'build']
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
ciTasks.push('set-version', 'check-licenses', 'lint', 'generate-asar')
ciTasks.push('mkdeb') if process.platform is 'linux'
@ -266,6 +262,7 @@ module.exports = (grunt) ->
ciTasks.push('publish-build') unless process.env.TRAVIS
grunt.registerTask('ci', ciTasks)
defaultTasks = ['download-atom-shell', 'download-atom-shell-chromedriver', 'build', 'set-version', 'generate-asar']
defaultTasks.push 'install' unless process.platform is 'linux'
defaultTasks = ['download-electron', 'download-electron-chromedriver', 'build', 'set-version', 'generate-asar']
unless process.platform is 'linux' or grunt.option('no-install')
defaultTasks.push 'install'
grunt.registerTask('default', defaultTasks)

View File

@ -6,12 +6,12 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"asar": "^0.5.0",
"asar": "^0.7.1",
"async": "~0.2.9",
"donna": "1.0.10",
"formidable": "~1.0.14",
"fs-plus": "2.x",
"github-releases": "~0.2.0",
"github-releases": "~0.3.0",
"glob": "^5.0.14",
"grunt": "~0.4.1",
"grunt-babel": "^5.0.1",
@ -20,9 +20,9 @@
"grunt-contrib-coffee": "~0.12.0",
"grunt-contrib-csslint": "~0.2.0",
"grunt-contrib-less": "~0.8.0",
"grunt-cson": "0.14.0",
"grunt-download-atom-shell": "~0.15.1",
"grunt-electron-installer": "1.0.0",
"grunt-cson": "0.15.0",
"grunt-download-electron": "^2.1.1",
"grunt-electron-installer": "1.0.3-0",
"grunt-lesslint": "0.17.0",
"grunt-peg": "~1.1.0",
"grunt-shell": "~0.3.1",

View File

@ -15,9 +15,17 @@ module.exports = (grunt) ->
mkdir path.dirname(buildDir)
if process.platform is 'darwin'
cp 'atom-shell/Atom.app', shellAppDir, filter: /default_app/
cp 'electron/Electron.app', shellAppDir, filter: /default_app/
fs.renameSync path.join(shellAppDir, 'Contents', 'MacOS', 'Electron'), path.join(shellAppDir, 'Contents', 'MacOS', 'Atom')
fs.renameSync path.join(shellAppDir, 'Contents', 'Frameworks', 'Electron Helper.app'), path.join(shellAppDir, 'Contents', 'Frameworks', 'Atom Helper.app')
fs.renameSync path.join(shellAppDir, 'Contents', 'Frameworks', 'Atom Helper.app', 'Contents', 'MacOS', 'Electron Helper'), path.join(shellAppDir, 'Contents', 'Frameworks', 'Atom Helper.app', 'Contents', 'MacOS', 'Atom Helper')
else
cp 'atom-shell', shellAppDir, filter: /default_app/
cp 'electron', shellAppDir, filter: /default_app/
if process.platform is 'win32'
fs.renameSync path.join(shellAppDir, 'electron.exe'), path.join(shellAppDir, 'atom.exe')
else
fs.renameSync path.join(shellAppDir, 'electron'), path.join(shellAppDir, 'atom')
mkdir appDir

View File

@ -12,6 +12,7 @@ module.exports = (grunt) ->
rm require('../src/less-compile-cache').cacheDir
rm path.join(tmpdir, 'atom-cached-atom-shells')
rm 'atom-shell'
rm 'electron'
grunt.registerTask 'clean', 'Delete all the build files', ->
homeDir = process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']

View File

@ -24,6 +24,13 @@ module.exports = (grunt) ->
return done("Unsupported arch #{process.arch}")
{name, version, description} = grunt.file.readJSON('package.json')
# RPM versions can't have dashes in them.
# * http://www.rpm.org/max-rpm/ch-rpm-file-format.html
# * https://github.com/mojombo/semver/issues/145
version = version.replace(/-beta$/, "~beta")
version = version.replace(/-dev$/, "~dev")
buildDir = grunt.config.get('atom.buildDir')
rpmDir = path.join(buildDir, 'rpm')

View File

@ -22,7 +22,7 @@ module.exports = (gruntObject) ->
grunt.registerTask 'publish-build', 'Publish the built app', ->
tasks = []
tasks.push('build-docs', 'prepare-docs') if process.platform is 'darwin'
tasks.push('upload-assets') if process.env.JANKY_SHA1 and process.env.JANKY_BRANCH is 'master'
tasks.push('upload-assets')
grunt.task.run(tasks)
grunt.registerTask 'prepare-docs', 'Move api.json to atom-api.json', ->
@ -31,6 +31,14 @@ module.exports = (gruntObject) ->
cp path.join(docsOutputDir, 'api.json'), path.join(buildDir, 'atom-api.json')
grunt.registerTask 'upload-assets', 'Upload the assets to a GitHub release', ->
switch process.env.JANKY_BRANCH
when 'stable'
isPrerelease = false
when 'beta'
isPrerelease = true
else
return
doneCallback = @async()
startTime = Date.now()
done = (args...) ->
@ -46,7 +54,7 @@ module.exports = (gruntObject) ->
zipAssets buildDir, assets, (error) ->
return done(error) if error?
getAtomDraftRelease (error, release) ->
getAtomDraftRelease isPrerelease, (error, release) ->
return done(error) if error?
assetNames = (asset.assetName for asset in assets)
deleteExistingAssets release, assetNames, (error) ->
@ -120,9 +128,9 @@ zipAssets = (buildDir, assets, callback) ->
tasks.push(zip.bind(this, buildDir, sourcePath, assetName))
async.parallel(tasks, callback)
getAtomDraftRelease = (callback) ->
getAtomDraftRelease = (isPrerelease, callback) ->
atomRepo = new GitHub({repo: 'atom/atom', token})
atomRepo.getReleases (error, releases=[]) ->
atomRepo.getReleases {prerelease: isPrerelease}, (error, releases=[]) ->
if error?
logError('Fetching atom/atom releases failed', error, releases)
callback(error)
@ -142,9 +150,9 @@ getAtomDraftRelease = (callback) ->
firstDraft.assets = assets
callback(null, firstDraft)
else
createAtomDraftRelease(callback)
createAtomDraftRelease(isPrerelease, callback)
createAtomDraftRelease = (callback) ->
createAtomDraftRelease = (isPrerelease, callback) ->
{version} = require('../../package.json')
options =
uri: 'https://api.github.com/repos/atom/atom/releases'
@ -152,6 +160,7 @@ createAtomDraftRelease = (callback) ->
headers: defaultHeaders
json:
tag_name: "v#{version}"
prerelease: isPrerelease
name: version
draft: true
body: """

View File

@ -5,7 +5,7 @@ module.exports = (grunt) ->
{spawn} = require('./task-helpers')(grunt)
getVersion = (callback) ->
onBuildMachine = process.env.JANKY_SHA1 and process.env.JANKY_BRANCH is 'master'
onBuildMachine = process.env.JANKY_SHA1 and process.env.JANKY_BRANCH in ['stable', 'beta']
inRepository = fs.existsSync(path.resolve(__dirname, '..', '..', '.git'))
{version} = require(path.join(grunt.config.get('atom.appDir'), 'package.json'))
if onBuildMachine or not inRepository

View File

@ -17,7 +17,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
### Ubuntu / Debian
* `sudo apt-get install build-essential git libgnome-keyring-dev fakeroot`
* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
* Instructions for [Node.js](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager#debian-and-ubuntu-based-linux-distributions).
* Make sure the command `node` is available after Node.js installation (some systems install it as `nodejs`).
* Use `which node` to check if it is available.
* Use `sudo update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10` to update it.
@ -25,7 +25,7 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
### Fedora / CentOS / RHEL
* `sudo dnf --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel rpmdevtools`
* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora).
* Instructions for [Node.js](https://github.com/nodejs/node-v0.x-archive/wiki/Installing-Node.js-via-package-manager#enterprise-linux-and-fedora).
### Arch

View File

@ -14,19 +14,19 @@
If it is installed elsewhere, you can create a symbolic link to the
directory containing the python.exe using:
`mklink /d %SystemDrive%\Python27 D:\elsewhere\Python27`
* [GitHub for Windows](http://windows.github.com/)
* [GitHub Desktop](http://desktop.github.com/)
### On Windows 8 or 10
* [Visual Studio Express 2013 or 2015 for Windows Desktop](http://www.visualstudio.com/en-us/downloads/download-visual-studio-vs#DownloadFamilies_2)
* [node.js](http://nodejs.org/download/) (0.10.x or 0.12.x) or [io.js](https://iojs.org) (1.x or 2.x)
* [Python](https://www.python.org/downloads/) v2.7.x (required by [node-gyp](https://github.com/TooTallNate/node-gyp))
* [GitHub for Windows](http://windows.github.com/)
* [GitHub Desktop](http://desktop.github.com/)
## Instructions
```bash
# Use the `Git Shell` program which was installed by GitHub for Windows.
# Also make sure that you are logged into GitHub for Windows.
# Use the `Git Shell` program which was installed by GitHub Desktop.
# Also make sure that you are logged into GitHub Desktop.
cd C:\
git clone https://github.com/atom/atom/
cd atom
@ -40,15 +40,14 @@ We will assume the git shell for these instructions.
* `--build-dir` - Build the application in this directory.
* `--verbose` - Verbose mode. A lot more information output.
## Why do I have to use GitHub for Windows?
## Why do I have to use GitHub Desktop?
You don't. You can use your existing Git! GitHub for Windows's Git Shell is just
easier to set up.
You don't. You can use your existing Git! GitHub Desktop's Git Shell is just easier to set up.
If you _prefer_ using your existing Git installation, make sure git's cmd directory is in your PATH env variable (e.g. `C:\Program Files (x86)\Git\cmd`) before you open your powershell or command window.
Note that you may have to open your command window as administrator. For powershell that doesn't seem to always be the case, though.
If none of this works, do install Github for Windows and use its Git shell. Makes life easier.
If none of this works, do install Github Desktop and use its Git shell. Makes life easier.
## Troubleshooting
@ -59,7 +58,6 @@ If none of this works, do install Github for Windows and use its Git shell. Make
* If you just installed node, you'll need to restart your computer before node is
available on your Path.
* `script/build` outputs only the Node and Python versions before returning
* Try moving the repository to `C:\atom`. Most likely, the path is too long.
@ -73,7 +71,7 @@ If none of this works, do install Github for Windows and use its Git shell. Make
* https://github.com/TooTallNate/node-gyp/issues/297
* https://code.google.com/p/gyp/issues/detail?id=393
* `script/build` stops at installing runas with 'Failed at the runas@0.5.4 install script.'
* `script/build` stops at installing runas with 'Failed at the runas@x.y.z install script.'
See the next item.

View File

@ -1,7 +1,7 @@
{
"name": "atom",
"productName": "Atom",
"version": "1.0.12",
"version": "1.0.12-dev",
"description": "A hackable text editor for the 21st Century.",
"main": "./src/browser/main.js",
"repository": {
@ -12,7 +12,7 @@
"url": "https://github.com/atom/atom/issues"
},
"license": "MIT",
"atomShellVersion": "0.22.3",
"electronVersion": "0.30.6",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^5.1.10",
@ -39,7 +39,7 @@
"mixto": "^1",
"normalize-package-data": "^2.0.0",
"nslog": "^2.0.0",
"oniguruma": "^4.1",
"oniguruma": "^4.2.2",
"pathwatcher": "^4.4.3",
"property-accessors": "^1.1.3",
"q": "^1.1.2",
@ -56,7 +56,7 @@
"space-pen": "3.8.2",
"stacktrace-parser": "0.1.1",
"temp": "0.8.1",
"text-buffer": "6.7.1",
"text-buffer": "6.7.2",
"theorist": "^1.0.2",
"typescript-simple": "1.0.0",
"underscore-plus": "^1.6.6",
@ -69,36 +69,36 @@
"atom-light-ui": "0.43.0",
"base16-tomorrow-dark-theme": "0.27.0",
"base16-tomorrow-light-theme": "0.9.0",
"one-dark-ui": "1.0.3",
"one-dark-ui": "1.0.4",
"one-dark-syntax": "1.1.0",
"one-light-syntax": "1.1.0",
"one-light-ui": "1.0.3",
"one-light-ui": "1.0.4",
"solarized-dark-syntax": "0.38.1",
"solarized-light-syntax": "0.22.1",
"about": "1.1.0",
"archive-view": "0.60.0",
"autocomplete-atom-api": "0.9.2",
"autocomplete-css": "0.10.1",
"autocomplete-css": "0.11.0",
"autocomplete-html": "0.7.2",
"autocomplete-plus": "2.19.0",
"autocomplete-plus": "2.19.1",
"autocomplete-snippets": "1.7.1",
"autoflow": "0.25.0",
"autosave": "0.22.0",
"background-tips": "0.26.0",
"bookmarks": "0.36.0",
"bookmarks": "0.37.0",
"bracket-matcher": "0.76.0",
"command-palette": "0.36.0",
"deprecation-cop": "0.54.0",
"dev-live-reload": "0.46.0",
"encoding-selector": "0.21.0",
"exception-reporting": "0.36.0",
"find-and-replace": "0.180.0",
"exception-reporting": "0.37.0",
"find-and-replace": "0.181.0",
"fuzzy-finder": "0.88.0",
"git-diff": "0.55.0",
"git-diff": "0.56.0",
"go-to-line": "0.30.0",
"grammar-selector": "0.47.0",
"image-view": "0.54.0",
"incompatible-packages": "0.24.1",
"incompatible-packages": "0.25.0",
"keybinding-resolver": "0.33.0",
"line-ending-selector": "0.0.5",
"link": "0.30.0",
@ -109,14 +109,14 @@
"package-generator": "0.40.0",
"release-notes": "0.53.0",
"settings-view": "0.216.0",
"snippets": "0.95.0",
"snippets": "0.98.0",
"spell-check": "0.59.0",
"status-bar": "0.79.0",
"styleguide": "0.44.0",
"symbols-view": "0.104.0",
"tabs": "0.84.0",
"timecop": "0.31.0",
"tree-view": "0.186.0",
"tree-view": "0.187.0",
"update-package-dependencies": "0.10.0",
"welcome": "0.30.0",
"whitespace": "0.31.0",
@ -124,7 +124,7 @@
"language-c": "0.47.1",
"language-clojure": "0.16.0",
"language-coffee-script": "0.41.0",
"language-csharp": "0.8.0",
"language-csharp": "0.10.0",
"language-css": "0.34.0",
"language-gfm": "0.81.0",
"language-git": "0.10.0",
@ -132,20 +132,20 @@
"language-html": "0.41.2",
"language-hyperlink": "0.14.0",
"language-java": "0.16.0",
"language-javascript": "0.93.0",
"language-javascript": "0.94.0",
"language-json": "0.16.0",
"language-less": "0.28.2",
"language-make": "0.17.0",
"language-mustache": "0.12.0",
"language-objective-c": "0.15.0",
"language-perl": "0.28.0",
"language-php": "0.29.0",
"language-perl": "0.29.0",
"language-php": "0.30.0",
"language-property-list": "0.8.0",
"language-python": "0.40.0",
"language-ruby": "0.57.0",
"language-ruby": "0.58.0",
"language-ruby-on-rails": "0.22.0",
"language-sass": "0.41.0",
"language-shellscript": "0.16.0",
"language-shellscript": "0.17.0",
"language-source": "0.9.0",
"language-sql": "0.17.0",
"language-text": "0.7.0",

View File

@ -62,6 +62,8 @@ function bootstrap() {
'temp'
];
process.env.ATOM_RESOURCE_PATH = path.resolve(__dirname, '..');
var buildInstallCommand = initialNpmCommand + npmFlags + 'install';
var buildInstallOptions = {cwd: path.resolve(__dirname, '..', 'build')};
var apmInstallCommand = npmPath + npmFlags + '--target=0.10.35 ' + 'install';

View File

@ -22,11 +22,16 @@ function loadEnvironmentVariables(filePath) {
}
function readEnvironmentVariables() {
if (process.platform === 'win32')
if (process.platform === 'win32') {
loadEnvironmentVariables(path.resolve('/jenkins/config/atomcredentials'));
else if (process.platform === 'darwin') {
} else if (process.platform === 'darwin') {
loadEnvironmentVariables('/var/lib/jenkins/config/atomcredentials');
loadEnvironmentVariables('/var/lib/jenkins/config/xcodekeychain');
} else if (process.platform === 'linux') {
// Use Clang for building native code, the GCC on Precise is too old.
process.env.CC = 'clang';
process.env.CXX = 'clang++';
process.env.npm_config_clang = '1';
}
}

View File

@ -26,6 +26,7 @@ var commands = [
[home, '.atom', '.npm'],
[home, '.atom', 'compile-cache'],
[home, '.atom', 'atom-shell'],
[home, '.atom', 'electron'],
[tmpdir, 'atom-build'],
[tmpdir, 'atom-cached-atom-shells'],
];

89
script/railcar Executable file
View File

@ -0,0 +1,89 @@
#!/usr/bin/env node
var fs = require('fs')
var exec = require('child_process').exec
var series = require('async').series
var semver = require('semver')
series([
section('Preparing to roll the railcars'),
checkCleanWorkingTree,
run('git fetch origin master:master beta:beta stable:stable'),
run('git fetch origin --tags'),
section('Updating stable branch'),
run('git checkout stable'),
run('git merge --ff-only origin/stable'),
run('git merge --ff-only origin/beta'),
bumpStableVersion,
section('Updating beta branch'),
run('git checkout beta'),
run('git merge --ff-only origin/beta'),
run('git merge --ff-only origin/master'),
run('git merge --strategy ours origin/stable'),
bumpBetaVersion,
section('Updating master branch'),
run('git checkout master'),
run('git merge --ff-only origin/master'),
run('git merge --strategy ours origin/beta'),
bumpDevVersion,
section('Pushing changes upstream'),
run('git push origin master:master beta:beta stable:stable'),
run('git push origin --tags')
], finish)
function checkCleanWorkingTree (next) {
run('git status --porcelain')(function (error, output) {
if (error) return next(error)
if (output.trim().length > 0) return next(new Error('Cannot run the railcars with a dirty working tree'))
next()
})
}
function bumpStableVersion (next) {
run('npm version patch')(next)
}
function bumpBetaVersion (next) {
var newVersion = semver.inc(getCurrentVersion(), 'prerelease', 'beta')
run('npm version ' + newVersion)(next)
}
function bumpDevVersion (next) {
var newVersion = semver.inc(getCurrentVersion(), 'preminor', 'dev')
series([
run('npm --no-git-tag-version version ' + newVersion),
run('git commit -am "' + newVersion + '"')
], next)
}
function finish (error) {
if (error) {
console.log('Error: ' + error.message)
process.exit(1)
}
console.log('OK, now just wait for all CI builds to pass on beta and stable')
}
function getCurrentVersion () {
return JSON.parse(fs.readFileSync(require.resolve('../package.json'))).version
}
function run (command) {
return function (next) {
console.log('>', command)
exec(command, next)
}
}
function section (message) {
return function (next) {
console.log()
console.log(message)
next()
}
}

View File

@ -0,0 +1,55 @@
DOMElementPool = require '../src/dom-element-pool'
describe "DOMElementPool", ->
domElementPool = null
beforeEach ->
domElementPool = new DOMElementPool
it "builds DOM nodes, recycling them when they are freed", ->
[div, span1, span2, span3, span4, span5] = elements = [
domElementPool.build("div")
domElementPool.build("span")
domElementPool.build("span")
domElementPool.build("span")
domElementPool.build("span")
domElementPool.build("span")
]
div.appendChild(span1)
span1.appendChild(span2)
div.appendChild(span3)
span3.appendChild(span4)
domElementPool.freeElementAndDescendants(div)
domElementPool.freeElementAndDescendants(span5)
expect(elements).toContain(domElementPool.build("div"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).toContain(domElementPool.build("span"))
expect(elements).not.toContain(domElementPool.build("div"))
expect(elements).not.toContain(domElementPool.build("span"))
it "forgets free nodes after being cleared", ->
span = domElementPool.build("span")
div = domElementPool.build("div")
domElementPool.freeElementAndDescendants(span)
domElementPool.freeElementAndDescendants(div)
domElementPool.clear()
expect(domElementPool.build("span")).not.toBe(span)
expect(domElementPool.build("div")).not.toBe(div)
it "throws an error when trying to free the same node twice", ->
div = domElementPool.build("div")
domElementPool.freeElementAndDescendants(div)
expect(-> domElementPool.freeElementAndDescendants(div)).toThrow()
it "throws an error when trying to free an invalid element", ->
expect(-> domElementPool.freeElementAndDescendants(null)).toThrow()
expect(-> domElementPool.freeElementAndDescendants(undefined)).toThrow()

View File

@ -1,5 +1,6 @@
Gutter = require '../src/gutter'
GutterContainerComponent = require '../src/gutter-container-component'
DOMElementPool = require '../src/dom-element-pool'
describe "GutterContainerComponent", ->
[gutterContainerComponent] = []
@ -22,9 +23,10 @@ describe "GutterContainerComponent", ->
mockTestState
beforeEach ->
domElementPool = new DOMElementPool
mockEditor = {}
mockMouseDown = ->
gutterContainerComponent = new GutterContainerComponent({editor: mockEditor, onMouseDown: mockMouseDown})
gutterContainerComponent = new GutterContainerComponent({editor: mockEditor, onMouseDown: mockMouseDown, domElementPool})
it "creates a DOM node with no child gutter nodes when it is initialized", ->
expect(gutterContainerComponent.getDomNode() instanceof HTMLElement).toBe true

View File

@ -9,7 +9,7 @@ webdriverio = require "../../../build/node_modules/webdriverio"
AtomPath = remote.process.argv[0]
AtomLauncherPath = path.join(__dirname, "..", "helpers", "atom-launcher.sh")
ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'atom-shell', 'chromedriver', 'chromedriver')
ChromedriverPath = path.resolve(__dirname, '..', '..', '..', 'electron', 'chromedriver', 'chromedriver')
SocketPath = path.join(temp.mkdirSync("socket-dir"), "atom-#{process.env.USER}.sock")
ChromedriverPort = 9515
ChromedriverURLBase = "/wd/hub"

View File

@ -7,6 +7,10 @@ describe "Package", ->
describe "when the package contains incompatible native modules", ->
beforeEach ->
spyOn(atom, 'inDevMode').andReturn(false)
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined
it "does not activate it", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
@ -17,14 +21,6 @@ describe "Package", ->
it "caches the incompatible native modules in local storage", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
cacheKey = null
cacheItem = null
spyOn(global.localStorage, 'setItem').andCallFake (key, item) ->
cacheKey = key
cacheItem = item
spyOn(global.localStorage, 'getItem').andCallFake (key) ->
return cacheItem if cacheKey is key
expect(new Package(packagePath).isCompatible()).toBe false
expect(global.localStorage.getItem.callCount).toBe 1
@ -34,6 +30,71 @@ describe "Package", ->
expect(global.localStorage.getItem.callCount).toBe 2
expect(global.localStorage.setItem.callCount).toBe 1
describe "::rebuild()", ->
beforeEach ->
spyOn(atom, 'inDevMode').andReturn(false)
items = {}
spyOn(global.localStorage, 'setItem').andCallFake (key, item) -> items[key] = item; undefined
spyOn(global.localStorage, 'getItem').andCallFake (key) -> items[key] ? null
spyOn(global.localStorage, 'removeItem').andCallFake (key) -> delete items[key]; undefined
it "returns a promise resolving to the results of `apm rebuild`", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index')
pack = new Package(packagePath)
rebuildCallbacks = []
spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback))
promise = pack.rebuild()
rebuildCallbacks[0]({code: 0, stdout: 'stdout output', stderr: 'stderr output'})
waitsFor (done) ->
promise.then (result) ->
expect(result).toEqual {code: 0, stdout: 'stdout output', stderr: 'stderr output'}
done()
it "persists build failures in local storage", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-index')
pack = new Package(packagePath)
expect(pack.isCompatible()).toBe true
expect(pack.getBuildFailureOutput()).toBeNull()
rebuildCallbacks = []
spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback))
pack.rebuild()
rebuildCallbacks[0]({code: 13, stderr: 'It is broken'})
expect(pack.getBuildFailureOutput()).toBe 'It is broken'
expect(pack.getIncompatibleNativeModules()).toEqual []
expect(pack.isCompatible()).toBe false
# A different package instance has the same failure output (simulates reload)
pack2 = new Package(packagePath)
expect(pack2.getBuildFailureOutput()).toBe 'It is broken'
expect(pack2.isCompatible()).toBe false
# Clears the build failure after a successful build
pack.rebuild()
rebuildCallbacks[1]({code: 0, stdout: 'It worked'})
expect(pack.getBuildFailureOutput()).toBeNull()
expect(pack2.getBuildFailureOutput()).toBeNull()
it "sets cached incompatible modules to an empty array when the rebuild completes (there may be a build error, but rebuilding *deletes* native modules)", ->
packagePath = atom.project.getDirectories()[0]?.resolve('packages/package-with-incompatible-native-module')
pack = new Package(packagePath)
expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0)
rebuildCallbacks = []
spyOn(pack, 'runRebuildProcess').andCallFake ((callback) -> rebuildCallbacks.push(callback))
pack.rebuild()
expect(pack.getIncompatibleNativeModules().length).toBeGreaterThan(0)
rebuildCallbacks[0]({code: 0, stdout: 'It worked'})
expect(pack.getIncompatibleNativeModules().length).toBe(0)
describe "theme", ->
theme = null

View File

@ -108,7 +108,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style.zIndex).toBe("2")
expect(tilesNodes[1].style.zIndex).toBe("1")
@ -118,7 +118,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style.zIndex).toBe("3")
expect(tilesNodes[1].style.zIndex).toBe("2")
@ -130,7 +130,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes.length).toBe(3)
@ -158,7 +158,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(component.lineNodeForScreenRow(2)).toBeUndefined()
expect(tilesNodes.length).toBe(3)
@ -187,7 +187,7 @@ describe "TextEditorComponent", ->
editor.getBuffer().deleteRows(0, 1)
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
@ -202,7 +202,7 @@ describe "TextEditorComponent", ->
editor.getBuffer().insert([0, 0], '\n\n')
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
expectTileContainsRow(tilesNodes[0], 0, top: 0 * lineHeightInPixels)
@ -294,7 +294,7 @@ describe "TextEditorComponent", ->
editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth()
for lineNode in lineNodes
expect(lineNode.style.width).toBe editorFullWidth + 'px'
expect(lineNode.getBoundingClientRect().width).toBe(editorFullWidth)
componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px'
component.measureDimensions()
@ -302,7 +302,7 @@ describe "TextEditorComponent", ->
scrollViewWidth = scrollViewNode.offsetWidth
for lineNode in lineNodes
expect(lineNode.style.width).toBe scrollViewWidth + 'px'
expect(lineNode.getBoundingClientRect().width).toBe(scrollViewWidth)
it "renders an nbsp on empty lines when no line-ending character is defined", ->
atom.config.set("editor.showInvisibles", false)
@ -313,7 +313,7 @@ describe "TextEditorComponent", ->
backgroundColor = getComputedStyle(wrapperNode).backgroundColor
expect(linesNode.style.backgroundColor).toBe backgroundColor
for tileNode in linesNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLines()
expect(tileNode.style.backgroundColor).toBe(backgroundColor)
wrapperNode.style.backgroundColor = 'rgb(255, 0, 0)'
@ -322,7 +322,7 @@ describe "TextEditorComponent", ->
advanceClock(atom.views.documentPollingInterval)
nextAnimationFrame()
expect(linesNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
for tileNode in linesNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLines()
expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)")
@ -633,7 +633,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes[0].style.zIndex).toBe("2")
expect(tilesNodes[1].style.zIndex).toBe("1")
@ -643,33 +643,13 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes[0].style.zIndex).toBe("3")
expect(tilesNodes[1].style.zIndex).toBe("2")
expect(tilesNodes[2].style.zIndex).toBe("1")
expect(tilesNodes[3].style.zIndex).toBe("0")
it "renders higher line numbers in front of lower ones", ->
wrapperNode.style.height = 6.5 * lineHeightInPixels + 'px'
component.measureDimensions()
nextAnimationFrame()
# Tile 0
expect(component.lineNumberNodeForScreenRow(0).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(1).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(2).style.zIndex).toBe("0")
# Tile 1
expect(component.lineNumberNodeForScreenRow(3).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(4).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(5).style.zIndex).toBe("0")
# Tile 2
expect(component.lineNumberNodeForScreenRow(6).style.zIndex).toBe("2")
expect(component.lineNumberNodeForScreenRow(7).style.zIndex).toBe("1")
expect(component.lineNumberNodeForScreenRow(8).style.zIndex).toBe("0")
it "gives the line numbers container the same height as the wrapper node", ->
linesNode = componentNode.querySelector(".line-numbers")
@ -690,7 +670,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(tilesNodes.length).toBe(3)
expect(tilesNodes[0].style['-webkit-transform']).toBe "translate3d(0px, 0px, 0px)"
@ -716,7 +696,7 @@ describe "TextEditorComponent", ->
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".line-numbers").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLineNumbers()
expect(component.lineNumberNodeForScreenRow(2)).toBeUndefined()
expect(tilesNodes.length).toBe(3)
@ -818,7 +798,7 @@ describe "TextEditorComponent", ->
lineNumbersNode = gutterNode.querySelector('.line-numbers')
{backgroundColor} = getComputedStyle(wrapperNode)
expect(lineNumbersNode.style.backgroundColor).toBe backgroundColor
for tileNode in lineNumbersNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLineNumbers()
expect(tileNode.style.backgroundColor).toBe(backgroundColor)
# favor gutter color if it's assigned
@ -827,7 +807,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
expect(lineNumbersNode.style.backgroundColor).toBe 'rgb(255, 0, 0)'
for tileNode in lineNumbersNode.querySelectorAll(".tile")
for tileNode in component.tileNodesForLineNumbers()
expect(tileNode.style.backgroundColor).toBe("rgb(255, 0, 0)")
it "hides or shows the gutter based on the '::isLineNumberGutterVisible' property on the model and the global 'editor.showLineNumbers' config setting", ->
@ -1060,10 +1040,11 @@ describe "TextEditorComponent", ->
cursor = componentNode.querySelector('.cursor')
cursorRect = cursor.getBoundingClientRect()
cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.source.js').firstChild
cursorLocationTextNode = component.lineNodeForScreenRow(0).querySelector('.source.js').childNodes[2]
range = document.createRange()
range.setStart(cursorLocationTextNode, 3)
range.setEnd(cursorLocationTextNode, 4)
range.setStart(cursorLocationTextNode, 0)
range.setEnd(cursorLocationTextNode, 1)
rangeRect = range.getBoundingClientRect()
expect(cursorRect.left).toBe rangeRect.left
@ -1187,7 +1168,7 @@ describe "TextEditorComponent", ->
it "renders 2 regions for 2-line selections", ->
editor.setSelectedScreenRange([[1, 6], [2, 10]])
nextAnimationFrame()
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0]
tileNode = component.tileNodesForLines()[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe 2
@ -1208,7 +1189,7 @@ describe "TextEditorComponent", ->
nextAnimationFrame()
# Tile 0
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[0]
tileNode = component.tileNodesForLines()[0]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
@ -1231,7 +1212,7 @@ describe "TextEditorComponent", ->
expect(region3Rect.right).toBe tileNode.getBoundingClientRect().right
# Tile 3
tileNode = componentNode.querySelector(".lines").querySelectorAll(".tile")[1]
tileNode = component.tileNodesForLines()[1]
regions = tileNode.querySelectorAll('.selection .region')
expect(regions.length).toBe(3)
@ -2462,7 +2443,7 @@ describe "TextEditorComponent", ->
component.measureDimensions()
nextAnimationFrame()
tilesNodes = componentNode.querySelector(".lines").querySelectorAll(".tile")
tilesNodes = component.tileNodesForLines()
top = 0
for tileNode in tilesNodes

View File

@ -2035,15 +2035,10 @@ describe "TextEditorPresenter", ->
lineNumberStateForScreenRow = (presenter, screenRow) ->
editor = presenter.model
tileRow = presenter.tileForRow(screenRow)
bufferRow = editor.bufferRowForScreenRow(screenRow)
wrapCount = screenRow - editor.screenRowForBufferRow(bufferRow)
if wrapCount > 0
key = bufferRow + '-' + wrapCount
else
key = bufferRow
line = editor.tokenizedLineForScreenRow(screenRow)
gutterState = getLineNumberGutterState(presenter)
gutterState.content.tiles[tileRow]?.lineNumbers[key]
gutterState.content.tiles[tileRow]?.lineNumbers[line?.id]
tiledContentContract (presenter) -> getLineNumberGutterState(presenter).content

View File

@ -17,9 +17,6 @@ describe 'text utilities', ->
expect(textUtils.hasPairedCharacter('\uFE0E\uFE0E')).toBe false
expect(textUtils.hasPairedCharacter('\u0301\u0301')).toBe false
expect(textUtils.hasPairedCharacter('\0\u0301')).toBe false
expect(textUtils.hasPairedCharacter('\0\uFE0E')).toBe false
describe '.isPairedCharacter(string, index)', ->
it 'returns true when the index is the start of a high/low surrogate pair, variation sequence, or combined character', ->
expect(textUtils.isPairedCharacter('a\uD835\uDF97b\uD835\uDF97c', 0)).toBe false
@ -47,6 +44,3 @@ describe 'text utilities', ->
expect(textUtils.isPairedCharacter('ae\u0301c', 2)).toBe false
expect(textUtils.isPairedCharacter('ae\u0301c', 3)).toBe false
expect(textUtils.isPairedCharacter('ae\u0301c', 4)).toBe false
expect(textUtils.isPairedCharacter('\0\u0301c', 0)).toBe false
expect(textUtils.isPairedCharacter('\0\uFE0E', 0)).toBe false

View File

@ -0,0 +1,35 @@
TextBuffer = require 'text-buffer'
TokenizedBuffer = require '../src/tokenized-buffer'
describe "TokenIterator", ->
it "correctly terminates scopes at the beginning of the line (regression)", ->
grammar = atom.grammars.createGrammar('test', {
'scopeName': 'text.broken'
'name': 'Broken grammar'
'patterns': [
{
'begin': 'start'
'end': '(?=end)'
'name': 'blue.broken'
}
{
'match': '.'
'name': 'yellow.broken'
}
]
})
buffer = new TextBuffer(text: """
start x
end x
x
""")
tokenizedBuffer = new TokenizedBuffer({buffer})
tokenizedBuffer.setGrammar(grammar)
tokenIterator = tokenizedBuffer.tokenizedLineForRow(1).getTokenIterator()
tokenIterator.next()
expect(tokenIterator.getBufferStart()).toBe 0
expect(tokenIterator.getScopeEnds()).toEqual []
expect(tokenIterator.getScopeStarts()).toEqual ['text.broken', 'yellow.broken']

View File

@ -1,5 +1,9 @@
global.shellStartTime = Date.now()
process.on 'uncaughtException', (error={}) ->
console.log(error.message) if error.message?
console.log(error.stack) if error.stack?
crashReporter = require 'crash-reporter'
app = require 'app'
fs = require 'fs-plus'
@ -8,11 +12,13 @@ yargs = require 'yargs'
console.log = require 'nslog'
start = ->
setupUncaughtExceptionHandler()
setupAtomHome()
setupCompileCache()
return if handleStartupEventWithSquirrel()
# NB: This prevents Win10 from showing dupe items in the taskbar
app.setAppUserModelId('com.squirrel.atom.atom')
args = parseCommandLine()
addPathToOpen = (event, pathToOpen) ->
@ -42,11 +48,6 @@ normalizeDriveLetterName = (filePath) ->
else
filePath
setupUncaughtExceptionHandler = ->
process.on 'uncaughtException', (error={}) ->
console.log(error.message) if error.message?
console.log(error.stack) if error.stack?
handleStartupEventWithSquirrel = ->
return false unless process.platform is 'win32'
SquirrelUpdate = require './squirrel-update'

View File

@ -175,12 +175,8 @@ class CommandRegistry
# * `commandName` {String} indicating the name of the command to dispatch.
dispatch: (target, commandName, detail) ->
event = new CustomEvent(commandName, {bubbles: true, detail})
eventWithTarget = Object.create event,
target: value: target
preventDefault: value: ->
stopPropagation: value: ->
stopImmediatePropagation: value: ->
@handleCommandEvent(eventWithTarget)
Object.defineProperty(event, 'target', value: target)
@handleCommandEvent(event)
# Public: Invoke the given callback before dispatching a command event.
#
@ -208,34 +204,36 @@ class CommandRegistry
@selectorBasedListenersByCommandName[commandName] = listeners.slice()
return
handleCommandEvent: (originalEvent) =>
handleCommandEvent: (event) =>
propagationStopped = false
immediatePropagationStopped = false
matched = false
currentTarget = originalEvent.target
currentTarget = event.target
{preventDefault, stopPropagation, stopImmediatePropagation, abortKeyBinding} = event
syntheticEvent = Object.create originalEvent,
eventPhase: value: Event.BUBBLING_PHASE
currentTarget: get: -> currentTarget
preventDefault: value: ->
originalEvent.preventDefault()
stopPropagation: value: ->
originalEvent.stopPropagation()
propagationStopped = true
stopImmediatePropagation: value: ->
originalEvent.stopImmediatePropagation()
propagationStopped = true
immediatePropagationStopped = true
abortKeyBinding: value: ->
originalEvent.abortKeyBinding?()
dispatchedEvent = new CustomEvent(event.type, {bubbles: true, detail: event.detail})
Object.defineProperty dispatchedEvent, 'eventPhase', value: Event.BUBBLING_PHASE
Object.defineProperty dispatchedEvent, 'currentTarget', get: -> currentTarget
Object.defineProperty dispatchedEvent, 'target', value: currentTarget
Object.defineProperty dispatchedEvent, 'preventDefault', value: ->
event.preventDefault()
Object.defineProperty dispatchedEvent, 'stopPropagation', value: ->
event.stopPropagation()
propagationStopped = true
Object.defineProperty dispatchedEvent, 'stopImmediatePropagation', value: ->
event.stopImmediatePropagation()
propagationStopped = true
immediatePropagationStopped = true
Object.defineProperty dispatchedEvent, 'abortKeyBinding', value: ->
event.abortKeyBinding?()
@emitter.emit 'will-dispatch', syntheticEvent
@emitter.emit 'will-dispatch', dispatchedEvent
loop
listeners = @inlineListenersByCommandName[originalEvent.type]?.get(currentTarget) ? []
listeners = @inlineListenersByCommandName[event.type]?.get(currentTarget) ? []
if currentTarget.webkitMatchesSelector?
selectorBasedListeners =
(@selectorBasedListenersByCommandName[originalEvent.type] ? [])
(@selectorBasedListenersByCommandName[event.type] ? [])
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
.sort (a, b) -> a.compare(b)
listeners = listeners.concat(selectorBasedListeners)
@ -244,13 +242,13 @@ class CommandRegistry
for listener in listeners
break if immediatePropagationStopped
listener.callback.call(currentTarget, syntheticEvent)
listener.callback.call(currentTarget, dispatchedEvent)
break if currentTarget is window
break if propagationStopped
currentTarget = currentTarget.parentNode ? window
@emitter.emit 'did-dispatch', syntheticEvent
@emitter.emit 'did-dispatch', dispatchedEvent
matched

View File

@ -0,0 +1,42 @@
module.exports =
class DOMElementPool
constructor: ->
@freeElementsByTagName = {}
@freedElements = new Set
clear: ->
@freedElements.clear()
for tagName, freeElements of @freeElementsByTagName
freeElements.length = 0
return
build: (tagName, className, textContent = "") ->
element = @freeElementsByTagName[tagName]?.pop()
element ?= document.createElement(tagName)
delete element.dataset[dataId] for dataId of element.dataset
element.removeAttribute("class")
element.removeAttribute("style")
element.className = className if className?
element.textContent = textContent
@freedElements.delete(element)
element
freeElementAndDescendants: (element) ->
@free(element)
for index in [element.children.length - 1..0] by -1
child = element.children[index]
@freeElementAndDescendants(child)
return
free: (element) ->
throw new Error("The element cannot be null or undefined.") unless element?
throw new Error("The element has already been freed!") if @freedElements.has(element)
tagName = element.tagName.toLowerCase()
@freeElementsByTagName[tagName] ?= []
@freeElementsByTagName[tagName].push(element)
@freedElements.add(element)
element.remove()

View File

@ -7,7 +7,7 @@ LineNumberGutterComponent = require './line-number-gutter-component'
module.exports =
class GutterContainerComponent
constructor: ({@onLineNumberGutterMouseDown, @editor}) ->
constructor: ({@onLineNumberGutterMouseDown, @editor, @domElementPool}) ->
# An array of objects of the form: {name: {String}, component: {Object}}
@gutterComponents = []
@gutterComponentsByGutterName = {}
@ -39,7 +39,7 @@ class GutterContainerComponent
gutterComponent = @gutterComponentsByGutterName[gutter.name]
if not gutterComponent
if gutter.name is 'line-number'
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter})
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter, @domElementPool})
@lineNumberGutterComponent = gutterComponent
else
gutterComponent = new CustomGutterComponent({gutter})

View File

@ -5,12 +5,11 @@ module.exports =
class HighlightsComponent
oldState: null
constructor: ->
constructor: (@domElementPool) ->
@highlightNodesById = {}
@regionNodesByHighlightId = {}
@domNode = document.createElement('div')
@domNode.classList.add('highlights')
@domNode = @domElementPool.build("div", "highlights")
getDomNode: ->
@domNode
@ -30,8 +29,7 @@ class HighlightsComponent
# add or update highlights
for id, highlightState of newState
unless @oldState[id]?
highlightNode = document.createElement('div')
highlightNode.classList.add('highlight')
highlightNode = @domElementPool.build("div", "highlight")
@highlightNodesById[id] = highlightNode
@regionNodesByHighlightId[id] = {}
@domNode.appendChild(highlightNode)
@ -75,12 +73,11 @@ class HighlightsComponent
for newRegionState, i in newHighlightState.regions
unless oldHighlightState.regions[i]?
oldHighlightState.regions[i] = {}
regionNode = document.createElement('div')
regionNode = @domElementPool.build("div", "region")
# This prevents highlights at the tiles boundaries to be hidden by the
# subsequent tile. When this happens, subpixel anti-aliasing gets
# disabled.
regionNode.style.boxSizing = "border-box"
regionNode.classList.add('region')
regionNode.classList.add(newHighlightState.deprecatedRegionClass) if newHighlightState.deprecatedRegionClass?
@regionNodesByHighlightId[id][i] = regionNode
highlightNode.appendChild(regionNode)

View File

@ -3,6 +3,7 @@ class InputComponent
constructor: ->
@domNode = document.createElement('input')
@domNode.classList.add('hidden-input')
@domNode.setAttribute('tabindex', -1)
@domNode.setAttribute('data-react-skip-selection-restoration', true)
@domNode.style['-webkit-transform'] = 'translateZ(0)'
@domNode.addEventListener 'paste', (event) -> event.preventDefault()

View File

@ -1,15 +1,17 @@
TiledComponent = require './tiled-component'
LineNumbersTileComponent = require './line-numbers-tile-component'
WrapperDiv = document.createElement('div')
DummyLineNumberComponent = LineNumbersTileComponent.createDummy()
DOMElementPool = require './dom-element-pool'
module.exports =
class LineNumberGutterComponent extends TiledComponent
dummyLineNumberNode: null
constructor: ({@onMouseDown, @editor, @gutter}) ->
constructor: ({@onMouseDown, @editor, @gutter, @domElementPool}) ->
@visible = true
@dummyLineNumberComponent = LineNumbersTileComponent.createDummy(@domElementPool)
@domNode = atom.views.getView(@gutter)
@lineNumbersNode = @domNode.firstChild
@lineNumbersNode.innerHTML = ''
@ -60,7 +62,7 @@ class LineNumberGutterComponent extends TiledComponent
@oldState.styles = {}
@oldState.maxLineNumberDigits = @newState.maxLineNumberDigits
buildComponentForTile: (id) -> new LineNumbersTileComponent({id})
buildComponentForTile: (id) -> new LineNumbersTileComponent({id, @domElementPool})
shouldRecreateAllTilesOnUpdate: ->
@newState.continuousReflow
@ -72,14 +74,13 @@ class LineNumberGutterComponent extends TiledComponent
# This dummy line number element holds the gutter to the appropriate width,
# since the real line numbers are absolutely positioned for performance reasons.
appendDummyLineNumber: ->
DummyLineNumberComponent.newState = @newState
WrapperDiv.innerHTML = DummyLineNumberComponent.buildLineNumberHTML({bufferRow: -1})
@dummyLineNumberNode = WrapperDiv.children[0]
@dummyLineNumberComponent.newState = @newState
@dummyLineNumberNode = @dummyLineNumberComponent.buildLineNumberNode({bufferRow: -1})
@lineNumbersNode.appendChild(@dummyLineNumberNode)
updateDummyLineNumber: ->
DummyLineNumberComponent.newState = @newState
@dummyLineNumberNode.innerHTML = DummyLineNumberComponent.buildLineNumberInnerHTML(0, false)
@dummyLineNumberComponent.newState = @newState
@dummyLineNumberComponent.setLineNumberInnerNodes(0, false, @dummyLineNumberNode)
onMouseDown: (event) =>
{target} = event

View File

@ -1,19 +1,20 @@
_ = require 'underscore-plus'
WrapperDiv = document.createElement('div')
module.exports =
class LineNumbersTileComponent
@createDummy: ->
new LineNumbersTileComponent({id: -1})
@createDummy: (domElementPool) ->
new LineNumbersTileComponent({id: -1, domElementPool})
constructor: ({@id}) ->
constructor: ({@id, @domElementPool}) ->
@lineNumberNodesById = {}
@domNode = document.createElement("div")
@domNode.classList.add("tile")
@domNode = @domElementPool.build("div")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@domNode.style.top = 0 # Cover the space occupied by a dummy lineNumber
destroy: ->
@domElementPool.freeElementAndDescendants(@domNode)
getDomNode: ->
@domNode
@ -47,7 +48,9 @@ class LineNumbersTileComponent
@oldTileState.zIndex = @newTileState.zIndex
if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits
node.remove() for id, node of @lineNumberNodesById
for id, node of @lineNumberNodesById
@domElementPool.freeElementAndDescendants(node)
@oldState.tiles[@id] = {lineNumbers: {}}
@oldTileState = @oldState.tiles[@id]
@lineNumberNodesById = {}
@ -57,11 +60,11 @@ class LineNumbersTileComponent
updateLineNumbers: ->
newLineNumberIds = null
newLineNumbersHTML = null
newLineNumberNodes = null
for id, lineNumberState of @oldTileState.lineNumbers
unless @newTileState.lineNumbers.hasOwnProperty(id)
@lineNumberNodesById[id].remove()
@domElementPool.freeElementAndDescendants(@lineNumberNodesById[id])
delete @lineNumberNodesById[id]
delete @oldTileState.lineNumbers[id]
@ -70,35 +73,40 @@ class LineNumbersTileComponent
@updateLineNumberNode(id, lineNumberState)
else
newLineNumberIds ?= []
newLineNumbersHTML ?= ""
newLineNumberNodes ?= []
newLineNumberIds.push(id)
newLineNumbersHTML += @buildLineNumberHTML(lineNumberState)
newLineNumberNodes.push(@buildLineNumberNode(lineNumberState))
@oldTileState.lineNumbers[id] = _.clone(lineNumberState)
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = _.toArray(WrapperDiv.children)
return unless newLineNumberIds?
node = @domNode
for id, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
node.appendChild(lineNumberNode)
for id, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
if nextNode = @findNodeNextTo(lineNumberNode)
@domNode.insertBefore(lineNumberNode, nextNode)
else
@domNode.appendChild(lineNumberNode)
findNodeNextTo: (node) ->
for nextNode in @domNode.children
return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode)
return
buildLineNumberHTML: (lineNumberState) ->
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
buildLineNumberNode: (lineNumberState) ->
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState
if screenRow?
style = "position: absolute; top: #{top}px; z-index: #{zIndex};"
else
style = "visibility: hidden;"
className = @buildLineNumberClassName(lineNumberState)
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped)
lineNumberNode = @domElementPool.build("div", className)
lineNumberNode.dataset.screenRow = screenRow
lineNumberNode.dataset.bufferRow = bufferRow
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
@setLineNumberInnerNodes(bufferRow, softWrapped, lineNumberNode)
lineNumberNode
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
setLineNumberInnerNodes: (bufferRow, softWrapped, lineNumberNode) ->
{maxLineNumberDigits} = @newState
if softWrapped
@ -106,9 +114,11 @@ class LineNumbersTileComponent
else
lineNumber = (bufferRow + 1).toString()
padding = _.multiplyString('&nbsp;', maxLineNumberDigits - lineNumber.length)
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
padding = _.multiplyString("\u00a0", maxLineNumberDigits - lineNumber.length)
iconRight = @domElementPool.build("div", "icon-right")
lineNumberNode.textContent = padding + lineNumber
lineNumberNode.appendChild(iconRight)
updateLineNumberNode: (lineNumberId, newLineNumberState) ->
oldLineNumberState = @oldTileState.lineNumbers[lineNumberId]
@ -119,18 +129,15 @@ class LineNumbersTileComponent
oldLineNumberState.foldable = newLineNumberState.foldable
oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses)
unless oldLineNumberState.top is newLineNumberState.top
node.style.top = newLineNumberState.top + 'px'
unless oldLineNumberState.screenRow is newLineNumberState.screenRow and oldLineNumberState.bufferRow is newLineNumberState.bufferRow
@setLineNumberInnerNodes(newLineNumberState.bufferRow, newLineNumberState.softWrapped, node)
node.dataset.screenRow = newLineNumberState.screenRow
oldLineNumberState.top = newLineNumberState.top
node.dataset.bufferRow = newLineNumberState.bufferRow
oldLineNumberState.screenRow = newLineNumberState.screenRow
unless oldLineNumberState.zIndex is newLineNumberState.zIndex
node.style.zIndex = newLineNumberState.zIndex
oldLineNumberState.zIndex = newLineNumberState.zIndex
oldLineNumberState.bufferRow = newLineNumberState.bufferRow
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) ->
className = "line-number line-number-#{bufferRow}"
className = "line-number"
className += " " + decorationClasses.join(' ') if decorationClasses?
className += " foldable" if foldable and not softWrapped
className

View File

@ -10,7 +10,7 @@ module.exports =
class LinesComponent extends TiledComponent
placeholderTextDiv: null
constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
constructor: ({@presenter, @hostElement, @useShadowDOM, visible, @domElementPool}) ->
@domNode = document.createElement('div')
@domNode.classList.add('lines')
@tilesNode = document.createElement("div")
@ -60,7 +60,7 @@ class LinesComponent extends TiledComponent
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter})
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool})
buildEmptyState: ->
{tiles: {}}

View File

@ -3,7 +3,6 @@ _ = require 'underscore-plus'
HighlightsComponent = require './highlights-component'
TokenIterator = require './token-iterator'
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
WrapperDiv = document.createElement('div')
TokenTextEscapeRegex = /[&"'<>]/g
MaxTokenLength = 20000
@ -14,20 +13,22 @@ cloneObject = (object) ->
module.exports =
class LinesTileComponent
constructor: ({@presenter, @id}) ->
constructor: ({@presenter, @id, @domElementPool}) ->
@tokenIterator = new TokenIterator
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@domNode = document.createElement("div")
@domNode.classList.add("tile")
@domNode = @domElementPool.build("div")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@highlightsComponent = new HighlightsComponent
@highlightsComponent = new HighlightsComponent(@domElementPool)
@domNode.appendChild(@highlightsComponent.getDomNode())
destroy: ->
@domElementPool.freeElementAndDescendants(@domNode)
getDomNode: ->
@domNode
@ -77,7 +78,7 @@ class LinesTileComponent
return
removeLineNode: (id) ->
@lineNodesByLineId[id].remove()
@domElementPool.freeElementAndDescendants(@lineNodesByLineId[id])
delete @lineNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
@ -89,89 +90,99 @@ class LinesTileComponent
@removeLineNode(id)
newLineIds = null
newLinesHTML = null
newLineNodes = null
for id, lineState of @newTileState.lines
if @oldTileState.lines.hasOwnProperty(id)
@updateLineNode(id)
else
newLineIds ?= []
newLinesHTML ?= ""
newLineNodes ?= []
newLineIds.push(id)
newLinesHTML += @buildLineHTML(id)
newLineNodes.push(@buildLineNode(id))
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldTileState.lines[id] = cloneObject(lineState)
return unless newLineIds?
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = _.toArray(WrapperDiv.children)
for id, i in newLineIds
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
@domNode.appendChild(lineNode)
if nextNode = @findNodeNextTo(lineNode)
@domNode.insertBefore(lineNode, nextNode)
else
@domNode.appendChild(lineNode)
findNodeNextTo: (node) ->
for nextNode, index in @domNode.children
continue if index is 0 # skips highlights node
return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode)
return
buildLineHTML: (id) ->
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
buildLineNode: (id) ->
{width} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
classes = ''
lineNode = @domElementPool.build("div", "line")
lineNode.dataset.screenRow = screenRow
if decorationClasses?
for decorationClass in decorationClasses
classes += decorationClass + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{width}px;\" data-screen-row=\"#{screenRow}\">"
lineNode.classList.add(decorationClass)
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
@setEmptyLineInnerNodes(id, lineNode)
else
lineHTML += @buildLineInnerHTML(id)
@setLineInnerNodes(id, lineNode)
lineHTML += '<span class="fold-marker"></span>' if fold
lineHTML += "</div>"
lineHTML
lineNode.appendChild(@domElementPool.build("span", "fold-marker")) if fold
lineNode
buildEmptyLineInnerHTML: (id) ->
setEmptyLineInnerNodes: (id, lineNode) ->
{indentGuidesVisible} = @newState
{indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
if indentGuidesVisible and indentLevel > 0
invisibleIndex = 0
lineHTML = ''
for i in [0...indentLevel]
lineHTML += "<span class='indent-guide'>"
indentGuide = @domElementPool.build("span", "indent-guide")
for j in [0...tabLength]
if invisible = endOfLineInvisibles?[invisibleIndex++]
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
indentGuide.appendChild(
@domElementPool.build("span", "invisible-character", invisible)
)
else
lineHTML += ' '
lineHTML += "</span>"
indentGuide.insertAdjacentText("beforeend", " ")
lineNode.appendChild(indentGuide)
while invisibleIndex < endOfLineInvisibles?.length
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
lineHTML
invisible = endOfLineInvisibles[invisibleIndex++]
lineNode.appendChild(
@domElementPool.build("span", "invisible-character", invisible)
)
else
@buildEndOfLineHTML(id) or '&nbsp;'
unless @appendEndOfLineNodes(id, lineNode)
lineNode.textContent = "\u00a0"
buildLineInnerHTML: (id) ->
setLineInnerNodes: (id, lineNode) ->
lineState = @newTileState.lines[id]
{firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0
innerHTML = ""
@tokenIterator.reset(lineState)
openScopeNode = lineNode
while @tokenIterator.next()
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
openScopeNode = openScopeNode.parentElement
for scope in @tokenIterator.getScopeStarts()
innerHTML += "<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
newScopeNode = @domElementPool.build("span", scope.replace(/\.+/g, ' '))
openScopeNode.appendChild(newScopeNode)
openScopeNode = newScopeNode
tokenStart = @tokenIterator.getScreenStart()
tokenEnd = @tokenIterator.getScreenEnd()
@ -196,87 +207,79 @@ class LinesTileComponent
(invisibles?.tab and isHardTab) or
(invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))
innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
@appendTokenNodes(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters, openScopeNode)
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
@appendEndOfLineNodes(id, lineNode)
for scope in @tokenIterator.getScopes()
innerHTML += "</span>"
innerHTML += @buildEndOfLineHTML(id)
innerHTML
buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
appendTokenNodes: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters, scopeNode) ->
if isHardTab
classes = 'hard-tab'
classes += ' leading-whitespace' if firstNonWhitespaceIndex?
classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
return "<span class='#{classes}'>#{@escapeTokenText(tokenText)}</span>"
hardTabNode = @domElementPool.build("span", "hard-tab", tokenText)
hardTabNode.classList.add("leading-whitespace") if firstNonWhitespaceIndex?
hardTabNode.classList.add("trailing-whitespace") if firstTrailingWhitespaceIndex?
hardTabNode.classList.add("indent-guide") if hasIndentGuide
hardTabNode.classList.add("invisible-character") if hasInvisibleCharacters
scopeNode.appendChild(hardTabNode)
else
startIndex = 0
endIndex = tokenText.length
leadingHtml = ''
trailingHtml = ''
leadingWhitespaceNode = null
trailingWhitespaceNode = null
if firstNonWhitespaceIndex?
leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)
leadingWhitespaceNode = @domElementPool.build(
"span",
"leading-whitespace",
tokenText.substring(0, firstNonWhitespaceIndex)
)
leadingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide
leadingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
classes = 'leading-whitespace'
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
leadingHtml = "<span class='#{classes}'>#{leadingWhitespace}</span>"
startIndex = firstNonWhitespaceIndex
if firstTrailingWhitespaceIndex?
tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)
classes = 'trailing-whitespace'
classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
classes += ' invisible-character' if hasInvisibleCharacters
trailingHtml = "<span class='#{classes}'>#{trailingWhitespace}</span>"
trailingWhitespaceNode = @domElementPool.build(
"span",
"trailing-whitespace",
tokenText.substring(firstTrailingWhitespaceIndex)
)
trailingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
trailingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
endIndex = firstTrailingWhitespaceIndex
html = leadingHtml
scopeNode.appendChild(leadingWhitespaceNode) if leadingWhitespaceNode?
if tokenText.length > MaxTokenLength
while startIndex < endIndex
html += "<span>" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + "</span>"
text = @sliceText(tokenText, startIndex, startIndex + MaxTokenLength)
scopeNode.appendChild(@domElementPool.build("span", null, text))
startIndex += MaxTokenLength
else
html += @escapeTokenText(tokenText, startIndex, endIndex)
scopeNode.insertAdjacentText("beforeend", @sliceText(tokenText, startIndex, endIndex))
html += trailingHtml
html
scopeNode.appendChild(trailingWhitespaceNode) if trailingWhitespaceNode?
escapeTokenText: (tokenText, startIndex, endIndex) ->
sliceText: (tokenText, startIndex, endIndex) ->
if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
tokenText = tokenText.slice(startIndex, endIndex)
tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)
tokenText
escapeTokenTextReplace: (match) ->
switch match
when '&' then '&amp;'
when '"' then '&quot;'
when "'" then '&#39;'
when '<' then '&lt;'
when '>' then '&gt;'
else match
buildEndOfLineHTML: (id) ->
appendEndOfLineNodes: (id, lineNode) ->
{endOfLineInvisibles} = @newTileState.lines[id]
html = ''
hasInvisibles = false
if endOfLineInvisibles?
for invisible in endOfLineInvisibles
html += "<span class='invisible-character'>#{invisible}</span>"
html
hasInvisibles = true
lineNode.appendChild(
@domElementPool.build("span", "invisible-character", invisible)
)
hasInvisibles
updateLineNode: (id) ->
oldLineState = @oldTileState.lines[id]
@ -284,9 +287,6 @@ class LinesTileComponent
lineNode = @lineNodesByLineId[id]
if @newState.width isnt @oldState.width
lineNode.style.width = @newState.width + 'px'
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses
@ -302,10 +302,6 @@ class LinesTileComponent
oldLineState.decorationClasses = newLineState.decorationClasses
if newLineState.top isnt oldLineState.top
lineNode.style.top = newLineState.top + 'px'
oldLineState.top = newLineState.top
if newLineState.screenRow isnt oldLineState.screenRow
lineNode.dataset.screenRow = newLineState.screenRow
oldLineState.screenRow = newLineState.screenRow
@ -343,8 +339,6 @@ class LinesTileComponent
charLength = 1
textIndex++
continue if char is '\0'
unless charWidths[char]?
unless textNode?
rangeForMeasurement ?= document.createRange()

View File

@ -11,6 +11,7 @@ Q = require 'q'
ModuleCache = require './module-cache'
ScopedProperties = require './scoped-properties'
BufferedProcess = require './buffered-process'
packagesCache = require('../package.json')?._atomPackages ? {}
@ -603,6 +604,70 @@ class Package
traversePath(path.join(@path, 'node_modules'))
nativeModulePaths
###
Section: Native Module Compatibility
###
# Extended: Are all native modules depended on by this package correctly
# compiled against the current version of Atom?
#
# Incompatible packages cannot be activated.
#
# Returns a {Boolean}, true if compatible, false if incompatible.
isCompatible: ->
return @compatible if @compatible?
if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0
# Bundled packages are always considered compatible
@compatible = true
else if @getMainModulePath()
@incompatibleModules = @getIncompatibleNativeModules()
@compatible = @incompatibleModules.length is 0 and not @getBuildFailureOutput()?
else
@compatible = true
# Extended: Rebuild native modules in this package's dependencies for the
# current version of Atom.
#
# Returns a {Promise} that resolves with an object containing `code`,
# `stdout`, and `stderr` properties based on the results of running
# `apm rebuild` on the package.
rebuild: ->
new Promise (resolve) =>
@runRebuildProcess (result) =>
if result.code is 0
global.localStorage.removeItem(@getBuildFailureOutputStorageKey())
else
@compatible = false
global.localStorage.setItem(@getBuildFailureOutputStorageKey(), result.stderr)
global.localStorage.setItem(@getIncompatibleNativeModulesStorageKey(), '[]')
resolve(result)
# Extended: If a previous rebuild failed, get the contents of stderr.
#
# Returns a {String} or null if no previous build failure occurred.
getBuildFailureOutput: ->
global.localStorage.getItem(@getBuildFailureOutputStorageKey())
runRebuildProcess: (callback) ->
stderr = ''
stdout = ''
new BufferedProcess({
command: atom.packages.getApmPath()
args: ['rebuild', '--no-color']
options: {cwd: @path}
stderr: (output) -> stderr += output
stdout: (output) -> stdout += output
exit: (code) -> callback({code, stdout, stderr})
})
getBuildFailureOutputStorageKey: ->
"installed-packages:#{@name}:#{@metadata.version}:build-error"
getIncompatibleNativeModulesStorageKey: ->
electronVersion = process.versions['electron'] ? process.versions['atom-shell']
"installed-packages:#{@name}:#{@metadata.version}:electron-#{electronVersion}:incompatible-native-modules"
# Get the incompatible native modules that this package depends on.
# This recurses through all dependencies and requires all modules that
# contain a `.node` file.
@ -610,11 +675,10 @@ class Package
# This information is cached in local storage on a per package/version basis
# to minimize the impact on startup time.
getIncompatibleNativeModules: ->
localStorageKey = "installed-packages:#{@name}:#{@metadata.version}"
unless atom.inDevMode()
try
{incompatibleNativeModules} = JSON.parse(global.localStorage.getItem(localStorageKey)) ? {}
return incompatibleNativeModules if incompatibleNativeModules?
if arrayAsString = global.localStorage.getItem(@getIncompatibleNativeModulesStorageKey())
return JSON.parse(arrayAsString)
incompatibleNativeModules = []
for nativeModulePath in @getNativeModuleDependencyPaths()
@ -629,28 +693,9 @@ class Package
version: version
error: error.message
global.localStorage.setItem(localStorageKey, JSON.stringify({incompatibleNativeModules}))
global.localStorage.setItem(@getIncompatibleNativeModulesStorageKey(), JSON.stringify(incompatibleNativeModules))
incompatibleNativeModules
# Public: Is this package compatible with this version of Atom?
#
# Incompatible packages cannot be activated. This will include packages
# installed to ~/.atom/packages that were built against node 0.11.10 but
# now need to be upgrade to node 0.11.13.
#
# Returns a {Boolean}, true if compatible, false if incompatible.
isCompatible: ->
return @compatible if @compatible?
if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0
# Bundled packages are always considered compatible
@compatible = true
else if @getMainModulePath()
@incompatibleModules = @getIncompatibleNativeModules()
@compatible = @incompatibleModules.length is 0
else
@compatible = true
handleError: (message, error) ->
if error.filename and error.location and (error instanceof SyntaxError)
location = "#{error.filename}:#{error.location.first_line + 1}:#{error.location.first_column + 1}"

View File

@ -12,6 +12,7 @@ LinesComponent = require './lines-component'
ScrollbarComponent = require './scrollbar-component'
ScrollbarCornerComponent = require './scrollbar-corner-component'
OverlayManager = require './overlay-manager'
DOMElementPool = require './dom-element-pool'
module.exports =
class TextEditorComponent
@ -54,6 +55,8 @@ class TextEditorComponent
@presenter.onDidUpdateState(@requestUpdate)
@domElementPool = new DOMElementPool
@domNode = document.createElement('div')
if @useShadowDOM
@domNode.classList.add('editor-contents--private')
@ -75,7 +78,7 @@ class TextEditorComponent
@hiddenInputComponent = new InputComponent
@scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM})
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
@ -107,6 +110,7 @@ class TextEditorComponent
@disposables.dispose()
@presenter.destroy()
@gutterContainerComponent?.destroy()
@domElementPool.clear()
getDomNode: ->
@domNode
@ -152,6 +156,10 @@ class TextEditorComponent
@overlayManager?.render(@newState)
if @clearPoolAfterUpdate
@domElementPool.clear()
@clearPoolAfterUpdate = false
if @editor.isAlive()
@updateParentViewFocusedClassIfNeeded()
@updateParentViewMiniClass()
@ -165,7 +173,7 @@ class TextEditorComponent
@overlayManager?.measureOverlays()
mountGutterContainerComponent: ->
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown})
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool})
@domNode.insertBefore(@gutterContainerComponent.getDomNode(), @domNode.firstChild)
becameVisible: ->
@ -649,6 +657,7 @@ class TextEditorComponent
{@fontSize, @fontFamily, @lineHeight} = getComputedStyle(@getTopmostDOMNode())
if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
@clearPoolAfterUpdate = true
@measureLineHeightAndDefaultCharWidth()
if (@fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily) and @performedInitialMeasurement
@ -749,6 +758,13 @@ class TextEditorComponent
tileComponent?.lineNumberNodeForScreenRow(screenRow)
tileNodesForLines: ->
@linesComponent.getTiles()
tileNodesForLineNumbers: ->
gutterComponent = @gutterContainerComponent.getLineNumberGutterComponent()
gutterComponent.getTiles()
screenRowForNode: (node) ->
while node?
if screenRow = node.dataset.screenRow

View File

@ -21,7 +21,7 @@ class TextEditorPresenter
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@gutterWidth ?= 0
@tileSize ?= 12
@tileSize ?= 6
@disposables = new CompositeDisposable
@emitter = new Emitter
@ -609,22 +609,15 @@ class TextEditorPresenter
if startRow > 0
rowBeforeStartRow = startRow - 1
lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow)
wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow)
else
lastBufferRow = null
wrapCount = 0
if endRow > startRow
bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1)
zIndex = bufferRows.length - 1
for bufferRow, i in bufferRows
if bufferRow is lastBufferRow
wrapCount++
id = bufferRow + '-' + wrapCount
softWrapped = true
else
id = bufferRow
wrapCount = 0
lastBufferRow = bufferRow
softWrapped = false
@ -632,10 +625,10 @@ class TextEditorPresenter
top = (screenRow - startRow) * @lineHeight
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
foldable = @model.isFoldableAtScreenRow(screenRow)
id = @model.tokenizedLineForScreenRow(screenRow).id
tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable, zIndex}
tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
visibleLineNumberIds[id] = true
zIndex--
for id of tileState.lineNumbers
delete tileState.lineNumbers[id] unless visibleLineNumberIds[id]

View File

@ -30,7 +30,6 @@ isSurrogatePair = (charCodeA, charCodeB) ->
#
# Return a {Boolean}.
isVariationSequence = (charCodeA, charCodeB) ->
return false if charCodeA is 0
not isVariationSelector(charCodeA) and isVariationSelector(charCodeB)
# Are the given character codes a combined character pair?
@ -40,7 +39,6 @@ isVariationSequence = (charCodeA, charCodeB) ->
#
# Return a {Boolean}.
isCombinedCharacter = (charCodeA, charCodeB) ->
return false if charCodeA is 0
not isCombiningCharacter(charCodeA) and isCombiningCharacter(charCodeB)
# Is the character at the given index the start of high/low surrogate pair

View File

@ -1,3 +1,5 @@
{values} = require 'underscore-plus'
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
@ -21,9 +23,7 @@ class TiledComponent
return
removeTileNode: (tileRow) ->
node = @componentsByTileId[tileRow].getDomNode()
node.remove()
@componentsByTileId[tileRow].destroy()
delete @componentsByTileId[tileRow]
delete @oldState.tiles[tileRow]
@ -49,3 +49,6 @@ class TiledComponent
getComponentForTile: (tileRow) ->
@componentsByTileId[tileRow]
getTiles: ->
values(@componentsByTileId).map (component) -> component.getDomNode()

View File

@ -31,11 +31,14 @@ class TokenIterator
while @index < tags.length
tag = tags[@index]
if tag < 0
scope = atom.grammars.scopeForId(tag)
if tag % 2 is 0
@scopeEnds.push(atom.grammars.scopeForId(tag + 1))
if @scopeStarts[@scopeStarts.length - 1] is scope
@scopeStarts.pop()
else
@scopeEnds.push(scope)
@scopes.pop()
else
scope = atom.grammars.scopeForId(tag)
@scopeStarts.push(scope)
@scopes.push(scope)
@index++

View File

@ -506,6 +506,15 @@ class Workspace extends Model
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# opener.
#
# Note that the opener will be called if and only if the URI is not already open
# in the current pane. The searchAllPanes flag expands the search from the
# current pane to all panes. If you wish to open a view of a different type for
# a file that is already open, consider changing the protocol of the URI. For
# example, perhaps you wish to preview a rendered version of the file `/foo/bar/baz.quux`
# that is already open in a text editor view. You could signal this by calling
# {Workspace::open} on the URI `quux-preview://foo/bar/baz.quux`. Then your opener
# can check the protocol for quux-preview and only handle those URIs that match.
addOpener: (opener) ->
if includeDeprecatedAPIs
packageName = @getCallingPackageName()

View File

@ -28,3 +28,4 @@
@import "text";
@import "utilities";
@import "octicons";
@import "cursors";

26
static/cursors.less Normal file
View File

@ -0,0 +1,26 @@
@import "./variables/syntax-variables";
@import "syntax-variables";
@import "./variables/ui-variables";
@import "ui-variables";
@ibeam-1x: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=');
@ibeam-2x: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC');
.cursor-white() {
cursor: -webkit-image-set(@ibeam-1x 1x, @ibeam-2x 2x) 5 8, text;
}
// Editors
& when ( lightness(@syntax-background-color) < 50% ) {
.platform-darwin atom-text-editor:not([mini])::shadow .editor-contents--private {
.cursor-white();
}
}
// Mini Editors
& when ( lightness(@input-background-color) < 50% ) {
.platform-darwin atom-text-editor[mini]::shadow .editor-contents--private {
.cursor-white();
}
}

View File

@ -46,6 +46,7 @@ atom-text-editor {
}
.line-number {
position: relative;
white-space: nowrap;
padding-left: .5em;
opacity: 0.6;

View File

@ -29,6 +29,7 @@
}
.line-number {
position: relative;
white-space: nowrap;
padding-left: .5em;
opacity: 0.6;

View File

@ -82,4 +82,4 @@
// Other
@font-family: 'SF UI Text', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif;
@font-family: '.SFNSText-Regular', 'SF UI Text', 'Lucida Grande', 'Segoe UI', Ubuntu, Cantarell, sans-serif;