Merge branch 'master' into asar

Conflicts:
	apm/package.json
This commit is contained in:
Cheng Zhao
2015-04-02 10:08:33 +08:00
23 changed files with 533 additions and 306 deletions

View File

@@ -94,8 +94,6 @@ class Cursor extends Model
Grim.deprecate("Use Cursor::onDidChangePosition instead")
when 'destroyed'
Grim.deprecate("Use Cursor::onDidDestroy instead")
when 'destroyed'
Grim.deprecate("Use Cursor::onDidDestroy instead")
else
Grim.deprecate("::on is no longer supported. Use the event subscription methods instead")
super
@@ -305,7 +303,7 @@ class Cursor extends Model
columnCount-- # subtract 1 for the row move
column = column - columnCount
@setScreenPosition({row, column})
@setScreenPosition({row, column}, clip: 'backward')
# Public: Moves the cursor right one screen column.
#
@@ -332,7 +330,7 @@ class Cursor extends Model
columnsRemainingInLine = rowLength
column = column + columnCount
@setScreenPosition({row, column}, skipAtomicTokens: true, wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
@setScreenPosition({row, column}, clip: 'forward', wrapBeyondNewlines: true, wrapAtSoftNewlines: true)
# Public: Moves the cursor to the top of the buffer.
moveToTop: ->

View File

@@ -20,7 +20,7 @@ nextId = -> idCounter++
# decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'})
# ```
#
# Best practice for destorying the decoration is by destroying the {Marker}.
# Best practice for destroying the decoration is by destroying the {Marker}.
#
# ```coffee
# marker.destroy()
@@ -56,7 +56,6 @@ class Decoration
@properties.id = @id
@flashQueue = null
@destroyed = false
@markerDestroyDisposable = @marker.onDidDestroy => @destroy()
# Essential: Destroy this marker.

View File

@@ -4,7 +4,6 @@ _ = require 'underscore-plus'
CursorsComponent = require './cursors-component'
HighlightsComponent = require './highlights-component'
OverlayManager = require './overlay-manager'
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
@@ -40,13 +39,6 @@ class LinesComponent
insertionPoint.setAttribute('select', '.overlayer')
@domNode.appendChild(insertionPoint)
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@overlayManager = new OverlayManager(@presenter, @hostElement)
@domNode.appendChild(insertionPoint)
else
@overlayManager = new OverlayManager(@presenter, @domNode)
updateSync: (state) ->
@newState = state.content
@oldState ?= {lines: {}}
@@ -82,8 +74,6 @@ class LinesComponent
@cursorsComponent.updateSync(state)
@highlightsComponent.updateSync(state)
@overlayManager?.render(state)
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
@oldState.scrollWidth = @newState.scrollWidth

View File

@@ -286,7 +286,6 @@ class Marker
# * `screenPosition` The new {Point} to use
# * `properties` (optional) {Object} properties to associate with the marker.
setHeadScreenPosition: (screenPosition, properties) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, properties)
@setHeadBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, properties))
# Extended: Retrieves the buffer position of the marker's tail.
@@ -313,7 +312,6 @@ class Marker
# * `screenPosition` The new {Point} to use
# * `properties` (optional) {Object} properties to associate with the marker.
setTailScreenPosition: (screenPosition, options) ->
screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)
@setTailBufferPosition(@displayBuffer.bufferPositionForScreenPosition(screenPosition, options))
# Extended: Returns a {Boolean} indicating whether the marker has a tail.

View File

