Comparar commits
44 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 6e42552891 | |||
| 70d4af9341 | |||
| 9c17e8b705 | |||
| 7954b5c1fd | |||
| 3d9b22bae4 | |||
| ae6a3fddb2 | |||
| f6a8a42a6d | |||
| 16765138b8 | |||
| 3e656f83b8 | |||
| b04b025a0c | |||
| 88743e8a71 | |||
| 30d71fc37a | |||
| 09aa62fae8 | |||
| f29e50b730 | |||
| dcd60a275a | |||
| 5e6dff9175 | |||
| dd5bc5891c | |||
| 79bc071353 | |||
| d3c2633bb3 | |||
| 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'), ''
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+13
-13
@@ -17,16 +17,16 @@
|
||||
"url": "http://github.com/atom/atom/raw/master/LICENSE.md"
|
||||
}
|
||||
],
|
||||
"atomShellVersion": "0.15.0",
|
||||
"atomShellVersion": "0.15.2",
|
||||
"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",
|
||||
@@ -34,6 +34,7 @@
|
||||
"git-utils": "^2.1.3",
|
||||
"grim": "0.11.0",
|
||||
"guid": "0.0.10",
|
||||
"immutable": "^2.0.4",
|
||||
"jasmine-tagged": "^1.1.2",
|
||||
"less-cache": "0.13.0",
|
||||
"mixto": "^1",
|
||||
@@ -45,12 +46,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",
|
||||
@@ -88,18 +89,18 @@
|
||||
"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",
|
||||
"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",
|
||||
@@ -109,11 +110,10 @@
|
||||
"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-css": "0.17.0",
|
||||
"language-gfm": "0.44.0",
|
||||
"language-gfm": "0.45.0",
|
||||
"language-git": "0.9.0",
|
||||
"language-go": "0.16.0",
|
||||
"language-html": "0.22.0",
|
||||
@@ -138,7 +138,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.
@@ -0,0 +1,345 @@
|
||||
Immutable = require 'immutable'
|
||||
_ = require 'underscore-plus'
|
||||
DisplayStateManager = require '../src/display-state-manager'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Editor = require '../src/editor'
|
||||
|
||||
fdescribe "DisplayStateManager", ->
|
||||
[buffer, editor, stateManager] = []
|
||||
|
||||
beforeEach ->
|
||||
@addMatchers
|
||||
toHaveValues: ToHaveValuesMatcher
|
||||
|
||||
spyOn(DisplayStateManager::, 'getTileSize').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)
|
||||
|
||||
stateManager = new DisplayStateManager(editor)
|
||||
|
||||
afterEach ->
|
||||
editor.destroy()
|
||||
|
||||
describe "initial state", ->
|
||||
it "breaks the visible lines into tiles", ->
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
startRow: 0
|
||||
left: 0
|
||||
top: 0
|
||||
width: editor.getScrollWidth()
|
||||
height: 50
|
||||
lineHeightInPixels: 10
|
||||
lines: editor.linesForScreenRows(0, 4)
|
||||
5:
|
||||
startRow: 5
|
||||
left: 0
|
||||
top: 50
|
||||
width: editor.getScrollWidth()
|
||||
height: 50
|
||||
lineHeightInPixels: 10
|
||||
lines: editor.linesForScreenRows(5, 9)
|
||||
10:
|
||||
startRow: 10
|
||||
left: 0
|
||||
top: 100
|
||||
width: editor.getScrollWidth()
|
||||
height: 50
|
||||
lineHeightInPixels: 10
|
||||
lines: editor.linesForScreenRows(10, 14)
|
||||
|
||||
describe "when the height is changed", ->
|
||||
it "updates the rendered tiles based on the new height", ->
|
||||
editor.setHeight(150)
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
10:
|
||||
startRow: 10
|
||||
top: 100
|
||||
15:
|
||||
startRow: 15
|
||||
top: 150
|
||||
|
||||
editor.setHeight(70)
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0
|
||||
5:
|
||||
startRow: 5
|
||||
top: 50
|
||||
|
||||
describe "when the width is changed", ->
|
||||
it "updates the tiles with the new width", ->
|
||||
editor.setWidth(700)
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
width: 700
|
||||
5:
|
||||
width: 700
|
||||
10:
|
||||
width: 700
|
||||
|
||||
describe "when the lineHeightInPixels is changed", ->
|
||||
it "updates the rendered tiles and assigns a new lineHeightInPixels value to all tiles", ->
|
||||
editor.setScrollTop(10)
|
||||
editor.setLineHeightInPixels(7)
|
||||
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
startRow: 0
|
||||
top: 0 - 10
|
||||
lineHeightInPixels: 7
|
||||
5:
|
||||
startRow: 5
|
||||
top: 7 * 5 - 10
|
||||
lineHeightInPixels: 7
|
||||
10:
|
||||
startRow: 10
|
||||
top: 7 * 10 - 10
|
||||
lineHeightInPixels: 7
|
||||
15:
|
||||
startRow: 15
|
||||
top: 7 * 15 - 10
|
||||
lineHeightInPixels: 7
|
||||
|
||||
describe "when the editor is scrolled vertically", ->
|
||||
it "updates the visible tiles and their top positions", ->
|
||||
editor.setScrollTop(20)
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
left: 0
|
||||
top: -20
|
||||
5:
|
||||
left: 0
|
||||
top: 30
|
||||
10:
|
||||
left: 0
|
||||
top: 80
|
||||
|
||||
editor.setScrollTop(70)
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
5:
|
||||
left: 0
|
||||
top: -20
|
||||
10:
|
||||
left: 0
|
||||
top: 30
|
||||
15:
|
||||
left: 0
|
||||
top: 80
|
||||
|
||||
describe "when the editor is scrolled horizontally", ->
|
||||
it "updates the left position of the visible tiles", ->
|
||||
editor.setScrollLeft(30)
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
left: -30
|
||||
5:
|
||||
left: -30
|
||||
10:
|
||||
left: -30
|
||||
|
||||
describe "when the lines are changed", ->
|
||||
it "updates the lines in the tiles", ->
|
||||
buffer.setTextInRange([[3, 5], [7, 0]], "a\nb\nc\nd")
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lines: editor.linesForScreenRows(0, 4)
|
||||
5:
|
||||
lines: editor.linesForScreenRows(5, 9)
|
||||
10:
|
||||
lines: editor.linesForScreenRows(10, 14)
|
||||
|
||||
describe "line decorations", ->
|
||||
marker = null
|
||||
|
||||
beforeEach ->
|
||||
marker = editor.markBufferRange([[3, 4], [5, 6]], invalidate: 'touch')
|
||||
|
||||
it "updates the display state when decorations are added, updated, invalidated, or removed", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'test')
|
||||
|
||||
decorationParamsById = {}
|
||||
decorationParamsById[decoration.id] = decoration.getParams()
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: decorationParamsById
|
||||
4: decorationParamsById
|
||||
5:
|
||||
lineDecorations:
|
||||
5: decorationParamsById
|
||||
|
||||
marker.setBufferRange([[8, 4], [10, 6]])
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: null
|
||||
4: null
|
||||
5:
|
||||
lineDecorations:
|
||||
5: null
|
||||
8: decorationParamsById
|
||||
9: decorationParamsById
|
||||
10:
|
||||
lineDecorations:
|
||||
10: decorationParamsById
|
||||
|
||||
buffer.insert([8, 5], 'invalidate marker')
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
5:
|
||||
lineDecorations:
|
||||
8: null
|
||||
9: null
|
||||
10:
|
||||
lineDecorations:
|
||||
10: null
|
||||
|
||||
buffer.undo()
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
5:
|
||||
lineDecorations:
|
||||
8: decorationParamsById
|
||||
9: decorationParamsById
|
||||
10:
|
||||
lineDecorations:
|
||||
10: decorationParamsById
|
||||
|
||||
marker.destroy()
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
5:
|
||||
lineDecorations:
|
||||
8: null
|
||||
9: null
|
||||
10:
|
||||
lineDecorations:
|
||||
10: null
|
||||
|
||||
it "renders line decorations in the initial state", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'test')
|
||||
|
||||
newStateManager = new DisplayStateManager(editor)
|
||||
|
||||
decorationParamsById = {}
|
||||
decorationParamsById[decoration.id] = decoration.getParams()
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: decorationParamsById
|
||||
4: decorationParamsById
|
||||
5:
|
||||
lineDecorations:
|
||||
5: decorationParamsById
|
||||
|
||||
describe "when the decoration's 'onlyHead' property is true", ->
|
||||
it "only applies the decoration to lines containing the marker's head", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'only-head', onlyHead: true)
|
||||
decorationParamsById = {}
|
||||
decorationParamsById[decoration.id] = decoration.getParams()
|
||||
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: null
|
||||
4: null
|
||||
5:
|
||||
lineDecorations:
|
||||
5: decorationParamsById
|
||||
|
||||
describe "when the decoration's 'onlyEmpty' property is true", ->
|
||||
it "only applies the decoration if the marker is empty", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'only-empty', onlyEmpty: true)
|
||||
decorationParamsById = {}
|
||||
decorationParamsById[decoration.id] = decoration.getParams()
|
||||
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: null
|
||||
4: null
|
||||
5:
|
||||
lineDecorations:
|
||||
5: null
|
||||
|
||||
marker.clearTail()
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: null
|
||||
4: null
|
||||
5:
|
||||
lineDecorations:
|
||||
5: decorationParamsById
|
||||
|
||||
describe "when the decoration's 'onlyNonEmpty' property is true", ->
|
||||
it "only applies the decoration if the marker is non-empty", ->
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'only-non-empty', onlyNonEmpty: true)
|
||||
decorationParamsById = {}
|
||||
decorationParamsById[decoration.id] = decoration.getParams()
|
||||
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: decorationParamsById
|
||||
4: decorationParamsById
|
||||
5:
|
||||
lineDecorations:
|
||||
5: decorationParamsById
|
||||
|
||||
marker.clearTail()
|
||||
expect(stateManager.getState().get('tiles')).toHaveValues
|
||||
0:
|
||||
lineDecorations:
|
||||
3: null
|
||||
4: null
|
||||
5:
|
||||
lineDecorations:
|
||||
5: 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})
|
||||
|
||||
|
||||
notText = if @isNot then " not" else ""
|
||||
this.message = => "Immutable object did not have expected values: #{jasmine.pp(wrongValues)}"
|
||||
checkValues(@actual.toJS(), expected)
|
||||
console.warn "Invalid 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
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
{Emitter, Subscriber} = require 'emissary'
|
||||
Immutable = require 'immutable'
|
||||
if Immutable.Map.update?
|
||||
throw new Error("Remove the Immutable.Map::update shim now that you've upgraded immutable")
|
||||
else
|
||||
Immutable.Map::update = (key, fn) -> @set(key, fn(@get(key)))
|
||||
|
||||
module.exports =
|
||||
class DisplayStateManager
|
||||
Emitter.includeInto(this)
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
constructor: (@editor) ->
|
||||
@buildInitialState()
|
||||
@observeEditor()
|
||||
|
||||
getState: -> @state
|
||||
|
||||
setState: (@state) ->
|
||||
@emit 'did-change-state', @state
|
||||
@state
|
||||
|
||||
getTileSize: -> 5
|
||||
|
||||
getLineWidth: ->
|
||||
Math.max(@editor.getScrollWidth(), @editor.getWidth())
|
||||
|
||||
observeEditor: ->
|
||||
@subscribe @editor.$width.changes, @onWidthChanged
|
||||
@subscribe @editor.$height.changes, @onHeightChanged
|
||||
@subscribe @editor.$lineHeightInPixels.changes, @onLineHeightInPixelsChanged
|
||||
@subscribe @editor.$scrollLeft.changes, @onScrollLeftChanged
|
||||
@subscribe @editor.$scrollTop.changes, @onScrollTopChanged
|
||||
@subscribe @editor, 'screen-lines-changed', @onScreenLinesChanged
|
||||
@subscribe @editor, 'decoration-added', @onDecorationAdded
|
||||
@subscribe @editor, 'decoration-removed', @onDecorationRemoved
|
||||
@subscribe @editor, 'decoration-changed', @onDecorationChanged
|
||||
|
||||
tileStartRowForScreenRow: (screenRow) ->
|
||||
screenRow - (screenRow % @getTileSize())
|
||||
|
||||
getVisibleRowRange: ->
|
||||
heightInLines = Math.floor(@editor.getHeight() / @editor.getLineHeightInPixels())
|
||||
startRow = Math.ceil(@editor.getScrollTop() / @editor.getLineHeightInPixels())
|
||||
endRow = Math.min(@editor.getLineCount(), startRow + heightInLines)
|
||||
[startRow, endRow]
|
||||
|
||||
getTileRowRange: ->
|
||||
[startRow, endRow] = @getVisibleRowRange()
|
||||
[@tileStartRowForScreenRow(startRow), @tileStartRowForScreenRow(endRow)]
|
||||
|
||||
buildInitialState: ->
|
||||
[startRow, endRow] = @getTileRowRange()
|
||||
@state = Immutable.Map
|
||||
tiles: Immutable.Map().withMutations (tiles) =>
|
||||
for tileStartRow in [startRow..endRow] by @getTileSize()
|
||||
tiles.set(tileStartRow, @buildTile(tileStartRow))
|
||||
|
||||
updateTiles: (fn) ->
|
||||
tileSize = @getTileSize()
|
||||
[startRow, endRow] = @getTileRowRange()
|
||||
|
||||
@setState @state.update 'tiles', (tiles) ->
|
||||
tiles.withMutations (tiles) ->
|
||||
# delete any tiles that are outside of the row range
|
||||
tiles.forEach (tile, tileStartRow) ->
|
||||
unless startRow <= tileStartRow <= endRow
|
||||
tiles.delete(tileStartRow)
|
||||
|
||||
# call the callback with the start row and existing state of visible tiles
|
||||
for tileStartRow in [startRow..endRow] by tileSize
|
||||
if newTile = fn(tileStartRow, tiles.get(tileStartRow))
|
||||
tiles.set(tileStartRow, newTile)
|
||||
|
||||
updateTilesIntersectingRowRange: (rangeStartRow, rangeEndRow, fn) ->
|
||||
tileSize = @getTileSize()
|
||||
|
||||
@updateTiles (tileStartRow, tile) ->
|
||||
tileEndRow = tileStartRow + tileSize
|
||||
if rangeEndRow < tileStartRow or tileEndRow <= rangeStartRow
|
||||
tile
|
||||
else
|
||||
fn(tileStartRow, tile)
|
||||
|
||||
buildTile: (tileStartRow) ->
|
||||
lineHeightInPixels = @editor.getLineHeightInPixels()
|
||||
tileSize = @getTileSize()
|
||||
tileEndRow = tileStartRow + tileSize
|
||||
|
||||
tile = Immutable.Map
|
||||
startRow: tileStartRow
|
||||
left: 0 - @editor.getScrollLeft()
|
||||
top: tileStartRow * lineHeightInPixels - @editor.getScrollTop()
|
||||
width: @getLineWidth()
|
||||
height: lineHeightInPixels * tileSize
|
||||
lineHeightInPixels: @editor.getLineHeightInPixels()
|
||||
lines: Immutable.Vector(@editor.linesForScreenRows(tileStartRow, tileEndRow - 1)...)
|
||||
lineDecorations: Immutable.Map()
|
||||
|
||||
@tileWithInitialLineDecorations(tile)
|
||||
|
||||
tileWithInitialLineDecorations: (tile) ->
|
||||
tileStart = tile.get('startRow')
|
||||
tileEnd = tileStart + @getTileSize()
|
||||
|
||||
for markerId, decorations of @editor.decorationsForScreenRowRange(tileStart, tileEnd)
|
||||
marker = @editor.getMarker(markerId)
|
||||
headPosition = marker.getHeadScreenPosition()
|
||||
tailPosition = marker.getTailScreenPosition()
|
||||
valid = marker.isValid()
|
||||
for decoration in decorations
|
||||
continue unless decoration.isType('line')
|
||||
|
||||
id = decoration.id
|
||||
params = decoration.getParams()
|
||||
if rowRange = @rowRangeForLineDecoration(params, headPosition, tailPosition, valid)
|
||||
[start, end] = rowRange
|
||||
unless end < tileStart or tileEnd <= start
|
||||
tile = @tileWithLineDecorations(tile, start, end, id, params)
|
||||
|
||||
tile
|
||||
|
||||
onWidthChanged: (width) =>
|
||||
@updateTiles (tileStartRow, tile) => tile.set('width', width)
|
||||
|
||||
onHeightChanged: =>
|
||||
@updateTiles (tileStartRow, tile) => tile ? @buildTile(tileStartRow)
|
||||
|
||||
onLineHeightInPixelsChanged: (lineHeightInPixels) =>
|
||||
scrollTop = @editor.getScrollTop()
|
||||
|
||||
@updateTiles (tileStartRow, tile) =>
|
||||
if tile?
|
||||
tile.withMutations (tile) ->
|
||||
tile.set('top', tileStartRow * lineHeightInPixels - scrollTop)
|
||||
tile.set('lineHeightInPixels', lineHeightInPixels)
|
||||
else
|
||||
@buildTile(tileStartRow)
|
||||
|
||||
onScrollTopChanged: (scrollTop) =>
|
||||
lineHeightInPixels = @editor.getLineHeightInPixels()
|
||||
|
||||
@updateTiles (tileStartRow, tile) =>
|
||||
if tile?
|
||||
tile.set('top', tileStartRow * lineHeightInPixels - scrollTop)
|
||||
else
|
||||
@buildTile(tileStartRow)
|
||||
|
||||
onScrollLeftChanged: (scrollLeft) =>
|
||||
@updateTiles (tileStartRow, tile) ->
|
||||
tile.set('left', 0 - scrollLeft)
|
||||
|
||||
onScreenLinesChanged: (change) =>
|
||||
@updateTiles (tileStartRow, tile) =>
|
||||
tileEndRow = tileStartRow + @getTileSize()
|
||||
if change.start < tileEndRow
|
||||
tile.set 'lines',
|
||||
Immutable.Vector(@editor.linesForScreenRows(tileStartRow, tileEndRow - 1)...)
|
||||
|
||||
onDecorationAdded: (marker, decoration) =>
|
||||
return unless decoration.isType('line')
|
||||
|
||||
id = decoration.id
|
||||
params = decoration.getParams()
|
||||
headPosition = marker.getHeadScreenPosition()
|
||||
tailPosition = marker.getTailScreenPosition()
|
||||
valid = marker.isValid()
|
||||
|
||||
if rowRange = @rowRangeForLineDecoration(params, headPosition, tailPosition, valid)
|
||||
[start, end] = rowRange
|
||||
@updateTilesIntersectingRowRange start, end, (tileStart, tile) =>
|
||||
@tileWithLineDecorations(tile, start, end, id, params)
|
||||
|
||||
onDecorationRemoved: (marker, decoration) =>
|
||||
return unless decoration.isType('line')
|
||||
|
||||
id = decoration.id
|
||||
params = decoration.getParams()
|
||||
headPosition = marker.getHeadScreenPosition()
|
||||
tailPosition = marker.getTailScreenPosition()
|
||||
valid = true # FIXME: Why is a marker invalidated when destroyed? That seems wrong.
|
||||
|
||||
if rowRange = @rowRangeForLineDecoration(decoration, headPosition, tailPosition, valid)
|
||||
[start, end] = rowRange
|
||||
@updateTilesIntersectingRowRange start, end, (tileStart, tile) =>
|
||||
@tileWithoutLineDecorations(tile, start, end, id)
|
||||
|
||||
onDecorationChanged: (marker, decoration, change) =>
|
||||
return unless decoration.isType('line')
|
||||
|
||||
params = decoration.getParams()
|
||||
|
||||
{oldHeadScreenPosition, oldTailScreenPosition, wasValid} = change
|
||||
if rowRangeToRemove = @rowRangeForLineDecoration(params, oldHeadScreenPosition, oldTailScreenPosition, wasValid)
|
||||
[start, end] = rowRangeToRemove
|
||||
@updateTilesIntersectingRowRange start, end, (tileStart, tile) =>
|
||||
@tileWithoutLineDecorations(tile, start, end, decoration.id)
|
||||
|
||||
{newHeadScreenPosition, newTailScreenPosition, isValid} = change
|
||||
if rowRangeToAdd = @rowRangeForLineDecoration(params, newHeadScreenPosition, newTailScreenPosition, isValid)
|
||||
[start, end] = rowRangeToAdd
|
||||
@updateTilesIntersectingRowRange start, end, (tileStart, tile) =>
|
||||
@tileWithLineDecorations(tile, start, end, decoration.id, params)
|
||||
|
||||
rowRangeForLineDecoration: (params, headPosition, tailPosition, valid) ->
|
||||
return unless valid
|
||||
|
||||
if params.onlyHead
|
||||
return [headPosition.row, headPosition.row]
|
||||
|
||||
if params.onlyEmpty
|
||||
return unless headPosition.isEqual(tailPosition)
|
||||
|
||||
if params.onlyNonEmpty
|
||||
return if headPosition.isEqual(tailPosition)
|
||||
|
||||
start = Math.min(headPosition.row, tailPosition.row)
|
||||
end = Math.max(headPosition.row, tailPosition.row)
|
||||
[start, end]
|
||||
|
||||
tileWithLineDecorations: (tile, start, end, decorationId, decorationParams) ->
|
||||
tileStart = tile.get('startRow')
|
||||
tileEnd = tileStart + @getTileSize()
|
||||
start = Math.max(start, tileStart)
|
||||
end = Math.min(end, tileEnd)
|
||||
|
||||
tile.update 'lineDecorations', (lineDecorations) ->
|
||||
lineDecorations.withMutations (lineDecorations) ->
|
||||
for row in [start..end]
|
||||
lineDecorations.update row, (decorationsById) ->
|
||||
decorationsById ?= Immutable.Map()
|
||||
decorationsById.set(decorationId, decorationParams)
|
||||
|
||||
tileWithoutLineDecorations: (tile, start, end, decorationId) ->
|
||||
tileStart = tile.get('startRow')
|
||||
tileEnd = tileStart + @getTileSize()
|
||||
start = Math.max(start, tileStart)
|
||||
end = Math.min(end, tileEnd)
|
||||
|
||||
tile.update 'lineDecorations', (lineDecorations) ->
|
||||
lineDecorations.withMutations (lineDecorations) ->
|
||||
for row in [start..end]
|
||||
lineDecorations.update row, (decorationsById) ->
|
||||
decorationsById?.delete(decorationId)
|
||||
if lineDecorations.get(row)?.length is 0
|
||||
lineDecorations.delete(row)
|
||||
@@ -10,6 +10,7 @@ LinesComponent = require './lines-component'
|
||||
ScrollbarComponent = require './scrollbar-component'
|
||||
ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
DisplayStateManager = require './display-state-manager'
|
||||
|
||||
module.exports =
|
||||
EditorComponent = React.createClass
|
||||
@@ -56,6 +57,9 @@ EditorComponent = React.createClass
|
||||
style = {}
|
||||
|
||||
if @performedInitialMeasurement
|
||||
displayState = @displayStateManager.getState()
|
||||
tilesState = displayState.get('tiles')
|
||||
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
cursorPixelRects = @getCursorPixelRects(renderedRowRange)
|
||||
@@ -106,7 +110,7 @@ EditorComponent = React.createClass
|
||||
onBlur: @onInputBlurred
|
||||
|
||||
LinesComponent {
|
||||
ref: 'lines',
|
||||
ref: 'lines', tilesState,
|
||||
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'))
|
||||
|
||||
@displayStateManager = new DisplayStateManager(@props.editor)
|
||||
@subscribe @displayStateManager, 'did-change-state', @requestUpdate
|
||||
|
||||
componentDidMount: ->
|
||||
{editor} = @props
|
||||
|
||||
@@ -213,19 +220,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 +302,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 +535,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 +557,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)
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
{extend, toArray, isEqual} = require 'underscore-plus'
|
||||
Decoration = require './decoration'
|
||||
|
||||
WrapperDiv = document.createElement('div')
|
||||
|
||||
module.exports =
|
||||
class EditorTileComponent
|
||||
constructor: (@state) ->
|
||||
@lineNodesByLineId = {}
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@renderedDecorationsByLineId = {}
|
||||
@cursorPixelRectsById = {}
|
||||
@cursorNodesById = {}
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
@domNode.style.position = 'absolute'
|
||||
@domNode.style['-webkit-transform'] = @getTransform()
|
||||
@domNode.style.height = @state.get('height') + 'px'
|
||||
@domNode.style.width = @state.get('width') + 'px'
|
||||
@buildLines()
|
||||
|
||||
stateChangedForKeys: ->
|
||||
for key in arguments
|
||||
return true if @prevState?.get(key) isnt @state.get(key)
|
||||
false
|
||||
|
||||
update: (newState) ->
|
||||
@prevState = @state
|
||||
@state = newState
|
||||
|
||||
if @stateChangedForKeys('top', 'left')
|
||||
@domNode.style['-webkit-transform'] = @getTransform()
|
||||
|
||||
if @stateChangedForKeys('width')
|
||||
@domNode.style.width = @state.get('width') + 'px'
|
||||
|
||||
if @stateChangedForKeys('lines', 'lineDecorations')
|
||||
@updateLines()
|
||||
|
||||
# @clearScreenRowCaches() if newProps.lineHeightInPixels isnt @props.lineHeightInPixels
|
||||
# @updateLines()
|
||||
# @updateCursors()
|
||||
|
||||
getTransform: ->
|
||||
"translate3d(#{@state.get('left')}px, #{@state.get('top')}px, 0px)"
|
||||
|
||||
buildLines: ->
|
||||
startRow = @state.get('startRow')
|
||||
lines = @state.get('lines')
|
||||
|
||||
linesHTML = ""
|
||||
lines.forEach (line, i) =>
|
||||
screenRow = startRow + i
|
||||
linesHTML += @buildLineHTML(line, screenRow)
|
||||
@domNode.innerHTML = linesHTML
|
||||
|
||||
lines.forEach (line, i) =>
|
||||
screenRow = startRow + i
|
||||
lineNode = @domNode.children[i]
|
||||
@lineNodesByLineId[line.id] = lineNode
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
updateLines: ->
|
||||
lines = @state.get('lines')
|
||||
@removeLineNodes(lines)
|
||||
@appendOrUpdateVisibleLineNodes(lines)
|
||||
|
||||
removeLineNodes: (lines=[]) ->
|
||||
lineIds = new Set
|
||||
lines.forEach (line) -> lineIds.add(line.id.toString())
|
||||
|
||||
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 @renderedDecorationsByLineId[lineId]
|
||||
@domNode.removeChild(lineNode)
|
||||
|
||||
appendOrUpdateVisibleLineNodes: (visibleLines) ->
|
||||
startRow = @state.get('startRow')
|
||||
|
||||
newLines = null
|
||||
newLinesHTML = null
|
||||
|
||||
visibleLines.forEach (line, index) =>
|
||||
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
|
||||
|
||||
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 = @state.get('startRow')
|
||||
lineWidth = @state.get('width')
|
||||
lineHeightInPixels = @state.get('lineHeightInPixels')
|
||||
|
||||
lineNode = @lineNodesByLineId[line.id]
|
||||
|
||||
unless @screenRowsByLineId[line.id] is screenRow
|
||||
lineNode.style.top = (screenRow - startRow) * lineHeightInPixels + 'px'
|
||||
lineNode.dataset.screenRow = screenRow
|
||||
@screenRowsByLineId[line.id] = screenRow
|
||||
@lineIdsByScreenRow[screenRow] = line.id
|
||||
|
||||
prevLineDecorations = @prevState?.get('lineDecorations').get(screenRow)
|
||||
lineDecorations = @state.get('lineDecorations').get(screenRow)
|
||||
if lineDecorations isnt prevLineDecorations
|
||||
prevLineDecorations?.forEach (decoration) ->
|
||||
unless lineDecorations?.has(decoration.id)
|
||||
lineNode.classList.remove(decoration.class)
|
||||
|
||||
lineDecorations?.forEach (decoration) ->
|
||||
unless prevLineDecorations?.has(decoration.id)
|
||||
lineNode.classList.add(decoration.class)
|
||||
|
||||
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 = @state.get('startRow')
|
||||
lineHeightInPixels = @state.get('lineHeightInPixels')
|
||||
width = @state.get('width')
|
||||
|
||||
{text, fold, isSoftWrapped, indentLevel} = line
|
||||
|
||||
classes = @getLineClasses(screenRow)
|
||||
top = (screenRow - startRow) * lineHeightInPixels
|
||||
style = "position: absolute; top: #{top}px; width: 100%;"
|
||||
|
||||
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 decorationsById = @state.get('lineDecorations').get(screenRow)
|
||||
decorationsById.forEach (decoration) ->
|
||||
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)
|
||||
|
||||
|
||||
+8
-2
@@ -1588,8 +1588,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()
|
||||
|
||||
+42
-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,18 @@ module.exports =
|
||||
LinesComponent = React.createClass
|
||||
displayName: 'LinesComponent'
|
||||
|
||||
tileSize: 5
|
||||
|
||||
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 = {}
|
||||
|
||||
shouldComponentUpdate: (newProps) ->
|
||||
return newProps.tilesState isnt @props.tilesState
|
||||
|
||||
return true unless isEqualForProperties(newProps, @props,
|
||||
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
|
||||
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible',
|
||||
@@ -78,209 +49,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(prevProps)
|
||||
@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 +74,44 @@ LinesComponent = React.createClass
|
||||
editor.setLineHeightInPixels(lineHeightInPixels)
|
||||
editor.setDefaultCharWidth(charWidth)
|
||||
|
||||
updateTiles: (prevProps) ->
|
||||
domNode = @getDOMNode()
|
||||
|
||||
prevProps.tilesState?.forEach (tileState, tileStartRow) =>
|
||||
unless @props.tilesState.has(tileStartRow)
|
||||
tileComponent = @tileComponentsByStartRow[tileStartRow]
|
||||
domNode.removeChild(tileComponent.domNode)
|
||||
delete @tileComponentsByStartRow[tileStartRow]
|
||||
|
||||
@props.tilesState.forEach (tileState, tileStartRow) =>
|
||||
if prevProps.tilesState?.has(tileStartRow)
|
||||
tileComponent = @tileComponentsByStartRow[tileStartRow]
|
||||
tileComponent.update(tileState)
|
||||
else
|
||||
tileComponent = new EditorTileComponent(tileState)
|
||||
@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
|
||||
|
||||
|
||||
@@ -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