WIP: Start on block decorations

This commit is contained in:
Nathan Sobo
2017-03-30 12:20:33 -06:00
committed by Antonio Scandurra
parent 171e4e88ca
commit b32b760ee4
2 changed files with 106 additions and 8 deletions

View File

@@ -1156,6 +1156,52 @@ describe('TextEditorComponent', () => {
})
})
describe('block decorations', () => {
ffit('renders visible and yet-to-be-measured block decorations, inserting them between the appropriate lines and refreshing them as needed', async () => {
const editor = buildEditor()
const {item: item1, decoration: decoration1} = createBlockDecorationAtScreenRow(editor, 0, {height: 80, position: 'before'})
const {item: item2, decoration: decoration2} = createBlockDecorationAtScreenRow(editor, 2, {height: 40, margin: 12, position: 'before'})
const {item: item3, decoration: decoration3} = createBlockDecorationAtScreenRow(editor, 4, {height: 100, position: 'before'})
const {item: item4, decoration: decoration4} = createBlockDecorationAtScreenRow(editor, 7, {height: 120, position: 'before'})
const {item: item5, decoration: decoration5} = createBlockDecorationAtScreenRow(editor, 7, {height: 42, position: 'after'})
const {item: item6, decoration: decoration6} = createBlockDecorationAtScreenRow(editor, 12, {height: 22, position: 'after'})
const {component, element} = buildComponent({editor, rowsPerTile: 3})
await setEditorHeightInLines(component, 5)
global.debugContent = true
return
expect(element.querySelectorAll('.line').length).toBe(3)
expect(component.getScrollHeight()).toBe(
editor.getScreenLineCount() * component.getLineHeight() +
item1.offsetHeight + item2.offsetHeight + item3.offsetHeight +
item4.offsetHeight + item5.offsetHeight + item6.offsetHeight
)
expect(tileNodeForScreenRow(0).offsetHeight).toBe(
3 * component.getLineHeight() + item1.offsetHeight + item2.offsetHeight
)
expect(item1.previousSibling).toBeNull()
expect(item1.nextSibling).toBe(lineNodeForScreenRow(component, 0))
expect(item2.previousSibling).toBe(lineNodeForScreenRow(component, 1))
expect(item2.nextSibling).toBe(lineNodeForScreenRow(component, 0))
expect(element.contains(item3)).toBe(false)
expect(element.contains(item4)).toBe(false)
expect(element.contains(item5)).toBe(false)
expect(element.contains(item6)).toBe(false)
})
function createBlockDecorationAtScreenRow(editor, screenRow, {height, margin, position}) {
const marker = editor.markScreenPosition([screenRow, 0], {invalidate: 'never'})
const item = document.createElement('div')
item.style.height = height + 'px'
if (margin != null) item.style.margin = margin + 'px'
item.style.width = 30 + 'px'
const decoration = editor.decorateMarker(marker, {type: 'block', item, position})
return {item, decoration}
}
})
describe('mouse input', () => {
describe('on the lines', () => {
it('positions the cursor on single-click', async () => {
@@ -1831,7 +1877,7 @@ describe('TextEditorComponent', () => {
})
})
function buildComponent (params = {}) {
function buildEditor (params = {}) {
const text = params.text != null ? params.text : SAMPLE_TEXT
const buffer = new TextBuffer({text})
const editorParams = {buffer}
@@ -1839,7 +1885,11 @@ function buildComponent (params = {}) {
for (const paramName of ['mini', 'autoHeight', 'autoWidth', 'lineNumberGutterVisible', 'placeholderText']) {
if (params[paramName] != null) editorParams[paramName] = params[paramName]
}
const editor = new TextEditor(editorParams)
return new TextEditor(editorParams)
}
function buildComponent (params = {}) {
const editor = params.editor || buildEditor(params)
const component = new TextEditorComponent({
model: editor,
rowsPerTile: params.rowsPerTile,

View File

@@ -2,6 +2,7 @@ const etch = require('etch')
const {CompositeDisposable} = require('event-kit')
const {Point, Range} = require('text-buffer')
const ResizeDetector = require('element-resize-detector')
const LineTopIndex = require('line-top-index')
const TextEditor = require('./text-editor')
const {isPairedCharacter} = require('./text-utils')
const $ = etch.dom
@@ -19,6 +20,15 @@ const MOUSE_DRAG_AUTOSCROLL_MARGIN = 40
const MOUSE_WHEEL_SCROLL_SENSITIVITY = 0.8
const CURSOR_BLINK_RESUME_DELAY = 300
const CURSOR_BLINK_PERIOD = 800
const BLOCK_DECORATION_MEASUREMENT_AREA_VNODE = $.div({
ref: 'blockDecorationMeasurementArea',
key: 'blockDecorationMeasurementArea',
style: {
contain: 'strict',
position: 'absolute',
visibility: 'hidden'
}
})
function scaleMouseDragAutoscrollDelta (delta) {
return Math.pow(delta / 3, 3) / 280
@@ -57,6 +67,7 @@ class TextEditorComponent {
this.didScrollDummyScrollbar = this.didScrollDummyScrollbar.bind(this)
this.didMouseDownOnContent = this.didMouseDownOnContent.bind(this)
this.disposables = new CompositeDisposable()
this.lineTopIndex = new LineTopIndex()
this.updateScheduled = false
this.measurements = null
this.visible = false
@@ -149,6 +160,8 @@ class TextEditorComponent {
return
}
this.measureBlockDecorations()
this.measuredContent = false
this.updateSyncBeforeMeasuringContent()
if (useScheduler === true) {
@@ -167,6 +180,31 @@ class TextEditorComponent {
}
}
measureBlockDecorations () {
const {blockDecorationMeasurementArea} = this.refs
blockDecorationMeasurementArea.appendChild(document.createElement('div'))
this.blockDecorationsToMeasure.forEach((decoration) => {
const {item} = decoration.getProperties()
blockDecorationMeasurementArea.appendChild(TextEditor.viewForItem(item))
blockDecorationMeasurementArea.appendChild(document.createElement('div'))
})
this.blockDecorationsToMeasure.forEach((decoration) => {
const {item, position} = decoration.getProperties()
const decorationElement = TextEditor.viewForItem(item)
const {previousSibling, nextSibling} = decorationElement
const height = nextSibling.offsetTop - previousSibling.offsetTop
const row = decoration.getMarker().getHeadScreenPosition().row
this.lineTopIndex.insertBlock(decoration.id, row, height, position === 'after')
})
while (blockDecorationMeasurementArea.firstChild) {
blockDecorationMeasurementArea.firstChild.remove()
}
this.blockDecorationsToMeasure.clear()
}
updateSyncBeforeMeasuringContent () {
this.horizontalPositionsToMeasure.clear()
if (this.pendingAutoscroll) this.autoscrollVertically()
@@ -230,9 +268,13 @@ class TextEditorComponent {
if (this.measurements) {
if (model.getAutoHeight()) {
style.height = this.getContentHeight() + 'px'
} else {
style.height = this.element.style.height
}
if (model.getAutoWidth()) {
style.width = this.getGutterContainerWidth() + this.getContentWidth() + 'px'
} else {
style.width = this.element.style.width
}
}
@@ -393,15 +435,19 @@ class TextEditorComponent {
children = [
this.renderCursorsAndInput(),
this.renderLineTiles(),
BLOCK_DECORATION_MEASUREMENT_AREA_VNODE,
this.renderPlaceholderText()
]
} else {
children = $.div({ref: 'characterMeasurementLine', className: 'line'},
$.span({ref: 'normalWidthCharacterSpan'}, NORMAL_WIDTH_CHARACTER),
$.span({ref: 'doubleWidthCharacterSpan'}, DOUBLE_WIDTH_CHARACTER),
$.span({ref: 'halfWidthCharacterSpan'}, HALF_WIDTH_CHARACTER),
$.span({ref: 'koreanCharacterSpan'}, KOREAN_CHARACTER)
)
children = [
BLOCK_DECORATION_MEASUREMENT_AREA_VNODE,
$.div({ref: 'characterMeasurementLine', className: 'line'},
$.span({ref: 'normalWidthCharacterSpan'}, NORMAL_WIDTH_CHARACTER),
$.span({ref: 'doubleWidthCharacterSpan'}, DOUBLE_WIDTH_CHARACTER),
$.span({ref: 'halfWidthCharacterSpan'}, HALF_WIDTH_CHARACTER),
$.span({ref: 'koreanCharacterSpan'}, KOREAN_CHARACTER)
)
]
}
return $.div(
@@ -1569,6 +1615,7 @@ class TextEditorComponent {
this.measurements.halfWidthCharacterWidth,
this.measurements.koreanCharacterWidth
)
this.lineTopIndex.setDefaultLineHeight(this.measurements.lineHeight)
}
measureGutterDimensions () {
@@ -1792,6 +1839,7 @@ class TextEditorComponent {
this.disposables.add(model.onDidRemoveGutter(scheduleUpdate))
this.disposables.add(model.selectionsMarkerLayer.onDidUpdate(this.didUpdateSelections.bind(this)))
this.disposables.add(model.onDidRequestAutoscroll(this.didRequestAutoscroll.bind(this)))
this.blockDecorationsToMeasure = new Set(model.getDecorations({type: 'block'}))
}
isVisible () {