@@ -1,39 +1,44 @@
module.exports =
class OverlayManager
constructor: (@presenter, @container) ->
@overlayNodesById = {}
@overlaysById = {}
render: (state) ->
for decorationId, {pixelPosition, item} of state.content.overlays
@renderOverlay(state, decorationId, item, pixelPosition)
for decorationId, overlay of state.content.overlays
if @shouldUpdateOverlay(decorationId, overlay)
@renderOverlay(state, decorationId, overlay)
for id, overlayNode of @overlayNodesById
for id, {overlayNode} of @overlaysById
unless state.content.overlays.hasOwnProperty(id)
delete @overlayNodesById[id]
delete @overlaysById[id]
overlayNode.remove()
return
shouldUpdateOverlay: (decorationId, overlay) ->
cachedOverlay = @overlaysById[decorationId]
return true unless cachedOverlay?
cachedOverlay.pixelPosition?.top isnt overlay.pixelPosition?.top or
cachedOverlay.pixelPosition?.left isnt overlay.pixelPosition?.left
renderOverlay: (state, decorationId, item, pixelPosition) ->
item = atom.views.getView(item)
unless overlayNode = @overlayNodesById[decorationId]
overlayNode = @overlayNodesById[decorationId] = document.createElement('atom-overlay')
overlayNode.appendChild(item)
measureOverlays: ->
for decorationId, {itemView} of @overlaysById
@measureOverlay(decorationId, itemView)
measureOverlay: (decorationId, itemView) ->
contentMargin = parseInt(getComputedStyle(itemView)['margin-left']) ? 0
@presenter.setOverlayDimensions(decorationId, itemView.offsetWidth, itemView.offsetHeight, contentMargin)
renderOverlay: (state, decorationId, {item, pixelPosition}) ->
itemView = atom.views.getView(item)
cachedOverlay = @overlaysById[decorationId]
unless overlayNode = cachedOverlay?.overlayNode
overlayNode = document.createElement('atom-overlay')
@container.appendChild(overlayNode)
@overlaysById[decorationId] = cachedOverlay = {overlayNode, itemView}
itemWidth = item.offsetWidth
itemHeight = item.offsetHeight
# The same node may be used in more than one overlay. This steals the node
# back if it has been displayed in another overlay.
overlayNode.appendChild(itemView) if overlayNode.childNodes.length == 0
{scrollTop, scrollLeft} = state.content
left = pixelPosition.left
if left + itemWidth - scrollLeft > @presenter.contentFrameWidth and left - itemWidth >= scrollLeft
left -= itemWidth
top = pixelPosition.top + @presenter.lineHeight
if top + itemHeight - scrollTop > @presenter.height and top - itemHeight - @presenter.lineHeight >= scrollTop
top -= itemHeight + @presenter.lineHeight
overlayNode.style.top = top + 'px'
overlayNode.style.left = left + 'px'
cachedOverlay.pixelPosition = pixelPosition
overlayNode.style.top = pixelPosition.top + 'px'
overlayNode.style.left = pixelPosition.left + 'px'

View File

@@ -393,7 +393,7 @@ class Selection extends Model
if options.select
@setBufferRange(newBufferRange, reversed: wasReversed)
else
@cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if wasReversed
@cursor.setBufferPosition(newBufferRange.end, clip: 'forward') if wasReversed
if autoIndentFirstLine
@editor.setIndentationForBufferRow(oldBufferRange.start.row, desiredIndentLevel)

View File

@@ -11,6 +11,7 @@ InputComponent = require './input-component'
LinesComponent = require './lines-component'
ScrollbarComponent = require './scrollbar-component'
ScrollbarCornerComponent = require './scrollbar-corner-component'
OverlayManager = require './overlay-manager'
module.exports =
class TextEditorComponent
@@ -56,8 +57,14 @@ class TextEditorComponent
@domNode = document.createElement('div')
if @useShadowDOM
@domNode.classList.add('editor-contents--private')
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@domNode.appendChild(insertionPoint)
@overlayManager = new OverlayManager(@presenter, @hostElement)
else
@domNode.classList.add('editor-contents')
@overlayManager = new OverlayManager(@presenter, @domNode)
@scrollViewNode = document.createElement('div')
@scrollViewNode.classList.add('scroll-view')
@@ -140,6 +147,8 @@ class TextEditorComponent
@verticalScrollbarComponent.updateSync(@newState)
@scrollbarCornerComponent.updateSync(@newState)
@overlayManager?.render(@newState)
if @editor.isAlive()
@updateParentViewFocusedClassIfNeeded()
@updateParentViewMiniClass()
@@ -149,6 +158,7 @@ class TextEditorComponent
readAfterUpdateSync: =>
@linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically
@overlayManager?.measureOverlays()
mountGutterComponent: ->
@gutterComponent = new GutterComponent({@editor, onMouseDown: @onGutterMouseDown})
@@ -159,7 +169,8 @@ class TextEditorComponent
@measureScrollbars() if @measureScrollbarsWhenShown
@sampleFontStyling()
@sampleBackgroundColors()
@measureHeightAndWidth()
@measureWindowSize()
@measureDimensions()
@measureLineHeightAndDefaultCharWidth() if @measureLineHeightAndDefaultCharWidthWhenShown
@remeasureCharacterWidths() if @remeasureCharacterWidthsWhenShown
@editor.setVisible(true)
@@ -556,8 +567,9 @@ class TextEditorComponent
pollDOM: =>
unless @checkForVisibilityChange()
@sampleBackgroundColors()
@measureHeightAndWidth()
@measureDimensions()
@sampleFontStyling()
@overlayManager?.measureOverlays()
checkForVisibilityChange: ->
if @isVisible()
@@ -575,13 +587,14 @@ class TextEditorComponent
@heightAndWidthMeasurementRequested = true
requestAnimationFrame =>
@heightAndWidthMeasurementRequested = false
@measureHeightAndWidth()
@measureDimensions()
@measureWindowSize()
# Measure explicitly-styled height and width and relay them to the model. If
# these values aren't explicitly styled, we assume the editor is unconstrained
# and use the scrollHeight / scrollWidth as its height and width in
# calculations.
measureHeightAndWidth: ->
measureDimensions: ->
return unless @mounted
{position} = getComputedStyle(@hostElement)
@@ -602,6 +615,12 @@ class TextEditorComponent
if clientWidth > 0
@presenter.setContentFrameWidth(clientWidth)
@presenter.setBoundingClientRect(@hostElement.getBoundingClientRect())
measureWindowSize: ->
return unless @mounted
@presenter.setWindowSize(window.innerWidth, window.innerHeight)
sampleFontStyling: =>
oldFontSize = @fontSize
oldFontFamily = @fontFamily

