Comparar commits

...

42 Commits

Autor SHA1 Mensagem Data
Nathan Sobo 0d587028af Preserve tiles that are the target of mousewheel events
This allows velocity-style mouse wheel scrolling to continue once the
tile has been scrolled off screen.
2014-08-08 14:39:25 -06:00
Nathan Sobo d4207c04d6 Render content and gutter tiles with an opaque background color 2014-08-07 18:40:57 -06:00
Nathan Sobo da864b6eec Account for scroll position when translating mouse clicks to positions 2014-08-07 15:22:47 -06:00
Nathan Sobo ce75ee011a Maintain scrollTop and scrollLeft as top-level presenter properties 2014-08-07 15:22:22 -06:00
Nathan Sobo 71b7fb3b5d Render line number decorations based on presenter state 2014-08-07 15:17:05 -06:00
Nathan Sobo f413a21a11 Add line number decorations to the presenter 2014-08-07 15:01:22 -06:00
Nathan Sobo 544cd54a41 Start rendering tiled line numbers. Still rough. 2014-08-07 15:00:35 -06:00
Nathan Sobo 8b1eec39b5 Add a dummy tile to the gutter presenter with the maxLineNumberDigits
This will be used to hold open the gutter's width despite the absolute
positioned tiles.
2014-08-07 09:02:14 -06:00
Nathan Sobo 02b955f823 Break out 'content' and 'gutter' presenters
For now they're just an extra level in the hash, but we'll need to store
the maxLineNumberDigits in the gutter presenter for maintenance of the
dummy line node.
2014-08-07 08:48:00 -06:00
Nathan Sobo 30d386527d Update maxLineNumberDigits in gutter tiles 2014-08-07 07:19:10 -06:00
Nathan Sobo 0af6dfc94c Decide how to handle screen line changes in each tile presenter 2014-08-07 07:19:10 -06:00
Nathan Sobo ef7a6b94b2 Combine specs for line and gutter tiles 2014-08-07 07:19:10 -06:00
Nathan Sobo 4d6be72f56 Start adding gutter tiles to EditorPresenter 2014-08-07 07:19:09 -06:00
Nathan Sobo 3ded5ec0f8 Return dot-separated line numbers to indicate soft wraps 2014-08-07 07:19:09 -06:00
Nathan Sobo 2f15f72139 Add DisplayBuffer::lineNumbersForScreenRows 2014-08-07 07:19:09 -06:00
Nathan Sobo 6f9fa39ad2 Remove DisplayStateManager. Using EditorPresenter instead. 2014-08-07 07:19:09 -06:00
Nathan Sobo f2eeb0629f Avoid occasional blank lines at the top and bottom of the editor 2014-08-07 07:19:09 -06:00
Nathan Sobo 1fdb4dbeda Un-f 2014-08-07 07:19:09 -06:00
Nathan Sobo 54c2139992 Add line decorations to EditorPresenter 2014-08-07 07:19:09 -06:00
Nathan Sobo 6e7dedb198 Explicit model tile height in presenter 2014-08-07 07:19:09 -06:00
Nathan Sobo 1fc33e1e21 Render and update lines based on mutable presenter 2014-08-07 07:19:09 -06:00
Nathan Sobo 583096a916 Handle scrollLeft changes 2014-08-07 07:19:09 -06:00
Nathan Sobo 4d88acd756 Start on a mutable EditorPresenter object 2014-08-07 07:19:09 -06:00
Nathan Sobo a3afaed950 Add proper line decorations to initial state 2014-08-07 07:19:09 -06:00
Nathan Sobo 716219b9a3 Handle 'onlyNonEmpty' option for line decorations 2014-08-07 07:19:08 -06:00
Nathan Sobo bc558194e9 Handle 'onlyEmpty' option for line decorations 2014-08-07 07:19:08 -06:00
Nathan Sobo c1ee2f420b Support 'onlyHead' option in line decorations 2014-08-07 07:19:08 -06:00
Nathan Sobo 58d9304c22 Refactor line decoration updates 2014-08-07 07:19:08 -06:00
Nathan Sobo 3226f54b97 Improve custom matcher to express omission of values 2014-08-07 07:19:08 -06:00
Nathan Sobo 63969a6467 Use line decorations to update display
Basic line decorations are working, but we still need to handle special
decoration options.
2014-08-07 07:19:08 -06:00
Nathan Sobo d5f7aca0ad Build line decorations into initial state 2014-08-07 07:19:08 -06:00
Nathan Sobo 94160044ad Fix fat finger 2014-08-07 07:19:08 -06:00
Nathan Sobo 4dcb35152f Only handle line decorations for now 2014-08-07 07:19:08 -06:00
Nathan Sobo 45ac962681 Update the display state on when line decorations are updated or removed 2014-08-07 07:19:08 -06:00
Nathan Sobo c5d502ab88 Add line decorations to display state when they're added 2014-08-07 07:19:08 -06:00
Nathan Sobo c75fc8aa01 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-07 07:19:07 -06:00
Nathan Sobo e954430599 Update tile states when display buffer changes 2014-08-07 07:19:07 -06:00
Nathan Sobo 44795da767 WIP: Base editor updates on immutable display state 2014-08-07 07:19:07 -06:00
Nathan Sobo 11df5c2855 Render cursors 2014-08-07 07:19:07 -06:00
Nathan Sobo 11189692b7 Break lines out into manually-updated tiles 2014-08-07 07:19:07 -06:00
Nathan Sobo a7fb07ccfa Avoid React handling of keydown/textInput to save ~1ms on keystrokes 2014-08-07 07:19:07 -06:00
Nathan Sobo a8df20271f WIP 2014-08-07 07:19:07 -06:00
12 arquivos alterados com 1594 adições e 422 exclusões
+1 -2
Ver Arquivo
@@ -45,7 +45,7 @@
"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",
@@ -109,7 +109,6 @@
"welcome": "0.17.0",
"whitespace": "0.25.0",
"wrap-guide": "0.21.0",
"language-c": "0.26.0",
"language-coffee-script": "0.29.0",
"language-css": "0.17.0",
+8
Ver Arquivo
@@ -1167,3 +1167,11 @@ describe "DisplayBuffer", ->
expect(displayBuffer.getScrollWidth()).toBe 10 * 63 + operatorWidth * 2 + cursorWidth
expect(changedSpy.callCount).toBe 1
describe "::lineNumbersForScreenRows(startRow, endRow)", ->
it "returns the line numbers for the given screen row range, inclusive of the endRow", ->
displayBuffer.createFold(4, 7)
displayBuffer.setEditorWidthInChars(30)
displayBuffer.setSoftWrap(true)
expect(displayBuffer.lineNumbersForScreenRows(6, 11)).toEqual ['4.2', '5', '9', '9.1', '9.2', '10']
+581
Ver Arquivo
@@ -0,0 +1,581 @@
TextBuffer = require 'text-buffer'
_ = require 'underscore-plus'
EditorPresenter = require '../src/editor-presenter'
Editor = require '../src/editor'
describe "DisplayStateManager", ->
[buffer, editor, presenter] = []
beforeEach ->
@addMatchers(toHaveValues: ToHaveValuesMatcher)
spyOn(EditorPresenter::, 'getContentTileSize').andReturn 5
spyOn(EditorPresenter::, 'getGutterTileSize').andReturn 5
buffer = new TextBuffer(filePath: atom.project.resolve('sample.js'))
buffer.loadSync()
buffer.insert([12, 3], '\n' + buffer.getText()) # repeat text so we have more lines
editor = new Editor({buffer})
editor.setLineHeightInPixels(10)
editor.setDefaultCharWidth(10)
editor.setHeight(100)
editor.setWidth(500)
presenter = new EditorPresenter(editor)
afterEach ->
editor.destroy()
describe "scrollPosition", ->
it "maintains the scrollTop and scrollLeft as top-level presenter properties", ->
expect(presenter.scrollTop).toBe 0
expect(presenter.scrollLeft).toBe 0
editor.setScrollTop(20)
editor.setScrollLeft(30)
expect(presenter.scrollTop).toBe 20
expect(presenter.scrollLeft).toBe 30
describe "tiles", ->
describe "initial state", ->
it "renders tiles that overlap the visible row range", ->
expect(presenter).toHaveValues
content:
tiles:
0:
startRow: 0
top: 0
width: editor.getWidth()
height: 5 * 10
lineHeightInPixels: 10
lines: editor.linesForScreenRows(0, 4)
5:
startRow: 5
top: 50
width: editor.getWidth()
height: 5 * 10
lineHeightInPixels: 10
lines: editor.linesForScreenRows(5, 9)
10:
startRow: 10
top: 100
width: editor.getWidth()
height: 5 * 10
lineHeightInPixels: 10
lines: editor.linesForScreenRows(10, 14)
gutter:
tiles:
0:
startRow: 0
top: 0
height: 5 * 10
lineHeightInPixels: 10
lineNumbers: editor.lineNumbersForScreenRows(0, 4)
5:
startRow: 5
top: 50
height: 5 * 10
lineHeightInPixels: 10
lineNumbers: editor.lineNumbersForScreenRows(5, 9)
10:
startRow: 10
top: 100
height: 5 * 10
lineHeightInPixels: 10
lineNumbers: editor.lineNumbersForScreenRows(10, 14)
it "renders a dummy gutter tile to maintain the proper gutter width", ->
expect(presenter.gutter).toHaveValues
dummyTile:
dummy: true
maxLineNumberDigits: 2
it "assigns the backgroundColor on the content and gutter tiles, favoring the gutter's background color if it's assigned", ->
editor.setBackgroundColor('#ff0')
presenter = new EditorPresenter(editor)
expect(presenter).toHaveValues
content:
tiles:
0: backgroundColor: '#ff0'
5: backgroundColor: '#ff0'
10: backgroundColor: '#ff0'
gutter:
tiles:
0: backgroundColor: '#ff0'
5: backgroundColor: '#ff0'
10: backgroundColor: '#ff0'
editor.setGutterBackgroundColor('#a00')
presenter = new EditorPresenter(editor)
expect(presenter).toHaveValues
content:
tiles:
0: backgroundColor: '#ff0'
5: backgroundColor: '#ff0'
10: backgroundColor: '#ff0'
gutter:
tiles:
0: backgroundColor: '#a00'
5: backgroundColor: '#a00'
10: backgroundColor: '#a00'
describe "when the width is changed", ->
it "updates the line tiles with the new width", ->
editor.setWidth(700)
expect(presenter.content.tiles).toHaveValues
0:
width: 700
5:
width: 700
10:
width: 700
describe "when the height is changed", ->
it "updates the rendered tiles to reflect the change", ->
editor.setHeight(160)
expect(presenter).toHaveValues
content:
tiles:
0:
startRow: 0
top: 0
5:
startRow: 5
top: 50
10:
startRow: 10
top: 100
15:
startRow: 15
top: 150
gutter:
tiles:
0:
startRow: 0
top: 0
5:
startRow: 5
top: 50
10:
startRow: 10
top: 100
15:
startRow: 15
top: 150
editor.setHeight(70)
expect(presenter).toHaveValues
content:
tiles:
0:
startRow: 0
top: 0
5:
startRow: 5
top: 50
gutter:
tiles:
0:
startRow: 0
top: 0
5:
startRow: 5
top: 50
describe "when lineHeightInPixels changes", ->
it "updates the rendered tiles to reflect the change", ->
editor.setScrollTop(10)
editor.setLineHeightInPixels(7)
expect(presenter).toHaveValues
content:
tiles:
0:
startRow: 0
top: 0 - 10
height: 5 * 7
lineHeightInPixels: 7
5:
startRow: 5
top: 7 * 5 - 10
height: 5 * 7
lineHeightInPixels: 7
10:
startRow: 10
top: 7 * 10 - 10
height: 5 * 7
lineHeightInPixels: 7
15:
startRow: 15
top: 7 * 15 - 10
height: 5 * 7
lineHeightInPixels: 7
gutter:
tiles:
0:
startRow: 0
top: 0 - 10
height: 5 * 7
lineHeightInPixels: 7
5:
startRow: 5
top: 7 * 5 - 10
height: 5 * 7
lineHeightInPixels: 7
10:
startRow: 10
top: 7 * 10 - 10
height: 5 * 7
lineHeightInPixels: 7
15:
startRow: 15
top: 7 * 15 - 10
height: 5 * 7
lineHeightInPixels: 7
describe "when scrollTop changes", ->
it "updates the rendered tiles to reflect the change", ->
editor.setScrollTop(20)
expect(presenter).toHaveValues
content:tiles:
0:
top: -20
lines: editor.linesForScreenRows(0, 4)
5:
top: 30
lines: editor.linesForScreenRows(5, 9)
10:
top: 80
lines: editor.linesForScreenRows(10, 14)
gutter:tiles:
0:
top: -20
lineNumbers: editor.lineNumbersForScreenRows(0, 4)
5:
top: 30
lineNumbers: editor.lineNumbersForScreenRows(5, 9)
10:
top: 80
lineNumbers: editor.lineNumbersForScreenRows(10, 14)
editor.setScrollTop(70)
expect(presenter.content.tiles).toHaveValues
5:
top: -20
lines: editor.linesForScreenRows(5, 9)
10:
top: 30
lines: editor.linesForScreenRows(10, 14)
15:
top: 80
lines: editor.linesForScreenRows(15, 19)
describe "when scrollLeft changes", ->
it "updates the rendered tiles to reflect the change", ->
expect(presenter.content.tiles).toHaveValues
0:
left: 0
5
left: 0
10:
left: 0
editor.setScrollLeft(30)
expect(presenter.content.tiles).toHaveValues
0:
left: -30
5:
left: -30
10:
left: -30
describe "when the backgroundColor or gutterBackgroundColor change", ->
it "updates the backgroundColor of the tiles", ->
editor.setBackgroundColor('#abe')
expect(presenter).toHaveValues
content:
tiles:
0: backgroundColor: '#abe'
5: backgroundColor: '#abe'
10: backgroundColor: '#abe'
gutter:
tiles:
0: backgroundColor: '#abe'
5: backgroundColor: '#abe'
10: backgroundColor: '#abe'
editor.setGutterBackgroundColor('#dad')
expect(presenter).toHaveValues
content:
tiles:
0: backgroundColor: '#abe'
5: backgroundColor: '#abe'
10: backgroundColor: '#abe'
gutter:
tiles:
0: backgroundColor: '#dad'
5: backgroundColor: '#dad'
10: backgroundColor: '#dad'
describe "lines", ->
describe "initial state", ->
it "breaks lines into tiles", ->
expect(presenter.content.tiles).toHaveValues
0:
startRow: 0
lines: editor.linesForScreenRows(0, 4)
5:
startRow: 5
lines: editor.linesForScreenRows(5, 9)
10:
startRow: 10
lines: editor.linesForScreenRows(10, 14)
describe "when the screen lines change", ->
it "updates the lines in the tiles to reflect the change", ->
buffer.setTextInRange([[3, 5], [7, 0]], "a\nb\nc\nd")
expect(presenter.content.tiles).toHaveValues
0:
startRow: 0
lines: editor.linesForScreenRows(0, 4)
5:
startRow: 5
lines: editor.linesForScreenRows(5, 9)
10
startRow: 10
lines: editor.linesForScreenRows(10, 14)
describe "line decorations", ->
marker = null
beforeEach ->
marker = editor.markBufferRange([[3, 4], [5, 6]], invalidate: 'touch')
describe "initial state", ->
it "renders existing line decorations on the appropriate lines", ->
decoration = editor.decorateMarker(marker, type: 'line', class: 'test')
presenter = new EditorPresenter(editor)
decorationsById = {}
decorationsById[decoration.id] = decoration.getParams()
expect(presenter.content.tiles).toHaveValues
0:
lineDecorations:
3: decorationsById
4: decorationsById
5:
lineDecorations:
5: decorationsById
describe "when a line decoration is added, updated, invalidated, or removed", ->
it "updates the presented line decorations accordingly", ->
decoration = editor.decorateMarker(marker, type: 'line', class: 'test')
decorationsById = {}
decorationsById[decoration.id] = decoration.getParams()
expect(presenter.content.tiles).toHaveValues
0:
lineDecorations:
3: decorationsById
4: decorationsById
5:
lineDecorations:
5: decorationsById
marker.setBufferRange([[8, 4], [10, 6]])
expect(presenter.content.tiles).toHaveValues
0:
lineDecorations:
3: null
4: null
5:
lineDecorations:
5: null
8: decorationsById
9: decorationsById
10:
lineDecorations:
10: decorationsById
buffer.insert([8, 5], 'invalidate marker')
expect(presenter.content.tiles).toHaveValues
5:
lineDecorations:
8: null
9: null
10:
lineDecorations:
10: null
buffer.undo()
expect(presenter.content.tiles).toHaveValues
5:
lineDecorations:
8: decorationsById
9: decorationsById
10:
lineDecorations:
10: decorationsById
marker.destroy()
expect(presenter.content.tiles).toHaveValues
5:
lineDecorations:
8: null
9: null
10:
lineDecorations:
10: null
describe "line numbers", ->
describe "when the screen lines change", ->
it "updates the line numbers to reflect the change", ->
editor.createFold(4, 7)
expect(presenter.gutter.tiles).toHaveValues
0:
lineNumbers: editor.lineNumbersForScreenRows(0, 4)
5:
lineNumbers: editor.lineNumbersForScreenRows(5, 9)
10:
lineNumbers: editor.lineNumbersForScreenRows(10, 14)
it "updates the maxLineNumberDigits if necessary", ->
buffer.setText('')
expect(presenter.gutter.dummyTile.maxLineNumberDigits).toBe 1
expect(presenter.gutter.tiles).toHaveValues
0:
maxLineNumberDigits: 1
buffer.setText([0..10].join('\n'))
expect(presenter.gutter.dummyTile.maxLineNumberDigits).toBe 2
expect(presenter.gutter.tiles).toHaveValues
0:
maxLineNumberDigits: 2
5:
maxLineNumberDigits: 2
10:
maxLineNumberDigits: 2
buffer.delete([[8, 0], [Infinity, 0]])
expect(presenter.gutter.dummyTile.maxLineNumberDigits).toBe 1
expect(presenter.gutter.tiles).toHaveValues
0:
maxLineNumberDigits: 1
5:
maxLineNumberDigits: 1
describe "line number decorations", ->
marker = null
beforeEach ->
marker = editor.markBufferRange([[3, 4], [5, 6]], invalidate: 'touch')
describe "initial state", ->
it "renders existing line number decorations on the appropriate lines", ->
decoration = editor.decorateMarker(marker, type: 'gutter', class: 'test')
presenter = new EditorPresenter(editor)
decorationsById = {}
decorationsById[decoration.id] = decoration.getParams()
expect(presenter.gutter.tiles).toHaveValues
0:
lineNumberDecorations:
3: decorationsById
4: decorationsById
5:
lineNumberDecorations:
5: decorationsById
describe "when a line number decorations is added, updated, invalidated, or removed", ->
it "updates the presented line decorations accordingly", ->
decoration = editor.decorateMarker(marker, type: 'gutter', class: 'test')
decorationsById = {}
decorationsById[decoration.id] = decoration.getParams()
expect(presenter.gutter.tiles).toHaveValues
0:
lineNumberDecorations:
3: decorationsById
4: decorationsById
5:
lineNumberDecorations:
5: decorationsById
marker.setBufferRange([[8, 4], [10, 6]])
expect(presenter.gutter.tiles).toHaveValues
0:
lineNumberDecorations:
3: null
4: null
5:
lineNumberDecorations:
5: null
8: decorationsById
9: decorationsById
10:
lineNumberDecorations:
10: decorationsById
buffer.insert([8, 5], 'invalidate marker')
expect(presenter.gutter.tiles).toHaveValues
5:
lineNumberDecorations:
8: null
9: null
10:
lineNumberDecorations:
10: null
buffer.undo()
expect(presenter.gutter.tiles).toHaveValues
5:
lineNumberDecorations:
8: decorationsById
9: decorationsById
10:
lineNumberDecorations:
10: decorationsById
marker.destroy()
expect(presenter.gutter.tiles).toHaveValues
5:
lineNumberDecorations:
8: null
9: null
10:
lineNumberDecorations:
10: null
ToHaveValuesMatcher = (expected) ->
hasAllValues = true
wrongValues = {}
checkValues = (actual, expected, keyPath=[]) ->
for key, expectedValue of expected
key = numericKey if numericKey = parseInt(key)
currentKeyPath = keyPath.concat([key])
if expectedValue?
if actual.hasOwnProperty(key)
actualValue = actual[key]
if expectedValue.constructor is Object and _.size(expectedValue) > 0
checkValues(actualValue, expectedValue, currentKeyPath)
else
unless _.isEqual(actualValue, expectedValue)
hasAllValues = false
_.setValueForKeyPath(wrongValues, currentKeyPath.join('.'), {actualValue, expectedValue})
else
hasAllValues = false
_.setValueForKeyPath(wrongValues, currentKeyPath.join('.'), {expectedValue})
else
actualValue = actual[key]
if actualValue?
hasAllValues = false
_.setValueForKeyPath(wrongValues, currentKeyPath.join('.'), {actualValue, expectedValue})
this.message = => "Object did not have expected values: #{jasmine.pp(wrongValues)}"
checkValues(@actual, expected)
console.warn "Object did not have expected values:", wrongValues unless hasAllValues
hasAllValues
+2 -2
Ver Arquivo
@@ -220,8 +220,8 @@ class DisplayBufferMarker
@oldTailScreenPosition, newTailScreenPosition,
@oldHeadBufferPosition, newHeadBufferPosition,
@oldTailBufferPosition, newTailBufferPosition,
textChanged,
isValid
@wasValid, isValid,
textChanged
}
@oldHeadBufferPosition = newHeadBufferPosition
+33 -1
Ver Arquivo
@@ -401,13 +401,45 @@ class DisplayBuffer extends Model
# buffer rows corresponding to every screen row in the range
#
# startScreenRow - The screen row {Number} to start at
# endScreenRow - The screen row {Number} to end at (default: the last screen row)
# endScreenRow - The screen row {Number} to end at, inclusive.
#
# Returns an {Array} of buffer rows as {Numbers}s.
bufferRowsForScreenRows: (startScreenRow, endScreenRow) ->
for screenRow in [startScreenRow..endScreenRow]
@rowMap.bufferRowRangeForScreenRow(screenRow)[0]
# Given starting and ending screen rows, this returns an array of the line
# number strings corresponding to every screen row in the range.
#
# Line numbers start at 1 as opposed to row numbers which start at 0.
# Soft wrapped lines are indicated by dot-separated strings. For example, if
# line 9 wraps twice, it will appear as '9', '9.1', '9.2'.
#
# startScreenRow - The screen row {Number} to start at
# endScreenRow - The screen row {Number} to end at, inclusive.
#
# Returns an {Array} of line numbers as {String}s.
lineNumbersForScreenRows: (startScreenRow, endScreenRow) ->
bufferRows = @bufferRowsForScreenRows(startScreenRow, endScreenRow)
# Pad the leading
leadingSoftWraps = 0
while @bufferRowForScreenRow(startScreenRow - leadingSoftWraps - 1) is bufferRows[0]
bufferRows.unshift(bufferRows[0])
leadingSoftWraps++
lineNumbers = []
for bufferRow in bufferRows
lineNumber = (bufferRow + 1).toString()
if bufferRow is lastBufferRow
lineNumber += ".#{++softWraps}"
else
lastBufferRow = bufferRow
softWraps = 0
lineNumbers.push(lineNumber)
lineNumbers[leadingSoftWraps..]
# Creates a new fold between two row numbers.
#
# startRow - The row {Number} to start folding at
+22 -7
Ver Arquivo
@@ -10,6 +10,7 @@ LinesComponent = require './lines-component'
ScrollbarComponent = require './scrollbar-component'
ScrollbarCornerComponent = require './scrollbar-corner-component'
SubscriberMixin = require './subscriber-mixin'
EditorPresenter = require './editor-presenter'
module.exports =
EditorComponent = React.createClass
@@ -50,6 +51,8 @@ EditorComponent = React.createClass
render: ->
{focused, showIndentGuide, showInvisibles, showLineNumbers, visible} = @state
{editor, mini, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
contentPresenter = @presenter.content
gutterPresenter = @presenter.gutter
maxLineNumberDigits = editor.getLineCount().toString().length
invisibles = if showInvisibles and not mini then @state.invisibles else {}
hasSelection = editor.getSelection()? and !editor.getSelection().isEmpty()
@@ -91,7 +94,8 @@ EditorComponent = React.createClass
div {className, style, tabIndex: -1},
if @shouldRenderGutter()
GutterComponent {
ref: 'gutter', onMouseDown: @onGutterMouseDown, lineDecorations,
ref: 'gutter', gutterPresenter
onMouseDown: @onGutterMouseDown, lineDecorations,
defaultCharWidth, editor, renderedRowRange, maxLineNumberDigits, scrollViewHeight,
scrollTop, scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
@useHardwareAcceleration, @performedInitialMeasurement, @backgroundColor, @gutterBackgroundColor
@@ -106,7 +110,7 @@ EditorComponent = React.createClass
onBlur: @onInputBlurred
LinesComponent {
ref: 'lines',
ref: 'lines', contentPresenter,
editor, lineHeightInPixels, defaultCharWidth, lineDecorations, highlightDecorations,
showIndentGuide, renderedRowRange, @pendingChanges, scrollTop, scrollLeft,
@scrollingVertically, scrollHeight, scrollWidth, mouseWheelScreenRow, invisibles,
@@ -167,6 +171,9 @@ EditorComponent = React.createClass
@observeConfig()
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
@presenter = new EditorPresenter(@props.editor)
@subscribe @presenter, 'did-change', @requestUpdate
componentDidMount: ->
{editor} = @props
@@ -214,19 +221,21 @@ EditorComponent = React.createClass
@measureScrollbars() if @measuringScrollbars
performInitialMeasurement: ->
console.log "INITIAL MEASUREMENT"
@updatesPaused = true
@measureHeightAndWidth()
@sampleFontStyling()
@sampleBackgroundColors()
@measureScrollbars()
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
# @remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
@props.editor.setVisible(true)
@updatesPaused = false
@performedInitialMeasurement = true
requestUpdate: ->
return unless @isMounted()
@pauseDOMPolling()
if @updatesPaused
@updateRequestedWhilePaused = true
@@ -294,7 +303,9 @@ EditorComponent = React.createClass
{cursor} = selection
screenRange = cursor.getScreenRange()
if renderedStartRow <= screenRange.start.row < renderedEndRow
cursorPixelRects[cursor.id] = editor.pixelRectForScreenRange(screenRange)
pixelRect = editor.pixelRectForScreenRange(screenRange)
pixelRect.startRow = screenRange.start.row
cursorPixelRects[cursor.id] = pixelRect
cursorPixelRects
getLineDecorations: (decorationsByMarkerId) ->
@@ -532,6 +543,7 @@ EditorComponent = React.createClass
event.preventDefault() unless event.data is ' '
return unless @isInputEnabled()
event.reactSkipEventDispatch = true
{editor} = @props
inputNode = event.target
@@ -831,17 +843,20 @@ EditorComponent = React.createClass
@remeasureCharacterWidths()
sampleBackgroundColors: (suppressUpdate) ->
{parentView} = @props
{editor, parentView} = @props
{showLineNumbers} = @state
{backgroundColor} = getComputedStyle(parentView.element)
if backgroundColor isnt @backgroundColor
editor.setBackgroundColor(backgroundColor)
@backgroundColor = backgroundColor
@requestUpdate() unless suppressUpdate
if @shouldRenderGutter()
gutterBackgroundColor = getComputedStyle(@refs.gutter.getDOMNode()).backgroundColor
gutterBackgroundColor = null if gutterBackgroundColor is 'rgba(0, 0, 0, 0)'
if gutterBackgroundColor isnt @gutterBackgroundColor
editor.setGutterBackgroundColor(backgroundColor)
@gutterBackgroundColor = gutterBackgroundColor
@requestUpdate() unless suppressUpdate
@@ -978,8 +993,8 @@ EditorComponent = React.createClass
{clientX, clientY} = event
linesClientRect = @refs.lines.getDOMNode().getBoundingClientRect()
top = clientY - linesClientRect.top
left = clientX - linesClientRect.left
top = clientY - linesClientRect.top + @presenter.scrollTop
left = clientX - linesClientRect.left + @presenter.scrollLeft
{top, left}
getModel: ->
+331
Ver Arquivo
@@ -0,0 +1,331 @@
{Emitter, Subscriber} = require 'emissary'
_ = require 'underscore-plus'
module.exports =
class EditorPresenter
Emitter.includeInto(this)
Subscriber.includeInto(this)
constructor: (@editor) ->
@content = {tiles: {}}
@gutter = {tiles: {}, dummyTile: {dummy: true}}
@updateTiles()
@updateDummyGutterTile()
@scrollTop = @editor.getScrollTop()
@scrollLeft = @editor.getScrollLeft()
@subscribe @editor.$width.changes, @onWidthChanged
@subscribe @editor.$height.changes, @onHeightChanged
@subscribe @editor.$lineHeightInPixels.changes, @onLineHeightInPixelsChanged
@subscribe @editor.$scrollTop.changes, @onScrollTopChanged
@subscribe @editor.$scrollLeft.changes, @onScrollLeftChanged
@subscribe @editor.$backgroundColor.changes, @onBackgroundColorChanged
@subscribe @editor.$gutterBackgroundColor.changes, @onBackgroundColorChanged
@subscribe @editor, 'screen-lines-changed', @onScreenLinesChanged
@subscribe @editor, 'decoration-added', @onDecorationAdded
@subscribe @editor, 'decoration-removed', @onDecorationRemoved
@subscribe @editor, 'decoration-changed', @onDecorationChanged
getContentTileSize: -> 5
getGutterTileSize: -> 20
getVisibleRowRange: ->
heightInLines = Math.ceil(@editor.getHeight() / @editor.getLineHeightInPixels())
startRow = Math.floor(@editor.getScrollTop() / @editor.getLineHeightInPixels())
endRow = Math.min(@editor.getLineCount(), startRow + heightInLines)
[startRow, endRow]
contentTileStartRowForRow: (startRow) ->
startRow - (startRow % @getContentTileSize())
gutterTileStartRowForRow: (startRow) ->
startRow - (startRow % @getGutterTileSize())
getContentTileRowRange: ->
[startRow, endRow] = @getVisibleRowRange()
startRow = @contentTileStartRowForRow(startRow)
endRow = @contentTileStartRowForRow(endRow) + @getContentTileSize()
[startRow, endRow]
getGutterTileRowRange: ->
[startRow, endRow] = @getVisibleRowRange()
startRow = @gutterTileStartRowForRow(startRow)
endRow = @gutterTileStartRowForRow(endRow) + @getGutterTileSize()
[startRow, endRow]
updateTiles: (fn) ->
@updateContentTiles(fn)
@updateGutterTiles(fn)
@emit 'did-change'
updateContentTiles: (fn) ->
[startRow, endRow] = @getContentTileRowRange()
for tileStartRow of @content.tiles
delete @content.tiles[tileStartRow] unless startRow <= tileStartRow < endRow
for tileStartRow in [startRow...endRow] by @getContentTileSize()
if existingTile = @content.tiles[tileStartRow]
fn?(existingTile)
else
tileEndRow = tileStartRow + @getContentTileSize()
@content.tiles[tileStartRow] = new ContentTilePresenter(@editor, tileStartRow, tileEndRow)
updateGutterTiles: (fn) ->
[startRow, endRow] = @getGutterTileRowRange()
for tileStartRow of @gutter.tiles
delete @gutter.tiles[tileStartRow] unless startRow <= tileStartRow < endRow
for tileStartRow in [startRow...endRow] by @getGutterTileSize()
if existingTile = @gutter.tiles[tileStartRow]
fn?(existingTile)
else
tileEndRow = tileStartRow + @getGutterTileSize()
@gutter.tiles[tileStartRow] = new GutterTilePresenter(@editor, tileStartRow, tileEndRow)
updateDummyGutterTile: ->
@gutter.dummyTile.maxLineNumberDigits = @editor.getLineCount().toString().length
onWidthChanged: =>
@updateTiles (tile) -> tile.updateWidth()
onHeightChanged: =>
@updateTiles()
onLineHeightInPixelsChanged: =>
@updateTiles (tile) -> tile.updateLineHeightInPixels()
onScrollTopChanged: (@scrollTop) =>
@updateTiles (tile) -> tile.updateScrollTop()
onScrollLeftChanged: (@scrollLeft) =>
@updateTiles (tile) -> tile.updateScrollLeft()
onScreenLinesChanged: (change) =>
@updateDummyGutterTile() if change.bufferDelta isnt 0
@updateTiles (tile) -> tile.onScreenLinesChanged(change)
onBackgroundColorChanged: =>
@updateTiles (tile) -> tile.updateBackgroundColor()
onDecorationAdded: (marker, decoration) =>
@updateTiles (tile) -> tile.onDecorationAdded(decoration)
onDecorationRemoved: (marker, decoration) =>
@updateTiles (tile) -> tile.onDecorationRemoved(decoration)
onDecorationChanged: (marker, decoration, change) =>
@updateTiles (tile) -> tile.onDecorationChanged(decoration, change)
class ContentTilePresenter
constructor: (@editor, @startRow, @endRow) ->
@lineDecorations = {}
@updateWidth()
@updateLineHeightInPixels()
@updateScrollTop()
@updateScrollLeft()
@updateBackgroundColor()
@updateLines()
@populateDecorations()
updateWidth: ->
@width = @editor.getWidth()
updateHeight: ->
@height = (@endRow - @startRow) * @editor.getLineHeightInPixels()
updateLineHeightInPixels: ->
@lineHeightInPixels = @editor.getLineHeightInPixels()
@updateTop()
@updateHeight()
updateScrollTop: ->
@updateTop()
updateScrollLeft: ->
@left = 0 - @editor.getScrollLeft()
updateBackgroundColor: ->
@backgroundColor = @editor.getBackgroundColor()
updateTop: ->
@top = @startRow * @editor.getLineHeightInPixels() - @editor.getScrollTop()
updateLines: ->
@lines = @editor.linesForScreenRows(@startRow, @endRow - 1)
populateDecorations: ->
for markerId, decorations of @editor.decorationsForScreenRowRange(@startRow, @endRow)
for decoration in decorations
@onDecorationAdded(decoration)
onScreenLinesChanged: (change) ->
@updateLines() if change.start < @endRow
onDecorationAdded: (decoration) ->
if decoration.isType('line')
@onLineDecorationAdded(decoration)
onDecorationRemoved: (decoration) ->
if decoration.isType('line')
@onLineDecorationRemoved(decoration)
onDecorationChanged: (decoration, change) ->
if decoration.isType('line')
@onLineDecorationChanged(decoration, change)
onLineDecorationAdded: (decoration) ->
marker = decoration.getMarker()
headRow = marker.getHeadScreenPosition().row
tailRow = marker.getTailScreenPosition().row
valid = marker.isValid()
params = decoration.getParams()
if rowRange = @rowRangeForLineDecoration(params, headRow, tailRow, valid)
@addLineDecorations(params, rowRange...)
onLineDecorationRemoved: (decoration) ->
marker = decoration.getMarker()
headRow = marker.getHeadScreenPosition().row
tailRow = marker.getTailScreenPosition().row
valid = true # FIXME: Markers shouldn't always be invalidated when destroyed
params = decoration.getParams()
if rowRange = @rowRangeForLineDecoration(params, headRow, tailRow, valid)
@removeLineDecorations(params, rowRange...)
onLineDecorationChanged: (decoration, change) ->
params = decoration.getParams()
{oldHeadScreenPosition, oldTailScreenPosition, wasValid} = change
if rowRange = @rowRangeForLineDecoration(params, oldHeadScreenPosition.row, oldTailScreenPosition.row, wasValid)
@removeLineDecorations(params, rowRange...)
{newHeadScreenPosition, newTailScreenPosition, isValid} = change
if rowRange = @rowRangeForLineDecoration(params, newHeadScreenPosition.row, newTailScreenPosition.row, isValid)
@addLineDecorations(params, rowRange...)
addLineDecorations: (params, decorationStartRow, decorationEndRow) ->
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
for row in [decorationStartRow..decorationEndRow]
@lineDecorations[row] ?= {}
@lineDecorations[row][params.id] = params
removeLineDecorations: (params, decorationStartRow, decorationEndRow) ->
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
for row in [decorationStartRow..decorationEndRow]
delete @lineDecorations[row][params.id]
delete @lineDecorations[row] if _.size(@lineDecorations[row]) is 0
rowRangeForLineDecoration: (decoration, headRow, tailRow, valid) ->
return unless valid
startRow = Math.min(headRow, tailRow)
endRow = Math.max(headRow, tailRow)
[startRow, endRow]
class GutterTilePresenter
constructor: (@editor, @startRow, @endRow) ->
@lineNumberDecorations = {}
@populateDecorations()
@updateLineHeightInPixels()
@updateScrollTop()
@updateBackgroundColor()
populateDecorations: ->
for markerId, decorations of @editor.decorationsForScreenRowRange(@startRow, @endRow)
for decoration in decorations
@onDecorationAdded(decoration)
updateLineHeightInPixels: ->
@lineHeightInPixels = @editor.getLineHeightInPixels()
@updateTop()
@updateHeight()
@updateLineNumbers()
updateScrollLeft: -> # NO-OP
updateScrollTop: ->
@updateTop()
updateBackgroundColor: ->
@backgroundColor = @editor.getGutterBackgroundColor() ? @editor.getBackgroundColor()
updateTop: ->
@top = @startRow * @editor.getLineHeightInPixels() - @editor.getScrollTop()
updateWidth: -> # NO-OP
updateHeight: ->
@height = (@endRow - @startRow) * @editor.getLineHeightInPixels()
updateLineNumbers: ->
@lineNumbers = @editor.lineNumbersForScreenRows(@startRow, @endRow - 1)
@maxLineNumberDigits = @editor.getLineCount().toString().length
updateMaxLineNumberDigits: ->
@maxLineNumberDigits = @editor.getLineCount().toString().length
onScreenLinesChanged: (change) ->
if change.bufferDelta isnt 0 or change.screenDelta isnt 0
@updateMaxLineNumberDigits()
@updateLineNumbers() if change.start < @endRow
onDecorationAdded: (decoration) ->
return unless decoration.isType('gutter')
marker = decoration.getMarker()
headRow = marker.getHeadScreenPosition().row
tailRow = marker.getTailScreenPosition().row
valid = marker.isValid()
params = decoration.getParams()
if rowRange = @rowRangeForLineNumberDecoration(params, headRow, tailRow, valid)
@addLineNumberDecorations(params, rowRange...)
onDecorationRemoved: (decoration) ->
return unless decoration.isType('gutter')
marker = decoration.getMarker()
headRow = marker.getHeadScreenPosition().row
tailRow = marker.getTailScreenPosition().row
valid = true # FIXME: Markers shouldn't always be invalidated when destroyed
params = decoration.getParams()
if rowRange = @rowRangeForLineNumberDecoration(params, headRow, tailRow, valid)
@removeLineNumberDecorations(params, rowRange...)
onDecorationChanged: (decoration, change) ->
return unless decoration.isType('gutter')
params = decoration.getParams()
{oldHeadScreenPosition, oldTailScreenPosition, wasValid} = change
if rowRange = @rowRangeForLineNumberDecoration(params, oldHeadScreenPosition.row, oldTailScreenPosition.row, wasValid)
@removeLineNumberDecorations(params, rowRange...)
{newHeadScreenPosition, newTailScreenPosition, isValid} = change
if rowRange = @rowRangeForLineNumberDecoration(params, newHeadScreenPosition.row, newTailScreenPosition.row, isValid)
@addLineNumberDecorations(params, rowRange...)
addLineNumberDecorations: (params, decorationStartRow, decorationEndRow) ->
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
for row in [decorationStartRow..decorationEndRow]
@lineNumberDecorations[row] ?= {}
@lineNumberDecorations[row][params.id] = params
removeLineNumberDecorations: (params, decorationStartRow, decorationEndRow) ->
unless decorationEndRow < @startRow or @endRow <= decorationStartRow
for row in [decorationStartRow..decorationEndRow]
delete @lineNumberDecorations[row][params.id]
delete @lineNumberDecorations[row] if _.size(@lineNumberDecorations[row]) is 0
rowRangeForLineNumberDecoration: (decoration, headRow, tailRow, valid) ->
return unless valid
startRow = Math.min(headRow, tailRow)
endRow = Math.max(headRow, tailRow)
[startRow, endRow]
+347
Ver Arquivo
@@ -0,0 +1,347 @@
{extend, toArray, isEqual, clone} = require 'underscore-plus'
Decoration = require './decoration'
WrapperDiv = document.createElement('div')
module.exports =
class EditorTileComponent
top: null
left: null
height: null
width: null
lineHeightInPixels: null
lineWidths: null
backgroundColor: null
preserved: false
constructor: (@presenter) ->
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@lineDecorationsByLineId = {}
@cursorPixelRectsById = {}
@cursorNodesById = {}
@domNode = document.createElement('div')
@domNode.dataset.tile = true
@domNode.style.position = 'absolute'
@domNode.style.overflow = 'hidden'
@buildLines()
@update()
stateChangedForKeys: ->
for key in arguments
return true if @prevState?.get(key) isnt @state.get(key)
false
update: ->
@updateTransform()
@updateWidth()
@updateHeight()
@updateBackgroundColor()
@updateLines()
# @clearScreenRowCaches() if newProps.lineHeightInPixels isnt @props.lineHeightInPixels
# @updateCursors()
preserve: ->
return if @preserved
@domNode.style.visibility = 'hidden'
@preserved = true
revive: (@presenter) ->
@domNode.style.visibility = ''
@visible = true
updateTransform: ->
{left, top} = @presenter
unless left is @left and top is @top
@domNode.style['-webkit-transform'] = "translate3d(#{left}px, #{top}px, 0px)"
@left = left
@top = top
updateWidth: ->
{width} = @presenter
unless width is @width
@domNode.style.width = width + 'px'
@width = width
updateHeight: ->
{height} = @presenter
unless height is @height
@domNode.style.height = height + 'px'
@height = height
updateBackgroundColor: ->
{backgroundColor} = @presenter
unless backgroundColor is @backgroundColor
@domNode.style.backgroundColor = backgroundColor
@backgroundColor = backgroundColor
buildLines: ->
{startRow, lines, lineDecorations} = @presenter
linesHTML = ""
for line, i in lines
screenRow = startRow + i
linesHTML += @buildLineHTML(line, screenRow)
@domNode.innerHTML = linesHTML
for line, i in lines
screenRow = startRow + i
lineNode = @domNode.children[i]
@lineNodesByLineId[line.id] = lineNode
@screenRowsByLineId[line.id] = screenRow
@lineDecorationsByLineId[line.id] = clone(lineDecorations[screenRow])
@lineIdsByScreenRow[screenRow] = line.id
updateLines: ->
{lines, width} = @presenter
@removeLineNodes()
@appendOrUpdateVisibleLineNodes()
@lineWidths = width
removeLineNodes: () ->
lineIds = new Set
lineIds.add(line.id.toString()) for line in @presenter.lines
for lineId, lineNode of @lineNodesByLineId when not lineIds.has(lineId)
screenRow = @screenRowsByLineId[lineId]
delete @lineNodesByLineId[lineId]
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
delete @screenRowsByLineId[lineId]
delete @lineDecorationsByLineId[lineId]
@domNode.removeChild(lineNode)
appendOrUpdateVisibleLineNodes: ->
{startRow, lines, lineHeightInPixels, width} = @presenter
newLines = null
newLinesHTML = null
for line, index in lines
screenRow = startRow + index
if @hasLineNode(line.id)
@updateLineNode(line, screenRow)
else
newLines ?= []
newLinesHTML ?= ""
newLines.push(line)
newLinesHTML += @buildLineHTML(line, screenRow)
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
@lineHeightInPixels = lineHeightInPixels
@lineWidths = width
return unless newLines?
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = toArray(WrapperDiv.children)
for line, i in newLines
lineNode = newLineNodes[i]
@lineNodesByLineId[line.id] = lineNode
@domNode.appendChild(lineNode)
updateLineNode: (line, screenRow) ->
{startRow, lineHeightInPixels, width} = @presenter
lineNode = @lineNodesByLineId[line.id]
unless width is @lineWidths
lineNode.style.width = width + 'px'
unless @screenRowsByLineId[line.id] is screenRow and lineHeightInPixels is @lineHeightInPixels
lineNode.style.top = (screenRow - startRow) * lineHeightInPixels + 'px'
unless @screenRowsByLineId[line.id] is screenRow
lineNode.dataset.screenRow = screenRow
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
@updateLineDecorations(lineNode, line, screenRow)
updateLineDecorations: (lineNode, line, screenRow) ->
desiredDecorations = @presenter.lineDecorations[screenRow]
if currentDecorations = @lineDecorationsByLineId[line.id]
for id, decoration of currentDecorations
unless desiredDecorations?[id]?
lineNode.classList.remove(decoration.class)
delete currentDecorations[id]
if desiredDecorations?
currentDecorations = (@lineDecorationsByLineId[line.id] ?= {})
for id, decoration of desiredDecorations
unless currentDecorations[id]?
lineNode.classList.add(decoration.class)
currentDecorations[id] = decoration
clearScreenRowCaches: ->
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
hasLineNode: (lineId) ->
@lineNodesByLineId.hasOwnProperty(lineId)
lineNodeForScreenRow: (screenRow) ->
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
hasDecoration: (decorations, decoration) ->
decorations? and decorations[decoration.id] is decoration
buildLineHTML: (line, screenRow) ->
{startRow, lineHeightInPixels, width} = @presenter
{text, fold, isSoftWrapped, indentLevel} = line
classes = @getLineClasses(screenRow)
top = (screenRow - startRow) * lineHeightInPixels
style = "position: absolute; top: #{top}px; width: #{width}px;"
lineHTML = """<div class="#{classes}" style="#{style}">"""
if text is ""
lineHTML += @buildEmptyLineInnerHTML(line)
else
lineHTML += @buildLineInnerHTML(line)
lineHTML += '<span class="fold-marker"></span>' if fold?
lineHTML += "</div>"
lineHTML
getLineClasses: (screenRow) ->
classes = ''
if lineDecorationsForScreenRow = @presenter.lineDecorations[screenRow]
for id, decoration of lineDecorationsForScreenRow
classes += decoration.class + ' '
classes + 'line'
buildEmptyLineInnerHTML: (line) ->
invisibles = {}
showIndentGuide = false
# {showIndentGuide, invisibles} = @props
{cr, eol} = invisibles
{indentLevel, tabLength} = line
if showIndentGuide and indentLevel > 0
invisiblesToRender = []
invisiblesToRender.push(cr) if cr? and line.lineEnding is '\r\n'
invisiblesToRender.push(eol) if eol?
lineHTML = ''
for i in [0...indentLevel]
lineHTML += "<span class='indent-guide'>"
for j in [0...tabLength]
if invisible = invisiblesToRender.shift()
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
else
lineHTML += ' '
lineHTML += "</span>"
while invisiblesToRender.length
lineHTML += "<span class='invisible-character'>#{invisiblesToRender.shift()}</span>"
lineHTML
else
# @buildEndOfLineHTML(line, @props.invisibles) or '&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]
+13
Ver Arquivo
@@ -152,6 +152,10 @@ class Editor extends Model
updateBatchDepth: 0
selectionFlashDuration: 500
@properties
backgroundColor: null
gutterBackgroundColor: null
@delegatesMethods 'suggestedIndentForBufferRow', 'autoIndentBufferRow', 'autoIndentBufferRows',
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
toProperty: 'languageMode'
@@ -591,6 +595,9 @@ class Editor extends Model
# {Delegates to: DisplayBuffer.bufferRowsForScreenRows}
bufferRowsForScreenRows: (startRow, endRow) -> @displayBuffer.bufferRowsForScreenRows(startRow, endRow)
# {Delegats to: DisplayBuffer.lineNumbersForScreenRows}
lineNumbersForScreenRows: (startRow, endRow) -> @displayBuffer.lineNumbersForScreenRows(startRow, endRow)
bufferRowForScreenRow: (row) -> @displayBuffer.bufferRowForScreenRow(row)
# Public: Get the syntactic scopes for the given position in buffer
@@ -2025,6 +2032,12 @@ class Editor extends Model
getScrollHeight: -> @displayBuffer.getScrollHeight()
getScrollWidth: (scrollWidth) -> @displayBuffer.getScrollWidth(scrollWidth)
setBackgroundColor: (@backgroundColor) -> @backgroundColor
getBackgroundColor: -> @backgroundColor
setGutterBackgroundColor: (@gutterBackgroundColor) -> @gutterBackgroundColor
getGutterBackgroundColor: -> @gutterBackgroundColor
getVisibleRowRange: -> @displayBuffer.getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow)
+35 -174
Ver Arquivo
@@ -4,6 +4,7 @@ React = require 'react-atom-fork'
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
Decoration = require './decoration'
SubscriberMixin = require './subscriber-mixin'
GutterTileComponent = require './gutter-tile-component'
WrapperDiv = document.createElement('div')
@@ -16,196 +17,51 @@ GutterComponent = React.createClass
measuredWidth: null
render: ->
{scrollHeight, scrollViewHeight, onMouseDown, backgroundColor, gutterBackgroundColor} = @props
if gutterBackgroundColor isnt 'rbga(0, 0, 0, 0)'
backgroundColor = gutterBackgroundColor
div className: 'gutter', onClick: @onClick, onMouseDown: onMouseDown,
div className: 'line-numbers', ref: 'lineNumbers', style:
height: Math.max(scrollHeight, scrollViewHeight)
WebkitTransform: @getTransform()
backgroundColor: backgroundColor
getTransform: ->
{scrollTop, useHardwareAcceleration} = @props
if useHardwareAcceleration
"translate3d(0px, #{-scrollTop}px, 0px)"
else
"translate(0px, #{-scrollTop}px)"
div className: 'gutter', onClick: @onClick, onMouseDown: @props.onMouseDown
componentWillMount: ->
@lineNumberNodesById = {}
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
@renderedDecorationsByLineNumberId = {}
@tileComponentsByStartRow = {}
componentDidMount: ->
@appendDummyLineNumber()
@updateLineNumbers() if @props.performedInitialMeasurement
# Only update the gutter if the visible row range has changed or if a
# non-zero-delta change to the screen lines has occurred within the current
# visible row range.
shouldComponentUpdate: (newProps) ->
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow', 'lineDecorations',
'scrollViewHeight', 'useHardwareAcceleration', 'backgroundColor', 'gutterBackgroundColor'
)
{renderedRowRange, pendingChanges, lineDecorations} = newProps
return false unless renderedRowRange?
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
false
@appendDummyTile()
@getDOMNode().addEventListener 'mousewheel', @onMouseWheel
componentDidUpdate: (oldProps) ->
return unless @props.performedInitialMeasurement
if @props.performedInitialMeasurement
@updateTiles()
unless isEqualForProperties(oldProps, @props, 'maxLineNumberDigits')
@updateDummyLineNumber()
@removeLineNumberNodes()
updateTiles: ->
{gutterPresenter} = @props
@clearScreenRowCaches() unless oldProps.lineHeightInPixels is @props.lineHeightInPixels
@updateLineNumbers()
domNode = @getDOMNode()
clearScreenRowCaches: ->
@lineNumberIdsByScreenRow = {}
@screenRowsByLineNumberId = {}
for tileStartRow, tileComponent of @tileComponentsByStartRow
unless gutterPresenter.tiles[tileStartRow]?
if tileComponent.domNode is @preservedTileNode
tileComponent.preserve()
else
domNode.removeChild(tileComponent.domNode)
delete @tileComponentsByStartRow[tileStartRow]
for tileStartRow, tilePresenter of gutterPresenter.tiles
if tileComponent = @tileComponentsByStartRow[tileStartRow]
tileComponent = @tileComponentsByStartRow[tileStartRow]
tileComponent.revive(tilePresenter) if tileComponent.preserved
tileComponent.update()
else
tileComponent = new GutterTileComponent(tilePresenter)
@tileComponentsByStartRow[tileStartRow] = tileComponent
domNode.appendChild(tileComponent.domNode)
# This dummy line number element holds the gutter to the appropriate width,
# since the real line numbers are absolutely positioned for performance reasons.
appendDummyLineNumber: ->
{maxLineNumberDigits} = @props
WrapperDiv.innerHTML = @buildLineNumberHTML(-1, false, maxLineNumberDigits)
@dummyLineNumberNode = WrapperDiv.children[0]
@refs.lineNumbers.getDOMNode().appendChild(@dummyLineNumberNode)
appendDummyTile: ->
@dummyTileComponent = new GutterTileComponent(@props.gutterPresenter.dummyTile)
@getDOMNode().appendChild(@dummyTileComponent.domNode)
updateDummyLineNumber: ->
@dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false, @props.maxLineNumberDigits)
updateLineNumbers: ->
lineNumberIdsToPreserve = @appendOrUpdateVisibleLineNumberNodes()
@removeLineNumberNodes(lineNumberIdsToPreserve)
appendOrUpdateVisibleLineNumberNodes: ->
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, lineDecorations} = @props
[startRow, endRow] = renderedRowRange
newLineNumberIds = null
newLineNumbersHTML = null
visibleLineNumberIds = new Set
wrapCount = 0
for bufferRow, index in editor.bufferRowsForScreenRows(startRow, endRow - 1)
screenRow = startRow + index
if bufferRow is lastBufferRow
id = "#{bufferRow}-#{wrapCount++}"
else
id = bufferRow.toString()
lastBufferRow = bufferRow
wrapCount = 0
visibleLineNumberIds.add(id)
if @hasLineNumberNode(id)
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0)
else
newLineNumberIds ?= []
newLineNumbersHTML ?= ""
newLineNumberIds.push(id)
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
@screenRowsByLineNumberId[id] = screenRow
@lineNumberIdsByScreenRow[screenRow] = id
@renderedDecorationsByLineNumberId[id] = lineDecorations[screenRow]
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = toArray(WrapperDiv.children)
node = @refs.lineNumbers.getDOMNode()
for lineNumberId, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[lineNumberId] = lineNumberNode
node.appendChild(lineNumberNode)
visibleLineNumberIds
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
{mouseWheelScreenRow} = @props
node = @refs.lineNumbers.getDOMNode()
for lineNumberId, lineNumberNode of @lineNumberNodesById when not lineNumberIdsToPreserve?.has(lineNumberId)
screenRow = @screenRowsByLineNumberId[lineNumberId]
if not screenRow? or screenRow isnt mouseWheelScreenRow
delete @lineNumberNodesById[lineNumberId]
delete @lineNumberIdsByScreenRow[screenRow] if @lineNumberIdsByScreenRow[screenRow] is lineNumberId
delete @screenRowsByLineNumberId[lineNumberId]
delete @renderedDecorationsByLineNumberId[lineNumberId]
node.removeChild(lineNumberNode)
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
{editor, lineHeightInPixels, lineDecorations} = @props
if screenRow?
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
else
style = "visibility: hidden;"
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
classes = ''
if lineDecorations? and decorations = lineDecorations[screenRow]
for id, decoration of decorations
if Decoration.isType(decoration, 'gutter')
classes += decoration.class + ' '
classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
classes += "line-number line-number-#{bufferRow}"
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
if softWrapped
lineNumber = ""
else
lineNumber = (bufferRow + 1).toString()
padding = multiplyString('&nbsp;', maxLineNumberDigits - lineNumber.length)
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped) ->
{editor, lineDecorations} = @props
node = @lineNumberNodesById[lineNumberId]
if editor.isFoldableAtBufferRow(bufferRow)
node.classList.add('foldable')
else
node.classList.remove('foldable')
decorations = lineDecorations[screenRow]
previousDecorations = @renderedDecorationsByLineNumberId[lineNumberId]
if previousDecorations?
for id, decoration of previousDecorations
if Decoration.isType(decoration, 'gutter') and not @hasDecoration(decorations, decoration)
node.classList.remove(decoration.class)
if decorations?
for id, decoration of decorations
if Decoration.isType(decoration, 'gutter') and not @hasDecoration(previousDecorations, decoration)
node.classList.add(decoration.class)
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
{lineHeightInPixels} = @props
node.style.top = screenRow * lineHeightInPixels + 'px'
node.dataset.screenRow = screenRow
@screenRowsByLineNumberId[lineNumberId] = screenRow
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
hasDecoration: (decorations, decoration) ->
decorations? and decorations[decoration.id] is decoration
@@ -226,3 +82,8 @@ GutterComponent = React.createClass
editor.unfoldBufferRow(bufferRow)
else
editor.foldBufferRow(bufferRow)
onMouseWheel: (event) ->
node = event.target
node = node.parentNode until not node? or node.dataset.tile
@preservedTileNode = node
+166
Ver Arquivo
@@ -0,0 +1,166 @@
{toArray, multiplyString, clone} = require 'underscore-plus'
WrapperDiv = document.createElement('div')
module.exports =
class GutterTileComponent
preserved: false
constructor: (@presenter) ->
@lineNumberNodesById = {}
@screenRowsByLineNumberId = {}
@lineNumberDecorationsByLineNumberId = {}
@domNode = document.createElement('div')
@domNode.dataset.tile = true
@domNode.style.overflow = 'hidden'
if @presenter.dummy
@domNode.style.visibility = 'hidden'
else
@domNode.style.position = 'absolute'
@domNode.style.top = '0px'
@appendDummyLineNumber()
@update()
appendDummyLineNumber: ->
WrapperDiv.innerHTML = @buildLineNumberHTML('0')
@domNode.appendChild(WrapperDiv.firstChild)
update: ->
return if @presenter.dummy
@updateTransform()
@updateHeight()
@updateBackgroundColor()
@updateLineNumbers()
preserve: ->
return if @preserved
@domNode.style.visibility = 'hidden'
@preserved = true
revive: (@presenter) ->
@domNode.style.visibility = ''
@visible = true
updateTransform: ->
{top} = @presenter
unless top is @top
@domNode.style['-webkit-transform'] = "translate3d(0px, #{top}px, 0px)"
@top = top
updateHeight: ->
{height} = @presenter
unless height is @height
@domNode.style.height = height + 'px'
@height = height
updateBackgroundColor: ->
{backgroundColor} = @presenter
unless backgroundColor is @backgroundColor
@domNode.style.backgroundColor = backgroundColor
@backgroundColor = backgroundColor
updateLineNumbers: ->
{lineNumbers, lineNumberDecorations} = @presenter
lineNumbersSet = new Set
lineNumbersSet.add(lineNumber) for lineNumber in lineNumbers
for lineNumberId, lineNumberNode of @lineNumberNodesById
unless lineNumbersSet.has(lineNumberId)
delete @lineNumberNodesById[lineNumberId]
delete @screenRowsByLineNumberId[lineNumberId]
delete @lineNumberDecorationsByLineNumberId[lineNumberId]
@domNode.removeChild(lineNumberNode)
newLineNumberIds = []
newLineNumbersHTML = ""
for lineNumberId, i in lineNumbers
screenRow = @presenter.startRow + i
if @lineNumberNodesById[lineNumberId]?
@updateLineNumberNode(lineNumberId, screenRow)
else
newLineNumberIds.push(lineNumberId)
newLineNumbersHTML += @buildLineNumberHTML(lineNumberId, screenRow)
@screenRowsByLineNumberId[lineNumberId] = screenRow
# @lineNumberIdsByScreenRow[screenRow] = id
@lineNumberDecorationsByLineNumberId[lineNumberId] = clone(lineNumberDecorations[screenRow])
if newLineNumberIds.length > 0
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = toArray(WrapperDiv.children)
for lineNumberId, i in newLineNumberIds
@lineNumberNodesById[lineNumberId] = newLineNumberNodes[i]
@lineNumberDecorationsByLineNumberId
@domNode.appendChild(newLineNumberNodes[i])
buildLineNumberHTML: (lineNumberId, screenRow) ->
{lineHeightInPixels, lineNumberDecorations} = @presenter
if screenRow?
top = (screenRow - @presenter.startRow) * lineHeightInPixels
style = "position: absolute; top: #{top}px;"
else
style = "visibility: hidden;"
innerHTML = @buildLineNumberInnerHTML(lineNumberId)
classes = ''
if decorationsById = lineNumberDecorations?[screenRow]
for id, decoration of decorationsById
classes += decoration.class + ' '
# classes += "foldable " if bufferRow >= 0 and editor.isFoldableAtBufferRow(bufferRow)
classes += "line-number line-number-#{lineNumberId.replace(/\..*/, '')}"
"<div class=\"#{classes}\" style=\"#{style}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
buildLineNumberInnerHTML: (lineNumberId) ->
{maxLineNumberDigits} = @presenter
softWrapped = lineNumberId.indexOf('.') isnt -1
if softWrapped
lineNumber = ""
else
lineNumber = lineNumberId
padding = multiplyString('&nbsp;', maxLineNumberDigits - lineNumber.length)
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
updateLineNumberNode: (lineNumberId, screenRow) ->
{lineDecorations} = @presenter
node = @lineNumberNodesById[lineNumberId]
# if editor.isFoldableAtBufferRow(bufferRow)
# node.classList.add('foldable')
# else
# node.classList.remove('foldable')
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
{lineHeightInPixels} = @presenter
node.style.top = screenRow * lineHeightInPixels + 'px'
node.dataset.screenRow = screenRow
@screenRowsByLineNumberId[lineNumberId] = screenRow
# @lineNumberIdsByScreenRow[screenRow] = lineNumberId
@updateLineNumberDecorations(node, lineNumberId, screenRow)
updateLineNumberDecorations: (lineNumberNode, lineNumberId, screenRow) ->
desiredDecorations = @presenter.lineNumberDecorations[screenRow]
if currentDecorations = @lineNumberDecorationsByLineNumberId[lineNumberId]
for id, decoration of currentDecorations
unless desiredDecorations?[id]?
lineNumberNode.classList.remove(decoration.class)
delete currentDecorations[id]
if desiredDecorations?
currentDecorations = (@lineNumberDecorationsByLineNumberId[lineNumberId] ?= {})
for id, decoration of desiredDecorations
unless currentDecorations[id]?
lineNumberNode.classList.add(decoration.class)
currentDecorations[id] = decoration
+55 -236
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,22 @@ module.exports =
LinesComponent = React.createClass
displayName: 'LinesComponent'
tileSize: 5
preservedTileNode: null
render: ->
{performedInitialMeasurement, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
if performedInitialMeasurement
{editor, highlightDecorations, scrollHeight, scrollWidth, placeholderText, backgroundColor} = @props
{lineHeightInPixels, defaultCharWidth, scrollViewHeight, scopedCharacterWidthsChangeCount} = @props
{scrollTop, scrollLeft, cursorPixelRects} = @props
style =
height: Math.max(scrollHeight, scrollViewHeight)
width: scrollWidth
WebkitTransform: @getTransform()
backgroundColor: backgroundColor
div {className: 'lines', style},
div className: 'placeholder-text', placeholderText if placeholderText?
CursorsComponent {
cursorPixelRects, cursorBlinkPeriod, cursorBlinkResumeDelay, lineHeightInPixels,
defaultCharWidth, scopedCharacterWidthsChangeCount, performedInitialMeasurement
}
HighlightsComponent {
editor, highlightDecorations, lineHeightInPixels, defaultCharWidth,
scopedCharacterWidthsChangeCount, performedInitialMeasurement
}
getTransform: ->
{scrollTop, scrollLeft, useHardwareAcceleration} = @props
if useHardwareAcceleration
"translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
else
"translate(#{-scrollLeft}px, #{-scrollTop}px)"
div className: 'lines'
componentWillMount: ->
@measuredLines = new WeakSet
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@renderedDecorationsByLineId = {}
@tileComponentsByStartRow = {}
componentDidMount: ->
@getDOMNode().addEventListener 'mousewheel', @onMouseWheel
shouldComponentUpdate: (newProps) ->
return true
return true unless isEqualForProperties(newProps, @props,
'renderedRowRange', 'lineDecorations', 'highlightDecorations', 'lineHeightInPixels', 'defaultCharWidth',
'scrollTop', 'scrollLeft', 'showIndentGuide', 'scrollingVertically', 'invisibles', 'visible',
@@ -78,209 +53,19 @@ LinesComponent = React.createClass
false
componentDidUpdate: (prevProps) ->
{visible, scrollingVertically, performedInitialMeasurement} = @props
{performedInitialMeasurement, visible, scrollingVertically} = @props
return unless performedInitialMeasurement
@clearScreenRowCaches() unless prevProps.lineHeightInPixels is @props.lineHeightInPixels
@removeLineNodes() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
@updateLines(@props.lineWidth isnt prevProps.lineWidth)
@clearTiles() unless isEqualForProperties(prevProps, @props, 'showIndentGuide', 'invisibles')
@updateTiles()
@measureCharactersInNewLines() if visible and not scrollingVertically
clearScreenRowCaches: ->
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
updateLines: (updateWidth) ->
{editor, renderedRowRange, showIndentGuide, selectionChanged, lineDecorations} = @props
[startRow, endRow] = renderedRowRange
visibleLines = editor.linesForScreenRows(startRow, endRow - 1)
@removeLineNodes(visibleLines)
@appendOrUpdateVisibleLineNodes(visibleLines, startRow, updateWidth)
removeLineNodes: (visibleLines=[]) ->
{mouseWheelScreenRow} = @props
visibleLineIds = new Set
visibleLineIds.add(line.id.toString()) for line in visibleLines
node = @getDOMNode()
for lineId, lineNode of @lineNodesByLineId when not visibleLineIds.has(lineId)
screenRow = @screenRowsByLineId[lineId]
if not screenRow? or screenRow isnt mouseWheelScreenRow
delete @lineNodesByLineId[lineId]
delete @lineIdsByScreenRow[screenRow] if @lineIdsByScreenRow[screenRow] is lineId
delete @screenRowsByLineId[lineId]
delete @renderedDecorationsByLineId[lineId]
node.removeChild(lineNode)
appendOrUpdateVisibleLineNodes: (visibleLines, startRow, updateWidth) ->
{lineDecorations} = @props
newLines = null
newLinesHTML = null
for line, index in visibleLines
screenRow = startRow + index
if @hasLineNode(line.id)
@updateLineNode(line, screenRow, updateWidth)
else
newLines ?= []
newLinesHTML ?= ""
newLines.push(line)
newLinesHTML += @buildLineHTML(line, screenRow)
@screenRowsByLineId[line.id] = screenRow
@lineIdsByScreenRow[screenRow] = line.id
@renderedDecorationsByLineId[line.id] = lineDecorations[screenRow]
return unless newLines?
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = toArray(WrapperDiv.children)
node = @getDOMNode()
for line, i in newLines
lineNode = newLineNodes[i]
@lineNodesByLineId[line.id] = lineNode
node.appendChild(lineNode)
hasLineNode: (lineId) ->
@lineNodesByLineId.hasOwnProperty(lineId)
buildLineHTML: (line, screenRow) ->
{editor, mini, showIndentGuide, lineHeightInPixels, lineDecorations, lineWidth} = @props
{tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
classes = ''
if decorations = lineDecorations[screenRow]
for id, decoration of decorations
if Decoration.isType(decoration, 'line')
classes += decoration.class + ' '
classes += 'line'
top = screenRow * lineHeightInPixels
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{lineWidth}px;\" data-screen-row=\"#{screenRow}\">"
if text is ""
lineHTML += @buildEmptyLineInnerHTML(line)
else
lineHTML += @buildLineInnerHTML(line)
lineHTML += '<span class="fold-marker"></span>' if fold
lineHTML += "</div>"
lineHTML
buildEmptyLineInnerHTML: (line) ->
{showIndentGuide, invisibles} = @props
{cr, eol} = invisibles
{indentLevel, tabLength} = line
if showIndentGuide and indentLevel > 0
invisiblesToRender = []
invisiblesToRender.push(cr) if cr? and line.lineEnding is '\r\n'
invisiblesToRender.push(eol) if eol?
lineHTML = ''
for i in [0...indentLevel]
lineHTML += "<span class='indent-guide'>"
for j in [0...tabLength]
if invisible = invisiblesToRender.shift()
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
else
lineHTML += ' '
lineHTML += "</span>"
while invisiblesToRender.length
lineHTML += "<span class='invisible-character'>#{invisiblesToRender.shift()}</span>"
lineHTML
else
@buildEndOfLineHTML(line, @props.invisibles) or '&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 +78,48 @@ LinesComponent = React.createClass
editor.setLineHeightInPixels(lineHeightInPixels)
editor.setDefaultCharWidth(charWidth)
updateTiles: ->
{contentPresenter} = @props
domNode = @getDOMNode()
for tileStartRow, tileComponent of @tileComponentsByStartRow
unless contentPresenter.tiles[tileStartRow]?
if tileComponent.domNode is @preservedTileNode
tileComponent.preserve()
else
domNode.removeChild(tileComponent.domNode)
delete @tileComponentsByStartRow[tileStartRow]
for tileStartRow, tilePresenter of contentPresenter.tiles
if tileComponent = @tileComponentsByStartRow[tileStartRow]
tileComponent = @tileComponentsByStartRow[tileStartRow]
tileComponent.revive(tilePresenter) if tileComponent.preserved
tileComponent.update()
else
tileComponent = new EditorTileComponent(tilePresenter)
@tileComponentsByStartRow[tileStartRow] = tileComponent
domNode.appendChild(tileComponent.domNode)
clearTiles: ->
for startRow, tileComponent of @tileComponentsByStartRow
domNode.removeChild(tileComponent.domNode)
delete @tileComponentsByStartRow[startRow]
remeasureCharacterWidths: ->
@clearScopedCharWidths()
@measureCharactersInNewLines()
measureCharactersInNewLines: ->
return
{editor} = @props
[visibleStartRow, visibleEndRow] = @props.renderedRowRange
node = @getDOMNode()
editor.batchCharacterMeasurement =>
for tokenizedLine in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
for tokenizedLine, i in editor.linesForScreenRows(visibleStartRow, visibleEndRow - 1)
screenRow = visibleStartRow + i
unless @measuredLines.has(tokenizedLine)
lineNode = @lineNodesByLineId[tokenizedLine.id]
lineNode = @lineNodeForScreenRow(screenRow)
@measureCharactersInLine(tokenizedLine, lineNode)
return
@@ -347,3 +161,8 @@ LinesComponent = React.createClass
clearScopedCharWidths: ->
@measuredLines.clear()
@props.editor.clearScopedCharWidths()
onMouseWheel: (event) ->
node = event.target
node = node.parentNode until node.dataset.tile
@preservedTileNode = node