mirror of
https://github.com/atom/atom.git
synced 2026-02-14 00:25:08 -05:00
Merge pull request #18773 from atom/aw/block-decoration-order
Explicit block decoration ordering
This commit is contained in:
@@ -65,7 +65,9 @@ describe "DecorationManager", ->
|
||||
|
||||
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
|
||||
expect(oldProperties).toEqual decorationProperties
|
||||
expect(newProperties).toEqual {type: 'line-number', gutterName: 'line-number', class: 'two'}
|
||||
expect(newProperties.type).toBe 'line-number'
|
||||
expect(newProperties.gutterName).toBe 'line-number'
|
||||
expect(newProperties.class).toBe 'two'
|
||||
|
||||
describe "::getDecorations(properties)", ->
|
||||
it "returns decorations matching the given optional properties", ->
|
||||
|
||||
@@ -2638,7 +2638,71 @@ describe('TextEditorComponent', () => {
|
||||
expect(editor.getCursorScreenPosition()).toEqual([0, 0])
|
||||
})
|
||||
|
||||
function createBlockDecorationAtScreenRow(editor, screenRow, {height, margin, marginTop, marginBottom, position, invalidate}) {
|
||||
it('uses the order property to control the order of block decorations at the same screen row', async () => {
|
||||
const editor = buildEditor({autoHeight: false})
|
||||
const {component, element} = buildComponent({editor})
|
||||
element.style.height = 10 * component.getLineHeight() + horizontalScrollbarHeight + 'px'
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
// Order parameters that differ from creation order; that collide; and that are not provided.
|
||||
const [beforeItems, beforeDecorations] = [30, 20, undefined, 20, 10, undefined].map(order => {
|
||||
return createBlockDecorationAtScreenRow(editor, 2, {height: 10, position: 'before', order})
|
||||
}).reduce((lists, result) => {
|
||||
lists[0].push(result.item)
|
||||
lists[1].push(result.decoration)
|
||||
return lists
|
||||
}, [[], []])
|
||||
|
||||
const [afterItems, afterDecorations] = [undefined, 1, 6, undefined, 6, 2].map(order => {
|
||||
return createBlockDecorationAtScreenRow(editor, 2, {height: 10, position: 'after', order})
|
||||
}).reduce((lists, result) => {
|
||||
lists[0].push(result.item)
|
||||
lists[1].push(result.decoration)
|
||||
return lists
|
||||
}, [[], []])
|
||||
|
||||
await component.getNextUpdatePromise()
|
||||
|
||||
expect(beforeItems[4].previousSibling).toBe(lineNodeForScreenRow(component, 1))
|
||||
expect(beforeItems[4].nextSibling).toBe(beforeItems[1])
|
||||
expect(beforeItems[1].nextSibling).toBe(beforeItems[3])
|
||||
expect(beforeItems[3].nextSibling).toBe(beforeItems[0])
|
||||
expect(beforeItems[0].nextSibling).toBe(beforeItems[2])
|
||||
expect(beforeItems[2].nextSibling).toBe(beforeItems[5])
|
||||
expect(beforeItems[5].nextSibling).toBe(lineNodeForScreenRow(component, 2))
|
||||
expect(afterItems[1].previousSibling).toBe(lineNodeForScreenRow(component, 2))
|
||||
expect(afterItems[1].nextSibling).toBe(afterItems[5])
|
||||
expect(afterItems[5].nextSibling).toBe(afterItems[2])
|
||||
expect(afterItems[2].nextSibling).toBe(afterItems[4])
|
||||
expect(afterItems[4].nextSibling).toBe(afterItems[0])
|
||||
expect(afterItems[0].nextSibling).toBe(afterItems[3])
|
||||
|
||||
// Create a decoration somewhere else and move it to the same screen row as the existing decorations
|
||||
const {item: later, decoration} = createBlockDecorationAtScreenRow(editor, 4, {height: 20, position: 'after', order: 3})
|
||||
await component.getNextUpdatePromise()
|
||||
expect(later.previousSibling).toBe(lineNodeForScreenRow(component, 4))
|
||||
expect(later.nextSibling).toBe(lineNodeForScreenRow(component, 5))
|
||||
|
||||
decoration.getMarker().setHeadScreenPosition([2, 0])
|
||||
await component.getNextUpdatePromise()
|
||||
expect(later.previousSibling).toBe(afterItems[5])
|
||||
expect(later.nextSibling).toBe(afterItems[2])
|
||||
|
||||
// Move a decoration away from its screen row and ensure the rest maintain their order
|
||||
beforeDecorations[3].getMarker().setHeadScreenPosition([5, 0])
|
||||
await component.getNextUpdatePromise()
|
||||
expect(beforeItems[3].previousSibling).toBe(lineNodeForScreenRow(component, 4))
|
||||
expect(beforeItems[3].nextSibling).toBe(lineNodeForScreenRow(component, 5))
|
||||
|
||||
expect(beforeItems[4].previousSibling).toBe(lineNodeForScreenRow(component, 1))
|
||||
expect(beforeItems[4].nextSibling).toBe(beforeItems[1])
|
||||
expect(beforeItems[1].nextSibling).toBe(beforeItems[0])
|
||||
expect(beforeItems[0].nextSibling).toBe(beforeItems[2])
|
||||
expect(beforeItems[2].nextSibling).toBe(beforeItems[5])
|
||||
expect(beforeItems[5].nextSibling).toBe(lineNodeForScreenRow(component, 2))
|
||||
});
|
||||
|
||||
function createBlockDecorationAtScreenRow(editor, screenRow, {height, margin, marginTop, marginBottom, position, order, invalidate}) {
|
||||
const marker = editor.markScreenPosition([screenRow, 0], {invalidate: invalidate || 'never'})
|
||||
const item = document.createElement('div')
|
||||
item.style.height = height + 'px'
|
||||
@@ -2646,7 +2710,7 @@ describe('TextEditorComponent', () => {
|
||||
if (marginTop != null) item.style.marginTop = marginTop + 'px'
|
||||
if (marginBottom != null) item.style.marginBottom = marginBottom + 'px'
|
||||
item.style.width = 30 + 'px'
|
||||
const decoration = editor.decorateMarker(marker, {type: 'block', item, position})
|
||||
const decoration = editor.decorateMarker(marker, {type: 'block', item, position, order})
|
||||
return {item, decoration, marker}
|
||||
}
|
||||
|
||||
|
||||
@@ -6861,7 +6861,7 @@ describe('TextEditor', () => {
|
||||
const marker = editor.markBufferRange([[2, 4], [6, 8]])
|
||||
const decoration = editor.decorateMarker(marker, {type: 'highlight', class: 'foo'})
|
||||
expect(editor.decorationsStateForScreenRowRange(0, 5)[decoration.id]).toEqual({
|
||||
properties: {type: 'highlight', class: 'foo'},
|
||||
properties: {id: decoration.id, order: Infinity, type: 'highlight', class: 'foo'},
|
||||
screenRange: marker.getScreenRange(),
|
||||
bufferRange: marker.getBufferRange(),
|
||||
rangeIsReversed: false
|
||||
|
||||
@@ -3,12 +3,17 @@ const {Emitter} = require('event-kit')
|
||||
let idCounter = 0
|
||||
const nextId = () => idCounter++
|
||||
|
||||
// Applies changes to a decorationsParam {Object} to make it possible to
|
||||
// differentiate decorations on custom gutters versus the line-number gutter.
|
||||
const translateDecorationParamsOldToNew = function (decorationParams) {
|
||||
if (decorationParams.type === 'line-number') {
|
||||
const normalizeDecorationProperties = function (decoration, decorationParams) {
|
||||
decorationParams.id = decoration.id
|
||||
|
||||
if (decorationParams.type === 'line-number' && decorationParams.gutterName == null) {
|
||||
decorationParams.gutterName = 'line-number'
|
||||
}
|
||||
|
||||
if (decorationParams.order == null) {
|
||||
decorationParams.order = Infinity
|
||||
}
|
||||
|
||||
return decorationParams
|
||||
}
|
||||
|
||||
@@ -164,7 +169,7 @@ class Decoration {
|
||||
setProperties (newProperties) {
|
||||
if (this.destroyed) { return }
|
||||
const oldProperties = this.properties
|
||||
this.properties = translateDecorationParamsOldToNew(newProperties)
|
||||
this.properties = normalizeDecorationProperties(this, newProperties)
|
||||
if (newProperties.type != null) {
|
||||
this.decorationManager.decorationDidChangeType(this)
|
||||
}
|
||||
|
||||
@@ -1191,6 +1191,10 @@ class TextEditorComponent {
|
||||
decorationsByScreenLine.set(screenLine.id, decorations)
|
||||
}
|
||||
decorations.push(decoration)
|
||||
|
||||
// Order block decorations by increasing values of their "order" property. Break ties with "id", which mirrors
|
||||
// their creation sequence.
|
||||
decorations.sort((a, b) => a.order !== b.order ? a.order - b.order : a.id - b.id)
|
||||
}
|
||||
|
||||
addTextDecorationToRender (decoration, screenRange, marker) {
|
||||
@@ -3862,15 +3866,24 @@ class LinesTileComponent {
|
||||
|
||||
if (blockDecorations) {
|
||||
blockDecorations.forEach((newDecorations, screenLineId) => {
|
||||
var oldDecorations = oldProps.blockDecorations ? oldProps.blockDecorations.get(screenLineId) : null
|
||||
for (var i = 0; i < newDecorations.length; i++) {
|
||||
var newDecoration = newDecorations[i]
|
||||
if (oldDecorations && oldDecorations.includes(newDecoration)) continue
|
||||
const oldDecorations = oldProps.blockDecorations ? oldProps.blockDecorations.get(screenLineId) : null
|
||||
const lineNode = lineComponentsByScreenLineId.get(screenLineId).element
|
||||
let lastAfter = lineNode
|
||||
|
||||
for (let i = 0; i < newDecorations.length; i++) {
|
||||
const newDecoration = newDecorations[i]
|
||||
const element = TextEditor.viewForItem(newDecoration.item)
|
||||
|
||||
if (oldDecorations && oldDecorations.includes(newDecoration)) {
|
||||
if (newDecoration.position === 'after') {
|
||||
lastAfter = element
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var element = TextEditor.viewForItem(newDecoration.item)
|
||||
var lineNode = lineComponentsByScreenLineId.get(screenLineId).element
|
||||
if (newDecoration.position === 'after') {
|
||||
this.element.insertBefore(element, lineNode.nextSibling)
|
||||
this.element.insertBefore(element, lastAfter.nextSibling)
|
||||
lastAfter = element
|
||||
} else {
|
||||
this.element.insertBefore(element, lineNode)
|
||||
}
|
||||
|
||||
@@ -2221,14 +2221,17 @@ class TextEditor {
|
||||
//
|
||||
// The following are the supported decorations types:
|
||||
//
|
||||
// * __line__: Adds your CSS `class` to the line nodes within the range
|
||||
// marked by the marker
|
||||
// * __line-number__: Adds your CSS `class` to the line number nodes within the
|
||||
// range marked by the marker
|
||||
// * __highlight__: Adds a new highlight div to the editor surrounding the
|
||||
// range marked by the marker. When the user selects text, the selection is
|
||||
// visualized with a highlight decoration internally. The structure of this
|
||||
// highlight will be
|
||||
// * __line__: Adds the given CSS `class` to the lines overlapping the rows
|
||||
// spanned by the marker.
|
||||
// * __line-number__: Adds the given CSS `class` to the line numbers overlapping
|
||||
// the rows spanned by the marker
|
||||
// * __text__: Injects spans into all text overlapping the marked range, then adds
|
||||
// the given `class` or `style` to these spans. Use this to manipulate the foreground
|
||||
// color or styling of text in a range.
|
||||
// * __highlight__: Creates an absolutely-positioned `.highlight` div to the editor
|
||||
// containing nested divs that cover the marked region. For example, when the user
|
||||
// selects text, the selection is implemented with a highlight decoration. The structure
|
||||
// of this highlight will be:
|
||||
// ```html
|
||||
// <div class="highlight <your-class>">
|
||||
// <!-- Will be one region for each row in the range. Spans 2 lines? There will be 2 regions. -->
|
||||
@@ -2236,45 +2239,25 @@ class TextEditor {
|
||||
// </div>
|
||||
// ```
|
||||
// * __overlay__: Positions the view associated with the given item at the head
|
||||
// or tail of the given `DisplayMarker`.
|
||||
// * __gutter__: A decoration that tracks a {DisplayMarker} in a {Gutter}. Gutter
|
||||
// decorations are created by calling {Gutter::decorateMarker} on the
|
||||
// desired `Gutter` instance.
|
||||
// or tail of the given `DisplayMarker`, depending on the `position` property.
|
||||
// * __gutter__: Tracks a {DisplayMarker} in a {Gutter}. Gutter decorations are created
|
||||
// by calling {Gutter::decorateMarker} on the desired `Gutter` instance.
|
||||
// * __block__: Positions the view associated with the given item before or
|
||||
// after the row of the given `TextEditorMarker`.
|
||||
// after the row of the given {DisplayMarker}, depending on the `position` property.
|
||||
// Block decorations at the same screen row are ordered by their `order` property.
|
||||
// * __cursor__: Render a cursor at the head of the {DisplayMarker}. If multiple cursor decorations
|
||||
// are created for the same marker, their class strings and style objects are combined
|
||||
// into a single cursor. This decoration type may be used to style existing cursors
|
||||
// by passing in their markers or to render artificial cursors that don't actaully
|
||||
// exist in the model by passing a marker that isn't associated with a real cursor.
|
||||
//
|
||||
// ## Arguments
|
||||
//
|
||||
// * `marker` A {DisplayMarker} you want this decoration to follow.
|
||||
// * `decorationParams` An {Object} representing the decoration e.g.
|
||||
// `{type: 'line-number', class: 'linter-error'}`
|
||||
// * `type` There are several supported decoration types. The behavior of the
|
||||
// types are as follows:
|
||||
// * `line` Adds the given `class` to the lines overlapping the rows
|
||||
// spanned by the `DisplayMarker`.
|
||||
// * `line-number` Adds the given `class` to the line numbers overlapping
|
||||
// the rows spanned by the `DisplayMarker`.
|
||||
// * `text` Injects spans into all text overlapping the marked range,
|
||||
// then adds the given `class` or `style` properties to these spans.
|
||||
// Use this to manipulate the foreground color or styling of text in
|
||||
// a given range.
|
||||
// * `highlight` Creates an absolutely-positioned `.highlight` div
|
||||
// containing nested divs to cover the marked region. For example, this
|
||||
// is used to implement selections.
|
||||
// * `overlay` Positions the view associated with the given item at the
|
||||
// head or tail of the given `DisplayMarker`, depending on the `position`
|
||||
// property.
|
||||
// * `gutter` Tracks a {DisplayMarker} in a {Gutter}. Created by calling
|
||||
// {Gutter::decorateMarker} on the desired `Gutter` instance.
|
||||
// * `block` Positions the view associated with the given item before or
|
||||
// after the row of the given `TextEditorMarker`, depending on the `position`
|
||||
// property.
|
||||
// * `cursor` Renders a cursor at the head of the given marker. If multiple
|
||||
// decorations are created for the same marker, their class strings and
|
||||
// style objects are combined into a single cursor. You can use this
|
||||
// decoration type to style existing cursors by passing in their markers
|
||||
// or render artificial cursors that don't actually exist in the model
|
||||
// by passing a marker that isn't actually associated with a cursor.
|
||||
// * `type` Determines the behavior and appearance of this {Decoration}. Supported decoration types
|
||||
// and their uses are listed above.
|
||||
// * `class` This CSS class will be applied to the decorated line number,
|
||||
// line, text spans, highlight regions, cursors, or overlay.
|
||||
// * `style` An {Object} containing CSS style properties to apply to the
|
||||
@@ -2300,12 +2283,15 @@ class TextEditor {
|
||||
// Controls where the view is positioned relative to the `TextEditorMarker`.
|
||||
// Values can be `'head'` (the default) or `'tail'` for overlay decorations, and
|
||||
// `'before'` (the default) or `'after'` for block decorations.
|
||||
// * `order` (optional) Only applicable to decorations of type `block`. Controls
|
||||
// where the view is positioned relative to other block decorations at the
|
||||
// same screen row. If unspecified, block decorations render oldest to newest.
|
||||
// * `avoidOverflow` (optional) Only applicable to decorations of type
|
||||
// `overlay`. Determines whether the decoration adjusts its horizontal or
|
||||
// vertical position to remain fully visible when it would otherwise
|
||||
// overflow the editor. Defaults to `true`.
|
||||
//
|
||||
// Returns a {Decoration} object
|
||||
// Returns the created {Decoration} object.
|
||||
decorateMarker (marker, decorationParams) {
|
||||
return this.decorationManager.decorateMarker(marker, decorationParams)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user