View File

@@ -9,9 +9,10 @@ class TextEditorPresenter
stoppedScrollingTimeoutId: null
mouseWheelScreenRow: null
scopedCharacterWidthsChangeCount: 0
overlayDimensions: {}
constructor: (params) ->
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft} = params
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @lineOverdrawMargin, @backgroundColor, @gutterBackgroundColor} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@@ -314,7 +315,7 @@ class TextEditorPresenter
@emitDidUpdateState()
updateOverlaysState: -> @batch "shouldUpdateOverlaysState", ->
return unless @hasPixelRectRequirements()
return unless @hasOverlayPositionRequirements()
visibleDecorationIds = {}
@@ -327,13 +328,39 @@ class TextEditorPresenter
else
screenPosition = decoration.getMarker().getHeadScreenPosition()
pixelPosition = @pixelPositionForScreenPosition(screenPosition)
{scrollTop, scrollLeft} = @state.content
gutterWidth = @boundingClientRect.width - @contentFrameWidth
top = pixelPosition.top + @lineHeight - scrollTop
left = pixelPosition.left + gutterWidth - scrollLeft
if overlayDimensions = @overlayDimensions[decoration.id]
{itemWidth, itemHeight, contentMargin} = overlayDimensions
rightDiff = left + @boundingClientRect.left + itemWidth + contentMargin - @windowWidth
left -= rightDiff if rightDiff > 0
leftDiff = left + @boundingClientRect.left + contentMargin
left -= leftDiff if leftDiff < 0
if top + @boundingClientRect.top + itemHeight > @windowHeight and top - (itemHeight + @lineHeight) >= 0
top -= itemHeight + @lineHeight
pixelPosition.top = top
pixelPosition.left = left
@state.content.overlays[decoration.id] ?= {item}
@state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition)
@state.content.overlays[decoration.id].pixelPosition = pixelPosition
visibleDecorationIds[decoration.id] = true
for id of @state.content.overlays
delete @state.content.overlays[id] unless visibleDecorationIds[id]
for id of @overlayDimensions
delete @overlayDimensions[id] unless visibleDecorationIds[id]
return
updateGutterState: -> @batch "shouldUpdateGutterState", ->
@@ -566,6 +593,7 @@ class TextEditorPresenter
@updateLinesState()
@updateCursorsState()
@updateLineNumbersState()
@updateOverlaysState()
didStartScrolling: ->
if @stoppedScrollingTimeoutId?
@@ -593,6 +621,7 @@ class TextEditorPresenter
@updateHorizontalScrollState()
@updateHiddenInputState()
@updateCursorsState() unless oldScrollLeft?
@updateOverlaysState()
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight
@@ -657,6 +686,24 @@ class TextEditorPresenter
@updateLinesState()
@updateCursorsState() unless oldContentFrameWidth?
setBoundingClientRect: (boundingClientRect) ->
unless @clientRectsEqual(@boundingClientRect, boundingClientRect)
@boundingClientRect = boundingClientRect
@updateOverlaysState()
clientRectsEqual: (clientRectA, clientRectB) ->
clientRectA? and clientRectB? and
clientRectA.top is clientRectB.top and
clientRectA.left is clientRectB.left and
clientRectA.width is clientRectB.width and
clientRectA.height is clientRectB.height
setWindowSize: (width, height) ->
if @windowWidth isnt width or @windowHeight isnt height
@windowWidth = width
@windowHeight = height
@updateOverlaysState()
setBackgroundColor: (backgroundColor) ->
unless @backgroundColor is backgroundColor
@backgroundColor = backgroundColor
@@ -777,6 +824,9 @@ class TextEditorPresenter
hasPixelRectRequirements: ->
@hasPixelPositionRequirements() and @scrollWidth?
hasOverlayPositionRequirements: ->
@hasPixelRectRequirements() and @boundingClientRect? and @windowWidth and @windowHeight
pixelRectForScreenRange: (screenRange) ->
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start).top
@@ -994,6 +1044,18 @@ class TextEditorPresenter
regions
setOverlayDimensions: (decorationId, itemWidth, itemHeight, contentMargin) ->
@overlayDimensions[decorationId] ?= {}
overlayState = @overlayDimensions[decorationId]
dimensionsAreEqual = overlayState.itemWidth is itemWidth and
overlayState.itemHeight is itemHeight and
overlayState.contentMargin is contentMargin
unless dimensionsAreEqual
overlayState.itemWidth = itemWidth
overlayState.itemHeight = itemHeight
overlayState.contentMargin = contentMargin
@updateOverlaysState()
observeCursor: (cursor) ->
didChangePositionDisposable = cursor.onDidChangePosition =>
@updateHiddenInputState() if cursor.isLastCursor()

