Comparar commits
209 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 0d587028af | |||
| d4207c04d6 | |||
| da864b6eec | |||
| ce75ee011a | |||
| 71b7fb3b5d | |||
| f413a21a11 | |||
| 544cd54a41 | |||
| 8b1eec39b5 | |||
| 02b955f823 | |||
| 30d386527d | |||
| 0af6dfc94c | |||
| ef7a6b94b2 | |||
| 4d6be72f56 | |||
| 3ded5ec0f8 | |||
| 2f15f72139 | |||
| 6f9fa39ad2 | |||
| f2eeb0629f | |||
| 1fdb4dbeda | |||
| 54c2139992 | |||
| 6e7dedb198 | |||
| 1fc33e1e21 | |||
| 583096a916 | |||
| 4d88acd756 | |||
| a3afaed950 | |||
| 716219b9a3 | |||
| bc558194e9 | |||
| c1ee2f420b | |||
| 58d9304c22 | |||
| 3226f54b97 | |||
| 63969a6467 | |||
| d5f7aca0ad | |||
| 94160044ad | |||
| 4dcb35152f | |||
| 45ac962681 | |||
| c5d502ab88 | |||
| c75fc8aa01 | |||
| e954430599 | |||
| 44795da767 | |||
| 11df5c2855 | |||
| 11189692b7 | |||
| a7fb07ccfa | |||
| a8df20271f | |||
| 84064a811c | |||
| 3c932d6d91 | |||
| a24d1d1af7 | |||
| edb5b43d64 | |||
| ac496e1fa4 | |||
| 103f3f8597 | |||
| 9842baedce | |||
| aaa916f78d | |||
| 7f6a4cccaf | |||
| 87edff1e42 | |||
| 96f35d3cde | |||
| 7e45ffa4c3 | |||
| 6af69b0fc7 | |||
| 99e02570d1 | |||
| 823cfcac57 | |||
| de6ccd8c08 | |||
| 2135d3be83 | |||
| 1c3720c160 | |||
| 6c72b13adc | |||
| 1404904d24 | |||
| db243936b4 | |||
| 6e72627e9e | |||
| 3d36ba7ecc | |||
| a7c0d6073f | |||
| f25b468272 | |||
| 2d0fb8ee6b | |||
| d875becc7a | |||
| cb72af63fd | |||
| f7187f1d5a | |||
| 700acdc5a2 | |||
| 18016ae9df | |||
| a30faa5bea | |||
| 05a113bb7a | |||
| f5d4ece9cd | |||
| 3bda37c56c | |||
| 62b52cb70a | |||
| a4fe594441 | |||
| 9d0e46126b | |||
| cb1bb4a691 | |||
| d3a24c3749 | |||
| 092849835e | |||
| b24ade4de5 | |||
| 0f77a2eef9 | |||
| 662c2fc9d3 | |||
| 510b1a7068 | |||
| c4f9914df6 | |||
| 2140ce3beb | |||
| a597bca75e | |||
| 2895aae121 | |||
| 4e20d93f03 | |||
| 1cc4e2e045 | |||
| 9fb427c468 | |||
| a9bd061144 | |||
| 0736b28abf | |||
| 9ac5b67b6e | |||
| 25601d691d | |||
| 0d1c11764b | |||
| 729ff461f1 | |||
| 0360a1918c | |||
| 23f21bcda2 | |||
| 800d65e3de | |||
| 5ce9b3ac55 | |||
| f86191dff8 | |||
| 412793697f | |||
| 3274ef9fb9 | |||
| f8e2231dfc | |||
| 837eaccd16 | |||
| 4f3570b56b | |||
| 8918a42b3b | |||
| bd77a02207 | |||
| eebbb99fc8 | |||
| 2b27c0b440 | |||
| 8e69b0c4a0 | |||
| 532744b4eb | |||
| ddd89ed6d1 | |||
| 5a53e5b96a | |||
| 69f84f7e6d | |||
| e2c65345ab | |||
| f47bcddf10 | |||
| 5e19230809 | |||
| f8961fbd53 | |||
| bef750cb1f | |||
| 4e2f06aec7 | |||
| 02c47ba1ea | |||
| f6cb59be47 | |||
| 397871a012 | |||
| 10239e0466 | |||
| 69ef99481b | |||
| fc20de82ce | |||
| 0232da27f5 | |||
| ba452e2400 | |||
| 9b5b8e7528 | |||
| 6e65947d54 | |||
| 93c5e241f3 | |||
| e0c61136a6 | |||
| c5cc13ddb3 | |||
| fd47c89f9d | |||
| 34ad902cb3 | |||
| 9678418e56 | |||
| 691d6c3b5f | |||
| 431555195a | |||
| b0aa5e6c88 | |||
| 7f882b00f5 | |||
| 3af3a0d27e | |||
| 7e415ffdb7 | |||
| 65ffd21574 | |||
| 393552a4b6 | |||
| 444c18be34 | |||
| dca096b8e3 | |||
| 57a03e7884 | |||
| fe1819f587 | |||
| aa157af93e | |||
| 0e58e03de7 | |||
| e011c80b07 | |||
| 9dc59b9807 | |||
| 15689ebfb5 | |||
| 6250419fcb | |||
| 70621afe62 | |||
| 6f29710d88 | |||
| 7b07d7116b | |||
| f175086865 | |||
| c6071a9802 | |||
| ac138c1dc8 | |||
| 85b7261d31 | |||
| d47348e8f9 | |||
| 56df7bdbe3 | |||
| 1ea909d4db | |||
| caa6f9b06e | |||
| 684f15ab89 | |||
| 4238e031da | |||
| a3d82e9414 | |||
| 7515fd94ba | |||
| 8ad8be2583 | |||
| b922f01257 | |||
| 83ad1fe8af | |||
| dcbf730129 | |||
| 39868a2330 | |||
| 772726ca96 | |||
| 5f7f5b5367 | |||
| 683f8e06f8 | |||
| bc4173f856 | |||
| 8099c46c8e | |||
| bbfd9b8178 | |||
| 6d34de68ac | |||
| 10fb929a1b | |||
| 52e049bedc | |||
| a6640f6da7 | |||
| eeadd823e6 | |||
| 8a5bd357cd | |||
| 42621805a7 | |||
| fed55b8896 | |||
| 73daa4bb74 | |||
| 92546c60b3 | |||
| 2fa91e5dfb | |||
| aac0913b8b | |||
| acc75ca859 | |||
| 8eb4e13df8 | |||
| 087d9c1da6 | |||
| 18336076a8 | |||
| 7fee5f5f25 | |||
| c2f04a00d2 | |||
| cc927123f9 | |||
| 97b426429b | |||
| 3686530943 | |||
| 621ef450da | |||
| 1e08bcd634 | |||
| ede468d4c9 |
+1
-1
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.83.0"
|
||||
"atom-package-manager": "0.88.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"read-package-json": "1.1.8",
|
||||
"request": "~2.27.0",
|
||||
"rimraf": "~2.2.2",
|
||||
"runas": "0.5.x",
|
||||
"runas": "~1.0.1",
|
||||
"underscore-plus": "1.x",
|
||||
"unzip": "~0.1.9",
|
||||
"vm-compatibility-layer": "~0.1.0"
|
||||
|
||||
@@ -148,9 +148,6 @@ module.exports = (grunt) ->
|
||||
grunt.file.copy(sourcePath, path.resolve(appDir, '..', subDirectory, filename))
|
||||
|
||||
if process.platform is 'win32'
|
||||
cp path.join('resources', 'win', 'msvcp100.dll'), path.join(shellAppDir, 'msvcp100.dll')
|
||||
cp path.join('resources', 'win', 'msvcr100.dll'), path.join(shellAppDir, 'msvcr100.dll')
|
||||
|
||||
# Set up chocolatey ignore and gui files
|
||||
fs.writeFileSync path.join(appDir, 'apm', 'node_modules', 'atom-package-manager', 'bin', 'node.exe.ignore'), ''
|
||||
fs.writeFileSync path.join(appDir, 'node_modules', 'symbols-view', 'vendor', 'ctags-win32.exe.ignore'), ''
|
||||
|
||||
@@ -45,8 +45,8 @@ module.exports = (grunt) ->
|
||||
|
||||
strings =
|
||||
CompanyName: 'GitHub, Inc.'
|
||||
FileDescription: 'The hackable editor'
|
||||
LegalCopyright: 'Copyright (C) 2013 GitHub, Inc. All rights reserved'
|
||||
FileDescription: 'Atom'
|
||||
LegalCopyright: 'Copyright (C) 2014 GitHub, Inc. All rights reserved'
|
||||
ProductName: 'Atom'
|
||||
ProductVersion: version
|
||||
|
||||
|
||||
@@ -6,21 +6,21 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
|
||||
* OS with 64-bit or 32-bit architecture
|
||||
* C++ toolchain
|
||||
* git
|
||||
* [node.js](http://nodejs.org/download/) v0.10.x
|
||||
* [npm](http://www.npmjs.org/) v1.4.x (bundled with node.js)
|
||||
* [Git](http://git-scm.com/)
|
||||
* [Node.js](http://nodejs.org/download/) v0.10.x
|
||||
* [npm](http://www.npmjs.org/) v1.4.x (bundled with Node.js)
|
||||
* `npm -v` to check the version.
|
||||
* `npm config set python /usr/bin/python2 -g` to ensure that gyp uses python2.
|
||||
* You might need to run this command as `sudo`, depending on how you have set up [npm](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
|
||||
* libgnome-keyring-dev
|
||||
* development headers for [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring)
|
||||
|
||||
### Ubuntu / Debian
|
||||
* `sudo apt-get install build-essential git libgnome-keyring-dev`
|
||||
* 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/joyent/node/wiki/Installing-Node.js-via-package-manager#ubuntu-mint-elementary-os).
|
||||
|
||||
### Fedora
|
||||
* `sudo yum --assumeyes install make gcc gcc-c++ glibc-devel git-core libgnome-keyring-devel`
|
||||
* Instructions for [node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora).
|
||||
* Instructions for [Node.js](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#fedora).
|
||||
|
||||
### Arch
|
||||
* `sudo pacman -S base-devel git nodejs libgnome-keyring`
|
||||
@@ -30,19 +30,28 @@ Ubuntu LTS 12.04 64-bit is the recommended platform.
|
||||
|
||||
If you have problems with permissions don't forget to prefix with `sudo`
|
||||
|
||||
```sh
|
||||
git clone https://github.com/atom/atom
|
||||
cd atom
|
||||
script/build # Creates application at $TMPDIR/atom-build/Atom
|
||||
sudo script/grunt install # Installs command to /usr/local/bin/atom
|
||||
script/grunt mkdeb # Generates a .deb package at $TMPDIR/atom-build, e.g. /tmp/atom-build
|
||||
```
|
||||
|
||||
To run `atom` and `apm` from a terminal open atom's command palette `ctrl+shift+p` and run `Window: Install Shell Commands`
|
||||
Create the atom application at `$TMPDIR/atom-build/Atom`:
|
||||
|
||||
```sh
|
||||
script/build
|
||||
```
|
||||
|
||||
Install the `atom` and `apm` commands to `/usr/local/bin`:
|
||||
|
||||
```sh
|
||||
sudo script/grunt install
|
||||
```
|
||||
|
||||
Generate a `.deb` package at `$TMPDIR/atom-build`: (*optional*)
|
||||
|
||||
```sh
|
||||
script/grunt mkdeb
|
||||
```
|
||||
|
||||
Use the newly installed atom by restarting any running atom instances.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
### Exception: "TypeError: Unable to watch path"
|
||||
|
||||
If you get following error with a big traceback right after Atom starts:
|
||||
@@ -69,7 +78,7 @@ See also https://github.com/atom/atom/issues/2082.
|
||||
### /usr/bin/env: node: No such file or directory
|
||||
|
||||
If you get this notice when attempting to `script/build`, you either do not
|
||||
have nodejs installed, or node isn't identified as nodejs on your machine.
|
||||
have Node.js installed, or node isn't identified as Node.js on your machine.
|
||||
If it's the latter, entering `sudo ln -s /usr/bin/nodejs /usr/bin/node` into
|
||||
your terminal may fix the issue.
|
||||
|
||||
|
||||
@@ -66,6 +66,19 @@ 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.'
|
||||
|
||||
See the next item.
|
||||
|
||||
* `error MSB8020: The build tools for Visual Studio 2010 (Platform Toolset = 'v100') cannot be found.`
|
||||
|
||||
* If you're building atom with Visual Studio 2013 try executing the following
|
||||
command in your Git shell and then re-run `script/build`:
|
||||
|
||||
```
|
||||
$env:GYP_MSVS_VERSION=2013
|
||||
```
|
||||
|
||||
* Other `node-gyp` errors on first build attempt, even though the right node and python versions are installed.
|
||||
* Do try the build command one more time, as experience shows it often works on second try in many of these cases.
|
||||
|
||||
|
||||
@@ -25,3 +25,5 @@ unless process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
|
||||
module.exports.View = View
|
||||
module.exports.WorkspaceView = require '../src/workspace-view'
|
||||
module.exports.Workspace = require '../src/workspace'
|
||||
module.exports.React = require 'react-atom-fork'
|
||||
module.exports.Reactionary = require 'reactionary-atom-fork'
|
||||
|
||||
+43
-43
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atom",
|
||||
"productName": "Atom",
|
||||
"version": "0.120.0",
|
||||
"version": "0.121.0",
|
||||
"description": "A hackable text editor for the 21st Century.",
|
||||
"main": "./src/browser/main.js",
|
||||
"repository": {
|
||||
@@ -17,108 +17,108 @@
|
||||
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"atomShellVersion": "0.13.3",
|
||||
"atomShellVersion": "0.15.3",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^0.28.0",
|
||||
"atom-keymap": "^1.0.2",
|
||||
"bootstrap": "git+https://github.com/atom/bootstrap.git#6af81906189f1747fd6c93479e3d998ebe041372",
|
||||
"clear-cut": "0.4.0",
|
||||
"coffee-script": "1.7.0",
|
||||
"coffeestack": "0.7.0",
|
||||
"delegato": "^1",
|
||||
"emissary": "^1.2.1",
|
||||
"first-mate": "^1.7.1",
|
||||
"fs-plus": "^2.2.4",
|
||||
"emissary": "^1.2.2",
|
||||
"first-mate": "^2.0.1",
|
||||
"fs-plus": "^2.2.6",
|
||||
"fstream": "0.1.24",
|
||||
"fuzzaldrin": "^1.1",
|
||||
"git-utils": "^1.6",
|
||||
"git-utils": "^2.1.3",
|
||||
"grim": "0.11.0",
|
||||
"guid": "0.0.10",
|
||||
"jasmine-tagged": "^1.1.2",
|
||||
"less-cache": "0.13.0",
|
||||
"mixto": "^1",
|
||||
"mkdirp": "0.3.5",
|
||||
"nslog": "0.5.0",
|
||||
"oniguruma": "^1.0.6",
|
||||
"nslog": "^1.0.1",
|
||||
"oniguruma": "^3.0.3",
|
||||
"optimist": "0.4.0",
|
||||
"pathwatcher": "^1.5",
|
||||
"pathwatcher": "^2.0.6",
|
||||
"property-accessors": "^1",
|
||||
"q": "^1.0.1",
|
||||
"random-words": "0.0.1",
|
||||
"react-atom-fork": "^0.10.0",
|
||||
"reactionary-atom-fork": "^0.9.0",
|
||||
"runas": "0.5.4",
|
||||
"scandal": "0.16.0",
|
||||
"react-atom-fork": "^0.11.2",
|
||||
"reactionary-atom-fork": "^1.0.0",
|
||||
"runas": "1.0.1",
|
||||
"scandal": "1.0.0",
|
||||
"scoped-property-store": "^0.9.0",
|
||||
"scrollbar-style": "^0.4.0",
|
||||
"scrollbar-style": "^1.0.2",
|
||||
"season": "^1.0.2",
|
||||
"semver": "1.1.4",
|
||||
"serializable": "^1",
|
||||
"space-pen": "3.2.0",
|
||||
"temp": "0.7.0",
|
||||
"text-buffer": "^2.4.2",
|
||||
"text-buffer": "^3.0.0",
|
||||
"theorist": "^1",
|
||||
"underscore-plus": "^1.5.0",
|
||||
"underscore-plus": "^1.5.1",
|
||||
"vm-compatibility-layer": "0.1.0"
|
||||
},
|
||||
"packageDependencies": {
|
||||
"atom-dark-syntax": "0.19.0",
|
||||
"atom-dark-ui": "0.32.0",
|
||||
"atom-dark-ui": "0.33.0",
|
||||
"atom-light-syntax": "0.20.0",
|
||||
"atom-light-ui": "0.28.0",
|
||||
"atom-light-ui": "0.29.0",
|
||||
"base16-tomorrow-dark-theme": "0.20.0",
|
||||
"base16-tomorrow-light-theme": "0.4.0",
|
||||
"solarized-dark-syntax": "0.21.0",
|
||||
"solarized-light-syntax": "0.11.0",
|
||||
"archive-view": "0.34.0",
|
||||
"autocomplete": "0.28.0",
|
||||
"solarized-dark-syntax": "0.22.0",
|
||||
"solarized-light-syntax": "0.12.0",
|
||||
"archive-view": "0.35.0",
|
||||
"autocomplete": "0.29.0",
|
||||
"autoflow": "0.17.0",
|
||||
"autosave": "0.14.0",
|
||||
"background-tips": "0.15.0",
|
||||
"bookmarks": "0.27.0",
|
||||
"bracket-matcher": "0.50.0",
|
||||
"bracket-matcher": "0.51.0",
|
||||
"command-palette": "0.24.0",
|
||||
"deprecation-cop": "0.7.0",
|
||||
"dev-live-reload": "0.32.0",
|
||||
"dev-live-reload": "0.33.0",
|
||||
"exception-reporting": "0.19.0",
|
||||
"feedback": "0.33.0",
|
||||
"find-and-replace": "0.127.0",
|
||||
"find-and-replace": "0.128.0",
|
||||
"fuzzy-finder": "0.57.0",
|
||||
"git-diff": "0.37.0",
|
||||
"go-to-line": "0.23.0",
|
||||
"grammar-selector": "0.27.0",
|
||||
"image-view": "0.36.0",
|
||||
"keybinding-resolver": "0.18.0",
|
||||
"link": "0.24.0",
|
||||
"markdown-preview": "0.93.0",
|
||||
"incompatible-packages": "0.5.0",
|
||||
"keybinding-resolver": "0.19.0",
|
||||
"link": "0.25.0",
|
||||
"markdown-preview": "0.95.0",
|
||||
"metrics": "0.33.0",
|
||||
"open-on-github": "0.29.0",
|
||||
"package-generator": "0.31.0",
|
||||
"release-notes": "0.36.0",
|
||||
"settings-view": "0.136.0",
|
||||
"snippets": "0.49.0",
|
||||
"spell-check": "0.38.0",
|
||||
"status-bar": "0.41.0",
|
||||
"settings-view": "0.138.0",
|
||||
"snippets": "0.51.0",
|
||||
"spell-check": "0.40.0",
|
||||
"status-bar": "0.42.0",
|
||||
"styleguide": "0.29.0",
|
||||
"symbols-view": "0.61.0",
|
||||
"tabs": "0.48.0",
|
||||
"symbols-view": "0.63.0",
|
||||
"tabs": "0.49.0",
|
||||
"timecop": "0.22.0",
|
||||
"tree-view": "0.111.0",
|
||||
"tree-view": "0.112.0",
|
||||
"update-package-dependencies": "0.6.0",
|
||||
"welcome": "0.17.0",
|
||||
"whitespace": "0.25.0",
|
||||
"wrap-guide": "0.21.0",
|
||||
|
||||
"language-c": "0.26.0",
|
||||
"language-coffee-script": "0.27.0",
|
||||
"language-coffee-script": "0.29.0",
|
||||
"language-css": "0.17.0",
|
||||
"language-gfm": "0.43.0",
|
||||
"language-gfm": "0.46.0",
|
||||
"language-git": "0.9.0",
|
||||
"language-go": "0.16.0",
|
||||
"language-html": "0.22.0",
|
||||
"language-hyperlink": "0.10.0",
|
||||
"language-java": "0.11.0",
|
||||
"language-javascript": "0.37.0",
|
||||
"language-javascript": "0.39.0",
|
||||
"language-json": "0.8.0",
|
||||
"language-less": "0.13.0",
|
||||
"language-make": "0.10.0",
|
||||
@@ -127,17 +127,17 @@
|
||||
"language-php": "0.15.0",
|
||||
"language-property-list": "0.7.0",
|
||||
"language-python": "0.18.0",
|
||||
"language-ruby": "0.33.0",
|
||||
"language-ruby": "0.34.0",
|
||||
"language-ruby-on-rails": "0.15.0",
|
||||
"language-sass": "0.14.0",
|
||||
"language-shellscript": "0.8.0",
|
||||
"language-source": "0.7.0",
|
||||
"language-source": "0.8.0",
|
||||
"language-sql": "0.9.0",
|
||||
"language-text": "0.6.0",
|
||||
"language-todo": "0.10.0",
|
||||
"language-toml": "0.12.0",
|
||||
"language-xml": "0.17.0",
|
||||
"language-yaml": "0.13.0"
|
||||
"language-yaml": "0.14.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
Arquivo binário não exibido.
Arquivo binário não exibido.
@@ -1167,3 +1167,11 @@ describe "DisplayBuffer", ->
|
||||
|
||||
expect(displayBuffer.getScrollWidth()).toBe 10 * 63 + operatorWidth * 2 + cursorWidth
|
||||
expect(changedSpy.callCount).toBe 1
|
||||
|
||||
describe "::lineNumbersForScreenRows(startRow, endRow)", ->
|
||||
it "returns the line numbers for the given screen row range, inclusive of the endRow", ->
|
||||
displayBuffer.createFold(4, 7)
|
||||
displayBuffer.setEditorWidthInChars(30)
|
||||
displayBuffer.setSoftWrap(true)
|
||||
|
||||
expect(displayBuffer.lineNumbersForScreenRows(6, 11)).toEqual ['4.2', '5', '9', '9.1', '9.2', '10']
|
||||
|
||||
+197
-211
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,581 @@
|
||||
TextBuffer = require 'text-buffer'
|
||||
_ = require 'underscore-plus'
|
||||
EditorPresenter = require '../src/editor-presenter'
|
||||
Editor = require '../src/editor'
|
||||
|
||||
describe "DisplayStateManager", ->
|
||||
[buffer, editor, presenter] = []
|
||||
|
||||
beforeEach ->
|
||||
@addMatchers(toHaveValues: ToHaveValuesMatcher)
|
||||
spyOn(EditorPresenter::, 'getContentTileSize').andReturn 5
|
||||
spyOn(EditorPresenter::, 'getGutterTileSize').andReturn 5
|
||||
|
||||
buffer = new TextBuffer(filePath: atom.project.resolve('sample.js'))
|
||||
buffer.loadSync()
|
||||
buffer.insert([12, 3], '\n' + buffer.getText()) # repeat text so we have more lines
|
||||
|
||||
editor = new Editor({buffer})
|
||||
editor.setLineHeightInPixels(10)
|
||||
editor.setDefaultCharWidth(10)
|
||||
editor.setHeight(100)
|
||||
editor.setWidth(500)
|
||||
|
||||
presenter = new EditorPresenter(editor)
|
||||
|
||||
afterEach ->
|
||||
editor.destroy()
|
||||
|
||||
describe "scrollPosition", ->
|
||||
it "maintains the scrollTop and scrollLeft as top-level presenter properties", ->
|
||||
expect(presenter.scrollTop).toBe 0
|
||||
expect(presenter.scrollLeft).toBe 0
|
||||
editor.setScrollTop(20)
|
||||
editor.setScrollLeft(30)
|
||||
expect(presenter.scrollTop).toBe 20
|
||||
expect(presenter.scrollLeft).toBe 30
|
||||
|
||||
describe "tiles", ->
|
||||
describe "initial state", ->
|
||||
it "renders tiles that overlap the visible row range", ->
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
width: editor.getWidth()
|
||||
height: 5 * 10
|
||||
lineHeightInPixels: 10
|
||||
lines: editor.linesForScreenRows(0, 4)
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
width: editor.getWidth()
|
||||
height: 5 * 10
|
||||
lineHeightInPixels: 10
|
||||
lines: editor.linesForScreenRows(5, 9)
|
||||
10:
|
||||
startRow: 10
|
||||
top: 100
|
||||
width: editor.getWidth()
|
||||
height: 5 * 10
|
||||
lineHeightInPixels: 10
|
||||
lines: editor.linesForScreenRows(10, 14)
|
||||
gutter:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
height: 5 * 10
|
||||
lineHeightInPixels: 10
|
||||
lineNumbers: editor.lineNumbersForScreenRows(0, 4)
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
height: 5 * 10
|
||||
lineHeightInPixels: 10
|
||||
lineNumbers: editor.lineNumbersForScreenRows(5, 9)
|
||||
10:
|
||||
startRow: 10
|
||||
top: 100
|
||||
height: 5 * 10
|
||||
lineHeightInPixels: 10
|
||||
lineNumbers: editor.lineNumbersForScreenRows(10, 14)
|
||||
|
||||
it "renders a dummy gutter tile to maintain the proper gutter width", ->
|
||||
expect(presenter.gutter).toHaveValues
|
||||
dummyTile:
|
||||
dummy: true
|
||||
maxLineNumberDigits: 2
|
||||
|
||||
it "assigns the backgroundColor on the content and gutter tiles, favoring the gutter's background color if it's assigned", ->
|
||||
editor.setBackgroundColor('#ff0')
|
||||
presenter = new EditorPresenter(editor)
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0: backgroundColor: '#ff0'
|
||||
5: backgroundColor: '#ff0'
|
||||
10: backgroundColor: '#ff0'
|
||||
gutter:
|
||||
tiles:
|
||||
0: backgroundColor: '#ff0'
|
||||
5: backgroundColor: '#ff0'
|
||||
10: backgroundColor: '#ff0'
|
||||
|
||||
editor.setGutterBackgroundColor('#a00')
|
||||
presenter = new EditorPresenter(editor)
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0: backgroundColor: '#ff0'
|
||||
5: backgroundColor: '#ff0'
|
||||
10: backgroundColor: '#ff0'
|
||||
gutter:
|
||||
tiles:
|
||||
0: backgroundColor: '#a00'
|
||||
5: backgroundColor: '#a00'
|
||||
10: backgroundColor: '#a00'
|
||||
|
||||
describe "when the width is changed", ->
|
||||
it "updates the line tiles with the new width", ->
|
||||
editor.setWidth(700)
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
width: 700
|
||||
5:
|
||||
width: 700
|
||||
10:
|
||||
width: 700
|
||||
|
||||
describe "when the height is changed", ->
|
||||
it "updates the rendered tiles to reflect the change", ->
|
||||
editor.setHeight(160)
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
10:
|
||||
startRow: 10
|
||||
top: 100
|
||||
15:
|
||||
startRow: 15
|
||||
top: 150
|
||||
gutter:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
10:
|
||||
startRow: 10
|
||||
top: 100
|
||||
15:
|
||||
startRow: 15
|
||||
top: 150
|
||||
|
||||
editor.setHeight(70)
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
gutter:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
|
||||
describe "when lineHeightInPixels changes", ->
|
||||
it "updates the rendered tiles to reflect the change", ->
|
||||
editor.setScrollTop(10)
|
||||
editor.setLineHeightInPixels(7)
|
||||
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
5:
|
||||
startRow: 5
|
||||
top: 7 * 5 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
10:
|
||||
startRow: 10
|
||||
top: 7 * 10 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
15:
|
||||
startRow: 15
|
||||
top: 7 * 15 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
gutter:
|
||||
tiles:
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
5:
|
||||
startRow: 5
|
||||
top: 7 * 5 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
10:
|
||||
startRow: 10
|
||||
top: 7 * 10 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
15:
|
||||
startRow: 15
|
||||
top: 7 * 15 - 10
|
||||
height: 5 * 7
|
||||
lineHeightInPixels: 7
|
||||
|
||||
describe "when scrollTop changes", ->
|
||||
it "updates the rendered tiles to reflect the change", ->
|
||||
editor.setScrollTop(20)
|
||||
expect(presenter).toHaveValues
|
||||
content:tiles:
|
||||
0:
|
||||
top: -20
|
||||
lines: editor.linesForScreenRows(0, 4)
|
||||
5:
|
||||
top: 30
|
||||
lines: editor.linesForScreenRows(5, 9)
|
||||
10:
|
||||
top: 80
|
||||
lines: editor.linesForScreenRows(10, 14)
|
||||
gutter:tiles:
|
||||
0:
|
||||
top: -20
|
||||
lineNumbers: editor.lineNumbersForScreenRows(0, 4)
|
||||
5:
|
||||
top: 30
|
||||
lineNumbers: editor.lineNumbersForScreenRows(5, 9)
|
||||
10:
|
||||
top: 80
|
||||
lineNumbers: editor.lineNumbersForScreenRows(10, 14)
|
||||
|
||||
editor.setScrollTop(70)
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
5:
|
||||
top: -20
|
||||
lines: editor.linesForScreenRows(5, 9)
|
||||
10:
|
||||
top: 30
|
||||
lines: editor.linesForScreenRows(10, 14)
|
||||
15:
|
||||
top: 80
|
||||
lines: editor.linesForScreenRows(15, 19)
|
||||
|
||||
describe "when scrollLeft changes", ->
|
||||
it "updates the rendered tiles to reflect the change", ->
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
left: 0
|
||||
5
|
||||
left: 0
|
||||
10:
|
||||
left: 0
|
||||
|
||||
editor.setScrollLeft(30)
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
left: -30
|
||||
5:
|
||||
left: -30
|
||||
10:
|
||||
left: -30
|
||||
|
||||
describe "when the backgroundColor or gutterBackgroundColor change", ->
|
||||
it "updates the backgroundColor of the tiles", ->
|
||||
editor.setBackgroundColor('#abe')
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0: backgroundColor: '#abe'
|
||||
5: backgroundColor: '#abe'
|
||||
10: backgroundColor: '#abe'
|
||||
gutter:
|
||||
tiles:
|
||||
0: backgroundColor: '#abe'
|
||||
5: backgroundColor: '#abe'
|
||||
10: backgroundColor: '#abe'
|
||||
|
||||
editor.setGutterBackgroundColor('#dad')
|
||||
expect(presenter).toHaveValues
|
||||
content:
|
||||
tiles:
|
||||
0: backgroundColor: '#abe'
|
||||
5: backgroundColor: '#abe'
|
||||
10: backgroundColor: '#abe'
|
||||
gutter:
|
||||
tiles:
|
||||
0: backgroundColor: '#dad'
|
||||
5: backgroundColor: '#dad'
|
||||
10: backgroundColor: '#dad'
|
||||
|
||||
describe "lines", ->
|
||||
describe "initial state", ->
|
||||
it "breaks lines into tiles", ->
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
startRow: 0
|
||||
lines: editor.linesForScreenRows(0, 4)
|
||||
5:
|
||||
startRow: 5
|
||||
lines: editor.linesForScreenRows(5, 9)
|
||||
10:
|
||||
startRow: 10
|
||||
lines: editor.linesForScreenRows(10, 14)
|
||||
|
||||
describe "when the screen lines change", ->
|
||||
it "updates the lines in the tiles to reflect the change", ->
|
||||
buffer.setTextInRange([[3, 5], [7, 0]], "a\nb\nc\nd")
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
startRow: 0
|
||||
lines: editor.linesForScreenRows(0, 4)
|
||||
5:
|
||||
startRow: 5
|
||||
lines: editor.linesForScreenRows(5, 9)
|
||||
10
|
||||
startRow: 10
|
||||
lines: editor.linesForScreenRows(10, 14)
|
||||
|
||||
describe "line decorations", ->
|
||||
marker = null
|
||||
|
||||
beforeEach ->
|
||||
marker = editor.markBufferRange([[3, 4], [5, 6]], invalidate: 'touch')
|
||||
|
||||
describe "initial state", ->
|
||||
it "renders existing line decorations on the appropriate lines", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'test')
|
||||
|
||||
presenter = new EditorPresenter(editor)
|
||||
|
||||
decorationsById = {}
|
||||
decorationsById[decoration.id] = decoration.getParams()
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: decorationsById
|
||||
4: decorationsById
|
||||
5:
|
||||
lineDecorations:
|
||||
5: decorationsById
|
||||
|
||||
describe "when a line decoration is added, updated, invalidated, or removed", ->
|
||||
it "updates the presented line decorations accordingly", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'test')
|
||||
|
||||
decorationsById = {}
|
||||
decorationsById[decoration.id] = decoration.getParams()
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: decorationsById
|
||||
4: decorationsById
|
||||
5:
|
||||
lineDecorations:
|
||||
5: decorationsById
|
||||
|
||||
marker.setBufferRange([[8, 4], [10, 6]])
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: null
|
||||
4: null
|
||||
5:
|
||||
lineDecorations:
|
||||
5: null
|
||||
8: decorationsById
|
||||
9: decorationsById
|
||||
10:
|
||||
lineDecorations:
|
||||
10: decorationsById
|
||||
|
||||
buffer.insert([8, 5], 'invalidate marker')
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
5:
|
||||
lineDecorations:
|
||||
8: null
|
||||
9: null
|
||||
10:
|
||||
lineDecorations:
|
||||
10: null
|
||||
|
||||
buffer.undo()
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
5:
|
||||
lineDecorations:
|
||||
8: decorationsById
|
||||
9: decorationsById
|
||||
10:
|
||||
lineDecorations:
|
||||
10: decorationsById
|
||||
|
||||
marker.destroy()
|
||||
expect(presenter.content.tiles).toHaveValues
|
||||
5:
|
||||
lineDecorations:
|
||||
8: null
|
||||
9: null
|
||||
10:
|
||||
lineDecorations:
|
||||
10: null
|
||||
|
||||
describe "line numbers", ->
|
||||
describe "when the screen lines change", ->
|
||||
it "updates the line numbers to reflect the change", ->
|
||||
editor.createFold(4, 7)
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
0:
|
||||
lineNumbers: editor.lineNumbersForScreenRows(0, 4)
|
||||
5:
|
||||
lineNumbers: editor.lineNumbersForScreenRows(5, 9)
|
||||
10:
|
||||
lineNumbers: editor.lineNumbersForScreenRows(10, 14)
|
||||
|
||||
it "updates the maxLineNumberDigits if necessary", ->
|
||||
buffer.setText('')
|
||||
expect(presenter.gutter.dummyTile.maxLineNumberDigits).toBe 1
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
0:
|
||||
maxLineNumberDigits: 1
|
||||
|
||||
buffer.setText([0..10].join('\n'))
|
||||
expect(presenter.gutter.dummyTile.maxLineNumberDigits).toBe 2
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
0:
|
||||
maxLineNumberDigits: 2
|
||||
5:
|
||||
maxLineNumberDigits: 2
|
||||
10:
|
||||
maxLineNumberDigits: 2
|
||||
|
||||
buffer.delete([[8, 0], [Infinity, 0]])
|
||||
expect(presenter.gutter.dummyTile.maxLineNumberDigits).toBe 1
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
0:
|
||||
maxLineNumberDigits: 1
|
||||
5:
|
||||
maxLineNumberDigits: 1
|
||||
|
||||
describe "line number decorations", ->
|
||||
marker = null
|
||||
|
||||
beforeEach ->
|
||||
marker = editor.markBufferRange([[3, 4], [5, 6]], invalidate: 'touch')
|
||||
|
||||
describe "initial state", ->
|
||||
it "renders existing line number decorations on the appropriate lines", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'gutter', class: 'test')
|
||||
|
||||
presenter = new EditorPresenter(editor)
|
||||
|
||||
decorationsById = {}
|
||||
decorationsById[decoration.id] = decoration.getParams()
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
0:
|
||||
lineNumberDecorations:
|
||||
3: decorationsById
|
||||
4: decorationsById
|
||||
5:
|
||||
lineNumberDecorations:
|
||||
5: decorationsById
|
||||
|
||||
describe "when a line number decorations is added, updated, invalidated, or removed", ->
|
||||
it "updates the presented line decorations accordingly", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'gutter', class: 'test')
|
||||
|
||||
decorationsById = {}
|
||||
decorationsById[decoration.id] = decoration.getParams()
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
0:
|
||||
lineNumberDecorations:
|
||||
3: decorationsById
|
||||
4: decorationsById
|
||||
5:
|
||||
lineNumberDecorations:
|
||||
5: decorationsById
|
||||
|
||||
marker.setBufferRange([[8, 4], [10, 6]])
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
0:
|
||||
lineNumberDecorations:
|
||||
3: null
|
||||
4: null
|
||||
5:
|
||||
lineNumberDecorations:
|
||||
5: null
|
||||
8: decorationsById
|
||||
9: decorationsById
|
||||
10:
|
||||
lineNumberDecorations:
|
||||
10: decorationsById
|
||||
|
||||
buffer.insert([8, 5], 'invalidate marker')
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
5:
|
||||
lineNumberDecorations:
|
||||
8: null
|
||||
9: null
|
||||
10:
|
||||
lineNumberDecorations:
|
||||
10: null
|
||||
|
||||
buffer.undo()
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
5:
|
||||
lineNumberDecorations:
|
||||
8: decorationsById
|
||||
9: decorationsById
|
||||
10:
|
||||
lineNumberDecorations:
|
||||
10: decorationsById
|
||||
|
||||
marker.destroy()
|
||||
expect(presenter.gutter.tiles).toHaveValues
|
||||
5:
|
||||
lineNumberDecorations:
|
||||
8: null
|
||||
9: null
|
||||
10:
|
||||
lineNumberDecorations:
|
||||
10: null
|
||||
|
||||
ToHaveValuesMatcher = (expected) ->
|
||||
hasAllValues = true
|
||||
wrongValues = {}
|
||||
|
||||
checkValues = (actual, expected, keyPath=[]) ->
|
||||
for key, expectedValue of expected
|
||||
key = numericKey if numericKey = parseInt(key)
|
||||
currentKeyPath = keyPath.concat([key])
|
||||
|
||||
if expectedValue?
|
||||
if actual.hasOwnProperty(key)
|
||||
actualValue = actual[key]
|
||||
if expectedValue.constructor is Object and _.size(expectedValue) > 0
|
||||
checkValues(actualValue, expectedValue, currentKeyPath)
|
||||
else
|
||||
unless _.isEqual(actualValue, expectedValue)
|
||||
hasAllValues = false
|
||||
_.setValueForKeyPath(wrongValues, currentKeyPath.join('.'), {actualValue, expectedValue})
|
||||
else
|
||||
hasAllValues = false
|
||||
_.setValueForKeyPath(wrongValues, currentKeyPath.join('.'), {expectedValue})
|
||||
else
|
||||
actualValue = actual[key]
|
||||
if actualValue?
|
||||
hasAllValues = false
|
||||
_.setValueForKeyPath(wrongValues, currentKeyPath.join('.'), {actualValue, expectedValue})
|
||||
|
||||
|
||||
this.message = => "Object did not have expected values: #{jasmine.pp(wrongValues)}"
|
||||
checkValues(@actual, expected)
|
||||
console.warn "Object did not have expected values:", wrongValues unless hasAllValues
|
||||
hasAllValues
|
||||
@@ -3301,20 +3301,25 @@ describe "Editor", ->
|
||||
expect(editor.getText()).toBe ' '
|
||||
|
||||
describe ".scrollToCursorPosition()", ->
|
||||
it "scrolls the last cursor into view", ->
|
||||
it "scrolls the last cursor into view, centering around the cursor if possible and the 'center' option isn't false", ->
|
||||
editor.setCursorScreenPosition([8, 8])
|
||||
editor.setLineHeightInPixels(10)
|
||||
editor.setDefaultCharWidth(10)
|
||||
editor.setHeight(50)
|
||||
editor.setHeight(60)
|
||||
editor.setWidth(50)
|
||||
editor.setHorizontalScrollbarHeight(0)
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
expect(editor.getScrollLeft()).toBe 0
|
||||
|
||||
editor.scrollToCursorPosition()
|
||||
expect(editor.getScrollBottom()).toBe (9 + editor.getVerticalScrollMargin()) * 10
|
||||
expect(editor.getScrollTop()).toBe (8.5 * 10) - 30
|
||||
expect(editor.getScrollBottom()).toBe (8.5 * 10) + 30
|
||||
expect(editor.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10
|
||||
|
||||
editor.setScrollTop(0)
|
||||
editor.scrollToCursorPosition(center: false)
|
||||
expect(editor.getScrollBottom()).toBe (9 + editor.getVerticalScrollMargin()) * 10
|
||||
|
||||
describe ".pageUp/Down()", ->
|
||||
it "scrolls one screen height up or down and moves the cursor one page length", ->
|
||||
editor.manageScrollPosition = true
|
||||
|
||||
@@ -2652,7 +2652,6 @@ describe "EditorView", ->
|
||||
expect(editor.getCursorBufferPosition()).toEqual [0, 0]
|
||||
expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
|
||||
expect(buffer.lineForRow(13)).toBe 'var a = function() {'
|
||||
editor.logScreenLines()
|
||||
expect(editor.isFoldedAtBufferRow(0)).toBe true
|
||||
expect(editor.isFoldedAtBufferRow(13)).toBe true
|
||||
|
||||
|
||||
gerado
externo
gerado
externo
+1
@@ -0,0 +1 @@
|
||||
throw new Error("this simulates a native module's failure to load")
|
||||
gerado
externo
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "native-module",
|
||||
"main": "./main.js"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "package-with-incompatible-native-module",
|
||||
"version": "1.0",
|
||||
"main": "./main.js"
|
||||
}
|
||||
@@ -1,8 +1,39 @@
|
||||
{$} = require 'atom'
|
||||
path = require 'path'
|
||||
Package = require '../src/package'
|
||||
ThemePackage = require '../src/theme-package'
|
||||
|
||||
describe "Package", ->
|
||||
describe "when the package contains incompatible native modules", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'inDevMode').andReturn(false)
|
||||
|
||||
it "does not activate it", ->
|
||||
packagePath = atom.project.resolve('packages/package-with-incompatible-native-module')
|
||||
pack = new Package(packagePath)
|
||||
expect(pack.isCompatible()).toBe false
|
||||
expect(pack.incompatibleModules[0].name).toBe 'native-module'
|
||||
expect(pack.incompatibleModules[0].path).toBe path.join(packagePath, 'node_modules', 'native-module')
|
||||
|
||||
it "caches the incompatible native modules in local storage", ->
|
||||
packagePath = atom.project.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
|
||||
expect(global.localStorage.setItem.callCount).toBe 1
|
||||
|
||||
expect(new Package(packagePath).isCompatible()).toBe false
|
||||
expect(global.localStorage.getItem.callCount).toBe 2
|
||||
expect(global.localStorage.setItem.callCount).toBe 1
|
||||
|
||||
describe "theme", ->
|
||||
theme = null
|
||||
|
||||
|
||||
@@ -130,6 +130,8 @@ afterEach ->
|
||||
atom.project?.destroy()
|
||||
atom.project = null
|
||||
|
||||
atom.themes.removeStylesheet('global-editor-styles')
|
||||
|
||||
delete atom.state.packageStates
|
||||
|
||||
$('#jasmine-content').empty() unless window.debugContent
|
||||
|
||||
@@ -20,7 +20,6 @@ describe "WorkspaceView", ->
|
||||
waitsForPromise ->
|
||||
atom.workspace.open(pathToOpen)
|
||||
|
||||
|
||||
describe "@deserialize()", ->
|
||||
viewState = null
|
||||
|
||||
@@ -294,3 +293,31 @@ describe "WorkspaceView", ->
|
||||
expect(atom.workspaceView).toHaveClass 'scrollbars-visible-always'
|
||||
scrollbarStyle.emitValue 'overlay'
|
||||
expect(atom.workspaceView).toHaveClass 'scrollbars-visible-when-scrolling'
|
||||
|
||||
describe "editor font styling", ->
|
||||
[editorNode, editor] = []
|
||||
|
||||
beforeEach ->
|
||||
atom.workspaceView.attachToDom()
|
||||
editorNode = atom.workspaceView.find('.editor')[0]
|
||||
editor = atom.workspaceView.find('.editor').view().getEditor()
|
||||
|
||||
it "updates the font-size based on the 'editor.fontSize' config value", ->
|
||||
initialCharWidth = editor.getDefaultCharWidth()
|
||||
expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px'
|
||||
atom.config.set('editor.fontSize', atom.config.get('editor.fontSize') + 5)
|
||||
expect(getComputedStyle(editorNode).fontSize).toBe atom.config.get('editor.fontSize') + 'px'
|
||||
expect(editor.getDefaultCharWidth()).toBeGreaterThan initialCharWidth
|
||||
|
||||
it "updates the font-family based on the 'editor.fontFamily' config value", ->
|
||||
initialCharWidth = editor.getDefaultCharWidth()
|
||||
expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily')
|
||||
atom.config.set('editor.fontFamily', 'sans-serif')
|
||||
expect(getComputedStyle(editorNode).fontFamily).toBe atom.config.get('editor.fontFamily')
|
||||
expect(editor.getDefaultCharWidth()).not.toBe initialCharWidth
|
||||
|
||||
it "updates the line-height based on the 'editor.lineHeight' config value", ->
|
||||
initialLineHeight = editor.getLineHeightInPixels()
|
||||
atom.config.set('editor.lineHeight', '30px')
|
||||
expect(getComputedStyle(editorNode).lineHeight).toBe atom.config.get('editor.lineHeight')
|
||||
expect(editor.getLineHeightInPixels()).not.toBe initialLineHeight
|
||||
|
||||
+2
-2
@@ -150,7 +150,7 @@ class Atom extends Model
|
||||
process.env.NODE_PATH = exportsPath
|
||||
|
||||
# Make react.js faster
|
||||
process.env.NODE_ENV ?= 'production'
|
||||
process.env.NODE_ENV ?= 'production' unless devMode
|
||||
|
||||
@config = new Config({configDirPath, resourcePath})
|
||||
@keymaps = new KeymapManager({configDirPath, resourcePath})
|
||||
@@ -438,7 +438,7 @@ class Atom extends Model
|
||||
# width - The {Number} of pixels.
|
||||
# height - The {Number} of pixels.
|
||||
setSize: (width, height) ->
|
||||
ipc.send('call-window-method', 'setSize', width, height)
|
||||
@getCurrentWindow().setSize(width, height)
|
||||
|
||||
# Public: Set the position of current window.
|
||||
#
|
||||
|
||||
@@ -18,11 +18,16 @@ getCachedJavaScript = (cachePath) ->
|
||||
try
|
||||
fs.readFileSync(cachePath, 'utf8')
|
||||
|
||||
convertFilePath = (filePath) ->
|
||||
if process.platform is 'win32'
|
||||
filePath = "/#{path.resolve(filePath).replace(/\\/g, '/')}"
|
||||
encodeURI(filePath)
|
||||
|
||||
compileCoffeeScript = (coffee, filePath, cachePath) ->
|
||||
{js, v3SourceMap} = CoffeeScript.compile(coffee, filename: filePath, sourceMap: true)
|
||||
# Include source map in the web page environment.
|
||||
if btoa? and JSON? and unescape? and encodeURIComponent?
|
||||
js = "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=#{filePath}"
|
||||
js = "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=#{convertFilePath(filePath)}"
|
||||
try
|
||||
fs.writeFileSync(cachePath, js)
|
||||
js
|
||||
|
||||
@@ -8,22 +8,11 @@ CursorComponent = React.createClass
|
||||
|
||||
render: ->
|
||||
{pixelRect, defaultCharWidth} = @props
|
||||
{height, width} = pixelRect
|
||||
{top, left, height, width} = pixelRect
|
||||
width = defaultCharWidth if width is 0
|
||||
WebkitTransform = @getTransform()
|
||||
WebkitTransform = "translate(#{left}px, #{top}px)"
|
||||
|
||||
div className: 'cursor', style: {height, width, WebkitTransform}
|
||||
|
||||
getTransform: ->
|
||||
{pixelRect, scrollTop, scrollLeft, useHardwareAcceleration} = @props
|
||||
{top, left} = pixelRect
|
||||
top -= scrollTop
|
||||
left -= scrollLeft
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(#{left}px, #{top}px, 0px)"
|
||||
else
|
||||
"translate(#{left}px, #{top}px)"
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
not isEqualForProperties(newProps, @props, 'pixelRect', 'scrollTop', 'scrollLeft', 'defaultCharWidth')
|
||||
not isEqualForProperties(newProps, @props, 'pixelRect', 'defaultCharWidth')
|
||||
|
||||
+2
-2
@@ -95,8 +95,8 @@ class Cursor extends Model
|
||||
getBufferPosition: ->
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
autoscroll: (options) ->
|
||||
@editor.scrollToScreenRange(@getScreenRange(), options)
|
||||
|
||||
# Public: If the marker range is empty, the cursor is marked as being visible.
|
||||
updateVisibility: ->
|
||||
|
||||
@@ -12,7 +12,7 @@ CursorsComponent = React.createClass
|
||||
cursorBlinkIntervalHandle: null
|
||||
|
||||
render: ->
|
||||
{performedInitialMeasurement, cursorPixelRects, scrollTop, scrollLeft, defaultCharWidth, useHardwareAcceleration} = @props
|
||||
{performedInitialMeasurement, cursorPixelRects, defaultCharWidth} = @props
|
||||
{blinkOff} = @state
|
||||
|
||||
className = 'cursors'
|
||||
@@ -21,7 +21,7 @@ CursorsComponent = React.createClass
|
||||
div {className},
|
||||
if performedInitialMeasurement
|
||||
for key, pixelRect of cursorPixelRects
|
||||
CursorComponent({key, pixelRect, scrollTop, scrollLeft, defaultCharWidth, useHardwareAcceleration})
|
||||
CursorComponent({key, pixelRect, defaultCharWidth})
|
||||
|
||||
getInitialState: ->
|
||||
blinkOff: false
|
||||
|
||||
@@ -220,8 +220,8 @@ class DisplayBufferMarker
|
||||
@oldTailScreenPosition, newTailScreenPosition,
|
||||
@oldHeadBufferPosition, newHeadBufferPosition,
|
||||
@oldTailBufferPosition, newTailBufferPosition,
|
||||
textChanged,
|
||||
isValid
|
||||
@wasValid, isValid,
|
||||
textChanged
|
||||
}
|
||||
|
||||
@oldHeadBufferPosition = newHeadBufferPosition
|
||||
|
||||
@@ -401,13 +401,45 @@ class DisplayBuffer extends Model
|
||||
# buffer rows corresponding to every screen row in the range
|
||||
#
|
||||
# startScreenRow - The screen row {Number} to start at
|
||||
# endScreenRow - The screen row {Number} to end at (default: the last screen row)
|
||||
# endScreenRow - The screen row {Number} to end at, inclusive.
|
||||
#
|
||||
# Returns an {Array} of buffer rows as {Numbers}s.
|
||||
bufferRowsForScreenRows: (startScreenRow, endScreenRow) ->
|
||||
for screenRow in [startScreenRow..endScreenRow]
|
||||
@rowMap.bufferRowRangeForScreenRow(screenRow)[0]
|
||||
|
||||
# Given starting and ending screen rows, this returns an array of the line
|
||||
# number strings corresponding to every screen row in the range.
|
||||
#
|
||||
# Line numbers start at 1 as opposed to row numbers which start at 0.
|
||||
# Soft wrapped lines are indicated by dot-separated strings. For example, if
|
||||
# line 9 wraps twice, it will appear as '9', '9.1', '9.2'.
|
||||
#
|
||||
# startScreenRow - The screen row {Number} to start at
|
||||
# endScreenRow - The screen row {Number} to end at, inclusive.
|
||||
#
|
||||
# Returns an {Array} of line numbers as {String}s.
|
||||
lineNumbersForScreenRows: (startScreenRow, endScreenRow) ->
|
||||
bufferRows = @bufferRowsForScreenRows(startScreenRow, endScreenRow)
|
||||
|
||||
# Pad the leading
|
||||
leadingSoftWraps = 0
|
||||
while @bufferRowForScreenRow(startScreenRow - leadingSoftWraps - 1) is bufferRows[0]
|
||||
bufferRows.unshift(bufferRows[0])
|
||||
leadingSoftWraps++
|
||||
|
||||
lineNumbers = []
|
||||
for bufferRow in bufferRows
|
||||
lineNumber = (bufferRow + 1).toString()
|
||||
if bufferRow is lastBufferRow
|
||||
lineNumber += ".#{++softWraps}"
|
||||
else
|
||||
lastBufferRow = bufferRow
|
||||
softWraps = 0
|
||||
lineNumbers.push(lineNumber)
|
||||
|
||||
lineNumbers[leadingSoftWraps..]
|
||||
|
||||
# Creates a new fold between two row numbers.
|
||||
#
|
||||
# startRow - The row {Number} to start folding at
|
||||
|
||||
@@ -6,13 +6,11 @@ scrollbarStyle = require 'scrollbar-style'
|
||||
|
||||
GutterComponent = require './gutter-component'
|
||||
InputComponent = require './input-component'
|
||||
CursorsComponent = require './cursors-component'
|
||||
LinesComponent = require './lines-component'
|
||||
ScrollbarComponent = require './scrollbar-component'
|
||||
ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
DummyHighlightDecoration = {id: 'dummy', startPixelPosition: {top: 0, left: 0}, endPixelPosition: {top: 0, left: 0}, decorations: [{class: 'dummy'}]}
|
||||
EditorPresenter = require './editor-presenter'
|
||||
|
||||
module.exports =
|
||||
EditorComponent = React.createClass
|
||||
@@ -43,7 +41,7 @@ EditorComponent = React.createClass
|
||||
scrollSensitivity: 0.4
|
||||
heightAndWidthMeasurementRequested: false
|
||||
measureLineHeightAndDefaultCharWidthWhenShown: false
|
||||
remeasureCharacterWidthsIfVisibleAfterNextUpdate: false
|
||||
remeasureCharacterWidthsWhenShown: false
|
||||
inputEnabled: true
|
||||
scopedCharacterWidthsChangeCount: null
|
||||
domPollingInterval: 100
|
||||
@@ -51,13 +49,14 @@ EditorComponent = React.createClass
|
||||
domPollingPaused: false
|
||||
|
||||
render: ->
|
||||
{focused, fontSize, lineHeight, fontFamily, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state
|
||||
{focused, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state
|
||||
{editor, mini, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
||||
contentPresenter = @presenter.content
|
||||
gutterPresenter = @presenter.gutter
|
||||
maxLineNumberDigits = editor.getLineCount().toString().length
|
||||
invisibles = if showInvisibles and not mini then @state.invisibles else {}
|
||||
hasSelection = editor.getSelection()? and !editor.getSelection().isEmpty()
|
||||
style = {fontSize, fontFamily}
|
||||
style.lineHeight = lineHeight unless mini
|
||||
style = {}
|
||||
|
||||
if @performedInitialMeasurement
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
@@ -95,7 +94,8 @@ EditorComponent = React.createClass
|
||||
div {className, style, tabIndex: -1},
|
||||
if @shouldRenderGutter()
|
||||
GutterComponent {
|
||||
ref: 'gutter', onMouseDown: @onGutterMouseDown, lineDecorations,
|
||||
ref: 'gutter', gutterPresenter
|
||||
onMouseDown: @onGutterMouseDown, lineDecorations,
|
||||
defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight,
|
||||
scrollTop, scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
|
||||
@useHardwareAcceleration, @performedInitialMeasurement, @backgroundColor, @gutterBackgroundColor
|
||||
@@ -109,18 +109,14 @@ EditorComponent = React.createClass
|
||||
onFocus: @onInputFocused
|
||||
onBlur: @onInputBlurred
|
||||
|
||||
CursorsComponent {
|
||||
scrollTop, scrollLeft, cursorPixelRects, cursorBlinkPeriod, cursorBlinkResumeDelay,
|
||||
lineHeightInPixels, defaultCharWidth, @scopedCharacterWidthsChangeCount, @useHardwareAcceleration,
|
||||
@performedInitialMeasurement
|
||||
}
|
||||
LinesComponent {
|
||||
ref: 'lines',
|
||||
ref: 'lines', contentPresenter,
|
||||
editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations,
|
||||
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
|
||||
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
|
||||
@visible, scrollViewHeight, @scopedCharacterWidthsChangeCount, lineWidth, @useHardwareAcceleration,
|
||||
placeholderText, @performedInitialMeasurement, @backgroundColor
|
||||
placeholderText, @performedInitialMeasurement, @backgroundColor, cursorPixelRects,
|
||||
cursorBlinkPeriod, cursorBlinkResumeDelay
|
||||
}
|
||||
|
||||
ScrollbarComponent
|
||||
@@ -167,7 +163,7 @@ EditorComponent = React.createClass
|
||||
getDefaultProps: ->
|
||||
cursorBlinkPeriod: 800
|
||||
cursorBlinkResumeDelay: 100
|
||||
lineOverdrawMargin: 8
|
||||
lineOverdrawMargin: 15
|
||||
|
||||
componentWillMount: ->
|
||||
@pendingChanges = []
|
||||
@@ -175,6 +171,9 @@ EditorComponent = React.createClass
|
||||
@observeConfig()
|
||||
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
|
||||
|
||||
@presenter = new EditorPresenter(@props.editor)
|
||||
@subscribe @presenter, 'did-change', @requestUpdate
|
||||
|
||||
componentDidMount: ->
|
||||
{editor} = @props
|
||||
|
||||
@@ -184,7 +183,7 @@ EditorComponent = React.createClass
|
||||
@listenForDOMEvents()
|
||||
@listenForCommands()
|
||||
|
||||
@subscribe atom.themes, 'stylesheet-added stylsheet-removed', @onStylesheetsChanged
|
||||
@subscribe atom.themes, 'stylesheet-added stylesheet-removed stylesheet-updated', @onStylesheetsChanged
|
||||
@subscribe scrollbarStyle.changes, @refreshScrollbars
|
||||
|
||||
if @visible = @isVisible()
|
||||
@@ -194,6 +193,7 @@ EditorComponent = React.createClass
|
||||
componentWillUnmount: ->
|
||||
@props.parentView.trigger 'editor:will-be-removed', [@props.parentView]
|
||||
@unsubscribe()
|
||||
window.removeEventListener 'resize', @requestHeightAndWidthMeasurement
|
||||
clearInterval(@domPollingIntervalId)
|
||||
@domPollingIntervalId = null
|
||||
|
||||
@@ -219,20 +219,24 @@ EditorComponent = React.createClass
|
||||
|
||||
if @performedInitialMeasurement
|
||||
@measureScrollbars() if @measuringScrollbars
|
||||
@measureLineHeightAndDefaultCharWidthIfNeeded(prevState)
|
||||
@remeasureCharacterWidthsIfNeeded(prevState)
|
||||
|
||||
performInitialMeasurement: ->
|
||||
console.log "INITIAL MEASUREMENT"
|
||||
@updatesPaused = true
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
@measureHeightAndWidth()
|
||||
@sampleFontStyling()
|
||||
@sampleBackgroundColors()
|
||||
@measureScrollbars()
|
||||
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
|
||||
# @remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
|
||||
@props.editor.setVisible(true)
|
||||
@updatesPaused = false
|
||||
@performedInitialMeasurement = true
|
||||
|
||||
requestUpdate: ->
|
||||
return unless @isMounted()
|
||||
@pauseDOMPolling()
|
||||
|
||||
if @updatesPaused
|
||||
@updateRequestedWhilePaused = true
|
||||
return
|
||||
@@ -241,7 +245,7 @@ EditorComponent = React.createClass
|
||||
@forceUpdate()
|
||||
else unless @updateRequested
|
||||
@updateRequested = true
|
||||
setImmediate =>
|
||||
requestAnimationFrame =>
|
||||
@updateRequested = false
|
||||
@forceUpdate() if @isMounted()
|
||||
|
||||
@@ -255,6 +259,9 @@ EditorComponent = React.createClass
|
||||
@updateRequestedWhilePaused = false
|
||||
@forceUpdate()
|
||||
|
||||
getTopmostDOMNode: ->
|
||||
@props.parentView.element
|
||||
|
||||
getRenderedRowRange: ->
|
||||
{editor, lineOverdrawMargin} = @props
|
||||
[visibleStartRow, visibleEndRow] = editor.getVisibleRowRange()
|
||||
@@ -296,7 +303,9 @@ EditorComponent = React.createClass
|
||||
{cursor} = selection
|
||||
screenRange = cursor.getScreenRange()
|
||||
if renderedStartRow <= screenRange.start.row < renderedEndRow
|
||||
cursorPixelRects[cursor.id] = editor.pixelRectForScreenRange(screenRange)
|
||||
pixelRect = editor.pixelRectForScreenRange(screenRange)
|
||||
pixelRect.startRow = screenRange.start.row
|
||||
cursorPixelRects[cursor.id] = pixelRect
|
||||
cursorPixelRects
|
||||
|
||||
getLineDecorations: (decorationsByMarkerId) ->
|
||||
@@ -346,11 +355,6 @@ EditorComponent = React.createClass
|
||||
decorations: []
|
||||
filteredDecorations[markerId].decorations.push decorationParams
|
||||
|
||||
# At least in Chromium 31, removing the last highlight causes a rendering
|
||||
# artifact where chunks of the lines disappear, so we always leave this
|
||||
# dummy highlight in place to prevent that.
|
||||
filteredDecorations['dummy'] = DummyHighlightDecoration
|
||||
|
||||
filteredDecorations
|
||||
|
||||
observeEditor: ->
|
||||
@@ -521,9 +525,6 @@ EditorComponent = React.createClass
|
||||
parentView.command command, listener
|
||||
|
||||
observeConfig: ->
|
||||
@subscribe atom.config.observe 'editor.fontFamily', @setFontFamily
|
||||
@subscribe atom.config.observe 'editor.fontSize', @setFontSize
|
||||
@subscribe atom.config.observe 'editor.lineHeight', @setLineHeight
|
||||
@subscribe atom.config.observe 'editor.showIndentGuide', @setShowIndentGuide
|
||||
@subscribe atom.config.observe 'editor.invisibles', @setInvisibles
|
||||
@subscribe atom.config.observe 'editor.showInvisibles', @setShowInvisibles
|
||||
@@ -535,7 +536,14 @@ EditorComponent = React.createClass
|
||||
@refs.input.focus()
|
||||
|
||||
onTextInput: (event) ->
|
||||
event.stopPropagation()
|
||||
|
||||
# If we prevent the insertion of a space character, then the browser
|
||||
# interprets the spacebar keypress as a page-down command.
|
||||
event.preventDefault() unless event.data is ' '
|
||||
|
||||
return unless @isInputEnabled()
|
||||
event.reactSkipEventDispatch = true
|
||||
|
||||
{editor} = @props
|
||||
inputNode = event.target
|
||||
@@ -550,9 +558,6 @@ EditorComponent = React.createClass
|
||||
editor.insertText(event.data)
|
||||
inputNode.value = event.data
|
||||
|
||||
# If we prevent the insertion of a space character, then the browser
|
||||
# interprets the spacebar keypress as a page-down command.
|
||||
event.preventDefault() unless event.data is ' '
|
||||
|
||||
onInputFocused: ->
|
||||
@setState(focused: true)
|
||||
@@ -678,10 +683,12 @@ EditorComponent = React.createClass
|
||||
editor.setSelectedScreenRange([tailPosition, [dragRow + 1, 0]])
|
||||
|
||||
onStylesheetsChanged: (stylesheet) ->
|
||||
return unless @performedInitialMeasurement
|
||||
|
||||
@refreshScrollbars() if @containsScrollbarSelector(stylesheet)
|
||||
@sampleFontStyling()
|
||||
@sampleBackgroundColors()
|
||||
@remeasureCharacterWidthsIfVisibleAfterNextUpdate = true
|
||||
@requestUpdate() if @visible
|
||||
@remeasureCharacterWidths()
|
||||
|
||||
onScreenLinesChanged: (change) ->
|
||||
{editor} = @props
|
||||
@@ -773,12 +780,13 @@ EditorComponent = React.createClass
|
||||
resumeDOMPollingAfterDelay: null # created lazily
|
||||
|
||||
pollDOM: ->
|
||||
return if @domPollingPaused or not @isMounted()
|
||||
return if @domPollingPaused or @updateRequested or not @isMounted()
|
||||
|
||||
wasVisible = @visible
|
||||
if @visible = @isVisible()
|
||||
if wasVisible
|
||||
@measureHeightAndWidth()
|
||||
@sampleFontStyling()
|
||||
@sampleBackgroundColors()
|
||||
else
|
||||
@performInitialMeasurement()
|
||||
@@ -821,46 +829,50 @@ EditorComponent = React.createClass
|
||||
clientWidth -= paddingLeft
|
||||
editor.setWidth(clientWidth) if clientWidth > 0
|
||||
|
||||
sampleFontStyling: ->
|
||||
oldFontSize = @fontSize
|
||||
oldFontFamily = @fontFamily
|
||||
oldLineHeight = @lineHeight
|
||||
|
||||
{@fontSize, @fontFamily, @lineHeight} = getComputedStyle(@getTopmostDOMNode())
|
||||
|
||||
if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
|
||||
if (@fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily) and @performedInitialMeasurement
|
||||
@remeasureCharacterWidths()
|
||||
|
||||
sampleBackgroundColors: (suppressUpdate) ->
|
||||
{parentView} = @props
|
||||
{editor, parentView} = @props
|
||||
{showLineNumbers} = @state
|
||||
{backgroundColor} = getComputedStyle(parentView.element)
|
||||
|
||||
if backgroundColor isnt @backgroundColor
|
||||
editor.setBackgroundColor(backgroundColor)
|
||||
@backgroundColor = backgroundColor
|
||||
@requestUpdate() unless suppressUpdate
|
||||
|
||||
if @shouldRenderGutter()
|
||||
gutterBackgroundColor = getComputedStyle(@refs.gutter.getDOMNode()).backgroundColor
|
||||
gutterBackgroundColor = null if gutterBackgroundColor is 'rgba(0, 0, 0, 0)'
|
||||
if gutterBackgroundColor isnt @gutterBackgroundColor
|
||||
editor.setGutterBackgroundColor(backgroundColor)
|
||||
@gutterBackgroundColor = gutterBackgroundColor
|
||||
@requestUpdate() unless suppressUpdate
|
||||
|
||||
measureLineHeightAndDefaultCharWidthIfNeeded: (prevState) ->
|
||||
if not isEqualForProperties(prevState, @state, 'lineHeight', 'fontSize', 'fontFamily')
|
||||
if @visible
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
else
|
||||
@measureLineHeightAndDefaultCharWidthWhenShown = true
|
||||
else if @measureLineHeightAndDefaultCharWidthWhenShown and @visible
|
||||
@measureLineHeightAndDefaultCharWidthWhenShown = false
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
|
||||
measureLineHeightAndDefaultCharWidth: ->
|
||||
@refs.lines.measureLineHeightAndDefaultCharWidth()
|
||||
|
||||
remeasureCharacterWidthsIfNeeded: (prevState) ->
|
||||
if not isEqualForProperties(prevState, @state, 'fontSize', 'fontFamily')
|
||||
if @visible
|
||||
@remeasureCharacterWidths()
|
||||
else
|
||||
@remeasureCharacterWidthsIfVisibleAfterNextUpdate = true
|
||||
else if @remeasureCharacterWidthsIfVisibleAfterNextUpdate and @visible
|
||||
@remeasureCharacterWidthsIfVisibleAfterNextUpdate = false
|
||||
@remeasureCharacterWidths()
|
||||
if @visible
|
||||
@measureLineHeightAndDefaultCharWidthWhenShown = false
|
||||
@refs.lines.measureLineHeightAndDefaultCharWidth()
|
||||
else
|
||||
@measureLineHeightAndDefaultCharWidthWhenShown = true
|
||||
|
||||
remeasureCharacterWidths: ->
|
||||
@refs.lines.remeasureCharacterWidths()
|
||||
if @visible
|
||||
@remeasureCharacterWidthsWhenShown = false
|
||||
@refs.lines.remeasureCharacterWidths()
|
||||
else
|
||||
@remeasureCharacterWidthsWhenShown = true
|
||||
|
||||
measureScrollbars: ->
|
||||
return unless @visible
|
||||
@@ -921,19 +933,22 @@ EditorComponent = React.createClass
|
||||
null
|
||||
|
||||
getFontSize: ->
|
||||
@state.fontSize
|
||||
parseInt(getComputedStyle(@getTopmostDOMNode()).fontSize)
|
||||
|
||||
setFontSize: (fontSize) ->
|
||||
@setState({fontSize})
|
||||
@getTopmostDOMNode().style.fontSize = fontSize + 'px'
|
||||
@sampleFontStyling()
|
||||
|
||||
getFontFamily: ->
|
||||
@state.fontFamily
|
||||
getComputedStyle(@getTopmostDOMNode()).fontFamily
|
||||
|
||||
setFontFamily: (fontFamily) ->
|
||||
@setState({fontFamily})
|
||||
@getTopmostDOMNode().style.fontFamily = fontFamily
|
||||
@sampleFontStyling()
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@setState({lineHeight})
|
||||
@getTopmostDOMNode().style.lineHeight = lineHeight
|
||||
@sampleFontStyling()
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@setState({showIndentGuide})
|
||||
@@ -978,8 +993,8 @@ EditorComponent = React.createClass
|
||||
{clientX, clientY} = event
|
||||
|
||||
linesClientRect = @refs.lines.getDOMNode().getBoundingClientRect()
|
||||
top = clientY - linesClientRect.top
|
||||
left = clientX - linesClientRect.left
|
||||
top = clientY - linesClientRect.top + @presenter.scrollTop
|
||||
left = clientX - linesClientRect.left + @presenter.scrollLeft
|
||||
{top, left}
|
||||
|
||||
getModel: ->
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
{Emitter, Subscriber} = require 'emissary'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
class EditorPresenter
|
||||
Emitter.includeInto(this)
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
constructor: (@editor) ->
|
||||
@content = {tiles: {}}
|
||||
@gutter = {tiles: {}, dummyTile: {dummy: true}}
|
||||
@updateTiles()
|
||||
@updateDummyGutterTile()
|
||||
@scrollTop = @editor.getScrollTop()
|
||||
@scrollLeft = @editor.getScrollLeft()
|
||||
|
||||
@subscribe @editor.$width.changes, @onWidthChanged
|
||||
@subscribe @editor.$height.changes, @onHeightChanged
|
||||
@subscribe @editor.$lineHeightInPixels.changes, @onLineHeightInPixelsChanged
|
||||
@subscribe @editor.$scrollTop.changes, @onScrollTopChanged
|
||||
@subscribe @editor.$scrollLeft.changes, @onScrollLeftChanged
|
||||
@subscribe @editor.$backgroundColor.changes, @onBackgroundColorChanged
|
||||
@subscribe @editor.$gutterBackgroundColor.changes, @onBackgroundColorChanged
|
||||
@subscribe @editor, 'screen-lines-changed', @onScreenLinesChanged
|
||||
@subscribe @editor, 'decoration-added', @onDecorationAdded
|
||||
@subscribe @editor, 'decoration-removed', @onDecorationRemoved
|
||||
@subscribe @editor, 'decoration-changed', @onDecorationChanged
|
||||
|
||||
getContentTileSize: -> 5
|
||||
|
||||
getGutterTileSize: -> 20
|
||||
|
||||
getVisibleRowRange: ->
|
||||
heightInLines = Math.ceil(@editor.getHeight() / @editor.getLineHeightInPixels())
|
||||
startRow = Math.floor(@editor.getScrollTop() / @editor.getLineHeightInPixels())
|
||||
endRow = Math.min(@editor.getLineCount(), startRow + heightInLines)
|
||||
[startRow, endRow]
|
||||
|
||||
contentTileStartRowForRow: (startRow) ->
|
||||
startRow - (startRow % @getContentTileSize())
|
||||
|
||||
gutterTileStartRowForRow: (startRow) ->
|
||||
startRow - (startRow % @getGutterTileSize())
|
||||
|
||||
getContentTileRowRange: ->
|
||||
[startRow, endRow] = @getVisibleRowRange()
|
||||
startRow = @contentTileStartRowForRow(startRow)
|
||||
endRow = @contentTileStartRowForRow(endRow) + @getContentTileSize()
|
||||
[startRow, endRow]
|
||||
|
||||
getGutterTileRowRange: ->
|
||||
[startRow, endRow] = @getVisibleRowRange()
|
||||
startRow = @gutterTileStartRowForRow(startRow)
|
||||
endRow = @gutterTileStartRowForRow(endRow) + @getGutterTileSize()
|
||||
[startRow, endRow]
|
||||
|
||||
updateTiles: (fn) ->
|
||||
@updateContentTiles(fn)
|
||||
@updateGutterTiles(fn)
|
||||
@emit 'did-change'
|
||||
|
||||
updateContentTiles: (fn) ->
|
||||
[startRow, endRow] = @getContentTileRowRange()
|
||||
|
||||
for tileStartRow of @content.tiles
|
||||
delete @content.tiles[tileStartRow] unless startRow <= tileStartRow < endRow
|
||||
|
||||
for tileStartRow in [startRow...endRow] by @getContentTileSize()
|
||||
if existingTile = @content.tiles[tileStartRow]
|
||||
fn?(existingTile)
|
||||
else
|
||||
tileEndRow = tileStartRow + @getContentTileSize()
|
||||
@content.tiles[tileStartRow] = new ContentTilePresenter(@editor, tileStartRow, tileEndRow)
|
||||
|
||||
updateGutterTiles: (fn) ->
|
||||
[startRow, endRow] = @getGutterTileRowRange()
|
||||
|
||||
for tileStartRow of @gutter.tiles
|
||||
delete @gutter.tiles[tileStartRow] unless startRow <= tileStartRow < endRow
|
||||
|
||||
for tileStartRow in [startRow...endRow] by @getGutterTileSize()
|
||||
if existingTile = @gutter.tiles[tileStartRow]
|
||||
fn?(existingTile)
|
||||
else
|
||||
tileEndRow = tileStartRow + @getGutterTileSize()
|
||||
@gutter.tiles[tileStartRow] = new GutterTilePresenter(@editor, tileStartRow, tileEndRow)
|
||||
|
||||
updateDummyGutterTile: ->
|
||||
@gutter.dummyTile.maxLineNumberDigits = @editor.getLineCount().toString().length
|
||||
|
||||
onWidthChanged: =>
|
||||
@updateTiles (tile) -> tile.updateWidth()
|
||||
|
||||
onHeightChanged: =>
|
||||
@updateTiles()
|
||||
|
||||
onLineHeightInPixelsChanged: =>
|
||||
@updateTiles (tile) -> tile.updateLineHeightInPixels()
|
||||
|
||||
onScrollTopChanged: (@scrollTop) =>
|
||||
@updateTiles (tile) -> tile.updateScrollTop()
|
||||
|
||||
onScrollLeftChanged: (@scrollLeft) =>
|
||||
@updateTiles (tile) -> tile.updateScrollLeft()
|
||||
|
||||
onScreenLinesChanged: (change) =>
|
||||
@updateDummyGutterTile() if change.bufferDelta isnt 0
|
||||
@updateTiles (tile) -> tile.onScreenLinesChanged(change)
|
||||
|
||||
onBackgroundColorChanged: =>
|
||||
@updateTiles (tile) -> tile.updateBackgroundColor()
|
||||
|
||||
onDecorationAdded: (marker, decoration) =>
|
||||
@updateTiles (tile) -> tile.onDecorationAdded(decoration)
|
||||
|
||||
onDecorationRemoved: (marker, decoration) =>
|
||||
@updateTiles (tile) -> tile.onDecorationRemoved(decoration)
|
||||
|
||||
onDecorationChanged: (marker, decoration, change) =>
|
||||
@updateTiles (tile) -> tile.onDecorationChanged(decoration, change)
|
||||
|
||||
class ContentTilePresenter
|
||||
constructor: (@editor, @startRow, @endRow) ->
|
||||
@lineDecorations = {}
|
||||
@updateWidth()
|
||||
@updateLineHeightInPixels()
|
||||
@updateScrollTop()
|
||||
@updateScrollLeft()
|
||||
@updateBackgroundColor()
|
||||
@updateLines()
|
||||
@populateDecorations()
|
||||
|
||||
updateWidth: ->
|
||||
@width = @editor.getWidth()
|
||||
|
||||
updateHeight: ->
|
||||
@height = (@endRow - @startRow) * @editor.getLineHeightInPixels()
|
||||
|
||||
updateLineHeightInPixels: ->
|
||||
@lineHeightInPixels = @editor.getLineHeightInPixels()
|
||||
@updateTop()
|
||||
@updateHeight()
|
||||
|
||||
updateScrollTop: ->
|
||||
@updateTop()
|
||||
|
||||
updateScrollLeft: ->
|
||||
@left = 0 - @editor.getScrollLeft()
|
||||
|
||||
updateBackgroundColor: ->
|
||||
@backgroundColor = @editor.getBackgroundColor()
|
||||
|
||||
updateTop: ->
|
||||
@top = @startRow * @editor.getLineHeightInPixels() - @editor.getScrollTop()
|
||||
|
||||
updateLines: ->
|
||||
@lines = @editor.linesForScreenRows(@startRow, @endRow - 1)
|
||||
|
||||
populateDecorations: ->
|
||||
for markerId, decorations of @editor.decorationsForScreenRowRange(@startRow, @endRow)
|
||||
for decoration in decorations
|
||||
@onDecorationAdded(decoration)
|
||||
|
||||
onScreenLinesChanged: (change) ->
|
||||
@updateLines() if change.start < @endRow
|
||||
|
||||
onDecorationAdded: (decoration) ->
|
||||
if decoration.isType('line')
|
||||
@onLineDecorationAdded(decoration)
|
||||
|
||||
onDecorationRemoved: (decoration) ->
|
||||
if decoration.isType('line')
|
||||
@onLineDecorationRemoved(decoration)
|
||||
|
||||
onDecorationChanged: (decoration, change) ->
|
||||
if decoration.isType('line')
|
||||
@onLineDecorationChanged(decoration, change)
|
||||
|
||||
onLineDecorationAdded: (decoration) ->
|
||||
marker = decoration.getMarker()
|
||||
headRow = marker.getHeadScreenPosition().row
|
||||
tailRow = marker.getTailScreenPosition().row
|
||||
valid = marker.isValid()
|
||||
params = decoration.getParams()
|
||||
|
||||
if rowRange = @rowRangeForLineDecoration(params, headRow, tailRow, valid)
|
||||
@addLineDecorations(params, rowRange...)
|
||||
|
||||
onLineDecorationRemoved: (decoration) ->
|
||||
marker = decoration.getMarker()
|
||||
headRow = marker.getHeadScreenPosition().row
|
||||
tailRow = marker.getTailScreenPosition().row
|
||||
valid = true # FIXME: Markers shouldn't always be invalidated when destroyed
|
||||
params = decoration.getParams()
|
||||
|
||||
if rowRange = @rowRangeForLineDecoration(params, headRow, tailRow, valid)
|
||||
@removeLineDecorations(params, rowRange...)
|
||||
|
||||
onLineDecorationChanged: (decoration, change) ->
|
||||
params = decoration.getParams()
|
||||
|
||||
{oldHeadScreenPosition, oldTailScreenPosition, wasValid} = change
|
||||
if rowRange = @rowRangeForLineDecoration(params, oldHeadScreenPosition.row, oldTailScreenPosition.row, wasValid)
|
||||
@removeLineDecorations(params, rowRange...)
|
||||
|
||||
{newHeadScreenPosition, newTailScreenPosition, isValid} = change
|
||||
if rowRange = @rowRangeForLineDecoration(params, newHeadScreenPosition.row, newTailScreenPosition.row, isValid)
|
||||
@addLineDecorations(params, rowRange...)
|
||||
|
||||
addLineDecorations: (params, decorationStartRow, decorationEndRow) ->
|
||||
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
|
||||
for row in [decorationStartRow..decorationEndRow]
|
||||
@lineDecorations[row] ?= {}
|
||||
@lineDecorations[row][params.id] = params
|
||||
|
||||
removeLineDecorations: (params, decorationStartRow, decorationEndRow) ->
|
||||
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
|
||||
for row in [decorationStartRow..decorationEndRow]
|
||||
delete @lineDecorations[row][params.id]
|
||||
delete @lineDecorations[row] if _.size(@lineDecorations[row]) is 0
|
||||
|
||||
rowRangeForLineDecoration: (decoration, headRow, tailRow, valid) ->
|
||||
return unless valid
|
||||
|
||||
startRow = Math.min(headRow, tailRow)
|
||||
endRow = Math.max(headRow, tailRow)
|
||||
[startRow, endRow]
|
||||
|
||||
class GutterTilePresenter
|
||||
constructor: (@editor, @startRow, @endRow) ->
|
||||
@lineNumberDecorations = {}
|
||||
|
||||
@populateDecorations()
|
||||
@updateLineHeightInPixels()
|
||||
@updateScrollTop()
|
||||
@updateBackgroundColor()
|
||||
|
||||
populateDecorations: ->
|
||||
for markerId, decorations of @editor.decorationsForScreenRowRange(@startRow, @endRow)
|
||||
for decoration in decorations
|
||||
@onDecorationAdded(decoration)
|
||||
|
||||
updateLineHeightInPixels: ->
|
||||
@lineHeightInPixels = @editor.getLineHeightInPixels()
|
||||
@updateTop()
|
||||
@updateHeight()
|
||||
@updateLineNumbers()
|
||||
|
||||
updateScrollLeft: -> # NO-OP
|
||||
|
||||
updateScrollTop: ->
|
||||
@updateTop()
|
||||
|
||||
updateBackgroundColor: ->
|
||||
@backgroundColor = @editor.getGutterBackgroundColor() ? @editor.getBackgroundColor()
|
||||
|
||||
updateTop: ->
|
||||
@top = @startRow * @editor.getLineHeightInPixels() - @editor.getScrollTop()
|
||||
|
||||
updateWidth: -> # NO-OP
|
||||
|
||||
updateHeight: ->
|
||||
@height = (@endRow - @startRow) * @editor.getLineHeightInPixels()
|
||||
|
||||
updateLineNumbers: ->
|
||||
@lineNumbers = @editor.lineNumbersForScreenRows(@startRow, @endRow - 1)
|
||||
@maxLineNumberDigits = @editor.getLineCount().toString().length
|
||||
|
||||
updateMaxLineNumberDigits: ->
|
||||
@maxLineNumberDigits = @editor.getLineCount().toString().length
|
||||
|
||||
onScreenLinesChanged: (change) ->
|
||||
if change.bufferDelta isnt 0 or change.screenDelta isnt 0
|
||||
@updateMaxLineNumberDigits()
|
||||
@updateLineNumbers() if change.start < @endRow
|
||||
|
||||
onDecorationAdded: (decoration) ->
|
||||
return unless decoration.isType('gutter')
|
||||
|
||||
marker = decoration.getMarker()
|
||||
headRow = marker.getHeadScreenPosition().row
|
||||
tailRow = marker.getTailScreenPosition().row
|
||||
valid = marker.isValid()
|
||||
params = decoration.getParams()
|
||||
|
||||
if rowRange = @rowRangeForLineNumberDecoration(params, headRow, tailRow, valid)
|
||||
@addLineNumberDecorations(params, rowRange...)
|
||||
|
||||
onDecorationRemoved: (decoration) ->
|
||||
return unless decoration.isType('gutter')
|
||||
|
||||
marker = decoration.getMarker()
|
||||
headRow = marker.getHeadScreenPosition().row
|
||||
tailRow = marker.getTailScreenPosition().row
|
||||
valid = true # FIXME: Markers shouldn't always be invalidated when destroyed
|
||||
params = decoration.getParams()
|
||||
|
||||
if rowRange = @rowRangeForLineNumberDecoration(params, headRow, tailRow, valid)
|
||||
@removeLineNumberDecorations(params, rowRange...)
|
||||
|
||||
onDecorationChanged: (decoration, change) ->
|
||||
return unless decoration.isType('gutter')
|
||||
|
||||
params = decoration.getParams()
|
||||
|
||||
{oldHeadScreenPosition, oldTailScreenPosition, wasValid} = change
|
||||
if rowRange = @rowRangeForLineNumberDecoration(params, oldHeadScreenPosition.row, oldTailScreenPosition.row, wasValid)
|
||||
@removeLineNumberDecorations(params, rowRange...)
|
||||
|
||||
{newHeadScreenPosition, newTailScreenPosition, isValid} = change
|
||||
if rowRange = @rowRangeForLineNumberDecoration(params, newHeadScreenPosition.row, newTailScreenPosition.row, isValid)
|
||||
@addLineNumberDecorations(params, rowRange...)
|
||||
|
||||
addLineNumberDecorations: (params, decorationStartRow, decorationEndRow) ->
|
||||
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
|
||||
for row in [decorationStartRow..decorationEndRow]
|
||||
@lineNumberDecorations[row] ?= {}
|
||||
@lineNumberDecorations[row][params.id] = params
|
||||
|
||||
removeLineNumberDecorations: (params, decorationStartRow, decorationEndRow) ->
|
||||
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
|
||||
for row in [decorationStartRow..decorationEndRow]
|
||||
delete @lineNumberDecorations[row][params.id]
|
||||
delete @lineNumberDecorations[row] if _.size(@lineNumberDecorations[row]) is 0
|
||||
|
||||
rowRangeForLineNumberDecoration: (decoration, headRow, tailRow, valid) ->
|
||||
return unless valid
|
||||
|
||||
startRow = Math.min(headRow, tailRow)
|
||||
endRow = Math.max(headRow, tailRow)
|
||||
[startRow, endRow]
|
||||
@@ -0,0 +1,347 @@
|
||||
{extend, toArray, isEqual, clone} = require 'underscore-plus'
|
||||
Decoration = require './decoration'
|
||||
|
||||
WrapperDiv = document.createElement('div')
|
||||
|
||||
module.exports =
|
||||
class EditorTileComponent
|
||||
top: null
|
||||
left: null
|
||||
height: null
|
||||
width: null
|
||||
lineHeightInPixels: null
|
||||
lineWidths: null
|
||||
backgroundColor: null
|
||||
preserved: false
|
||||
|
||||
constructor: (@presenter) ->
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@lineDecorationsByLineId = {}
|
||||
@cursorPixelRectsById = {}
|
||||
@cursorNodesById = {}
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.dataset.tile = true
|
||||
@domNode.style.position = 'absolute'
|
||||
@domNode.style.overflow = 'hidden'
|
||||
|
||||
@buildLines()
|
||||
@update()
|
||||
|
||||
stateChangedForKeys: ->
|
||||
for key in arguments
|
||||
return true if @prevState?.get(key) isnt @state.get(key)
|
||||
false
|
||||
|
||||
update: ->
|
||||
@updateTransform()
|
||||
@updateWidth()
|
||||
@updateHeight()
|
||||
@updateBackgroundColor()
|
||||
@updateLines()
|
||||
|
||||
# @clearScreenRowCaches() if newProps.lineHeightInPixels isnt @props.lineHeightInPixels
|
||||
# @updateCursors()
|
||||
|
||||
preserve: ->
|
||||
return if @preserved
|
||||
@domNode.style.visibility = 'hidden'
|
||||
@preserved = true
|
||||
|
||||
revive: (@presenter) ->
|
||||
@domNode.style.visibility = ''
|
||||
@visible = true
|
||||
|
||||
updateTransform: ->
|
||||
{left, top} = @presenter
|
||||
unless left is @left and top is @top
|
||||
@domNode.style['-webkit-transform'] = "translate3d(#{left}px, #{top}px, 0px)"
|
||||
@left = left
|
||||
@top = top
|
||||
|
||||
updateWidth: ->
|
||||
{width} = @presenter
|
||||
unless width is @width
|
||||
@domNode.style.width = width + 'px'
|
||||
@width = width
|
||||
|
||||
updateHeight: ->
|
||||
{height} = @presenter
|
||||
unless height is @height
|
||||
@domNode.style.height = height + 'px'
|
||||
@height = height
|
||||
|
||||
updateBackgroundColor: ->
|
||||
{backgroundColor} = @presenter
|
||||
unless backgroundColor is @backgroundColor
|
||||
@domNode.style.backgroundColor = backgroundColor
|
||||
@backgroundColor = backgroundColor
|
||||
|
||||
buildLines: ->
|
||||
{startRow, lines, lineDecorations} = @presenter
|
||||
|
||||
linesHTML = ""
|
||||
for line, i in lines
|
||||
screenRow = startRow + i
|
||||
linesHTML += @buildLineHTML(line, screenRow)
|
||||
@domNode.innerHTML = linesHTML
|
||||
|
||||
for line, i in lines
|
||||
screenRow = startRow + i
|
||||
lineNode = @domNode.children[i]
|
||||
@lineNodesByLineId[line.id] = lineNode
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineDecorationsByLineId[line.id] = clone(lineDecorations[screenRow])
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
updateLines: ->
|
||||
{lines, width} = @presenter
|
||||
@removeLineNodes()
|
||||
@appendOrUpdateVisibleLineNodes()
|
||||
@lineWidths = width
|
||||
|
||||
removeLineNodes: () ->
|
||||
lineIds = new Set
|
||||
lineIds.add(line.id.toString()) for line in @presenter.lines
|
||||
|
||||
for lineId, lineNode of @lineNodesByLineId when not lineIds.has(lineId)
|
||||
screenRow = @screenRowsByLineId[lineId]
|
||||
delete @lineNodesByLineId[lineId]
|
||||
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
|
||||
delete @screenRowsByLineId[lineId]
|
||||
delete @lineDecorationsByLineId[lineId]
|
||||
@domNode.removeChild(lineNode)
|
||||
|
||||
appendOrUpdateVisibleLineNodes: ->
|
||||
{startRow, lines, lineHeightInPixels, width} = @presenter
|
||||
|
||||
newLines = null
|
||||
newLinesHTML = null
|
||||
|
||||
for line, index in lines
|
||||
screenRow = startRow + index
|
||||
|
||||
if @hasLineNode(line.id)
|
||||
@updateLineNode(line, screenRow)
|
||||
else
|
||||
newLines ?= []
|
||||
newLinesHTML ?= ""
|
||||
newLines.push(line)
|
||||
newLinesHTML += @buildLineHTML(line, screenRow)
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
@lineHeightInPixels = lineHeightInPixels
|
||||
@lineWidths = width
|
||||
|
||||
return unless newLines?
|
||||
|
||||
WrapperDiv.innerHTML = newLinesHTML
|
||||
newLineNodes = toArray(WrapperDiv.children)
|
||||
for line, i in newLines
|
||||
lineNode = newLineNodes[i]
|
||||
@lineNodesByLineId[line.id] = lineNode
|
||||
@domNode.appendChild(lineNode)
|
||||
|
||||
updateLineNode: (line, screenRow) ->
|
||||
{startRow, lineHeightInPixels, width} = @presenter
|
||||
|
||||
lineNode = @lineNodesByLineId[line.id]
|
||||
|
||||
unless width is @lineWidths
|
||||
lineNode.style.width = width + 'px'
|
||||
|
||||
unless @screenRowsByLineId[line.id] is screenRow and lineHeightInPixels is @lineHeightInPixels
|
||||
lineNode.style.top = (screenRow - startRow) * lineHeightInPixels + 'px'
|
||||
|
||||
unless @screenRowsByLineId[line.id] is screenRow
|
||||
lineNode.dataset.screenRow = screenRow
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
@updateLineDecorations(lineNode, line, screenRow)
|
||||
|
||||
updateLineDecorations: (lineNode, line, screenRow) ->
|
||||
desiredDecorations = @presenter.lineDecorations[screenRow]
|
||||
|
||||
if currentDecorations = @lineDecorationsByLineId[line.id]
|
||||
for id, decoration of currentDecorations
|
||||
unless desiredDecorations?[id]?
|
||||
lineNode.classList.remove(decoration.class)
|
||||
delete currentDecorations[id]
|
||||
|
||||
if desiredDecorations?
|
||||
currentDecorations = (@lineDecorationsByLineId[line.id] ?= {})
|
||||
for id, decoration of desiredDecorations
|
||||
unless currentDecorations[id]?
|
||||
lineNode.classList.add(decoration.class)
|
||||
currentDecorations[id] = decoration
|
||||
|
||||
clearScreenRowCaches: ->
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
|
||||
hasLineNode: (lineId) ->
|
||||
@lineNodesByLineId.hasOwnProperty(lineId)
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
hasDecoration: (decorations, decoration) ->
|
||||
decorations? and decorations[decoration.id] is decoration
|
||||
|
||||
buildLineHTML: (line, screenRow) ->
|
||||
{startRow, lineHeightInPixels, width} = @presenter
|
||||
{text, fold, isSoftWrapped, indentLevel} = line
|
||||
|
||||
classes = @getLineClasses(screenRow)
|
||||
top = (screenRow - startRow) * lineHeightInPixels
|
||||
style = "position: absolute; top: #{top}px; width: #{width}px;"
|
||||
|
||||
lineHTML = """<div class="#{classes}" style="#{style}">"""
|
||||
|
||||
if text is ""
|
||||
lineHTML += @buildEmptyLineInnerHTML(line)
|
||||
else
|
||||
lineHTML += @buildLineInnerHTML(line)
|
||||
|
||||
lineHTML += '<span class="fold-marker"></span>' if fold?
|
||||
lineHTML += "</div>"
|
||||
lineHTML
|
||||
|
||||
getLineClasses: (screenRow) ->
|
||||
classes = ''
|
||||
if lineDecorationsForScreenRow = @presenter.lineDecorations[screenRow]
|
||||
for id, decoration of lineDecorationsForScreenRow
|
||||
classes += decoration.class + ' '
|
||||
classes + 'line'
|
||||
|
||||
buildEmptyLineInnerHTML: (line) ->
|
||||
invisibles = {}
|
||||
showIndentGuide = false
|
||||
# {showIndentGuide, invisibles} = @props
|
||||
{cr, eol} = invisibles
|
||||
{indentLevel, tabLength} = line
|
||||
|
||||
if showIndentGuide and indentLevel > 0
|
||||
invisiblesToRender = []
|
||||
invisiblesToRender.push(cr) if cr? and line.lineEnding is '\r\n'
|
||||
invisiblesToRender.push(eol) if eol?
|
||||
|
||||
lineHTML = ''
|
||||
for i in [0...indentLevel]
|
||||
lineHTML += "<span class='indent-guide'>"
|
||||
for j in [0...tabLength]
|
||||
if invisible = invisiblesToRender.shift()
|
||||
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
|
||||
else
|
||||
lineHTML += ' '
|
||||
lineHTML += "</span>"
|
||||
|
||||
while invisiblesToRender.length
|
||||
lineHTML += "<span class='invisible-character'>#{invisiblesToRender.shift()}</span>"
|
||||
|
||||
lineHTML
|
||||
else
|
||||
# @buildEndOfLineHTML(line, @props.invisibles) or ' '
|
||||
@buildEndOfLineHTML(line, {}) or ' '
|
||||
|
||||
buildLineInnerHTML: (line) ->
|
||||
# {invisibles, mini, showIndentGuide} = @props
|
||||
invisibles = {}
|
||||
mini = false
|
||||
showIndentGuide = false
|
||||
{tokens, text} = line
|
||||
innerHTML = ""
|
||||
|
||||
scopeStack = []
|
||||
firstTrailingWhitespacePosition = text.search(/\s*$/)
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
||||
for token in tokens
|
||||
innerHTML += @updateScopeStack(scopeStack, token.scopes)
|
||||
hasIndentGuide = not mini and showIndentGuide and (token.hasLeadingWhitespace or (token.hasTrailingWhitespace and lineIsWhitespaceOnly))
|
||||
innerHTML += token.getValueAsHtml({invisibles, hasIndentGuide})
|
||||
|
||||
innerHTML += @popScope(scopeStack) while scopeStack.length > 0
|
||||
innerHTML += @buildEndOfLineHTML(line, invisibles)
|
||||
innerHTML
|
||||
|
||||
buildEndOfLineHTML: (line, invisibles) ->
|
||||
# return '' if @props.mini or line.isSoftWrapped()
|
||||
return '' if line.isSoftWrapped()
|
||||
|
||||
html = ''
|
||||
# Note the lack of '?' in the character checks. A user can set the chars
|
||||
# to an empty string which we will interpret as not-set
|
||||
if invisibles.cr and line.lineEnding is '\r\n'
|
||||
html += "<span class='invisible-character'>#{invisibles.cr}</span>"
|
||||
if invisibles.eol
|
||||
html += "<span class='invisible-character'>#{invisibles.eol}</span>"
|
||||
|
||||
html
|
||||
|
||||
updateScopeStack: (scopeStack, desiredScopes) ->
|
||||
html = ""
|
||||
|
||||
# Find a common prefix
|
||||
for scope, i in desiredScopes
|
||||
break unless scopeStack[i] is desiredScopes[i]
|
||||
|
||||
# Pop scopes until we're at the common prefx
|
||||
until scopeStack.length is i
|
||||
html += @popScope(scopeStack)
|
||||
|
||||
# Push onto common prefix until scopeStack equals desiredScopes
|
||||
for j in [i...desiredScopes.length]
|
||||
html += @pushScope(scopeStack, desiredScopes[j])
|
||||
|
||||
html
|
||||
|
||||
popScope: (scopeStack) ->
|
||||
scopeStack.pop()
|
||||
"</span>"
|
||||
|
||||
pushScope: (scopeStack, scope) ->
|
||||
scopeStack.push(scope)
|
||||
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||
|
||||
updateCursors: ->
|
||||
return
|
||||
{cursorPixelRects, startRow, lineHeightInPixels} = @props
|
||||
|
||||
for id of @cursorPixelRectsById
|
||||
@removeCursorNode(id) unless cursorPixelRects?.hasOwnProperty(id)
|
||||
|
||||
if cursorPixelRects?
|
||||
for id, newPixelRect of cursorPixelRects
|
||||
newPixelRect.top -= startRow * lineHeightInPixels
|
||||
|
||||
if oldPixelRect = @cursorPixelRectsById[id]
|
||||
unless isEqual(oldPixelRect, newPixelRect)
|
||||
@updateCursorNode(id, newPixelRect)
|
||||
else
|
||||
@buildCursorNode(id, newPixelRect)
|
||||
|
||||
updateCursorNode: (id, pixelRect) ->
|
||||
{top, left, height, width} = pixelRect
|
||||
@cursorNodesById[id].style.top = top + 'px'
|
||||
@cursorNodesById[id].style.left = left + 'px'
|
||||
@cursorNodesById[id].style.height = height + 'px'
|
||||
@cursorNodesById[id].style.width = width + 'px'
|
||||
@cursorPixelRectsById[id] = pixelRect
|
||||
|
||||
buildCursorNode: (id, pixelRect) ->
|
||||
cursorNode = document.createElement('div')
|
||||
cursorNode.className = 'cursor'
|
||||
cursorNode.style.position = 'absolute'
|
||||
@cursorNodesById[id] = cursorNode
|
||||
@cursorPixelRectsById[id] = pixelRect
|
||||
@updateCursorNode(id, pixelRect)
|
||||
@domNode.appendChild(cursorNode)
|
||||
|
||||
removeCursorNode: (id) ->
|
||||
@domNode.removeChild(@cursorNodesById[id])
|
||||
delete @cursorPixelRectsById[id]
|
||||
delete @cursorNodesById[id]
|
||||
@@ -641,9 +641,9 @@ class EditorView extends View
|
||||
@scrollBottom(@editor.getScreenLineCount() * @lineHeight)
|
||||
|
||||
# Public: Scrolls the editor to the position of the most recently added
|
||||
# cursor.
|
||||
# cursor if it isn't current on screen.
|
||||
#
|
||||
# The editor is also centered.
|
||||
# The editor is centered around the cursor's position if possible.
|
||||
scrollToCursorPosition: ->
|
||||
@scrollToBufferPosition(@editor.getCursorBufferPosition(), center: true)
|
||||
|
||||
|
||||
+21
-2
@@ -152,6 +152,10 @@ class Editor extends Model
|
||||
updateBatchDepth: 0
|
||||
selectionFlashDuration: 500
|
||||
|
||||
@properties
|
||||
backgroundColor: null
|
||||
gutterBackgroundColor: null
|
||||
|
||||
@delegatesMethods 'suggestedIndentForBufferRow', 'autoIndentBufferRow', 'autoIndentBufferRows',
|
||||
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
|
||||
toProperty: 'languageMode'
|
||||
@@ -591,6 +595,9 @@ class Editor extends Model
|
||||
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
|
||||
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
|
||||
|
||||
# {Delegats to: DisplayBuffer.lineNumbersForScreenRows}
|
||||
lineNumbersForScreenRows: (startRow, endRow) -> @displayBuffer.lineNumbersForScreenRows(startRow, endRow)
|
||||
|
||||
bufferRowForScreenRow: (row) -> @displayBuffer.bufferRowForScreenRow(row)
|
||||
|
||||
# Public: Get the syntactic scopes for the given position in buffer
|
||||
@@ -1588,8 +1595,14 @@ class Editor extends Model
|
||||
moveCursorToBeginningOfPreviousParagraph: ->
|
||||
@moveCursors (cursor) -> cursor.moveToBeginningOfPreviousParagraph()
|
||||
|
||||
scrollToCursorPosition: ->
|
||||
@getCursor().autoscroll()
|
||||
# Public: Scroll the editor to reveal the most recently added cursor if it is
|
||||
# off-screen.
|
||||
#
|
||||
# options - An optional hash of options.
|
||||
# :center - Center the editor around the cursor if possible. Defauls to
|
||||
# true.
|
||||
scrollToCursorPosition: (options) ->
|
||||
@getCursor().autoscroll(center: options?.center ? true)
|
||||
|
||||
pageUp: ->
|
||||
newScrollTop = @getScrollTop() - @getHeight()
|
||||
@@ -2019,6 +2032,12 @@ class Editor extends Model
|
||||
getScrollHeight: -> @displayBuffer.getScrollHeight()
|
||||
getScrollWidth: (scrollWidth) -> @displayBuffer.getScrollWidth(scrollWidth)
|
||||
|
||||
setBackgroundColor: (@backgroundColor) -> @backgroundColor
|
||||
getBackgroundColor: -> @backgroundColor
|
||||
|
||||
setGutterBackgroundColor: (@gutterBackgroundColor) -> @gutterBackgroundColor
|
||||
getGutterBackgroundColor: -> @gutterBackgroundColor
|
||||
|
||||
getVisibleRowRange: -> @displayBuffer.getVisibleRowRange()
|
||||
|
||||
intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow)
|
||||
|
||||
+35
-174
@@ -4,6 +4,7 @@ React = require 'react-atom-fork'
|
||||
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||
Decoration = require './decoration'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
GutterTileComponent = require './gutter-tile-component'
|
||||
|
||||
WrapperDiv = document.createElement('div')
|
||||
|
||||
@@ -16,196 +17,51 @@ GutterComponent = React.createClass
|
||||
measuredWidth: null
|
||||
|
||||
render: ->
|
||||
{scrollHeight, scrollViewHeight, onMouseDown, backgroundColor, gutterBackgroundColor} = @props
|
||||
|
||||
if gutterBackgroundColor isnt 'rbga(0, 0, 0, 0)'
|
||||
backgroundColor = gutterBackgroundColor
|
||||
|
||||
div className: 'gutter', onClick: @onClick, onMouseDown: onMouseDown,
|
||||
div className: 'line-numbers', ref: 'lineNumbers', style:
|
||||
height: Math.max(scrollHeight, scrollViewHeight)
|
||||
WebkitTransform: @getTransform()
|
||||
backgroundColor: backgroundColor
|
||||
|
||||
getTransform: ->
|
||||
{scrollTop, useHardwareAcceleration} = @props
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(0px, #{-scrollTop}px, 0px)"
|
||||
else
|
||||
"translate(0px, #{-scrollTop}px)"
|
||||
div className: 'gutter', onClick: @onClick, onMouseDown: @props.onMouseDown
|
||||
|
||||
componentWillMount: ->
|
||||
@lineNumberNodesById = {}
|
||||
@lineNumberIdsByScreenRow = {}
|
||||
@screenRowsByLineNumberId = {}
|
||||
@renderedDecorationsByLineNumberId = {}
|
||||
@tileComponentsByStartRow = {}
|
||||
|
||||
componentDidMount: ->
|
||||
@appendDummyLineNumber()
|
||||
@updateLineNumbers() if @props.performedInitialMeasurement
|
||||
|
||||
# Only update the gutter if the visible row range has changed or if a
|
||||
# non-zero-delta change to the screen lines has occurred within the current
|
||||
# visible row range.
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return true unless isEqualForProperties(newProps, @props,
|
||||
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow', 'lineDecorations',
|
||||
'scrollViewHeight', 'useHardwareAcceleration', 'backgroundColor', 'gutterBackgroundColor'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges, lineDecorations} = newProps
|
||||
return false unless renderedRowRange?
|
||||
|
||||
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
|
||||
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
||||
|
||||
false
|
||||
@appendDummyTile()
|
||||
@getDOMNode().addEventListener 'mousewheel', @onMouseWheel
|
||||
|
||||
componentDidUpdate: (oldProps) ->
|
||||
return unless @props.performedInitialMeasurement
|
||||
if @props.performedInitialMeasurement
|
||||
@updateTiles()
|
||||
|
||||
unless isEqualForProperties(oldProps, @props, 'maxLineNumberDigits')
|
||||
@updateDummyLineNumber()
|
||||
@removeLineNumberNodes()
|
||||
updateTiles: ->
|
||||
{gutterPresenter} = @props
|
||||
|
||||
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
|
||||
@updateLineNumbers()
|
||||
domNode = @getDOMNode()
|
||||
|
||||
clearScreenRowCaches: ->
|
||||
@lineNumberIdsByScreenRow = {}
|
||||
@screenRowsByLineNumberId = {}
|
||||
for tileStartRow, tileComponent of @tileComponentsByStartRow
|
||||
unless gutterPresenter.tiles[tileStartRow]?
|
||||
if tileComponent.domNode is @preservedTileNode
|
||||
tileComponent.preserve()
|
||||
else
|
||||
domNode.removeChild(tileComponent.domNode)
|
||||
delete @tileComponentsByStartRow[tileStartRow]
|
||||
|
||||
for tileStartRow, tilePresenter of gutterPresenter.tiles
|
||||
if tileComponent = @tileComponentsByStartRow[tileStartRow]
|
||||
tileComponent = @tileComponentsByStartRow[tileStartRow]
|
||||
tileComponent.revive(tilePresenter) if tileComponent.preserved
|
||||
tileComponent.update()
|
||||
else
|
||||
tileComponent = new GutterTileComponent(tilePresenter)
|
||||
@tileComponentsByStartRow[tileStartRow] = tileComponent
|
||||
domNode.appendChild(tileComponent.domNode)
|
||||
|
||||
# This dummy line number element holds the gutter to the appropriate width,
|
||||
# since the real line numbers are absolutely positioned for performance reasons.
|
||||
appendDummyLineNumber: ->
|
||||
{maxLineNumberDigits} = @props
|
||||
WrapperDiv.innerHTML = @buildLineNumberHTML(-1, false, maxLineNumberDigits)
|
||||
@dummyLineNumberNode = WrapperDiv.children[0]
|
||||
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
|
||||
appendDummyTile: ->
|
||||
@dummyTileComponent = new GutterTileComponent(@props.gutterPresenter.dummyTile)
|
||||
@getDOMNode().appendChild(@dummyTileComponent.domNode)
|
||||
|
||||
updateDummyLineNumber: ->
|
||||
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false, @props.maxLineNumberDigits)
|
||||
|
||||
updateLineNumbers: ->
|
||||
lineNumberIdsToPreserve = @appendOrUpdateVisibleLineNumberNodes()
|
||||
@removeLineNumberNodes(lineNumberIdsToPreserve)
|
||||
|
||||
appendOrUpdateVisibleLineNumberNodes: ->
|
||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, lineDecorations} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
newLineNumberIds = null
|
||||
newLineNumbersHTML = null
|
||||
visibleLineNumberIds = new Set
|
||||
|
||||
wrapCount = 0
|
||||
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
|
||||
screenRow = startRow + index
|
||||
|
||||
if bufferRow is lastBufferRow
|
||||
id = "#{bufferRow}-#{wrapCount++}"
|
||||
else
|
||||
id = bufferRow.toString()
|
||||
lastBufferRow = bufferRow
|
||||
wrapCount = 0
|
||||
|
||||
visibleLineNumberIds.add(id)
|
||||
|
||||
if @hasLineNumberNode(id)
|
||||
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0)
|
||||
else
|
||||
newLineNumberIds ?= []
|
||||
newLineNumbersHTML ?= ""
|
||||
newLineNumberIds.push(id)
|
||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
|
||||
@screenRowsByLineNumberId[id] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = id
|
||||
|
||||
@renderedDecorationsByLineNumberId[id] = lineDecorations[screenRow]
|
||||
|
||||
if newLineNumberIds?
|
||||
WrapperDiv.innerHTML = newLineNumbersHTML
|
||||
newLineNumberNodes = toArray(WrapperDiv.children)
|
||||
|
||||
node = @refs.lineNumbers.getDOMNode()
|
||||
for lineNumberId, i in newLineNumberIds
|
||||
lineNumberNode = newLineNumberNodes[i]
|
||||
@lineNumberNodesById[lineNumberId] = lineNumberNode
|
||||
node.appendChild(lineNumberNode)
|
||||
|
||||
visibleLineNumberIds
|
||||
|
||||
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
|
||||
{mouseWheelScreenRow} = @props
|
||||
node = @refs.lineNumbers.getDOMNode()
|
||||
for lineNumberId, lineNumberNode of @lineNumberNodesById when not lineNumberIdsToPreserve?.has(lineNumberId)
|
||||
screenRow = @screenRowsByLineNumberId[lineNumberId]
|
||||
if not screenRow? or screenRow isnt mouseWheelScreenRow
|
||||
delete @lineNumberNodesById[lineNumberId]
|
||||
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
|
||||
delete @screenRowsByLineNumberId[lineNumberId]
|
||||
delete @renderedDecorationsByLineNumberId[lineNumberId]
|
||||
node.removeChild(lineNumberNode)
|
||||
|
||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
|
||||
{editor, lineHeightInPixels, lineDecorations} = @props
|
||||
if screenRow?
|
||||
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
|
||||
else
|
||||
style = "visibility: hidden;"
|
||||
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
|
||||
|
||||
classes = ''
|
||||
if lineDecorations? and decorations = lineDecorations[screenRow]
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'gutter')
|
||||
classes += decoration.class + ' '
|
||||
|
||||
classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
|
||||
classes += "line-number line-number-#{bufferRow}"
|
||||
|
||||
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
|
||||
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
|
||||
if softWrapped
|
||||
lineNumber = "•"
|
||||
else
|
||||
lineNumber = (bufferRow + 1).toString()
|
||||
|
||||
padding = multiplyString(' ', maxLineNumberDigits - lineNumber.length)
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
padding + lineNumber + iconHTML
|
||||
|
||||
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped) ->
|
||||
{editor, lineDecorations} = @props
|
||||
node = @lineNumberNodesById[lineNumberId]
|
||||
|
||||
if editor.isFoldableAtBufferRow(bufferRow)
|
||||
node.classList.add('foldable')
|
||||
else
|
||||
node.classList.remove('foldable')
|
||||
|
||||
decorations = lineDecorations[screenRow]
|
||||
previousDecorations = @renderedDecorationsByLineNumberId[lineNumberId]
|
||||
|
||||
if previousDecorations?
|
||||
for id, decoration of previousDecorations
|
||||
if Decoration.isType(decoration, 'gutter') and not @hasDecoration(decorations, decoration)
|
||||
node.classList.remove(decoration.class)
|
||||
|
||||
if decorations?
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'gutter') and not @hasDecoration(previousDecorations, decoration)
|
||||
node.classList.add(decoration.class)
|
||||
|
||||
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
||||
{lineHeightInPixels} = @props
|
||||
node.style.top = screenRow * lineHeightInPixels + 'px'
|
||||
node.dataset.screenRow = screenRow
|
||||
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
|
||||
|
||||
hasDecoration: (decorations, decoration) ->
|
||||
decorations? and decorations[decoration.id] is decoration
|
||||
|
||||
@@ -226,3 +82,8 @@ GutterComponent = React.createClass
|
||||
editor.unfoldBufferRow(bufferRow)
|
||||
else
|
||||
editor.foldBufferRow(bufferRow)
|
||||
|
||||
onMouseWheel: (event) ->
|
||||
node = event.target
|
||||
node = node.parentNode until not node? or node.dataset.tile
|
||||
@preservedTileNode = node
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
{toArray, multiplyString, clone} = require 'underscore-plus'
|
||||
WrapperDiv = document.createElement('div')
|
||||
|
||||
module.exports =
|
||||
class GutterTileComponent
|
||||
preserved: false
|
||||
|
||||
constructor: (@presenter) ->
|
||||
@lineNumberNodesById = {}
|
||||
@screenRowsByLineNumberId = {}
|
||||
@lineNumberDecorationsByLineNumberId = {}
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.dataset.tile = true
|
||||
@domNode.style.overflow = 'hidden'
|
||||
if @presenter.dummy
|
||||
@domNode.style.visibility = 'hidden'
|
||||
else
|
||||
@domNode.style.position = 'absolute'
|
||||
@domNode.style.top = '0px'
|
||||
|
||||
@appendDummyLineNumber()
|
||||
@update()
|
||||
|
||||
appendDummyLineNumber: ->
|
||||
WrapperDiv.innerHTML = @buildLineNumberHTML('0')
|
||||
@domNode.appendChild(WrapperDiv.firstChild)
|
||||
|
||||
update: ->
|
||||
return if @presenter.dummy
|
||||
@updateTransform()
|
||||
@updateHeight()
|
||||
@updateBackgroundColor()
|
||||
@updateLineNumbers()
|
||||
|
||||
preserve: ->
|
||||
return if @preserved
|
||||
@domNode.style.visibility = 'hidden'
|
||||
@preserved = true
|
||||
|
||||
revive: (@presenter) ->
|
||||
@domNode.style.visibility = ''
|
||||
@visible = true
|
||||
|
||||
updateTransform: ->
|
||||
{top} = @presenter
|
||||
unless top is @top
|
||||
@domNode.style['-webkit-transform'] = "translate3d(0px, #{top}px, 0px)"
|
||||
@top = top
|
||||
|
||||
updateHeight: ->
|
||||
{height} = @presenter
|
||||
unless height is @height
|
||||
@domNode.style.height = height + 'px'
|
||||
@height = height
|
||||
|
||||
updateBackgroundColor: ->
|
||||
{backgroundColor} = @presenter
|
||||
unless backgroundColor is @backgroundColor
|
||||
@domNode.style.backgroundColor = backgroundColor
|
||||
@backgroundColor = backgroundColor
|
||||
|
||||
updateLineNumbers: ->
|
||||
{lineNumbers, lineNumberDecorations} = @presenter
|
||||
|
||||
lineNumbersSet = new Set
|
||||
lineNumbersSet.add(lineNumber) for lineNumber in lineNumbers
|
||||
|
||||
for lineNumberId, lineNumberNode of @lineNumberNodesById
|
||||
unless lineNumbersSet.has(lineNumberId)
|
||||
delete @lineNumberNodesById[lineNumberId]
|
||||
delete @screenRowsByLineNumberId[lineNumberId]
|
||||
delete @lineNumberDecorationsByLineNumberId[lineNumberId]
|
||||
@domNode.removeChild(lineNumberNode)
|
||||
|
||||
newLineNumberIds = []
|
||||
newLineNumbersHTML = ""
|
||||
|
||||
for lineNumberId, i in lineNumbers
|
||||
screenRow = @presenter.startRow + i
|
||||
|
||||
if @lineNumberNodesById[lineNumberId]?
|
||||
@updateLineNumberNode(lineNumberId, screenRow)
|
||||
else
|
||||
newLineNumberIds.push(lineNumberId)
|
||||
newLineNumbersHTML += @buildLineNumberHTML(lineNumberId, screenRow)
|
||||
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
||||
# @lineNumberIdsByScreenRow[screenRow] = id
|
||||
@lineNumberDecorationsByLineNumberId[lineNumberId] = clone(lineNumberDecorations[screenRow])
|
||||
|
||||
if newLineNumberIds.length > 0
|
||||
WrapperDiv.innerHTML = newLineNumbersHTML
|
||||
newLineNumberNodes = toArray(WrapperDiv.children)
|
||||
|
||||
for lineNumberId, i in newLineNumberIds
|
||||
@lineNumberNodesById[lineNumberId] = newLineNumberNodes[i]
|
||||
@lineNumberDecorationsByLineNumberId
|
||||
@domNode.appendChild(newLineNumberNodes[i])
|
||||
|
||||
buildLineNumberHTML: (lineNumberId, screenRow) ->
|
||||
{lineHeightInPixels, lineNumberDecorations} = @presenter
|
||||
|
||||
if screenRow?
|
||||
top = (screenRow - @presenter.startRow) * lineHeightInPixels
|
||||
style = "position: absolute; top: #{top}px;"
|
||||
else
|
||||
style = "visibility: hidden;"
|
||||
|
||||
innerHTML = @buildLineNumberInnerHTML(lineNumberId)
|
||||
|
||||
classes = ''
|
||||
if decorationsById = lineNumberDecorations?[screenRow]
|
||||
for id, decoration of decorationsById
|
||||
classes += decoration.class + ' '
|
||||
|
||||
# classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
|
||||
classes += "line-number line-number-#{lineNumberId.replace(/\..*/, '')}"
|
||||
|
||||
"<div class=\"#{classes}\" style=\"#{style}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
|
||||
buildLineNumberInnerHTML: (lineNumberId) ->
|
||||
{maxLineNumberDigits} = @presenter
|
||||
softWrapped = lineNumberId.indexOf('.') isnt -1
|
||||
|
||||
if softWrapped
|
||||
lineNumber = "•"
|
||||
else
|
||||
lineNumber = lineNumberId
|
||||
|
||||
padding = multiplyString(' ', maxLineNumberDigits - lineNumber.length)
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
padding + lineNumber + iconHTML
|
||||
|
||||
updateLineNumberNode: (lineNumberId, screenRow) ->
|
||||
{lineDecorations} = @presenter
|
||||
node = @lineNumberNodesById[lineNumberId]
|
||||
|
||||
# if editor.isFoldableAtBufferRow(bufferRow)
|
||||
# node.classList.add('foldable')
|
||||
# else
|
||||
# node.classList.remove('foldable')
|
||||
|
||||
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
||||
{lineHeightInPixels} = @presenter
|
||||
node.style.top = screenRow * lineHeightInPixels + 'px'
|
||||
node.dataset.screenRow = screenRow
|
||||
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
||||
# @lineNumberIdsByScreenRow[screenRow] = lineNumberId
|
||||
|
||||
@updateLineNumberDecorations(node, lineNumberId, screenRow)
|
||||
|
||||
updateLineNumberDecorations: (lineNumberNode, lineNumberId, screenRow) ->
|
||||
desiredDecorations = @presenter.lineNumberDecorations[screenRow]
|
||||
|
||||
if currentDecorations = @lineNumberDecorationsByLineNumberId[lineNumberId]
|
||||
for id, decoration of currentDecorations
|
||||
unless desiredDecorations?[id]?
|
||||
lineNumberNode.classList.remove(decoration.class)
|
||||
delete currentDecorations[id]
|
||||
|
||||
if desiredDecorations?
|
||||
currentDecorations = (@lineNumberDecorationsByLineNumberId[lineNumberId] ?= {})
|
||||
for id, decoration of desiredDecorations
|
||||
unless currentDecorations[id]?
|
||||
lineNumberNode.classList.add(decoration.class)
|
||||
currentDecorations[id] = decoration
|
||||
+10
-10
@@ -43,12 +43,12 @@ class LanguageMode
|
||||
commentStartRegex = new OnigRegExp("^(\\s*)(#{commentStartRegexString})")
|
||||
|
||||
if commentEndString
|
||||
shouldUncomment = commentStartRegex.test(buffer.lineForRow(start))
|
||||
shouldUncomment = commentStartRegex.testSync(buffer.lineForRow(start))
|
||||
if shouldUncomment
|
||||
commentEndRegexString = _.escapeRegExp(commentEndString).replace(/^(\s+)/, '(?:$1)?')
|
||||
commentEndRegex = new OnigRegExp("(#{commentEndRegexString})(\\s*)$")
|
||||
startMatch = commentStartRegex.search(buffer.lineForRow(start))
|
||||
endMatch = commentEndRegex.search(buffer.lineForRow(end))
|
||||
startMatch = commentStartRegex.searchSync(buffer.lineForRow(start))
|
||||
endMatch = commentEndRegex.searchSync(buffer.lineForRow(end))
|
||||
if startMatch and endMatch
|
||||
buffer.transact ->
|
||||
columnStart = startMatch[1].length
|
||||
@@ -72,13 +72,13 @@ class LanguageMode
|
||||
blank = line?.match(/^\s*$/)
|
||||
|
||||
allBlank = false unless blank
|
||||
allBlankOrCommented = false unless blank or commentStartRegex.test(line)
|
||||
allBlankOrCommented = false unless blank or commentStartRegex.testSync(line)
|
||||
|
||||
shouldUncomment = allBlankOrCommented and not allBlank
|
||||
|
||||
if shouldUncomment
|
||||
for row in [start..end]
|
||||
if match = commentStartRegex.search(buffer.lineForRow(row))
|
||||
if match = commentStartRegex.searchSync(buffer.lineForRow(row))
|
||||
columnStart = match[1].length
|
||||
columnEnd = columnStart + match[2].length
|
||||
buffer.setTextInRange([[row, columnStart], [row, columnEnd]], "")
|
||||
@@ -168,7 +168,7 @@ class LanguageMode
|
||||
continue if @editor.isBufferRowBlank(row)
|
||||
indentation = @editor.indentationForBufferRow(row)
|
||||
if indentation <= startIndentLevel
|
||||
includeRowInFold = indentation == startIndentLevel and @foldEndRegexForScopes(scopes)?.search(@editor.lineForBufferRow(row))
|
||||
includeRowInFold = indentation == startIndentLevel and @foldEndRegexForScopes(scopes)?.searchSync(@editor.lineForBufferRow(row))
|
||||
foldEndRow = row if includeRowInFold
|
||||
break
|
||||
|
||||
@@ -250,10 +250,10 @@ class LanguageMode
|
||||
|
||||
precedingLine = @buffer.lineForRow(precedingRow)
|
||||
desiredIndentLevel = @editor.indentationForBufferRow(precedingRow)
|
||||
desiredIndentLevel += 1 if increaseIndentRegex.test(precedingLine) and not @editor.isBufferRowCommented(precedingRow)
|
||||
desiredIndentLevel += 1 if increaseIndentRegex.testSync(precedingLine) and not @editor.isBufferRowCommented(precedingRow)
|
||||
|
||||
return desiredIndentLevel unless decreaseIndentRegex = @decreaseIndentRegexForScopes(scopes)
|
||||
desiredIndentLevel -= 1 if decreaseIndentRegex.test(currentLine)
|
||||
desiredIndentLevel -= 1 if decreaseIndentRegex.testSync(currentLine)
|
||||
|
||||
Math.max(desiredIndentLevel, 0)
|
||||
|
||||
@@ -293,7 +293,7 @@ class LanguageMode
|
||||
return unless increaseIndentRegex and decreaseIndentRegex
|
||||
|
||||
line = @buffer.lineForRow(bufferRow)
|
||||
return unless decreaseIndentRegex.test(line)
|
||||
return unless decreaseIndentRegex.testSync(line)
|
||||
|
||||
currentIndentLevel = @editor.indentationForBufferRow(bufferRow)
|
||||
return if currentIndentLevel is 0
|
||||
@@ -302,7 +302,7 @@ class LanguageMode
|
||||
precedingLine = @buffer.lineForRow(precedingRow)
|
||||
|
||||
desiredIndentLevel = @editor.indentationForBufferRow(precedingRow)
|
||||
desiredIndentLevel -= 1 unless increaseIndentRegex.test(precedingLine)
|
||||
desiredIndentLevel -= 1 unless increaseIndentRegex.testSync(precedingLine)
|
||||
if desiredIndentLevel >= 0 and desiredIndentLevel < currentIndentLevel
|
||||
@editor.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
|
||||
|
||||
|
||||
+57
-227
@@ -5,7 +5,9 @@ React = require 'react-atom-fork'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
Decoration = require './decoration'
|
||||
CursorsComponent = require './cursors-component'
|
||||
HighlightsComponent = require './highlights-component'
|
||||
EditorTileComponent = require './editor-tile-component'
|
||||
|
||||
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
|
||||
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
|
||||
@@ -15,43 +17,27 @@ module.exports =
|
||||
LinesComponent = React.createClass
|
||||
displayName: 'LinesComponent'
|
||||
|
||||
tileSize: 5
|
||||
preservedTileNode: null
|
||||
|
||||
render: ->
|
||||
{performedInitialMeasurement} = @props
|
||||
|
||||
if performedInitialMeasurement
|
||||
{editor, highlightDecorations, scrollHeight, scrollWidth, placeholderText, backgroundColor} = @props
|
||||
{lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props
|
||||
style =
|
||||
height: Math.max(scrollHeight, scrollViewHeight)
|
||||
width: scrollWidth
|
||||
WebkitTransform: @getTransform()
|
||||
backgroundColor: backgroundColor
|
||||
|
||||
div {className: 'lines', style},
|
||||
div className: 'placeholder-text', placeholderText if placeholderText?
|
||||
HighlightsComponent({editor, highlightDecorations, lineHeightInPixels, defaultCharWidth, scopedCharacterWidthsChangeCount, performedInitialMeasurement})
|
||||
|
||||
getTransform: ->
|
||||
{scrollTop, scrollLeft, useHardwareAcceleration} = @props
|
||||
|
||||
if useHardwareAcceleration
|
||||
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
|
||||
else
|
||||
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
|
||||
div className: 'lines'
|
||||
|
||||
componentWillMount: ->
|
||||
@measuredLines = new WeakSet
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@renderedDecorationsByLineId = {}
|
||||
@tileComponentsByStartRow = {}
|
||||
|
||||
componentDidMount: ->
|
||||
@getDOMNode().addEventListener 'mousewheel', @onMouseWheel
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return true
|
||||
|
||||
return true unless isEqualForProperties(newProps, @props,
|
||||
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
|
||||
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible',
|
||||
'scrollViewHeight', 'mouseWheelScreenRow', 'scopedCharacterWidthsChangeCount', 'lineWidth', 'useHardwareAcceleration',
|
||||
'placeholderText', 'performedInitialMeasurement', 'backgroundColor'
|
||||
'placeholderText', 'performedInitialMeasurement', 'backgroundColor', 'cursorPixelRects'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
@@ -67,209 +53,19 @@ LinesComponent = React.createClass
|
||||
false
|
||||
|
||||
componentDidUpdate: (prevProps) ->
|
||||
{visible, scrollingVertically, performedInitialMeasurement} = @props
|
||||
{performedInitialMeasurement, visible, scrollingVertically} = @props
|
||||
return unless performedInitialMeasurement
|
||||
|
||||
@clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels
|
||||
@removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
|
||||
@updateLines(@props.lineWidth isnt prevProps.lineWidth)
|
||||
@clearTiles() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
|
||||
@updateTiles()
|
||||
@measureCharactersInNewLines() if visible and not scrollingVertically
|
||||
|
||||
clearScreenRowCaches: ->
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
|
||||
updateLines: (updateWidth) ->
|
||||
{editor, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
visibleLines = editor.linesForScreenRows(startRow, endRow - 1)
|
||||
@removeLineNodes(visibleLines)
|
||||
@appendOrUpdateVisibleLineNodes(visibleLines, startRow, updateWidth)
|
||||
|
||||
removeLineNodes: (visibleLines=[]) ->
|
||||
{mouseWheelScreenRow} = @props
|
||||
visibleLineIds = new Set
|
||||
visibleLineIds.add(line.id.toString()) for line in visibleLines
|
||||
node = @getDOMNode()
|
||||
for lineId, lineNode of @lineNodesByLineId when not visibleLineIds.has(lineId)
|
||||
screenRow = @screenRowsByLineId[lineId]
|
||||
if not screenRow? or screenRow isnt mouseWheelScreenRow
|
||||
delete @lineNodesByLineId[lineId]
|
||||
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
|
||||
delete @screenRowsByLineId[lineId]
|
||||
delete @renderedDecorationsByLineId[lineId]
|
||||
node.removeChild(lineNode)
|
||||
|
||||
appendOrUpdateVisibleLineNodes: (visibleLines, startRow, updateWidth) ->
|
||||
{lineDecorations} = @props
|
||||
|
||||
newLines = null
|
||||
newLinesHTML = null
|
||||
|
||||
for line, index in visibleLines
|
||||
screenRow = startRow + index
|
||||
|
||||
if @hasLineNode(line.id)
|
||||
@updateLineNode(line, screenRow, updateWidth)
|
||||
else
|
||||
newLines ?= []
|
||||
newLinesHTML ?= ""
|
||||
newLines.push(line)
|
||||
newLinesHTML += @buildLineHTML(line, screenRow)
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
@renderedDecorationsByLineId[line.id] = lineDecorations[screenRow]
|
||||
|
||||
return unless newLines?
|
||||
|
||||
WrapperDiv.innerHTML = newLinesHTML
|
||||
newLineNodes = toArray(WrapperDiv.children)
|
||||
node = @getDOMNode()
|
||||
for line, i in newLines
|
||||
lineNode = newLineNodes[i]
|
||||
@lineNodesByLineId[line.id] = lineNode
|
||||
node.appendChild(lineNode)
|
||||
|
||||
hasLineNode: (lineId) ->
|
||||
@lineNodesByLineId.hasOwnProperty(lineId)
|
||||
|
||||
buildLineHTML: (line, screenRow) ->
|
||||
{editor, mini, showIndentGuide, lineHeightInPixels, lineDecorations, lineWidth} = @props
|
||||
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
|
||||
|
||||
classes = ''
|
||||
if decorations = lineDecorations[screenRow]
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'line')
|
||||
classes += decoration.class + ' '
|
||||
classes += 'line'
|
||||
|
||||
top = screenRow * lineHeightInPixels
|
||||
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{lineWidth}px;\" data-screen-row=\"#{screenRow}\">"
|
||||
|
||||
if text is ""
|
||||
lineHTML += @buildEmptyLineInnerHTML(line)
|
||||
else
|
||||
lineHTML += @buildLineInnerHTML(line)
|
||||
|
||||
lineHTML += '<span class="fold-marker"></span>' if fold
|
||||
lineHTML += "</div>"
|
||||
lineHTML
|
||||
|
||||
buildEmptyLineInnerHTML: (line) ->
|
||||
{showIndentGuide, invisibles} = @props
|
||||
{cr, eol} = invisibles
|
||||
{indentLevel, tabLength} = line
|
||||
|
||||
if showIndentGuide and indentLevel > 0
|
||||
invisiblesToRender = []
|
||||
invisiblesToRender.push(cr) if cr? and line.lineEnding is '\r\n'
|
||||
invisiblesToRender.push(eol) if eol?
|
||||
|
||||
lineHTML = ''
|
||||
for i in [0...indentLevel]
|
||||
lineHTML += "<span class='indent-guide'>"
|
||||
for j in [0...tabLength]
|
||||
if invisible = invisiblesToRender.shift()
|
||||
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
|
||||
else
|
||||
lineHTML += ' '
|
||||
lineHTML += "</span>"
|
||||
|
||||
while invisiblesToRender.length
|
||||
lineHTML += "<span class='invisible-character'>#{invisiblesToRender.shift()}</span>"
|
||||
|
||||
lineHTML
|
||||
else
|
||||
@buildEndOfLineHTML(line, @props.invisibles) or ' '
|
||||
|
||||
buildLineInnerHTML: (line) ->
|
||||
{invisibles, mini, showIndentGuide, invisibles} = @props
|
||||
{tokens, text} = line
|
||||
innerHTML = ""
|
||||
|
||||
scopeStack = []
|
||||
firstTrailingWhitespacePosition = text.search(/\s*$/)
|
||||
lineIsWhitespaceOnly = firstTrailingWhitespacePosition is 0
|
||||
for token in tokens
|
||||
innerHTML += @updateScopeStack(scopeStack, token.scopes)
|
||||
hasIndentGuide = not mini and showIndentGuide and (token.hasLeadingWhitespace or (token.hasTrailingWhitespace and lineIsWhitespaceOnly))
|
||||
innerHTML += token.getValueAsHtml({invisibles, hasIndentGuide})
|
||||
|
||||
innerHTML += @popScope(scopeStack) while scopeStack.length > 0
|
||||
innerHTML += @buildEndOfLineHTML(line, invisibles)
|
||||
innerHTML
|
||||
|
||||
buildEndOfLineHTML: (line, invisibles) ->
|
||||
return '' if @props.mini or line.isSoftWrapped()
|
||||
|
||||
html = ''
|
||||
# Note the lack of '?' in the character checks. A user can set the chars
|
||||
# to an empty string which we will interpret as not-set
|
||||
if invisibles.cr and line.lineEnding is '\r\n'
|
||||
html += "<span class='invisible-character'>#{invisibles.cr}</span>"
|
||||
if invisibles.eol
|
||||
html += "<span class='invisible-character'>#{invisibles.eol}</span>"
|
||||
|
||||
html
|
||||
|
||||
updateScopeStack: (scopeStack, desiredScopes) ->
|
||||
html = ""
|
||||
|
||||
# Find a common prefix
|
||||
for scope, i in desiredScopes
|
||||
break unless scopeStack[i] is desiredScopes[i]
|
||||
|
||||
# Pop scopes until we're at the common prefx
|
||||
until scopeStack.length is i
|
||||
html += @popScope(scopeStack)
|
||||
|
||||
# Push onto common prefix until scopeStack equals desiredScopes
|
||||
for j in [i...desiredScopes.length]
|
||||
html += @pushScope(scopeStack, desiredScopes[j])
|
||||
|
||||
html
|
||||
|
||||
popScope: (scopeStack) ->
|
||||
scopeStack.pop()
|
||||
"</span>"
|
||||
|
||||
pushScope: (scopeStack, scope) ->
|
||||
scopeStack.push(scope)
|
||||
"<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
|
||||
|
||||
updateLineNode: (line, screenRow, updateWidth) ->
|
||||
{editor, lineHeightInPixels, lineDecorations, lineWidth} = @props
|
||||
lineNode = @lineNodesByLineId[line.id]
|
||||
|
||||
decorations = lineDecorations[screenRow]
|
||||
previousDecorations = @renderedDecorationsByLineId[line.id]
|
||||
|
||||
if previousDecorations?
|
||||
for id, decoration of previousDecorations
|
||||
if Decoration.isType(decoration, 'line') and not @hasDecoration(decorations, decoration)
|
||||
lineNode.classList.remove(decoration.class)
|
||||
|
||||
if decorations?
|
||||
for id, decoration of decorations
|
||||
if Decoration.isType(decoration, 'line') and not @hasDecoration(previousDecorations, decoration)
|
||||
lineNode.classList.add(decoration.class)
|
||||
|
||||
lineNode.style.width = lineWidth + 'px' if updateWidth
|
||||
|
||||
unless @screenRowsByLineId[line.id] is screenRow
|
||||
lineNode.style.top = screenRow * lineHeightInPixels + 'px'
|
||||
lineNode.dataset.screenRow = screenRow
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
hasDecoration: (decorations, decoration) ->
|
||||
decorations? and decorations[decoration.id] is decoration
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
tileComponent = @tileComponentsByStartRow[@tileStartRowForScreenRow(screenRow)]
|
||||
tileComponent?.lineNodeForScreenRow(screenRow)
|
||||
|
||||
tileStartRowForScreenRow: (screenRow) ->
|
||||
screenRow - (screenRow % @tileSize)
|
||||
|
||||
measureLineHeightAndDefaultCharWidth: ->
|
||||
node = @getDOMNode()
|
||||
@@ -282,19 +78,48 @@ LinesComponent = React.createClass
|
||||
editor.setLineHeightInPixels(lineHeightInPixels)
|
||||
editor.setDefaultCharWidth(charWidth)
|
||||
|
||||
updateTiles: ->
|
||||
{contentPresenter} = @props
|
||||
domNode = @getDOMNode()
|
||||
|
||||
for tileStartRow, tileComponent of @tileComponentsByStartRow
|
||||
unless contentPresenter.tiles[tileStartRow]?
|
||||
if tileComponent.domNode is @preservedTileNode
|
||||
tileComponent.preserve()
|
||||
else
|
||||
domNode.removeChild(tileComponent.domNode)
|
||||
delete @tileComponentsByStartRow[tileStartRow]
|
||||
|
||||
for tileStartRow, tilePresenter of contentPresenter.tiles
|
||||
if tileComponent = @tileComponentsByStartRow[tileStartRow]
|
||||
tileComponent = @tileComponentsByStartRow[tileStartRow]
|
||||
tileComponent.revive(tilePresenter) if tileComponent.preserved
|
||||
tileComponent.update()
|
||||
else
|
||||
tileComponent = new EditorTileComponent(tilePresenter)
|
||||
@tileComponentsByStartRow[tileStartRow] = tileComponent
|
||||
domNode.appendChild(tileComponent.domNode)
|
||||
|
||||
clearTiles: ->
|
||||
for startRow, tileComponent of @tileComponentsByStartRow
|
||||
domNode.removeChild(tileComponent.domNode)
|
||||
delete @tileComponentsByStartRow[startRow]
|
||||
|
||||
remeasureCharacterWidths: ->
|
||||
@clearScopedCharWidths()
|
||||
@measureCharactersInNewLines()
|
||||
|
||||
measureCharactersInNewLines: ->
|
||||
return
|
||||
{editor} = @props
|
||||
[visibleStartRow, visibleEndRow] = @props.renderedRowRange
|
||||
node = @getDOMNode()
|
||||
|
||||
editor.batchCharacterMeasurement =>
|
||||
for tokenizedLine in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
|
||||
for tokenizedLine, i in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
|
||||
screenRow = visibleStartRow + i
|
||||
unless @measuredLines.has(tokenizedLine)
|
||||
lineNode = @lineNodesByLineId[tokenizedLine.id]
|
||||
lineNode = @lineNodeForScreenRow(screenRow)
|
||||
@measureCharactersInLine(tokenizedLine, lineNode)
|
||||
return
|
||||
|
||||
@@ -336,3 +161,8 @@ LinesComponent = React.createClass
|
||||
clearScopedCharWidths: ->
|
||||
@measuredLines.clear()
|
||||
@props.editor.clearScopedCharWidths()
|
||||
|
||||
onMouseWheel: (event) ->
|
||||
node = event.target
|
||||
node = node.parentNode until node.dataset.tile
|
||||
@preservedTileNode = node
|
||||
|
||||
@@ -269,6 +269,7 @@ class Package
|
||||
|
||||
requireMainModule: ->
|
||||
return @mainModule if @mainModule?
|
||||
return unless @isCompatible()
|
||||
mainModulePath = @getMainModulePath()
|
||||
@mainModule = require(mainModulePath) if fs.isFileSync(mainModulePath)
|
||||
|
||||
@@ -340,3 +341,72 @@ class Package
|
||||
for eventHandler in eventHandlers
|
||||
eventHandler.handler = eventHandler.disabledHandler
|
||||
delete eventHandler.disabledHandler
|
||||
|
||||
# Does the given module path contain native code?
|
||||
isNativeModule: (modulePath) ->
|
||||
try
|
||||
fs.listSync(path.join(modulePath, 'build', 'Release'), ['.node']).length > 0
|
||||
catch error
|
||||
false
|
||||
|
||||
# Get an array of all the native modules that this package depends on.
|
||||
# This will recurse through all dependencies.
|
||||
getNativeModuleDependencyPaths: ->
|
||||
nativeModulePaths = []
|
||||
|
||||
traversePath = (nodeModulesPath) =>
|
||||
try
|
||||
for modulePath in fs.listSync(nodeModulesPath)
|
||||
nativeModulePaths.push(modulePath) if @isNativeModule(modulePath)
|
||||
traversePath(path.join(modulePath, 'node_modules'))
|
||||
|
||||
traversePath(path.join(@path, 'node_modules'))
|
||||
nativeModulePaths
|
||||
|
||||
# Get the incompatible native modules that this package depends on.
|
||||
# This recurses through all dependencies and requires all modules that
|
||||
# contain a `.node` file.
|
||||
#
|
||||
# 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?
|
||||
|
||||
incompatibleNativeModules = []
|
||||
for nativeModulePath in @getNativeModuleDependencyPaths()
|
||||
try
|
||||
require(nativeModulePath)
|
||||
catch error
|
||||
try
|
||||
version = require("#{nativeModulePath}/package.json").version
|
||||
incompatibleNativeModules.push
|
||||
path: nativeModulePath
|
||||
name: path.basename(nativeModulePath)
|
||||
version: version
|
||||
error: error.message
|
||||
|
||||
global.localStorage.setItem(localStorageKey, 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 packageMain = @getMainModulePath()
|
||||
@incompatibleModules = @getIncompatibleNativeModules()
|
||||
@compatible = @incompatibleModules.length is 0
|
||||
else
|
||||
@compatible = true
|
||||
|
||||
@@ -115,7 +115,7 @@ class ReactEditorView extends View
|
||||
@find('.lines').prepend(view)
|
||||
|
||||
beforeRemove: ->
|
||||
React.unmountComponentAtNode(@element)
|
||||
React.unmountComponentAtNode(@element) if @component.isMounted()
|
||||
@attached = false
|
||||
@trigger 'editor:detached', this
|
||||
|
||||
|
||||
@@ -57,8 +57,7 @@ class WindowEventHandler
|
||||
|
||||
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious
|
||||
|
||||
@subscribe $(document), 'keydown', (event) ->
|
||||
atom.keymaps.handleKeyboardEvent(event.originalEvent)
|
||||
document.addEventListener 'keydown', @onKeydown
|
||||
|
||||
@subscribe $(document), 'drop', (e) ->
|
||||
e.preventDefault()
|
||||
@@ -95,6 +94,10 @@ class WindowEventHandler
|
||||
bindCommandToAction('core:redo', 'redo:')
|
||||
bindCommandToAction('core:select-all', 'selectAll:')
|
||||
|
||||
onKeydown: (event) ->
|
||||
atom.keymaps.handleKeyboardEvent(event)
|
||||
event.stopImmediatePropagation()
|
||||
|
||||
openLink: ({target, currentTarget}) ->
|
||||
location = target?.getAttribute('href') or currentTarget?.getAttribute('href')
|
||||
if location and location[0] isnt '#' and /^https?:\/\//.test(location)
|
||||
|
||||
@@ -96,6 +96,11 @@ class WorkspaceView extends View
|
||||
when 'overlay'
|
||||
@addClass("scrollbars-visible-when-scrolling")
|
||||
|
||||
|
||||
@subscribe atom.config.observe 'editor.fontSize', @setEditorFontSize
|
||||
@subscribe atom.config.observe 'editor.fontFamily', @setEditorFontFamily
|
||||
@subscribe atom.config.observe 'editor.lineHeight', @setEditorLineHeight
|
||||
|
||||
@updateTitle()
|
||||
|
||||
@on 'focus', (e) => @handleFocus(e)
|
||||
@@ -340,6 +345,26 @@ class WorkspaceView extends View
|
||||
beforeRemove: ->
|
||||
@model.destroy()
|
||||
|
||||
setEditorFontSize: (fontSize) =>
|
||||
@setEditorStyle('font-size', fontSize + 'px')
|
||||
|
||||
setEditorFontFamily: (fontFamily) =>
|
||||
@setEditorStyle('font-family', fontFamily)
|
||||
|
||||
setEditorLineHeight: (lineHeight) =>
|
||||
@setEditorStyle('line-height', lineHeight)
|
||||
|
||||
setEditorStyle: (property, value) ->
|
||||
unless styleNode = atom.themes.stylesheetElementForId('global-editor-styles')[0]
|
||||
atom.themes.applyStylesheet('global-editor-styles', '.editor {}')
|
||||
styleNode = atom.themes.stylesheetElementForId('global-editor-styles')[0]
|
||||
|
||||
{sheet} = styleNode
|
||||
editorRule = sheet.cssRules[0]
|
||||
editorRule.style[property] = value
|
||||
atom.themes.emit 'stylesheet-updated', sheet
|
||||
atom.themes.emit 'stylesheets-changed'
|
||||
|
||||
# Deprecated
|
||||
eachPane: (callback) ->
|
||||
deprecate("Use WorkspaceView::eachPaneView instead")
|
||||
|
||||
@@ -83,8 +83,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor.mini {
|
||||
font-size: @input-font-size;
|
||||
line-height: @component-line-height;
|
||||
max-height: @component-line-height + 2; // +2 for borders
|
||||
|
||||
.placeholder-text {
|
||||
position: absolute;
|
||||
color: @text-color-subtle;
|
||||
}
|
||||
}
|
||||
@@ -300,8 +307,4 @@
|
||||
.scroll-view {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
color: @text-color-subtle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
// Sizes
|
||||
|
||||
@font-size: 13px;
|
||||
@input-font-size: 14px;
|
||||
|
||||
@disclosure-arrow-size: 12px;
|
||||
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário