Comparar commits

..

44 Commits

Autor SHA1 Mensagem Data
Nathan Sobo 6e42552891 Add proper line decorations to initial state 2014-08-06 11:52:45 -06:00
Nathan Sobo 70d4af9341 Handle 'onlyNonEmpty' option for line decorations 2014-08-06 11:26:14 -06:00
Nathan Sobo 9c17e8b705 Handle 'onlyEmpty' option for line decorations 2014-08-06 11:24:16 -06:00
Nathan Sobo 7954b5c1fd Support 'onlyHead' option in line decorations 2014-08-06 11:16:01 -06:00
Nathan Sobo 3d9b22bae4 Refactor line decoration updates 2014-08-06 11:13:21 -06:00
Nathan Sobo ae6a3fddb2 Improve custom matcher to express omission of values 2014-08-06 11:13:05 -06:00
Nathan Sobo f6a8a42a6d Use line decorations to update display
Basic line decorations are working, but we still need to handle special
decoration options.
2014-08-05 19:37:54 -06:00
Nathan Sobo 16765138b8 Build line decorations into initial state 2014-08-05 19:36:55 -06:00
Nathan Sobo 3e656f83b8 Fix fat finger 2014-08-05 19:36:24 -06:00
Nathan Sobo b04b025a0c Only handle line decorations for now 2014-08-05 19:36:02 -06:00
Nathan Sobo 88743e8a71 Update the display state on when line decorations are updated or removed 2014-08-05 18:24:57 -06:00
Nathan Sobo 30d71fc37a Add line decorations to display state when they're added 2014-08-05 17:14:54 -06:00
Nathan Sobo 09aa62fae8 Make ::updateTiles an iterator for operation-specific updates
Previously, I was trying to update everything with the same method. Now
I'm performing updates that are specifically tailored to each type of
operation on the model.
2014-08-05 16:35:27 -06:00
Nathan Sobo f29e50b730 Update tile states when display buffer changes 2014-08-05 14:10:18 -06:00
Nathan Sobo dcd60a275a WIP: Base editor updates on immutable display state 2014-08-04 21:09:17 -06:00
Nathan Sobo 5e6dff9175 Render cursors 2014-08-04 12:04:46 -06:00
Nathan Sobo dd5bc5891c Break lines out into manually-updated tiles 2014-08-04 12:04:46 -06:00
Nathan Sobo 79bc071353 Avoid React handling of keydown/textInput to save ~1ms on keystrokes 2014-08-04 12:04:46 -06:00
Nathan Sobo d3c2633bb3 WIP 2014-08-04 12:04:00 -06:00
Nathan Sobo 7e45ffa4c3 Center around the cursor in Editor::scrollToCursorPosition by default
Fixes #3131
2014-08-04 11:58:07 -06:00
Kevin Sawicki 6af69b0fc7 Merge pull request #3164 from Bengt/patch-1
Correct Node.js' spelling, link Git and GNOME Keyring
2014-08-04 10:39:12 -07:00
Cheng Zhao 99e02570d1 Upgrade to atom-shell@0.15.2 2014-08-04 22:12:44 +08:00
Bengt Lüers 823cfcac57 Correct Node.js' spelling, link Git and GNOME Keyring 2014-08-03 16:06:18 +02:00
Nathan Sobo de6ccd8c08 Merge pull request #3146 from atom/ns-latency
Improve cursor movement and typing latency a bit
2014-08-02 09:21:51 -07:00
Ben Ogle 2135d3be83 Update statusbar to add toggle 2014-08-01 10:22:14 -07:00
Nathan Sobo 1c3720c160 Upgrade keybinding-resolver for spec fix 2014-07-31 15:23:58 -06:00
Nathan Sobo 6c72b13adc Upgrade keymap to avoid temp objects in keystrokeForKeyboardEvent 2014-07-31 15:21:37 -06:00
Kevin Sawicki 1404904d24 Upgrade to language-gfm@0.45 2014-07-31 14:15:01 -07:00
Nathan Sobo db243936b4 Update emissary for Emitter::emit optimization 2014-07-31 15:11:25 -06:00
Nathan Sobo 6e72627e9e Stop propagation of keydown/textInput events to prevent React handler
React's global synthetic event handler is somewhat expensive. This
prevents it from being invoked on every keystroke, saving ~1ms.
2014-07-31 15:11:06 -06:00
Kevin Sawicki 3d36ba7ecc Upgrade to scrollbar-style 1.0.2 2014-07-31 13:30:27 -07:00
Kevin Sawicki a7c0d6073f Upgrade to markdown-preview@0.95 2014-07-31 13:24:18 -07:00
Kevin Sawicki f25b468272 Upgrade to apm 0.88 2014-07-31 13:07:44 -07:00
Kevin Sawicki 2d0fb8ee6b Upgrade to incompatible-packages@0.5 2014-07-31 09:19:15 -07:00
Kevin Sawicki d875becc7a Upgrade to snippets@0.51 2014-07-31 09:06:38 -07:00
Kevin Sawicki cb72af63fd Upgrade to language-yaml@0.14 2014-07-31 08:57:09 -07:00
Kevin Sawicki f7187f1d5a Spy on atom.inDevMode() 2014-07-31 08:42:31 -07:00
Kevin Sawicki 700acdc5a2 Upgrade to incompatible-packages@0.4 2014-07-31 08:37:18 -07:00
Kevin Sawicki 18016ae9df 💄 Use unless instead of if not 2014-07-31 08:33:36 -07:00
Kevin Sawicki a30faa5bea Merge pull request #3139 from maschs/ms-incompatibleModulesDev
In devmode do not load incompatible modules from cache
2014-07-31 08:32:58 -07:00
Kevin Sawicki 05a113bb7a Merge pull request #3120 from atom/ks-remove-vendored-dlls
Remove vendored dlls
2014-07-31 08:32:53 -07:00
Kevin Sawicki f5d4ece9cd Remove vendored dlls
These are now provided by atom-shell
2014-07-31 08:20:33 -07:00
Cheng Zhao 3bda37c56c Upgrade to atom-shell@0.15.1 2014-07-31 23:19:50 +08:00
Maximilian Schüßler 62b52cb70a In devmode do not load incompatible from cache 2014-07-31 16:24:29 +02:00
20 arquivos alterados com 1011 adições e 394 exclusões
+1 -1
Ver Arquivo
@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "0.87.0"
"atom-package-manager": "0.88.0"
}
}
-3
Ver Arquivo
@@ -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'), ''
-112
Ver Arquivo
@@ -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
+7 -7
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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.
+345
Ver Arquivo
@@ -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
+8 -3
Ver Arquivo
@@ -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
+3
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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: ->
+2 -2
Ver Arquivo
@@ -220,8 +220,8 @@ class DisplayBufferMarker
@oldTailScreenPosition, newTailScreenPosition,
@oldHeadBufferPosition, newHeadBufferPosition,
@oldTailBufferPosition, newTailBufferPosition,
textChanged,
isValid
@wasValid, isValid,
textChanged
}
@oldHeadBufferPosition = newHeadBufferPosition
+246
Ver Arquivo
@@ -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)
+21 -6
Ver Arquivo
@@ -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)
+302
Ver Arquivo
@@ -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 '&nbsp;'
@buildEndOfLineHTML(line, {}) or '&nbsp;'
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]
+2 -2
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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 '&nbsp;'
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
+4 -3
Ver Arquivo
@@ -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()
+5 -2
Ver Arquivo
@@ -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)