mirror of
https://github.com/atom/atom.git
synced 2026-02-16 09:35:54 -05:00
Merge branch 'master' into asar
Conflicts: apm/package.json
This commit is contained in:
@@ -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: ->
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user