Comparar commits

..

3 Commits

Autor SHA1 Mensagem Data
Ben Ogle ec5cd6a68e Add method organization docs 2014-08-19 19:09:56 -06:00
Ben Ogle 0fc94faf29 Changes based on feedback 2014-08-01 11:03:13 -07:00
Ben Ogle de2d9739c8 Initial API guidelines doc 2014-07-31 12:33:59 -07:00
20 arquivos alterados com 394 adições e 1011 exclusões
+1 -1
Ver Arquivo
@@ -6,6 +6,6 @@
"url": "https://github.com/atom/atom.git"
},
"dependencies": {
"atom-package-manager": "0.88.0"
"atom-package-manager": "0.87.0"
}
}
+3
Ver Arquivo
@@ -148,6 +148,9 @@ 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
@@ -0,0 +1,112 @@
# 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](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)
* git
* [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).
* development headers for [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring)
* libgnome-keyring-dev
### 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 Node.js installed, or node isn't identified as Node.js on your machine.
have nodejs installed, or node isn't identified as nodejs 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.2",
"atomShellVersion": "0.15.0",
"dependencies": {
"async": "0.2.6",
"atom-keymap": "^1.0.2",
"atom-keymap": "^1.0.0",
"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.2",
"emissary": "^1.2.1",
"first-mate": "^2.0.1",
"fs-plus": "^2.2.6",
"fstream": "0.1.24",
@@ -34,7 +34,6 @@
"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",
@@ -46,12 +45,12 @@
"property-accessors": "^1",
"q": "^1.0.1",
"random-words": "0.0.1",
"react-atom-fork": "^0.11.2",
"react-atom-fork": "^0.11.1",
"reactionary-atom-fork": "^1.0.0",
"runas": "1.0.1",
"scandal": "1.0.0",
"scoped-property-store": "^0.9.0",
"scrollbar-style": "^1.0.2",
"scrollbar-style": "^1.0.1",
"season": "^1.0.2",
"semver": "1.1.4",
"serializable": "^1",
@@ -89,18 +88,18 @@
"go-to-line": "0.23.0",
"grammar-selector": "0.27.0",
"image-view": "0.36.0",
"incompatible-packages": "0.5.0",
"keybinding-resolver": "0.19.0",
"incompatible-packages": "0.3.0",
"keybinding-resolver": "0.18.0",
"link": "0.25.0",
"markdown-preview": "0.95.0",
"markdown-preview": "0.94.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.51.0",
"snippets": "0.50.0",
"spell-check": "0.40.0",
"status-bar": "0.42.0",
"status-bar": "0.41.0",
"styleguide": "0.29.0",
"symbols-view": "0.63.0",
"tabs": "0.48.0",
@@ -110,10 +109,11 @@
"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.45.0",
"language-gfm": "0.44.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.14.0"
"language-yaml": "0.13.0"
},
"private": true,
"scripts": {
Arquivo binário não exibido.
Arquivo binário não exibido.
-345
Ver Arquivo
@@ -1,345 +0,0 @@
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
+3 -8
Ver Arquivo
@@ -3301,24 +3301,19 @@ describe "Editor", ->
expect(editor.getText()).toBe ' '
describe ".scrollToCursorPosition()", ->
it "scrolls the last cursor into view, centering around the cursor if possible and the 'center' option isn't false", ->
it "scrolls the last cursor into view", ->
editor.setCursorScreenPosition([8, 8])
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(60)
editor.setHeight(50)
editor.setWidth(50)
editor.setHorizontalScrollbarHeight(0)
expect(editor.getScrollTop()).toBe 0
expect(editor.getScrollLeft()).toBe 0
editor.scrollToCursorPosition()
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
expect(editor.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10
describe ".pageUp/Down()", ->
it "scrolls one screen height up or down and moves the cursor one page length", ->
-3
Ver Arquivo
@@ -5,9 +5,6 @@ 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: (options) ->
@editor.scrollToScreenRange(@getScreenRange(), options)
autoscroll: ->
@editor.scrollToScreenRange(@getScreenRange())
# 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,
@wasValid, isValid,
textChanged
textChanged,
isValid
}
@oldHeadBufferPosition = newHeadBufferPosition
-246
Ver Arquivo
@@ -1,246 +0,0 @@
{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)
+6 -21
Ver Arquivo
@@ -10,7 +10,6 @@ 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
@@ -57,9 +56,6 @@ EditorComponent = React.createClass
style = {}
if @performedInitialMeasurement
displayState = @displayStateManager.getState()
tilesState = displayState.get('tiles')
renderedRowRange = @getRenderedRowRange()
[renderedStartRow, renderedEndRow] = renderedRowRange
cursorPixelRects = @getCursorPixelRects(renderedRowRange)
@@ -110,7 +106,7 @@ EditorComponent = React.createClass
onBlur: @onInputBlurred
LinesComponent {
ref: 'lines', tilesState,
ref: 'lines',
editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations,
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
@@ -171,9 +167,6 @@ EditorComponent = React.createClass
@observeConfig()
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
@displayStateManager = new DisplayStateManager(@props.editor)
@subscribe @displayStateManager, 'did-change-state', @requestUpdate
componentDidMount: ->
{editor} = @props
@@ -220,21 +213,19 @@ 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
@@ -302,9 +293,7 @@ EditorComponent = React.createClass
{cursor} = selection
screenRange = cursor.getScreenRange()
if renderedStartRow <= screenRange.start.row < renderedEndRow
pixelRect = editor.pixelRectForScreenRange(screenRange)
pixelRect.startRow = screenRange.start.row
cursorPixelRects[cursor.id] = pixelRect
cursorPixelRects[cursor.id] = editor.pixelRectForScreenRange(screenRange)
cursorPixelRects
getLineDecorations: (decorationsByMarkerId) ->
@@ -535,14 +524,7 @@ 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
@@ -557,6 +539,9 @@ 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
@@ -1,302 +0,0 @@
{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 if it isn't current on screen.
# cursor.
#
# The editor is centered around the cursor's position if possible.
# The editor is also centered.
scrollToCursorPosition: ->
@scrollToBufferPosition(@editor.getCursorBufferPosition(), center: true)
+2 -8
Ver Arquivo
@@ -1588,14 +1588,8 @@ class Editor extends Model
moveCursorToBeginningOfPreviousParagraph: ->
@moveCursors (cursor) -> cursor.moveToBeginningOfPreviousParagraph()
# 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)
scrollToCursorPosition: ->
@getCursor().autoscroll()
pageUp: ->
newScrollTop = @getScrollTop() - @getHeight()
+236 -42
Ver Arquivo
@@ -7,7 +7,6 @@ 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}
@@ -17,18 +16,48 @@ module.exports =
LinesComponent = React.createClass
displayName: 'LinesComponent'
tileSize: 5
render: ->
div className: 'lines'
{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)"
componentWillMount: ->
@measuredLines = new WeakSet
@tileComponentsByStartRow = {}
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@renderedDecorationsByLineId = {}
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',
@@ -49,19 +78,209 @@ LinesComponent = React.createClass
false
componentDidUpdate: (prevProps) ->
{performedInitialMeasurement, visible, scrollingVertically} = @props
{visible, scrollingVertically, performedInitialMeasurement} = @props
return unless performedInitialMeasurement
@clearTiles() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
@updateTiles(prevProps)
@clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels
@removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
@updateLines(@props.lineWidth isnt prevProps.lineWidth)
@measureCharactersInNewLines() if visible and not scrollingVertically
lineNodeForScreenRow: (screenRow) ->
tileComponent = @tileComponentsByStartRow[@tileStartRowForScreenRow(screenRow)]
tileComponent?.lineNodeForScreenRow(screenRow)
clearScreenRowCaches: ->
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
tileStartRowForScreenRow: (screenRow) ->
screenRow - (screenRow % @tileSize)
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]]
measureLineHeightAndDefaultCharWidth: ->
node = @getDOMNode()
@@ -74,44 +293,19 @@ 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, i in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
screenRow = visibleStartRow + i
for tokenizedLine in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
unless @measuredLines.has(tokenizedLine)
lineNode = @lineNodeForScreenRow(screenRow)
lineNode = @lineNodesByLineId[tokenizedLine.id]
@measureCharactersInLine(tokenizedLine, lineNode)
return
+3 -4
Ver Arquivo
@@ -371,10 +371,9 @@ class Package
# to minimize the impact on startup time.
getIncompatibleNativeModules: ->
localStorageKey = "installed-packages:#{@name}:#{@metadata.version}"
unless atom.inDevMode()
try
{incompatibleNativeModules} = JSON.parse(global.localStorage.getItem(localStorageKey)) ? {}
return incompatibleNativeModules if incompatibleNativeModules?
try
{incompatibleNativeModules} = JSON.parse(global.localStorage.getItem(localStorageKey)) ? {}
return incompatibleNativeModules if incompatibleNativeModules?
incompatibleNativeModules = []
for nativeModulePath in @getNativeModuleDependencyPaths()
+2 -5
Ver Arquivo
@@ -57,7 +57,8 @@ class WindowEventHandler
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious
document.addEventListener 'keydown', @onKeydown
@subscribe $(document), 'keydown', (event) ->
atom.keymaps.handleKeyboardEvent(event.originalEvent)
@subscribe $(document), 'drop', (e) ->
e.preventDefault()
@@ -94,10 +95,6 @@ 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)