View File

@@ -41,10 +41,20 @@ class TokenizedLine
copy: ->
new TokenizedLine({@tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold})
# This clips a given screen column to a valid column that's within the line
# and not in the middle of any atomic tokens.
#
# column - A {Number} representing the column to clip
# options - A hash with the key clip. Valid values for this key:
# 'closest' (default): clip to the closest edge of an atomic token.
# 'forward': clip to the forward edge.
# 'backward': clip to the backward edge.
#
# Returns a {Number} representing the clipped column.
clipScreenColumn: (column, options={}) ->
return 0 if @tokens.length == 0
{ skipAtomicTokens } = options
{ clip } = options
column = Math.min(column, @getMaxScreenColumn())
tokenStartColumn = 0
@@ -55,10 +65,15 @@ class TokenizedLine
if @isColumnInsideSoftWrapIndentation(tokenStartColumn)
@softWrapIndentationDelta
else if token.isAtomic and tokenStartColumn < column
if skipAtomicTokens
if clip == 'forward'
tokenStartColumn + token.screenDelta
else
else if clip == 'backward'
tokenStartColumn
else #'closest'
if column > tokenStartColumn + (token.screenDelta / 2)
tokenStartColumn + token.screenDelta
else
tokenStartColumn
else
column
@@ -67,7 +82,7 @@ class TokenizedLine
screenColumn = 0
currentBufferColumn = 0
for token in @tokens
break if currentBufferColumn > bufferColumn
break if currentBufferColumn + token.bufferDelta > bufferColumn
screenColumn += token.screenDelta
currentBufferColumn += token.bufferDelta
@clipScreenColumn(screenColumn + (bufferColumn - currentBufferColumn))

View File

@@ -178,6 +178,9 @@ class ViewRegistry
@documentPollers = @documentPollers.filter (poller) -> poller isnt fn
@stopPollingDocument() if @documentPollers.length is 0
pollAfterNextUpdate: ->
@performDocumentPollAfterUpdate = true
clearDocumentRequests: ->
@documentReaders = []
@documentWriters = []
@@ -194,6 +197,7 @@ class ViewRegistry
writer() while writer = @documentWriters.shift()
reader() while reader = @documentReaders.shift()
@performDocumentPoll() if @performDocumentPollAfterUpdate
@performDocumentPollAfterUpdate = false
startPollingDocument: ->
@pollIntervalHandle = window.setInterval(@performDocumentPoll, @documentPollingInterval)
@@ -205,6 +209,5 @@ class ViewRegistry
if @documentUpdateRequested
@performDocumentPollAfterUpdate = true
else
@performDocumentPollAfterUpdate = false
poller() for poller in @documentPollers
return