mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Render selections
This commit is contained in:
@@ -42,45 +42,115 @@ describe "EditorComponent", ->
|
||||
expect(spacers[0].offsetHeight).toBe 2 * lineHeightInPixels
|
||||
expect(spacers[1].offsetHeight).toBe (editor.getScreenLineCount() - 7) * lineHeightInPixels
|
||||
|
||||
it "renders the currently visible cursors", ->
|
||||
cursor1 = editor.getCursor()
|
||||
cursor1.setScreenPosition([0, 5])
|
||||
describe "cursor rendering", ->
|
||||
it "renders the currently visible cursors", ->
|
||||
cursor1 = editor.getCursor()
|
||||
cursor1.setScreenPosition([0, 5])
|
||||
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.updateAllDimensions()
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.updateAllDimensions()
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].offsetHeight).toBe lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetWidth).toBe charWidth
|
||||
expect(cursorNodes[0].offsetTop).toBe 0
|
||||
expect(cursorNodes[0].offsetLeft).toBe 5 * charWidth
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].offsetHeight).toBe lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetWidth).toBe charWidth
|
||||
expect(cursorNodes[0].offsetTop).toBe 0
|
||||
expect(cursorNodes[0].offsetLeft).toBe 5 * charWidth
|
||||
|
||||
cursor2 = editor.addCursorAtScreenPosition([6, 11])
|
||||
cursor3 = editor.addCursorAtScreenPosition([4, 10])
|
||||
cursor2 = editor.addCursorAtScreenPosition([6, 11])
|
||||
cursor3 = editor.addCursorAtScreenPosition([4, 10])
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].offsetTop).toBe 0
|
||||
expect(cursorNodes[0].offsetLeft).toBe 5 * charWidth
|
||||
expect(cursorNodes[1].offsetTop).toBe 4 * lineHeightInPixels
|
||||
expect(cursorNodes[1].offsetLeft).toBe 10 * charWidth
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].offsetTop).toBe 0
|
||||
expect(cursorNodes[0].offsetLeft).toBe 5 * charWidth
|
||||
expect(cursorNodes[1].offsetTop).toBe 4 * lineHeightInPixels
|
||||
expect(cursorNodes[1].offsetLeft).toBe 10 * charWidth
|
||||
|
||||
node.querySelector('.vertical-scrollbar').scrollTop = 2.5 * lineHeightInPixels
|
||||
component.onVerticalScroll()
|
||||
node.querySelector('.vertical-scrollbar').scrollTop = 2.5 * lineHeightInPixels
|
||||
component.onVerticalScroll()
|
||||
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].offsetTop).toBe 6 * lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetLeft).toBe 11 * charWidth
|
||||
expect(cursorNodes[1].offsetTop).toBe 4 * lineHeightInPixels
|
||||
expect(cursorNodes[1].offsetLeft).toBe 10 * charWidth
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 2
|
||||
expect(cursorNodes[0].offsetTop).toBe 6 * lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetLeft).toBe 11 * charWidth
|
||||
expect(cursorNodes[1].offsetTop).toBe 4 * lineHeightInPixels
|
||||
expect(cursorNodes[1].offsetLeft).toBe 10 * charWidth
|
||||
|
||||
cursor3.destroy()
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].offsetTop).toBe 6 * lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetLeft).toBe 11 * charWidth
|
||||
|
||||
it "accounts for character widths when positioning cursors", ->
|
||||
atom.config.set('editor.fontFamily', 'sans-serif')
|
||||
editor.setCursorScreenPosition([0, 16])
|
||||
|
||||
cursor = node.querySelector('.cursor')
|
||||
cursorRect = cursor.getBoundingClientRect()
|
||||
|
||||
cursorLocationTextNode = node.querySelector('.storage.type.function.js').firstChild.firstChild
|
||||
range = document.createRange()
|
||||
range.setStart(cursorLocationTextNode, 0)
|
||||
range.setEnd(cursorLocationTextNode, 1)
|
||||
rangeRect = range.getBoundingClientRect()
|
||||
|
||||
expect(cursorRect.left).toBe rangeRect.left
|
||||
expect(cursorRect.width).toBe rangeRect.width
|
||||
|
||||
describe "selection rendering", ->
|
||||
it "renders 1 region for 1-line selections", ->
|
||||
# 1-line selection
|
||||
editor.setSelectedScreenRange([[1, 6], [1, 10]])
|
||||
regions = node.querySelectorAll('.selection .region')
|
||||
expect(regions.length).toBe 1
|
||||
regionRect = regions[0].getBoundingClientRect()
|
||||
expect(regionRect.top).toBe 1 * lineHeightInPixels
|
||||
expect(regionRect.height).toBe 1 * lineHeightInPixels
|
||||
expect(regionRect.left).toBe 6 * charWidth
|
||||
expect(regionRect.width).toBe 4 * charWidth
|
||||
|
||||
it "renders 2 regions for 2-line selections", ->
|
||||
editor.setSelectedScreenRange([[1, 6], [2, 10]])
|
||||
regions = node.querySelectorAll('.selection .region')
|
||||
expect(regions.length).toBe 2
|
||||
|
||||
region1Rect = regions[0].getBoundingClientRect()
|
||||
expect(region1Rect.top).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.left).toBe 6 * charWidth
|
||||
expect(region1Rect.right).toBe node.clientWidth
|
||||
|
||||
region2Rect = regions[1].getBoundingClientRect()
|
||||
expect(region2Rect.top).toBe 2 * lineHeightInPixels
|
||||
expect(region2Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region2Rect.left).toBe 0
|
||||
expect(region2Rect.width).toBe 10 * charWidth
|
||||
|
||||
it "renders 3 regions for selections with more than 2 lines", ->
|
||||
editor.setSelectedScreenRange([[1, 6], [5, 10]])
|
||||
regions = node.querySelectorAll('.selection .region')
|
||||
expect(regions.length).toBe 3
|
||||
|
||||
region1Rect = regions[0].getBoundingClientRect()
|
||||
expect(region1Rect.top).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region1Rect.left).toBe 6 * charWidth
|
||||
expect(region1Rect.right).toBe node.clientWidth
|
||||
|
||||
region2Rect = regions[1].getBoundingClientRect()
|
||||
expect(region2Rect.top).toBe 2 * lineHeightInPixels
|
||||
expect(region2Rect.height).toBe 3 * lineHeightInPixels
|
||||
expect(region2Rect.left).toBe 0
|
||||
expect(region2Rect.right).toBe node.clientWidth
|
||||
|
||||
region3Rect = regions[2].getBoundingClientRect()
|
||||
expect(region3Rect.top).toBe 5 * lineHeightInPixels
|
||||
expect(region3Rect.height).toBe 1 * lineHeightInPixels
|
||||
expect(region3Rect.left).toBe 0
|
||||
expect(region3Rect.width).toBe 10 * charWidth
|
||||
|
||||
cursor3.destroy()
|
||||
cursorNodes = node.querySelectorAll('.cursor')
|
||||
expect(cursorNodes.length).toBe 1
|
||||
expect(cursorNodes[0].offsetTop).toBe 6 * lineHeightInPixels
|
||||
expect(cursorNodes[0].offsetLeft).toBe 11 * charWidth
|
||||
|
||||
it "updates the scroll bar when the scrollTop is changed in the model", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
@@ -92,22 +162,6 @@ describe "EditorComponent", ->
|
||||
editor.setScrollTop(10)
|
||||
expect(scrollbarNode.scrollTop).toBe 10
|
||||
|
||||
it "accounts for character widths when positioning cursors", ->
|
||||
atom.config.set('editor.fontFamily', 'sans-serif')
|
||||
editor.setCursorScreenPosition([0, 16])
|
||||
|
||||
cursor = node.querySelector('.cursor')
|
||||
cursorRect = cursor.getBoundingClientRect()
|
||||
|
||||
cursorLocationTextNode = node.querySelector('.storage.type.function.js').firstChild.firstChild
|
||||
range = document.createRange()
|
||||
range.setStart(cursorLocationTextNode, 0)
|
||||
range.setEnd(cursorLocationTextNode, 1)
|
||||
rangeRect = range.getBoundingClientRect()
|
||||
|
||||
expect(cursorRect.left).toBe rangeRect.left
|
||||
expect(cursorRect.width).toBe rangeRect.width
|
||||
|
||||
it "transfers focus to the hidden input", ->
|
||||
expect(document.activeElement).toBe document.body
|
||||
node.focus()
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
{React, div} = require 'reactionary'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
module.exports =
|
||||
CursorComponent = React.createClass
|
||||
mixins: [SubscriberMixin]
|
||||
|
||||
render: ->
|
||||
{top, left, height, width} = @props.cursor.getPixelRect()
|
||||
div className: 'cursor', style: {top, left, height, width}
|
||||
|
||||
componentDidMount: ->
|
||||
@subscribe @props.cursor, 'moved', => @forceUpdate()
|
||||
|
||||
componentWillUnmount: ->
|
||||
@unsubscribe()
|
||||
|
||||
@@ -109,7 +109,7 @@ class DisplayBuffer extends Model
|
||||
|
||||
getScrollTop: -> @scrollTop
|
||||
setScrollTop: (scrollTop) ->
|
||||
@scrollTop = Math.min(@getScrollHeight(), Math.max(0, scrollTop))
|
||||
@scrollTop = Math.min(@getScrollHeight() - @getHeight(), Math.max(0, scrollTop))
|
||||
|
||||
getScrollBottom: -> @scrollTop + @height
|
||||
setScrollBottom: (scrollBottom) ->
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
{React, div, span} = require 'reactionary'
|
||||
{$$} = require 'space-pen'
|
||||
|
||||
SelectionComponent = require './selection-component'
|
||||
InputComponent = require './input-component'
|
||||
SelectionComponent = require './selection-component'
|
||||
CursorComponent = require './cursor-component'
|
||||
CustomEventMixin = require './custom-event-mixin'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
|
||||
@@ -23,7 +24,7 @@ EditorCompont = React.createClass
|
||||
{fontSize, lineHeight, fontFamily} = @state
|
||||
{editor} = @props
|
||||
|
||||
div className: 'editor', tabIndex: -1, style: {fontSize, lineHeight, fontFamily},
|
||||
div className: 'editor react', tabIndex: -1, style: {fontSize, lineHeight, fontFamily},
|
||||
div className: 'scroll-view', ref: 'scrollView',
|
||||
InputComponent ref: 'hiddenInput', className: 'hidden-input', onInput: @onInput
|
||||
@renderScrollableContent()
|
||||
@@ -38,13 +39,14 @@ EditorCompont = React.createClass
|
||||
div className: 'scrollable-content', style: {height, WebkitTransform},
|
||||
@renderOverlayer()
|
||||
@renderVisibleLines()
|
||||
@renderUnderlayer()
|
||||
|
||||
renderOverlayer: ->
|
||||
{editor} = @props
|
||||
|
||||
div className: 'overlayer',
|
||||
for selection in @props.editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
|
||||
SelectionComponent({selection})
|
||||
for selection in editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
|
||||
CursorComponent(cursor: selection.cursor)
|
||||
|
||||
renderVisibleLines: ->
|
||||
{editor} = @props
|
||||
@@ -60,6 +62,13 @@ EditorCompont = React.createClass
|
||||
div className: 'spacer', key: 'bottom-spacer', style: {height: followingHeight}
|
||||
]
|
||||
|
||||
renderUnderlayer: ->
|
||||
{editor} = @props
|
||||
|
||||
div className: 'underlayer',
|
||||
for selection in editor.getSelections() when editor.selectionIntersectsVisibleRowRange(selection)
|
||||
SelectionComponent({selection})
|
||||
|
||||
getInitialState: -> {}
|
||||
|
||||
getDefaultProps: -> updateSync: true
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{React, div} = require 'reactionary'
|
||||
SubscriberMixin = require './subscriber-mixin'
|
||||
CursorComponent = require './cursor-component'
|
||||
|
||||
module.exports =
|
||||
SelectionComponent = React.createClass
|
||||
@@ -8,7 +7,8 @@ SelectionComponent = React.createClass
|
||||
|
||||
render: ->
|
||||
div className: 'selection',
|
||||
CursorComponent(cursor: @props.selection.cursor)
|
||||
for regionRect, i in @props.selection.getRegionRects()
|
||||
div className: 'region', key: i, style: regionRect
|
||||
|
||||
componentDidMount: ->
|
||||
@subscribe @props.selection, 'screen-range-changed', => @forceUpdate()
|
||||
|
||||
@@ -570,6 +570,47 @@ class Selection
|
||||
compare: (otherSelection) ->
|
||||
@getBufferRange().compare(otherSelection.getBufferRange())
|
||||
|
||||
# Get the pixel dimensions of rectangular regions that cover selection's area
|
||||
# on the screen. Used by SelectionComponent for rendering.
|
||||
getRegionRects: ->
|
||||
lineHeight = @editor.getLineHeight()
|
||||
{start, end} = @getScreenRange()
|
||||
rowCount = end.row - start.row + 1
|
||||
startPixelPosition = @editor.pixelPositionForScreenPosition(start)
|
||||
endPixelPosition = @editor.pixelPositionForScreenPosition(end)
|
||||
|
||||
if rowCount is 1
|
||||
# Single line selection
|
||||
rects = [{
|
||||
top: startPixelPosition.top
|
||||
height: lineHeight
|
||||
left: startPixelPosition.left
|
||||
width: endPixelPosition.left - startPixelPosition.left
|
||||
}]
|
||||
else
|
||||
# Multi-line selection
|
||||
rects = []
|
||||
|
||||
# First row, extending from selection start to the right side of screen
|
||||
rects.push {
|
||||
top: startPixelPosition.top
|
||||
left: startPixelPosition.left
|
||||
height: lineHeight
|
||||
right: 0
|
||||
}
|
||||
if rowCount > 2
|
||||
# Middle rows, extending from left side to right side of screen
|
||||
rects.push {
|
||||
top: startPixelPosition.top + lineHeight
|
||||
height: (rowCount - 2) * lineHeight
|
||||
left: 0
|
||||
right: 0
|
||||
}
|
||||
# Last row, extending from left side of screen to selection end
|
||||
rects.push {top: endPixelPosition.top, height: lineHeight, left: 0, width: endPixelPosition.left }
|
||||
|
||||
rects
|
||||
|
||||
screenRangeChanged: ->
|
||||
screenRange = @getScreenRange()
|
||||
@emit 'screen-range-changed', screenRange
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
@import "octicon-utf-codes";
|
||||
@import "octicon-mixins";
|
||||
|
||||
.editor.react .underlayer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.editor {
|
||||
overflow: hidden;
|
||||
cursor: text;
|
||||
|
||||
Reference in New Issue
Block a user