Comparar commits
78 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 |
+1
-1
@@ -6,6 +6,6 @@
|
||||
"url": "https://github.com/atom/atom.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-package-manager": "0.87.0"
|
||||
"atom-package-manager": "0.88.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
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
# API Guidelines
|
||||
|
||||
__Note__: These are not all in practice yet. We are still sorting this out, and plan to move the entire API this way.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
We should strive to have only one way to do something, and make it clear what that way is.
|
||||
|
||||
__Bad__
|
||||
* Several ways to get the `Editor` model from the view.
|
||||
* `EditorView.editor`
|
||||
* `EditorView.getModel()`
|
||||
* `EditorView.getEditor()`
|
||||
* Delegated methods on the views when there is a model counterpart
|
||||
* `Editor.toggleSoftTabs()`
|
||||
* `EditorView.toggleSoftTabs()`
|
||||
|
||||
__Good__
|
||||
* One way to get the `Editor` model from the view
|
||||
* `EditorView.getModel()`
|
||||
* Use the method on the model
|
||||
* `Editor.toggleSoftTabs()`
|
||||
* One clear way to subscribe to events
|
||||
* `subscription = @subscribe thing, 'event', -> ...`
|
||||
* One clear way to unsubscribe to events
|
||||
* `subscription.off()` only; not `thing.off 'event', -> ...`
|
||||
|
||||
## Essential vs Extended
|
||||
|
||||
There are two groups of classes / methods. We break them up to facilitate a gentle introduction into the API and help authors build a knowledge foundation more quickly.
|
||||
|
||||
### Essential
|
||||
|
||||
These are classes, methods, and concepts nearly every package author will need to know about. Need to create commands? Subscribe to atom events? Get a reference to all the editors? Highlight a line in the editor? Patterns and methods for these things will be explained in the essential API.
|
||||
|
||||
We want to keep the essential API minimal and focused.
|
||||
|
||||
### Extended
|
||||
|
||||
The extended API contains The Power. Need to move one cursor independent of the others? Want to do some processing on the markers? You can do it with the extended API.
|
||||
|
||||
## View / Model
|
||||
|
||||
Operations on Views should be limited to DOM manipulation only. A package author should only need access to, say, the `EditorView` when it needs to directly modify the `EditorView`'s DOM.
|
||||
|
||||
## Properties
|
||||
|
||||
No public properties. Use methods instead.
|
||||
|
||||
## Methods
|
||||
|
||||
### Naming
|
||||
|
||||
We strive to fit the [Objective C][naming] naming conventions for the sake of readability.
|
||||
|
||||
* Be descriptive, always write out the whole word. `selection` not `sel`, `cursor` not `cur`.
|
||||
* Describe the arguments names in the method name eg. `decorationsForMarker(marker)`, `objectAtIndex(index)`
|
||||
* Use `get` prefix only when there are no arguments `getCursors()`, `getLastCursor()`, `cursorForMarker(marker)`
|
||||
* Prefix bool methods with `is` eg. `isDefault()`
|
||||
|
||||
Array accessor methods would be written as follows
|
||||
|
||||
```coffee
|
||||
getObjects()
|
||||
addObject(object)
|
||||
removeObject(object)
|
||||
removeObjectAtIndex(index)
|
||||
objectAtIndex(index)
|
||||
objectsForThing(thing)
|
||||
objectForThing(thing)
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
There will be no `off()` method on objects. `on()` will return a subscription object which contains the `off()` method.
|
||||
|
||||
* Events should be emitted with one event object as an argument, rather than a bunch of arguments.
|
||||
* If an event is cancelable, it will provide a `cancel` function in the event object.
|
||||
|
||||
### Naming
|
||||
|
||||
Past tense. ???
|
||||
|
||||
## Documentation
|
||||
|
||||
Comment doc strings on methods, events, etc
|
||||
|
||||
* how to doc sections?
|
||||
* events?
|
||||
* props?
|
||||
* callback args?
|
||||
* option hashes?
|
||||
|
||||
### Method organization
|
||||
|
||||
* All methods in classes should be grouped into sections by usage pattern.
|
||||
* Methods within a section should be ordered with the most commonly used methods at the top. `Essential` methods always go above `Extended` methods.
|
||||
* Sections should be ordered in the class with the most commonly used sections at the top.
|
||||
|
||||
A section:
|
||||
|
||||
```coffee
|
||||
###
|
||||
Section: Reading Text
|
||||
###
|
||||
|
||||
# Essential: Returns a {String} representing the entire contents of the editor.
|
||||
getText: -> @buffer.getText()
|
||||
```
|
||||
|
||||
|
||||
[naming]:https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingMethods.html
|
||||
@@ -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`
|
||||
@@ -78,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.
|
||||
|
||||
|
||||
+17
-18
@@ -17,16 +17,16 @@
|
||||
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"atomShellVersion": "0.15.0",
|
||||
"atomShellVersion": "0.15.3",
|
||||
"dependencies": {
|
||||
"async": "0.2.6",
|
||||
"atom-keymap": "^1.0.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",
|
||||
"emissary": "^1.2.2",
|
||||
"first-mate": "^2.0.1",
|
||||
"fs-plus": "^2.2.6",
|
||||
"fstream": "0.1.24",
|
||||
@@ -45,12 +45,12 @@
|
||||
"property-accessors": "^1",
|
||||
"q": "^1.0.1",
|
||||
"random-words": "0.0.1",
|
||||
"react-atom-fork": "^0.11.1",
|
||||
"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": "^1.0.1",
|
||||
"scrollbar-style": "^1.0.2",
|
||||
"season": "^1.0.2",
|
||||
"semver": "1.1.4",
|
||||
"serializable": "^1",
|
||||
@@ -82,38 +82,37 @@
|
||||
"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",
|
||||
"incompatible-packages": "0.3.0",
|
||||
"keybinding-resolver": "0.18.0",
|
||||
"incompatible-packages": "0.5.0",
|
||||
"keybinding-resolver": "0.19.0",
|
||||
"link": "0.25.0",
|
||||
"markdown-preview": "0.94.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.137.0",
|
||||
"snippets": "0.50.0",
|
||||
"settings-view": "0.138.0",
|
||||
"snippets": "0.51.0",
|
||||
"spell-check": "0.40.0",
|
||||
"status-bar": "0.41.0",
|
||||
"status-bar": "0.42.0",
|
||||
"styleguide": "0.29.0",
|
||||
"symbols-view": "0.63.0",
|
||||
"tabs": "0.48.0",
|
||||
"tabs": "0.49.0",
|
||||
"timecop": "0.22.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.28.0",
|
||||
"language-coffee-script": "0.29.0",
|
||||
"language-css": "0.17.0",
|
||||
"language-gfm": "0.44.0",
|
||||
"language-gfm": "0.46.0",
|
||||
"language-git": "0.9.0",
|
||||
"language-go": "0.16.0",
|
||||
"language-html": "0.22.0",
|
||||
@@ -128,7 +127,7 @@
|
||||
"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",
|
||||
@@ -138,7 +137,7 @@
|
||||
"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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,9 @@ 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)
|
||||
|
||||
+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: ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,7 @@ LinesComponent = require './lines-component'
|
||||
ScrollbarComponent = require './scrollbar-component'
|
||||
ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
EditorPresenter = require './editor-presenter'
|
||||
|
||||
module.exports =
|
||||
EditorComponent = React.createClass
|
||||
@@ -50,6 +51,8 @@ EditorComponent = React.createClass
|
||||
render: ->
|
||||
{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()
|
||||
@@ -91,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
|
||||
@@ -106,7 +110,7 @@ EditorComponent = React.createClass
|
||||
onBlur: @onInputBlurred
|
||||
|
||||
LinesComponent {
|
||||
ref: 'lines',
|
||||
ref: 'lines', contentPresenter,
|
||||
editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations,
|
||||
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
|
||||
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
|
||||
@@ -167,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
|
||||
|
||||
@@ -186,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
|
||||
|
||||
@@ -213,19 +221,21 @@ EditorComponent = React.createClass
|
||||
@measureScrollbars() if @measuringScrollbars
|
||||
|
||||
performInitialMeasurement: ->
|
||||
console.log "INITIAL MEASUREMENT"
|
||||
@updatesPaused = true
|
||||
@measureHeightAndWidth()
|
||||
@sampleFontStyling()
|
||||
@sampleBackgroundColors()
|
||||
@measureScrollbars()
|
||||
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
|
||||
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
|
||||
# @remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
|
||||
@props.editor.setVisible(true)
|
||||
@updatesPaused = false
|
||||
@performedInitialMeasurement = true
|
||||
|
||||
requestUpdate: ->
|
||||
return unless @isMounted()
|
||||
@pauseDOMPolling()
|
||||
|
||||
if @updatesPaused
|
||||
@updateRequestedWhilePaused = true
|
||||
@@ -293,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) ->
|
||||
@@ -524,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
|
||||
@@ -539,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)
|
||||
@@ -827,17 +843,20 @@ EditorComponent = React.createClass
|
||||
@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
|
||||
|
||||
@@ -974,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
|
||||
+55
-236
@@ -7,6 +7,7 @@ React = require 'react-atom-fork'
|
||||
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}
|
||||
@@ -16,48 +17,22 @@ module.exports =
|
||||
LinesComponent = React.createClass
|
||||
displayName: 'LinesComponent'
|
||||
|
||||
tileSize: 5
|
||||
preservedTileNode: null
|
||||
|
||||
render: ->
|
||||
{performedInitialMeasurement, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
||||
|
||||
if performedInitialMeasurement
|
||||
{editor, highlightDecorations, scrollHeight, scrollWidth, placeholderText, backgroundColor} = @props
|
||||
{lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props
|
||||
{scrollTop, scrollLeft, cursorPixelRects} = @props
|
||||
style =
|
||||
height: Math.max(scrollHeight, scrollViewHeight)
|
||||
width: scrollWidth
|
||||
WebkitTransform: @getTransform()
|
||||
backgroundColor: backgroundColor
|
||||
|
||||
div {className: 'lines', style},
|
||||
div className: 'placeholder-text', placeholderText if placeholderText?
|
||||
|
||||
CursorsComponent {
|
||||
cursorPixelRects, cursorBlinkPeriod, cursorBlinkResumeDelay, lineHeightInPixels,
|
||||
defaultCharWidth, scopedCharacterWidthsChangeCount, performedInitialMeasurement
|
||||
}
|
||||
|
||||
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',
|
||||
@@ -78,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()
|
||||
@@ -293,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
|
||||
|
||||
@@ -347,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
|
||||
|
||||
@@ -371,9 +371,10 @@ class Package
|
||||
# to minimize the impact on startup time.
|
||||
getIncompatibleNativeModules: ->
|
||||
localStorageKey = "installed-packages:#{@name}:#{@metadata.version}"
|
||||
try
|
||||
{incompatibleNativeModules} = JSON.parse(global.localStorage.getItem(localStorageKey)) ? {}
|
||||
return incompatibleNativeModules if incompatibleNativeModules?
|
||||
unless atom.inDevMode()
|
||||
try
|
||||
{incompatibleNativeModules} = JSON.parse(global.localStorage.getItem(localStorageKey)) ? {}
|
||||
return incompatibleNativeModules if incompatibleNativeModules?
|
||||
|
||||
incompatibleNativeModules = []
|
||||
for nativeModulePath in @getNativeModuleDependencyPaths()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário