mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 07:28:08 +03:00
Merge branch 'master' into as-continuous-reflow
This commit is contained in:
commit
91bb8f518d
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ npm-debug.log
|
||||
debug.log
|
||||
/tags
|
||||
/atom-shell/
|
||||
/electron/
|
||||
docs/output
|
||||
docs/includes
|
||||
spec/fixtures/evil-files/
|
||||
|
@ -10,6 +10,8 @@ env:
|
||||
- ATOM_ACCESS_TOKEN=da809a6077bb1b0aa7c5623f7b2d5f1fec2faae4
|
||||
- NODE_VERSION=0.12
|
||||
|
||||
compiler: clang
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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')
|
||||
|
@ -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: """
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
42
package.json
42
package.json
@ -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",
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
89
script/railcar
Executable 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()
|
||||
}
|
||||
}
|
55
spec/dom-element-pool-spec.coffee
Normal file
55
spec/dom-element-pool-spec.coffee
Normal 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()
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
35
spec/token-iterator-spec.coffee
Normal file
35
spec/token-iterator-spec.coffee
Normal 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']
|
@ -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'
|
||||
|
@ -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
|
||||
|
||||
|
42
src/dom-element-pool.coffee
Normal file
42
src/dom-element-pool.coffee
Normal 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()
|
@ -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})
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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(' ', 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
|
||||
|
@ -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: {}}
|
||||
|
@ -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 ' '
|
||||
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 '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
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()
|
||||
|
@ -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}"
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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++
|
||||
|
@ -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()
|
||||
|
@ -28,3 +28,4 @@
|
||||
@import "text";
|
||||
@import "utilities";
|
||||
@import "octicons";
|
||||
@import "cursors";
|
||||
|
26
static/cursors.less
Normal file
26
static/cursors.less
Normal 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();
|
||||
}
|
||||
}
|
@ -46,6 +46,7 @@ atom-text-editor {
|
||||
}
|
||||
|
||||
.line-number {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
padding-left: .5em;
|
||||
opacity: 0.6;
|
||||
|
@ -29,6 +29,7 @@
|
||||
}
|
||||
|
||||
.line-number {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
padding-left: .5em;
|
||||
opacity: 0.6;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user