mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge branch 'master' into dh-async-repo
This commit is contained in:
@@ -11,11 +11,12 @@
|
||||
{ label: 'Downloading Update', enabled: false, visible: false}
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences…', command: 'application:show-settings' }
|
||||
{ label: 'Open Your Config', command: 'application:open-your-config' }
|
||||
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
|
||||
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
|
||||
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
|
||||
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Config…', command: 'application:open-your-config' }
|
||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
|
||||
{ type: 'separator' }
|
||||
|
||||
@@ -83,11 +83,12 @@
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ label: '&Preferences', command: 'application:show-settings' }
|
||||
{ label: 'Open Your Config', command: 'application:open-your-config' }
|
||||
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
|
||||
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
|
||||
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
|
||||
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Config…', command: 'application:open-your-config' }
|
||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
{ label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Se&ttings', command: 'application:show-settings' }
|
||||
{ label: 'Open Your Config', command: 'application:open-your-config' }
|
||||
{ label: 'Open Your Init Script', command: 'application:open-your-init-script' }
|
||||
{ label: 'Open Your Keymap', command: 'application:open-your-keymap' }
|
||||
{ label: 'Open Your Snippets', command: 'application:open-your-snippets' }
|
||||
{ label: 'Open Your Stylesheet', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Config…', command: 'application:open-your-config' }
|
||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
||||
{ type: 'separator' }
|
||||
{ label: '&Save', command: 'core:save' }
|
||||
{ label: 'Save &As…', command: 'core:save-as' }
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"jquery": "2.1.4",
|
||||
"key-path-helpers": "^0.4.0",
|
||||
"less-cache": "0.22",
|
||||
"line-top-index": "0.2.0",
|
||||
"marked": "^0.3.4",
|
||||
"nodegit": "0.7.0",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
@@ -81,14 +82,14 @@
|
||||
"autoflow": "0.26.0",
|
||||
"autosave": "0.23.0",
|
||||
"background-tips": "0.26.0",
|
||||
"bookmarks": "0.38.0",
|
||||
"bookmarks": "0.38.2",
|
||||
"bracket-matcher": "0.79.0",
|
||||
"command-palette": "0.38.0",
|
||||
"deprecation-cop": "0.54.0",
|
||||
"dev-live-reload": "0.47.0",
|
||||
"encoding-selector": "0.21.0",
|
||||
"exception-reporting": "0.37.0",
|
||||
"find-and-replace": "0.195.0",
|
||||
"find-and-replace": "0.196.0",
|
||||
"fuzzy-finder": "0.94.0",
|
||||
"git-diff": "0.57.0",
|
||||
"go-to-line": "0.30.0",
|
||||
@@ -111,7 +112,7 @@
|
||||
"symbols-view": "0.110.1",
|
||||
"tabs": "0.88.0",
|
||||
"timecop": "0.33.0",
|
||||
"tree-view": "0.198.1",
|
||||
"tree-view": "0.199.0",
|
||||
"update-package-dependencies": "0.10.0",
|
||||
"welcome": "0.33.0",
|
||||
"whitespace": "0.32.1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
module.exports =
|
||||
class FakeLinesYardstick
|
||||
constructor: (@model) ->
|
||||
constructor: (@model, @lineTopIndex) ->
|
||||
@characterWidthsByScope = {}
|
||||
|
||||
getScopedCharacterWidth: (scopeNames, char) ->
|
||||
@@ -26,7 +26,7 @@ class FakeLinesYardstick
|
||||
targetColumn = screenPosition.column
|
||||
baseCharacterWidth = @model.getDefaultCharWidth()
|
||||
|
||||
top = targetRow * @model.getLineHeightInPixels()
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow)
|
||||
left = 0
|
||||
column = 0
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
LinesYardstick = require "../src/lines-yardstick"
|
||||
LinesYardstick = require '../src/lines-yardstick'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
{toArray} = require 'underscore-plus'
|
||||
{Point} = require 'text-buffer'
|
||||
|
||||
@@ -45,7 +46,10 @@ describe "LinesYardstick", ->
|
||||
textNodes
|
||||
|
||||
editor.setLineHeightInPixels(14)
|
||||
linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, atom.grammars)
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: editor.getLineHeightInPixels()
|
||||
})
|
||||
linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars)
|
||||
|
||||
afterEach ->
|
||||
lineNode.remove() for lineNode in createdLineNodes
|
||||
|
||||
@@ -1651,6 +1651,214 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('block decorations rendering', function () {
|
||||
function createBlockDecorationBeforeScreenRow(screenRow, {className}) {
|
||||
let item = document.createElement("div")
|
||||
item.className = className || ""
|
||||
let blockDecoration = editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
|
||||
{type: "block", item: item, position: "before"}
|
||||
)
|
||||
return [item, blockDecoration]
|
||||
}
|
||||
|
||||
function createBlockDecorationAfterScreenRow(screenRow, {className}) {
|
||||
let item = document.createElement("div")
|
||||
item.className = className || ""
|
||||
let blockDecoration = editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], {invalidate: "never"}),
|
||||
{type: "block", item: item, position: "after"}
|
||||
)
|
||||
return [item, blockDecoration]
|
||||
}
|
||||
|
||||
beforeEach(async function () {
|
||||
wrapperNode.style.height = 5 * lineHeightInPixels + 'px'
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
atom.themes.removeStylesheet('test')
|
||||
})
|
||||
|
||||
it("renders visible and yet-to-be-measured block decorations, inserting them between the appropriate lines and refreshing them as needed", async function () {
|
||||
let [item1, blockDecoration1] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
|
||||
let [item2, blockDecoration2] = createBlockDecorationBeforeScreenRow(2, {className: "decoration-2"})
|
||||
let [item3, blockDecoration3] = createBlockDecorationBeforeScreenRow(4, {className: "decoration-3"})
|
||||
let [item4, blockDecoration4] = createBlockDecorationBeforeScreenRow(7, {className: "decoration-4"})
|
||||
let [item5, blockDecoration5] = createBlockDecorationAfterScreenRow(7, {className: "decoration-5"})
|
||||
|
||||
atom.styles.addStyleSheet(
|
||||
`atom-text-editor .decoration-1 { width: 30px; height: 80px; }
|
||||
atom-text-editor .decoration-2 { width: 30px; height: 40px; }
|
||||
atom-text-editor .decoration-3 { width: 30px; height: 100px; }
|
||||
atom-text-editor .decoration-4 { width: 30px; height: 120px; }
|
||||
atom-text-editor .decoration-5 { width: 30px; height: 42px; }`,
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 80 + 40 + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBe(item1)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
|
||||
|
||||
expect(item1.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 0)
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 2 + 80)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 4 + 80 + 40)
|
||||
|
||||
editor.setCursorScreenPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
blockDecoration1.destroy()
|
||||
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 40 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
|
||||
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40)
|
||||
|
||||
atom.styles.addStyleSheet(
|
||||
'atom-text-editor .decoration-2 { height: 60px; }',
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
|
||||
await nextAnimationFramePromise() // causes the DOM to update and to retrieve new styles
|
||||
await nextAnimationFramePromise() // applies the changes
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(7)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 60 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBeNull()
|
||||
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60)
|
||||
|
||||
item2.style.height = "20px"
|
||||
wrapperNode.invalidateBlockDecorationDimensions(blockDecoration2)
|
||||
await nextAnimationFramePromise()
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getDomNode().querySelectorAll(".line").length).toBe(9)
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 100 + 20 + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + 42 + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull()
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-2")).toBe(item2)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBe(item4)
|
||||
expect(component.getTopmostDOMNode().querySelector(".decoration-5")).toBe(item5)
|
||||
|
||||
expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3)
|
||||
expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 20)
|
||||
expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100)
|
||||
expect(item5.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 20 + 100 + 120 + lineHeightInPixels)
|
||||
})
|
||||
|
||||
it("correctly sets screen rows on <content> elements, both initially and when decorations move", async function () {
|
||||
let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
|
||||
atom.styles.addStyleSheet(
|
||||
'atom-text-editor .decoration-1 { width: 30px; height: 80px; }',
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
let tileNode, contentElements
|
||||
|
||||
tileNode = component.tileNodesForLines()[0]
|
||||
contentElements = tileNode.querySelectorAll("content")
|
||||
|
||||
expect(contentElements.length).toBe(1)
|
||||
expect(contentElements[0].dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
|
||||
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
tileNode = component.tileNodesForLines()[0]
|
||||
contentElements = tileNode.querySelectorAll("content")
|
||||
|
||||
expect(contentElements.length).toBe(1)
|
||||
expect(contentElements[0].dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
|
||||
|
||||
blockDecoration.getMarker().setHeadBufferPosition([2, 0])
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
tileNode = component.tileNodesForLines()[0]
|
||||
contentElements = tileNode.querySelectorAll("content")
|
||||
|
||||
expect(contentElements.length).toBe(1)
|
||||
expect(contentElements[0].dataset.screenRow).toBe("2")
|
||||
expect(component.lineNodeForScreenRow(0).dataset.screenRow).toBe("0")
|
||||
expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1")
|
||||
expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2")
|
||||
})
|
||||
|
||||
it('measures block decorations taking into account both top and bottom margins', async function () {
|
||||
let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"})
|
||||
atom.styles.addStyleSheet(
|
||||
'atom-text-editor .decoration-1 { width: 30px; height: 30px; margin-top: 10px; margin-bottom: 5px; }',
|
||||
{context: 'atom-text-editor'}
|
||||
)
|
||||
|
||||
await nextAnimationFramePromise() // causes the DOM to update and to retrieve new styles
|
||||
await nextAnimationFramePromise() // applies the changes
|
||||
|
||||
expect(component.tileNodesForLines()[0].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 30 + 10 + 5 + "px")
|
||||
expect(component.tileNodesForLines()[0].style.webkitTransform).toBe("translate3d(0px, 0px, 0px)")
|
||||
expect(component.tileNodesForLines()[1].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[1].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight}px, 0px)`)
|
||||
expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + "px")
|
||||
expect(component.tileNodesForLines()[2].style.webkitTransform).toBe(`translate3d(0px, ${component.tileNodesForLines()[0].offsetHeight + component.tileNodesForLines()[1].offsetHeight}px, 0px)`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('highlight decoration rendering', function () {
|
||||
let decoration, marker, scrollViewClientLeft
|
||||
|
||||
@@ -3433,6 +3641,40 @@ describe('TextEditorComponent', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the mousewheel event\'s target is a block decoration', function () {
|
||||
it('keeps it on the DOM if it is scrolled off-screen', async function () {
|
||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
wrapperNode.style.width = 20 * charWidth + 'px'
|
||||
component.measureDimensions()
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
let item = document.createElement("div")
|
||||
item.style.width = "30px"
|
||||
item.style.height = "30px"
|
||||
item.className = "decoration-1"
|
||||
editor.decorateMarker(
|
||||
editor.markScreenPosition([0, 0], {invalidate: "never"}),
|
||||
{type: "block", item: item}
|
||||
)
|
||||
|
||||
await nextViewUpdatePromise()
|
||||
|
||||
let wheelEvent = new WheelEvent('mousewheel', {
|
||||
wheelDeltaX: 0,
|
||||
wheelDeltaY: -500
|
||||
})
|
||||
Object.defineProperty(wheelEvent, 'target', {
|
||||
get: function () {
|
||||
return item
|
||||
}
|
||||
})
|
||||
componentNode.dispatchEvent(wheelEvent)
|
||||
await nextAnimationFramePromise()
|
||||
|
||||
expect(component.getTopmostDOMNode().contains(item)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('only prevents the default action of the mousewheel event if it actually lead to scrolling', async function () {
|
||||
spyOn(WheelEvent.prototype, 'preventDefault').andCallThrough()
|
||||
wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
|
||||
@@ -5,6 +5,7 @@ TextBuffer = require 'text-buffer'
|
||||
TextEditor = require '../src/text-editor'
|
||||
TextEditorPresenter = require '../src/text-editor-presenter'
|
||||
FakeLinesYardstick = require './fake-lines-yardstick'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
|
||||
describe "TextEditorPresenter", ->
|
||||
# These `describe` and `it` blocks mirror the structure of the ::state object.
|
||||
@@ -29,13 +30,29 @@ describe "TextEditorPresenter", ->
|
||||
presenter.getPreMeasurementState()
|
||||
presenter.getPostMeasurementState()
|
||||
|
||||
addBlockDecorationBeforeScreenRow = (screenRow, item) ->
|
||||
editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], invalidate: "never"),
|
||||
type: "block", item: item, position: "before"
|
||||
)
|
||||
|
||||
addBlockDecorationAfterScreenRow = (screenRow, item) ->
|
||||
editor.decorateMarker(
|
||||
editor.markScreenPosition([screenRow, 0], invalidate: "never"),
|
||||
type: "block", item: item, position: "after"
|
||||
)
|
||||
|
||||
buildPresenterWithoutMeasurements = (params={}) ->
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: editor.getLineHeightInPixels()
|
||||
})
|
||||
_.defaults params,
|
||||
model: editor
|
||||
config: atom.config
|
||||
contentFrameWidth: 500
|
||||
lineTopIndex: lineTopIndex
|
||||
presenter = new TextEditorPresenter(params)
|
||||
presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter))
|
||||
presenter.setLinesYardstick(new FakeLinesYardstick(editor, lineTopIndex))
|
||||
presenter
|
||||
|
||||
buildPresenter = (params={}) ->
|
||||
@@ -164,6 +181,99 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expect(stateFn(presenter).tiles[12]).toBeUndefined()
|
||||
|
||||
describe "when there are block decorations", ->
|
||||
it "computes each tile's height and scrollTop based on block decorations' height", ->
|
||||
presenter = buildPresenter(explicitHeight: 120, scrollTop: 0, lineHeight: 10, tileSize: 2)
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(5)
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(5)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 1)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 30)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 40)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 50)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].height).toBe(2 * 10 + 1)
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].height).toBe(2 * 10 + 30)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 1)
|
||||
expect(stateFn(presenter).tiles[4].height).toBe(2 * 10 + 40 + 50)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 1 + 30)
|
||||
expect(stateFn(presenter).tiles[6].height).toBe(2 * 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 1 + 30 + 40 + 50)
|
||||
expect(stateFn(presenter).tiles[8]).toBeUndefined()
|
||||
|
||||
presenter.setScrollTop(21)
|
||||
|
||||
expect(stateFn(presenter).tiles[0]).toBeUndefined()
|
||||
expect(stateFn(presenter).tiles[2].height).toBe(2 * 10 + 30)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 1 - 21)
|
||||
expect(stateFn(presenter).tiles[4].height).toBe(2 * 10 + 40 + 50)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 1 + 30 - 21)
|
||||
expect(stateFn(presenter).tiles[6].height).toBe(2 * 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 1 + 30 + 40 + 50 - 21)
|
||||
expect(stateFn(presenter).tiles[8]).toBeUndefined()
|
||||
|
||||
blockDecoration3.getMarker().setHeadScreenPosition([6, 0])
|
||||
|
||||
expect(stateFn(presenter).tiles[0]).toBeUndefined()
|
||||
expect(stateFn(presenter).tiles[2].height).toBe(2 * 10 + 30)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 1 - 21)
|
||||
expect(stateFn(presenter).tiles[4].height).toBe(2 * 10 + 50)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 1 + 30 - 21)
|
||||
expect(stateFn(presenter).tiles[6].height).toBe(2 * 10 + 40)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 1 + 30 + 50 - 21)
|
||||
expect(stateFn(presenter).tiles[8]).toBeUndefined()
|
||||
|
||||
it "works correctly when soft wrapping is enabled", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(4)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(8)
|
||||
|
||||
presenter = buildPresenter(explicitHeight: 330, lineHeight: 10, tileSize: 2, baseCharacterWidth: 5)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 30)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[10].top).toBe(10 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[12].top).toBe(12 * 10 + 10 + 20 + 30)
|
||||
|
||||
editor.setSoftWrapped(true)
|
||||
presenter.setContentFrameWidth(5 * 25)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[10].top).toBe(10 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[12].top).toBe(12 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[14].top).toBe(14 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[16].top).toBe(16 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[18].top).toBe(18 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[20].top).toBe(20 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[22].top).toBe(22 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[24].top).toBe(24 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[26].top).toBe(26 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[28].top).toBe(28 * 10 + 10 + 20 + 30)
|
||||
|
||||
editor.setSoftWrapped(false)
|
||||
|
||||
expect(stateFn(presenter).tiles[0].top).toBe(0 * 10)
|
||||
expect(stateFn(presenter).tiles[2].top).toBe(2 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[4].top).toBe(4 * 10 + 10)
|
||||
expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 10 + 20)
|
||||
expect(stateFn(presenter).tiles[10].top).toBe(10 * 10 + 10 + 20 + 30)
|
||||
expect(stateFn(presenter).tiles[12].top).toBe(12 * 10 + 10 + 20 + 30)
|
||||
|
||||
it "includes state for all tiles if no external ::explicitHeight is assigned", ->
|
||||
presenter = buildPresenter(explicitHeight: null, tileSize: 2)
|
||||
expect(stateFn(presenter).tiles[0]).toBeDefined()
|
||||
@@ -363,7 +473,7 @@ describe "TextEditorPresenter", ->
|
||||
expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(getState(presenter).horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
|
||||
|
||||
it "updates when ::softWrapped changes on the editor", ->
|
||||
@@ -482,6 +592,32 @@ describe "TextEditorPresenter", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 500)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe 500
|
||||
|
||||
it "updates when new block decorations are measured, changed or destroyed", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 95.2)
|
||||
|
||||
linesHeight = editor.getScreenLineCount() * 10
|
||||
blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3)
|
||||
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration3.destroy()
|
||||
runs ->
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
it "updates when the ::lineHeight changes", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expectStateUpdate presenter, -> presenter.setLineHeight(20)
|
||||
@@ -608,7 +744,7 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(getState(presenter).hiddenInput.width).toBe 20
|
||||
|
||||
it "is 2px at the end of lines", ->
|
||||
@@ -630,6 +766,32 @@ describe "TextEditorPresenter", ->
|
||||
expect(getState(presenter).content.maxHeight).toBe(50)
|
||||
|
||||
describe ".scrollHeight", ->
|
||||
it "updates when new block decorations are measured, changed or destroyed", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 95.2)
|
||||
|
||||
linesHeight = editor.getScreenLineCount() * 10
|
||||
blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2)
|
||||
expect(getState(presenter).content.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3)
|
||||
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2)
|
||||
expect(getState(presenter).content.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration3.destroy()
|
||||
runs ->
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3)
|
||||
expect(getState(presenter).content.scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
it "is initialized based on the lineHeight, the number of lines, and the height", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).content.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
@@ -705,7 +867,7 @@ describe "TextEditorPresenter", ->
|
||||
expect(getState(presenter).content.scrollWidth).toBe 10 * maxLineLength + 1
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(getState(presenter).content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide
|
||||
|
||||
it "updates when ::softWrapped changes on the editor", ->
|
||||
@@ -811,6 +973,9 @@ describe "TextEditorPresenter", ->
|
||||
editor.insertNewline()
|
||||
expect(getState(presenter).content.scrollTop).toBe(10)
|
||||
|
||||
editor.insertNewline()
|
||||
expect(getState(presenter).content.scrollTop).toBe(20)
|
||||
|
||||
it "never exceeds the computed scroll height minus the computed client height", ->
|
||||
didChangeScrollTopSpy = jasmine.createSpy()
|
||||
presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 10)
|
||||
@@ -1165,6 +1330,142 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 0).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.eol')]
|
||||
expect(lineStateForScreenRow(presenter, 1).endOfLineInvisibles).toEqual [atom.config.get('editor.invisibles.cr'), atom.config.get('editor.invisibles.eol')]
|
||||
|
||||
describe ".blockDecorations", ->
|
||||
it "contains all block decorations that are present before/after a line, both initially and when decorations change", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter()
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(7)
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([blockDecoration2])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([blockDecoration3])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([blockDecoration4])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration1.getMarker().setHeadBufferPosition([1, 0])
|
||||
blockDecoration2.getMarker().setHeadBufferPosition([9, 0])
|
||||
blockDecoration3.getMarker().setHeadBufferPosition([9, 0])
|
||||
blockDecoration4.getMarker().setHeadBufferPosition([8, 0])
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([blockDecoration4])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([blockDecoration2, blockDecoration3])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration4.destroy()
|
||||
blockDecoration3.destroy()
|
||||
blockDecoration1.getMarker().setHeadBufferPosition([0, 0])
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([blockDecoration2])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 0).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 1).precedingBlockDecorations).toEqual([blockDecoration1])
|
||||
expect(lineStateForScreenRow(presenter, 1).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 2).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 3).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 4).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 5).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 6).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 7).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 8).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 9).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 10).precedingBlockDecorations).toEqual([blockDecoration2])
|
||||
expect(lineStateForScreenRow(presenter, 10).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 11).followingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).precedingBlockDecorations).toEqual([])
|
||||
expect(lineStateForScreenRow(presenter, 12).followingBlockDecorations).toEqual([])
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
@@ -1360,6 +1661,45 @@ describe "TextEditorPresenter", ->
|
||||
presenter.setHorizontalScrollbarHeight(10)
|
||||
expect(getState(presenter).content.cursors).not.toEqual({})
|
||||
|
||||
it "updates when block decorations change", ->
|
||||
editor.setSelectedBufferRanges([
|
||||
[[1, 2], [1, 2]],
|
||||
[[2, 4], [2, 4]],
|
||||
[[3, 4], [3, 5]]
|
||||
[[5, 12], [5, 12]],
|
||||
[[8, 4], [8, 4]]
|
||||
])
|
||||
presenter = buildPresenter(explicitHeight: 80, scrollTop: 0)
|
||||
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 10, left: 2 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 20, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10, left: 4 * 10, width: 10, height: 10}
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(1)
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
|
||||
|
||||
runs ->
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 50, left: 2 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 1)).toEqual {top: 60, left: 4 * 10, width: 10, height: 10}
|
||||
expect(stateForCursor(presenter, 2)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 3)).toBeUndefined()
|
||||
expect(stateForCursor(presenter, 4)).toBeUndefined()
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration2.destroy()
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
editor.insertNewline()
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
|
||||
runs ->
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 0, left: 0, width: 10, height: 10}
|
||||
|
||||
it "updates when ::scrollTop changes", ->
|
||||
editor.setSelectedBufferRanges([
|
||||
[[1, 2], [1, 2]],
|
||||
@@ -1434,12 +1774,12 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'v', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10}
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.type.var.js'], 'r', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
|
||||
|
||||
it "updates when cursors are added, moved, hidden, shown, or destroyed", ->
|
||||
@@ -1785,7 +2125,7 @@ describe "TextEditorPresenter", ->
|
||||
}
|
||||
expectStateUpdate presenter, ->
|
||||
presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'keyword.control.js'], 'i', 20)
|
||||
presenter.characterWidthsChanged()
|
||||
presenter.measurementsChanged()
|
||||
expectValues stateForSelectionInTile(presenter, 0, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 20 + 10, height: 10}]
|
||||
}
|
||||
@@ -1897,6 +2237,223 @@ describe "TextEditorPresenter", ->
|
||||
flashCount: 2
|
||||
}
|
||||
|
||||
describe ".blockDecorations", ->
|
||||
stateForBlockDecoration = (presenter, decoration) ->
|
||||
getState(presenter).content.blockDecorations[decoration.id]
|
||||
|
||||
it "contains state for measured block decorations that are not visible when they are on ::mouseWheelScreenRow", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0, stoppedScrollingDelay: 200)
|
||||
getState(presenter) # flush pending state
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 0)
|
||||
|
||||
presenter.setScrollTop(100)
|
||||
presenter.setMouseWheelScreenRow(0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
advanceClock(presenter.stoppedScrollingDelay)
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
it "invalidates block decorations that intersect a change in the buffer", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(9)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(10)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(11)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 9
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
|
||||
|
||||
editor.setSelectedScreenRange([[10, 0], [12, 0]])
|
||||
editor.delete()
|
||||
presenter.setScrollTop(0) # deleting the buffer causes the editor to autoscroll
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
it "invalidates all block decorations when content frame width, window size or bounding client rect change", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(11)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
presenter.setBoundingClientRect({top: 0, left: 0, width: 50, height: 30})
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
presenter.setContentFrameWidth(100)
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
presenter.setWindowSize(100, 200)
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 11
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20)
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
|
||||
it "contains state for on-screen and unmeasured block decorations, both initially and when they are updated or destroyed", ->
|
||||
item = {}
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0, item)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(4, item)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(4, item)
|
||||
blockDecoration4 = addBlockDecorationBeforeScreenRow(10, item)
|
||||
presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 4
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 4
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: false
|
||||
}
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 20)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: true
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 4
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 4
|
||||
isVisible: false
|
||||
}
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined()
|
||||
|
||||
blockDecoration3.getMarker().setHeadScreenPosition([5, 0])
|
||||
presenter.setScrollTop(90)
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration2), {
|
||||
decoration: blockDecoration2
|
||||
screenRow: 4
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration3), {
|
||||
decoration: blockDecoration3
|
||||
screenRow: 5
|
||||
isVisible: false
|
||||
}
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
presenter.invalidateBlockDecorationDimensions(blockDecoration1)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10)
|
||||
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration1), {
|
||||
decoration: blockDecoration1
|
||||
screenRow: 0
|
||||
isVisible: false
|
||||
}
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
blockDecoration1.destroy()
|
||||
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined()
|
||||
expect(stateForBlockDecoration(presenter, blockDecoration3)).toBeUndefined()
|
||||
expectValues stateForBlockDecoration(presenter, blockDecoration4), {
|
||||
decoration: blockDecoration4
|
||||
screenRow: 10
|
||||
isVisible: true
|
||||
}
|
||||
|
||||
it "doesn't throw an error when setting the dimensions for a destroyed decoration", ->
|
||||
blockDecoration = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter()
|
||||
|
||||
blockDecoration.destroy()
|
||||
presenter.setBlockDecorationDimensions(blockDecoration, 30, 30)
|
||||
|
||||
expect(getState(presenter).content.blockDecorations).toEqual({})
|
||||
|
||||
describe ".overlays", ->
|
||||
[item] = []
|
||||
stateForOverlay = (presenter, decoration) ->
|
||||
@@ -2396,6 +2953,76 @@ describe "TextEditorPresenter", ->
|
||||
expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 5, softWrapped: false}
|
||||
expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 6, softWrapped: false}
|
||||
|
||||
describe ".blockDecorationsHeight", ->
|
||||
it "adds the sum of all block decorations' heights to the relevant line number state objects, both initially and when decorations change", ->
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter = buildPresenter(tileSize: 2, explicitHeight: 300)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration4 = addBlockDecorationBeforeScreenRow(7)
|
||||
blockDecoration5 = addBlockDecorationAfterScreenRow(7)
|
||||
blockDecoration6 = addBlockDecorationAfterScreenRow(10)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 30)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 35)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 40)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration5, 0, 50)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration6, 0, 60)
|
||||
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(10)
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).blockDecorationsHeight).toBe(20 + 30)
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).blockDecorationsHeight).toBe(40)
|
||||
expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(0) # 0 because we're at the start of a tile.
|
||||
expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration1.getMarker().setHeadBufferPosition([1, 0])
|
||||
blockDecoration2.getMarker().setHeadBufferPosition([5, 0])
|
||||
blockDecoration3.getMarker().setHeadBufferPosition([9, 0])
|
||||
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(10)
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).blockDecorationsHeight).toBe(20)
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).blockDecorationsHeight).toBe(40)
|
||||
expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(0) # 0 because we're at the start of a tile.
|
||||
expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(30)
|
||||
expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)
|
||||
|
||||
waitsForStateToUpdate presenter, ->
|
||||
blockDecoration1.destroy()
|
||||
blockDecoration3.destroy()
|
||||
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).blockDecorationsHeight).toBe(20)
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).blockDecorationsHeight).toBe(40)
|
||||
expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(0) # 0 because we're at the start of a tile.
|
||||
expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0)
|
||||
expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60)
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
@@ -2625,6 +3252,56 @@ describe "TextEditorPresenter", ->
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration3.id].top).toBeDefined()
|
||||
|
||||
it "updates when block decorations are added, changed or removed", ->
|
||||
# block decoration before decoration1
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 3)
|
||||
# block decoration between decoration1 and decoration2
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 5)
|
||||
# block decoration between decoration2 and decoration3
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(10)
|
||||
blockDecoration4 = addBlockDecorationAfterScreenRow(10)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 7)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration4, 0, 11)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].top).toBe lineHeight * marker1.getScreenRange().start.row + 3
|
||||
expect(decorationState[decoration1.id].height).toBe lineHeight * marker1.getScreenRange().getRowCount()
|
||||
expect(decorationState[decoration1.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration1.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 3 + 5
|
||||
expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7 + 11
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
presenter.setScrollTop(scrollTop + lineHeight * 5)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 3 + 5
|
||||
expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7 + 11
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id].top).toBe lineHeight * marker3.getScreenRange().start.row + 3 + 5 + 7 + 11
|
||||
expect(decorationState[decoration3.id].height).toBe lineHeight * marker3.getScreenRange().getRowCount()
|
||||
expect(decorationState[decoration3.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id].class).toBe 'test-class'
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration1.destroy()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBe lineHeight * marker2.getScreenRange().start.row + 5
|
||||
expect(decorationState[decoration2.id].height).toBe lineHeight * marker2.getScreenRange().getRowCount() + 7 + 11
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id].top).toBe lineHeight * marker3.getScreenRange().start.row + 5 + 7 + 11
|
||||
expect(decorationState[decoration3.id].height).toBe lineHeight * marker3.getScreenRange().getRowCount()
|
||||
expect(decorationState[decoration3.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id].class).toBe 'test-class'
|
||||
|
||||
it "updates when ::scrollTop changes", ->
|
||||
# This update will scroll decoration1 out of view, and decoration3 into view.
|
||||
expectStateUpdate presenter, -> presenter.setScrollTop(scrollTop + lineHeight * 5)
|
||||
@@ -2648,6 +3325,7 @@ describe "TextEditorPresenter", ->
|
||||
expectStateUpdate presenter, -> presenter.setLineHeight(Math.ceil(1.0 * explicitHeight / marker3.getBufferRange().end.row))
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
|
||||
expect(decorationState[decoration1.id].top).toBeDefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id].top).toBeDefined()
|
||||
@@ -2830,6 +3508,32 @@ describe "TextEditorPresenter", ->
|
||||
customGutter.destroy()
|
||||
|
||||
describe ".scrollHeight", ->
|
||||
it "updates when new block decorations are measured, changed or destroyed", ->
|
||||
presenter = buildPresenter(scrollTop: 0, lineHeight: 10)
|
||||
expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
||||
blockDecoration1 = addBlockDecorationBeforeScreenRow(0)
|
||||
blockDecoration2 = addBlockDecorationBeforeScreenRow(3)
|
||||
blockDecoration3 = addBlockDecorationBeforeScreenRow(7)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3)
|
||||
presenter.setBlockDecorationDimensions(blockDecoration3, 0, 95.2)
|
||||
|
||||
linesHeight = editor.getScreenLineCount() * 10
|
||||
blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2)
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3)
|
||||
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2)
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
waitsForStateToUpdate presenter, -> blockDecoration3.destroy()
|
||||
runs ->
|
||||
blockDecorationsHeight = Math.round(35.8 + 100.3)
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight)
|
||||
|
||||
it "is initialized based on ::lineHeight, the number of lines, and ::explicitHeight", ->
|
||||
presenter = buildPresenter()
|
||||
expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe editor.getScreenLineCount() * 10
|
||||
|
||||
72
src/block-decorations-component.coffee
Normal file
72
src/block-decorations-component.coffee
Normal file
@@ -0,0 +1,72 @@
|
||||
cloneObject = (object) ->
|
||||
clone = {}
|
||||
clone[key] = value for key, value of object
|
||||
clone
|
||||
|
||||
module.exports =
|
||||
class BlockDecorationsComponent
|
||||
constructor: (@container, @views, @presenter, @domElementPool) ->
|
||||
@newState = null
|
||||
@oldState = null
|
||||
@blockDecorationNodesById = {}
|
||||
@domNode = @domElementPool.buildElement("content")
|
||||
@domNode.setAttribute("select", ".atom--invisible-block-decoration")
|
||||
@domNode.style.visibility = "hidden"
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
|
||||
updateSync: (state) ->
|
||||
@newState = state.content
|
||||
@oldState ?= {blockDecorations: {}, width: 0}
|
||||
|
||||
if @newState.width isnt @oldState.width
|
||||
@domNode.style.width = @newState.width + "px"
|
||||
@oldState.width = @newState.width
|
||||
|
||||
for id, blockDecorationState of @oldState.blockDecorations
|
||||
unless @newState.blockDecorations.hasOwnProperty(id)
|
||||
@blockDecorationNodesById[id].remove()
|
||||
delete @blockDecorationNodesById[id]
|
||||
delete @oldState.blockDecorations[id]
|
||||
|
||||
for id, blockDecorationState of @newState.blockDecorations
|
||||
if @oldState.blockDecorations.hasOwnProperty(id)
|
||||
@updateBlockDecorationNode(id)
|
||||
else
|
||||
@oldState.blockDecorations[id] = {}
|
||||
@createAndAppendBlockDecorationNode(id)
|
||||
|
||||
measureBlockDecorations: ->
|
||||
for decorationId, blockDecorationNode of @blockDecorationNodesById
|
||||
style = getComputedStyle(blockDecorationNode)
|
||||
decoration = @newState.blockDecorations[decorationId].decoration
|
||||
marginBottom = parseInt(style.marginBottom) ? 0
|
||||
marginTop = parseInt(style.marginTop) ? 0
|
||||
@presenter.setBlockDecorationDimensions(
|
||||
decoration,
|
||||
blockDecorationNode.offsetWidth,
|
||||
blockDecorationNode.offsetHeight + marginTop + marginBottom
|
||||
)
|
||||
|
||||
createAndAppendBlockDecorationNode: (id) ->
|
||||
blockDecorationState = @newState.blockDecorations[id]
|
||||
blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item)
|
||||
blockDecorationNode.id = "atom--block-decoration-#{id}"
|
||||
@container.appendChild(blockDecorationNode)
|
||||
@blockDecorationNodesById[id] = blockDecorationNode
|
||||
@updateBlockDecorationNode(id)
|
||||
|
||||
updateBlockDecorationNode: (id) ->
|
||||
newBlockDecorationState = @newState.blockDecorations[id]
|
||||
oldBlockDecorationState = @oldState.blockDecorations[id]
|
||||
blockDecorationNode = @blockDecorationNodesById[id]
|
||||
|
||||
if newBlockDecorationState.isVisible
|
||||
blockDecorationNode.classList.remove("atom--invisible-block-decoration")
|
||||
else
|
||||
blockDecorationNode.classList.add("atom--invisible-block-decoration")
|
||||
|
||||
if oldBlockDecorationState.screenRow isnt newBlockDecorationState.screenRow
|
||||
blockDecorationNode.dataset.screenRow = newBlockDecorationState.screenRow
|
||||
oldBlockDecorationState.screenRow = newBlockDecorationState.screenRow
|
||||
@@ -35,7 +35,6 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
# the marker.
|
||||
module.exports =
|
||||
class Decoration
|
||||
|
||||
# Private: Check if the `decorationProperties.type` matches `type`
|
||||
#
|
||||
# * `decorationProperties` {Object} eg. `{type: 'line-number', class: 'my-new-class'}`
|
||||
@@ -154,6 +153,13 @@ class Decoration
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
|
||||
|
||||
###
|
||||
Section: Utility
|
||||
###
|
||||
|
||||
inspect: ->
|
||||
"<Decoration #{@id}>"
|
||||
|
||||
###
|
||||
Section: Private methods
|
||||
###
|
||||
|
||||
@@ -96,12 +96,13 @@ class LineNumbersTileComponent
|
||||
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
|
||||
|
||||
buildLineNumberNode: (lineNumberState) ->
|
||||
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState
|
||||
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex, blockDecorationsHeight} = lineNumberState
|
||||
|
||||
className = @buildLineNumberClassName(lineNumberState)
|
||||
lineNumberNode = @domElementPool.buildElement("div", className)
|
||||
lineNumberNode.dataset.screenRow = screenRow
|
||||
lineNumberNode.dataset.bufferRow = bufferRow
|
||||
lineNumberNode.style.marginTop = blockDecorationsHeight + "px"
|
||||
|
||||
@setLineNumberInnerNodes(bufferRow, softWrapped, lineNumberNode)
|
||||
lineNumberNode
|
||||
@@ -139,6 +140,10 @@ class LineNumbersTileComponent
|
||||
oldLineNumberState.screenRow = newLineNumberState.screenRow
|
||||
oldLineNumberState.bufferRow = newLineNumberState.bufferRow
|
||||
|
||||
unless oldLineNumberState.blockDecorationsHeight is newLineNumberState.blockDecorationsHeight
|
||||
node.style.marginTop = newLineNumberState.blockDecorationsHeight + "px"
|
||||
oldLineNumberState.blockDecorationsHeight = newLineNumberState.blockDecorationsHeight
|
||||
|
||||
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) ->
|
||||
className = "line-number"
|
||||
className += " " + decorationClasses.join(' ') if decorationClasses?
|
||||
|
||||
@@ -20,6 +20,8 @@ class LinesTileComponent
|
||||
@screenRowsByLineId = {}
|
||||
@lineIdsByScreenRow = {}
|
||||
@textNodesByLineId = {}
|
||||
@insertionPointsBeforeLineById = {}
|
||||
@insertionPointsAfterLineById = {}
|
||||
@domNode = @domElementPool.buildElement("div")
|
||||
@domNode.style.position = "absolute"
|
||||
@domNode.style.display = "block"
|
||||
@@ -80,6 +82,9 @@ class LinesTileComponent
|
||||
|
||||
removeLineNode: (id) ->
|
||||
@domElementPool.freeElementAndDescendants(@lineNodesByLineId[id])
|
||||
@removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
@removeBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
delete @lineNodesByLineId[id]
|
||||
delete @textNodesByLineId[id]
|
||||
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
|
||||
@@ -116,6 +121,71 @@ class LinesTileComponent
|
||||
else
|
||||
@domNode.appendChild(lineNode)
|
||||
|
||||
@insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
@insertBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
removeBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
if insertionPoint = @insertionPointsBeforeLineById[id]
|
||||
@domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete @insertionPointsBeforeLineById[id]
|
||||
|
||||
insertBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
{hasPrecedingBlockDecorations, screenRow} = @newTileState.lines[id]
|
||||
|
||||
if hasPrecedingBlockDecorations
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
insertionPoint = @domElementPool.buildElement("content")
|
||||
@domNode.insertBefore(insertionPoint, lineNode)
|
||||
@insertionPointsBeforeLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
@updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
|
||||
updateBlockDecorationInsertionPointBeforeLine: (id) ->
|
||||
oldLineState = @oldTileState.lines[id]
|
||||
newLineState = @newTileState.lines[id]
|
||||
insertionPoint = @insertionPointsBeforeLineById[id]
|
||||
return unless insertionPoint?
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
|
||||
precedingBlockDecorationsSelector = newLineState.precedingBlockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',')
|
||||
|
||||
if precedingBlockDecorationsSelector isnt oldLineState.precedingBlockDecorationsSelector
|
||||
insertionPoint.setAttribute("select", precedingBlockDecorationsSelector)
|
||||
oldLineState.precedingBlockDecorationsSelector = precedingBlockDecorationsSelector
|
||||
|
||||
removeBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
if insertionPoint = @insertionPointsAfterLineById[id]
|
||||
@domElementPool.freeElementAndDescendants(insertionPoint)
|
||||
delete @insertionPointsAfterLineById[id]
|
||||
|
||||
insertBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
{hasFollowingBlockDecorations, screenRow} = @newTileState.lines[id]
|
||||
|
||||
if hasFollowingBlockDecorations
|
||||
lineNode = @lineNodesByLineId[id]
|
||||
insertionPoint = @domElementPool.buildElement("content")
|
||||
@domNode.insertBefore(insertionPoint, lineNode.nextSibling)
|
||||
@insertionPointsAfterLineById[id] = insertionPoint
|
||||
insertionPoint.dataset.screenRow = screenRow
|
||||
@updateBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
updateBlockDecorationInsertionPointAfterLine: (id) ->
|
||||
oldLineState = @oldTileState.lines[id]
|
||||
newLineState = @newTileState.lines[id]
|
||||
insertionPoint = @insertionPointsAfterLineById[id]
|
||||
return unless insertionPoint?
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
insertionPoint.dataset.screenRow = newLineState.screenRow
|
||||
|
||||
followingBlockDecorationsSelector = newLineState.followingBlockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',')
|
||||
|
||||
if followingBlockDecorationsSelector isnt oldLineState.followingBlockDecorationsSelector
|
||||
insertionPoint.setAttribute("select", followingBlockDecorationsSelector)
|
||||
oldLineState.followingBlockDecorationsSelector = followingBlockDecorationsSelector
|
||||
|
||||
findNodeNextTo: (node) ->
|
||||
for nextNode, index in @domNode.children
|
||||
continue if index is 0 # skips highlights node
|
||||
@@ -336,12 +406,28 @@ class LinesTileComponent
|
||||
|
||||
oldLineState.decorationClasses = newLineState.decorationClasses
|
||||
|
||||
if not oldLineState.hasPrecedingBlockDecorations and newLineState.hasPrecedingBlockDecorations
|
||||
@insertBlockDecorationInsertionPointBeforeLine(id)
|
||||
else if oldLineState.hasPrecedingBlockDecorations and not newLineState.hasPrecedingBlockDecorations
|
||||
@removeBlockDecorationInsertionPointBeforeLine(id)
|
||||
|
||||
if not oldLineState.hasFollowingBlockDecorations and newLineState.hasFollowingBlockDecorations
|
||||
@insertBlockDecorationInsertionPointAfterLine(id)
|
||||
else if oldLineState.hasFollowingBlockDecorations and not newLineState.hasFollowingBlockDecorations
|
||||
@removeBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
if newLineState.screenRow isnt oldLineState.screenRow
|
||||
lineNode.dataset.screenRow = newLineState.screenRow
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
@lineIdsByScreenRow[newLineState.screenRow] = id
|
||||
@screenRowsByLineId[id] = newLineState.screenRow
|
||||
|
||||
@updateBlockDecorationInsertionPointBeforeLine(id)
|
||||
@updateBlockDecorationInsertionPointAfterLine(id)
|
||||
|
||||
oldLineState.screenRow = newLineState.screenRow
|
||||
oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations
|
||||
oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations
|
||||
|
||||
lineNodeForScreenRow: (screenRow) ->
|
||||
@lineNodesByLineId[@lineIdsByScreenRow[screenRow]]
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ TokenIterator = require './token-iterator'
|
||||
|
||||
module.exports =
|
||||
class LinesYardstick
|
||||
constructor: (@model, @lineNodesProvider, grammarRegistry) ->
|
||||
constructor: (@model, @lineNodesProvider, @lineTopIndex, grammarRegistry) ->
|
||||
@tokenIterator = new TokenIterator({grammarRegistry})
|
||||
@rangeForMeasurement = document.createRange()
|
||||
@invalidateCache()
|
||||
@@ -20,8 +20,8 @@ class LinesYardstick
|
||||
targetTop = pixelPosition.top
|
||||
targetLeft = pixelPosition.left
|
||||
defaultCharWidth = @model.getDefaultCharWidth()
|
||||
row = Math.floor(targetTop / @model.getLineHeightInPixels())
|
||||
targetLeft = 0 if row < 0
|
||||
row = @lineTopIndex.rowForPixelPosition(targetTop)
|
||||
targetLeft = 0 if targetTop < 0
|
||||
targetLeft = Infinity if row > @model.getLastScreenRow()
|
||||
row = Math.min(row, @model.getLastScreenRow())
|
||||
row = Math.max(0, row)
|
||||
@@ -81,7 +81,7 @@ class LinesYardstick
|
||||
targetRow = screenPosition.row
|
||||
targetColumn = screenPosition.column
|
||||
|
||||
top = targetRow * @model.getLineHeightInPixels()
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow)
|
||||
left = @leftPixelPositionForScreenPosition(targetRow, targetColumn)
|
||||
|
||||
{top, left}
|
||||
|
||||
@@ -13,6 +13,8 @@ ScrollbarCornerComponent = require './scrollbar-corner-component'
|
||||
OverlayManager = require './overlay-manager'
|
||||
DOMElementPool = require './dom-element-pool'
|
||||
LinesYardstick = require './lines-yardstick'
|
||||
BlockDecorationsComponent = require './block-decorations-component'
|
||||
LineTopIndex = require 'line-top-index'
|
||||
|
||||
module.exports =
|
||||
class TextEditorComponent
|
||||
@@ -48,6 +50,9 @@ class TextEditorComponent
|
||||
@observeConfig()
|
||||
@setScrollSensitivity(@config.get('editor.scrollSensitivity'))
|
||||
|
||||
lineTopIndex = new LineTopIndex({
|
||||
defaultLineHeight: @editor.getLineHeightInPixels()
|
||||
})
|
||||
@presenter = new TextEditorPresenter
|
||||
model: @editor
|
||||
tileSize: tileSize
|
||||
@@ -55,11 +60,11 @@ class TextEditorComponent
|
||||
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
|
||||
stoppedScrollingDelay: 200
|
||||
config: @config
|
||||
lineTopIndex: lineTopIndex
|
||||
|
||||
@presenter.onDidUpdateState(@requestUpdate)
|
||||
|
||||
@domElementPool = new DOMElementPool
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
if @useShadowDOM
|
||||
@domNode.classList.add('editor-contents--private')
|
||||
@@ -68,6 +73,7 @@ class TextEditorComponent
|
||||
insertionPoint.setAttribute('select', 'atom-overlay')
|
||||
@domNode.appendChild(insertionPoint)
|
||||
@overlayManager = new OverlayManager(@presenter, @hostElement, @views)
|
||||
@blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool)
|
||||
else
|
||||
@domNode.classList.add('editor-contents')
|
||||
@overlayManager = new OverlayManager(@presenter, @domNode, @views)
|
||||
@@ -82,7 +88,10 @@ class TextEditorComponent
|
||||
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars})
|
||||
@scrollViewNode.appendChild(@linesComponent.getDomNode())
|
||||
|
||||
@linesYardstick = new LinesYardstick(@editor, @linesComponent, @grammars)
|
||||
if @blockDecorationsComponent?
|
||||
@linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode())
|
||||
|
||||
@linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex, @grammars)
|
||||
@presenter.setLinesYardstick(@linesYardstick)
|
||||
|
||||
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
|
||||
@@ -158,6 +167,7 @@ class TextEditorComponent
|
||||
|
||||
@hiddenInputComponent.updateSync(@newState)
|
||||
@linesComponent.updateSync(@newState)
|
||||
@blockDecorationsComponent?.updateSync(@newState)
|
||||
@horizontalScrollbarComponent.updateSync(@newState)
|
||||
@verticalScrollbarComponent.updateSync(@newState)
|
||||
@scrollbarCornerComponent.updateSync(@newState)
|
||||
@@ -177,6 +187,7 @@ class TextEditorComponent
|
||||
|
||||
readAfterUpdateSync: =>
|
||||
@overlayManager?.measureOverlays()
|
||||
@blockDecorationsComponent?.measureBlockDecorations() if @isVisible()
|
||||
|
||||
mountGutterContainerComponent: ->
|
||||
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views})
|
||||
@@ -279,13 +290,13 @@ class TextEditorComponent
|
||||
observeConfig: ->
|
||||
@disposables.add @config.onDidChange 'editor.fontSize', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
@disposables.add @config.onDidChange 'editor.fontFamily', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
@disposables.add @config.onDidChange 'editor.lineHeight', =>
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
onGrammarChanged: =>
|
||||
if @scopedConfigDisposables?
|
||||
@@ -485,6 +496,9 @@ class TextEditorComponent
|
||||
@editor.screenPositionForBufferPosition(bufferPosition)
|
||||
)
|
||||
|
||||
invalidateBlockDecorationDimensions: ->
|
||||
@presenter.invalidateBlockDecorationDimensions(arguments...)
|
||||
|
||||
onMouseDown: (event) =>
|
||||
unless event.button is 0 or (event.button is 1 and process.platform is 'linux')
|
||||
# Only handle mouse down events for left mouse button on all platforms
|
||||
@@ -602,7 +616,7 @@ class TextEditorComponent
|
||||
handleStylingChange: =>
|
||||
@sampleFontStyling()
|
||||
@sampleBackgroundColors()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
handleDragUntilMouseUp: (dragHandler) ->
|
||||
dragging = false
|
||||
@@ -756,7 +770,7 @@ class TextEditorComponent
|
||||
if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
|
||||
@clearPoolAfterUpdate = true
|
||||
@measureLineHeightAndDefaultCharWidth()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
sampleBackgroundColors: (suppressUpdate) ->
|
||||
{backgroundColor} = getComputedStyle(@hostElement)
|
||||
@@ -866,7 +880,7 @@ class TextEditorComponent
|
||||
setFontSize: (fontSize) ->
|
||||
@getTopmostDOMNode().style.fontSize = fontSize + 'px'
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
getFontFamily: ->
|
||||
getComputedStyle(@getTopmostDOMNode()).fontFamily
|
||||
@@ -874,16 +888,16 @@ class TextEditorComponent
|
||||
setFontFamily: (fontFamily) ->
|
||||
@getTopmostDOMNode().style.fontFamily = fontFamily
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
setLineHeight: (lineHeight) ->
|
||||
@getTopmostDOMNode().style.lineHeight = lineHeight
|
||||
@sampleFontStyling()
|
||||
@invalidateCharacterWidths()
|
||||
@invalidateMeasurements()
|
||||
|
||||
invalidateCharacterWidths: ->
|
||||
invalidateMeasurements: ->
|
||||
@linesYardstick.invalidateCache()
|
||||
@presenter.characterWidthsChanged()
|
||||
@presenter.measurementsChanged()
|
||||
|
||||
setShowIndentGuide: (showIndentGuide) ->
|
||||
@config.set("editor.showIndentGuide", showIndentGuide)
|
||||
|
||||
@@ -347,4 +347,13 @@ class TextEditorElement extends HTMLElement
|
||||
getHeight: ->
|
||||
@offsetHeight
|
||||
|
||||
# Experimental: Invalidate the passed block {Decoration} dimensions, forcing
|
||||
# them to be recalculated and the surrounding content to be adjusted on the
|
||||
# next animation frame.
|
||||
#
|
||||
# * {blockDecoration} A {Decoration} representing the block decoration you
|
||||
# want to update the dimensions of.
|
||||
invalidateBlockDecorationDimensions: ->
|
||||
@component.invalidateBlockDecorationDimensions(arguments...)
|
||||
|
||||
module.exports = TextEditorElement = document.registerElement 'atom-text-editor', prototype: TextEditorElement.prototype
|
||||
|
||||
@@ -13,7 +13,7 @@ class TextEditorPresenter
|
||||
minimumReflowInterval: 200
|
||||
|
||||
constructor: (params) ->
|
||||
{@model, @config} = params
|
||||
{@model, @config, @lineTopIndex} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params
|
||||
{@contentFrameWidth} = params
|
||||
|
||||
@@ -28,6 +28,9 @@ class TextEditorPresenter
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterName = {}
|
||||
@observedBlockDecorations = new Set()
|
||||
@invalidatedDimensionsByBlockDecoration = new Set()
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
@screenRowsToMeasure = []
|
||||
@transferMeasurementsToModel()
|
||||
@transferMeasurementsFromModel()
|
||||
@@ -85,6 +88,7 @@ class TextEditorPresenter
|
||||
if @shouldUpdateDecorations
|
||||
@fetchDecorations()
|
||||
@updateLineDecorations()
|
||||
@updateBlockDecorations()
|
||||
|
||||
@updateTilesState()
|
||||
|
||||
@@ -126,7 +130,8 @@ class TextEditorPresenter
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
observeModel: ->
|
||||
@disposables.add @model.onDidChange =>
|
||||
@disposables.add @model.onDidChange ({start, end, screenDelta}) =>
|
||||
@spliceBlockDecorationsInRange(start, end, screenDelta)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -134,6 +139,11 @@ class TextEditorPresenter
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add @model.onDidAddDecoration(@didAddBlockDecoration.bind(this))
|
||||
|
||||
for decoration in @model.getDecorations({type: 'block'})
|
||||
this.didAddBlockDecoration(decoration)
|
||||
|
||||
@disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this))
|
||||
@disposables.add @model.onDidChangePlaceholderText(@emitDidUpdateState.bind(this))
|
||||
@disposables.add @model.onDidChangeMini =>
|
||||
@@ -192,6 +202,7 @@ class TextEditorPresenter
|
||||
highlights: {}
|
||||
overlays: {}
|
||||
cursors: {}
|
||||
blockDecorations: {}
|
||||
gutters: []
|
||||
# Shared state that is copied into ``@state.gutters`.
|
||||
@sharedGutterStyles = {}
|
||||
@@ -327,6 +338,7 @@ class TextEditorPresenter
|
||||
zIndex = 0
|
||||
|
||||
for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize
|
||||
tileEndRow = @constrainRow(tileStartRow + @tileSize)
|
||||
rowsWithinTile = []
|
||||
|
||||
while screenRowIndex >= 0
|
||||
@@ -337,17 +349,21 @@ class TextEditorPresenter
|
||||
|
||||
continue if rowsWithinTile.length is 0
|
||||
|
||||
top = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow))
|
||||
bottom = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileEndRow))
|
||||
height = bottom - top
|
||||
|
||||
tile = @state.content.tiles[tileStartRow] ?= {}
|
||||
tile.top = tileStartRow * @lineHeight - @scrollTop
|
||||
tile.top = top - @scrollTop
|
||||
tile.left = -@scrollLeft
|
||||
tile.height = @tileSize * @lineHeight
|
||||
tile.height = height
|
||||
tile.display = "block"
|
||||
tile.zIndex = zIndex
|
||||
tile.highlights ?= {}
|
||||
|
||||
gutterTile = @lineNumberGutter.tiles[tileStartRow] ?= {}
|
||||
gutterTile.top = tileStartRow * @lineHeight - @scrollTop
|
||||
gutterTile.height = @tileSize * @lineHeight
|
||||
gutterTile.top = top - @scrollTop
|
||||
gutterTile.height = height
|
||||
gutterTile.display = "block"
|
||||
gutterTile.zIndex = zIndex
|
||||
|
||||
@@ -380,10 +396,16 @@ class TextEditorPresenter
|
||||
throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}")
|
||||
|
||||
visibleLineIds[line.id] = true
|
||||
precedingBlockDecorations = @precedingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
followingBlockDecorations = @followingBlockDecorationsByScreenRow[screenRow] ? []
|
||||
if tileState.lines.hasOwnProperty(line.id)
|
||||
lineState = tileState.lines[line.id]
|
||||
lineState.screenRow = screenRow
|
||||
lineState.decorationClasses = @lineDecorationClassesForRow(screenRow)
|
||||
lineState.precedingBlockDecorations = precedingBlockDecorations
|
||||
lineState.followingBlockDecorations = followingBlockDecorations
|
||||
lineState.hasPrecedingBlockDecorations = precedingBlockDecorations.length > 0
|
||||
lineState.hasFollowingBlockDecorations = followingBlockDecorations.length > 0
|
||||
else
|
||||
tileState.lines[line.id] =
|
||||
screenRow: screenRow
|
||||
@@ -400,6 +422,10 @@ class TextEditorPresenter
|
||||
tabLength: line.tabLength
|
||||
fold: line.fold
|
||||
decorationClasses: @lineDecorationClassesForRow(screenRow)
|
||||
precedingBlockDecorations: precedingBlockDecorations
|
||||
followingBlockDecorations: followingBlockDecorations
|
||||
hasPrecedingBlockDecorations: precedingBlockDecorations.length > 0
|
||||
hasFollowingBlockDecorations: followingBlockDecorations.length > 0
|
||||
|
||||
for id, line of tileState.lines
|
||||
delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id)
|
||||
@@ -536,9 +562,11 @@ class TextEditorPresenter
|
||||
|
||||
continue unless @gutterIsVisible(gutter)
|
||||
for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName]
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row)
|
||||
bottom = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRange.end.row + 1)
|
||||
@customGutterDecorations[gutterName][decorationId] =
|
||||
top: @lineHeight * screenRange.start.row
|
||||
height: @lineHeight * screenRange.getRowCount()
|
||||
top: top
|
||||
height: bottom - top
|
||||
item: properties.item
|
||||
class: properties.class
|
||||
|
||||
@@ -586,8 +614,13 @@ class TextEditorPresenter
|
||||
line = @model.tokenizedLineForScreenRow(screenRow)
|
||||
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
|
||||
foldable = @model.isFoldableAtScreenRow(screenRow)
|
||||
blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow)
|
||||
blockDecorationsHeight = blockDecorationsBeforeCurrentScreenRowHeight
|
||||
if screenRow % @tileSize isnt 0
|
||||
blockDecorationsAfterPreviousScreenRowHeight = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) - @lineHeight - @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow - 1)
|
||||
blockDecorationsHeight += blockDecorationsAfterPreviousScreenRowHeight
|
||||
|
||||
tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable}
|
||||
tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight}
|
||||
visibleLineNumberIds[line.id] = true
|
||||
|
||||
for id of tileState.lineNumbers
|
||||
@@ -598,16 +631,15 @@ class TextEditorPresenter
|
||||
updateStartRow: ->
|
||||
return unless @scrollTop? and @lineHeight?
|
||||
|
||||
startRow = Math.floor(@scrollTop / @lineHeight)
|
||||
@startRow = Math.max(0, startRow)
|
||||
@startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop))
|
||||
|
||||
updateEndRow: ->
|
||||
return unless @scrollTop? and @lineHeight? and @height?
|
||||
|
||||
startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight))
|
||||
visibleLinesCount = Math.ceil(@height / @lineHeight) + 1
|
||||
endRow = startRow + visibleLinesCount
|
||||
@endRow = Math.min(@model.getScreenLineCount(), endRow)
|
||||
@endRow = Math.min(
|
||||
@model.getScreenLineCount(),
|
||||
@lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight - 1) + 1
|
||||
)
|
||||
|
||||
updateRowsPerPage: ->
|
||||
rowsPerPage = Math.floor(@getClientHeight() / @lineHeight)
|
||||
@@ -639,7 +671,7 @@ class TextEditorPresenter
|
||||
updateVerticalDimensions: ->
|
||||
if @lineHeight?
|
||||
oldContentHeight = @contentHeight
|
||||
@contentHeight = @lineHeight * @model.getScreenLineCount()
|
||||
@contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getScreenLineCount()))
|
||||
|
||||
if @contentHeight isnt oldContentHeight
|
||||
@updateHeight()
|
||||
@@ -806,6 +838,7 @@ class TextEditorPresenter
|
||||
didStopScrolling: ->
|
||||
if @mouseWheelScreenRow?
|
||||
@mouseWheelScreenRow = null
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -898,12 +931,15 @@ class TextEditorPresenter
|
||||
@editorWidthInChars = null
|
||||
@updateScrollbarDimensions()
|
||||
@updateClientWidth()
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBoundingClientRect: (boundingClientRect) ->
|
||||
unless @clientRectsEqual(@boundingClientRect, boundingClientRect)
|
||||
@boundingClientRect = boundingClientRect
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
clientRectsEqual: (clientRectA, clientRectB) ->
|
||||
@@ -917,6 +953,8 @@ class TextEditorPresenter
|
||||
if @windowWidth isnt width or @windowHeight isnt height
|
||||
@windowWidth = width
|
||||
@windowHeight = height
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -941,6 +979,8 @@ class TextEditorPresenter
|
||||
setLineHeight: (lineHeight) ->
|
||||
unless @lineHeight is lineHeight
|
||||
@lineHeight = lineHeight
|
||||
@model.setLineHeightInPixels(@lineHeight)
|
||||
@lineTopIndex.setDefaultLineHeight(@lineHeight)
|
||||
@restoreScrollTopIfNeeded()
|
||||
@model.setLineHeightInPixels(lineHeight)
|
||||
@shouldUpdateDecorations = true
|
||||
@@ -959,9 +999,10 @@ class TextEditorPresenter
|
||||
@koreanCharWidth = koreanCharWidth
|
||||
@model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
|
||||
@restoreScrollLeftIfNeeded()
|
||||
@characterWidthsChanged()
|
||||
@measurementsChanged()
|
||||
|
||||
characterWidthsChanged: ->
|
||||
measurementsChanged: ->
|
||||
@invalidateAllBlockDecorationsDimensions = true
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@@ -1014,6 +1055,43 @@ class TextEditorPresenter
|
||||
return unless 0 <= @startRow <= @endRow <= Infinity
|
||||
@decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1)
|
||||
|
||||
updateBlockDecorations: ->
|
||||
@blockDecorationsToRenderById = {}
|
||||
@precedingBlockDecorationsByScreenRow = {}
|
||||
@followingBlockDecorationsByScreenRow = {}
|
||||
visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1)
|
||||
|
||||
if @invalidateAllBlockDecorationsDimensions
|
||||
for decoration in @model.getDecorations(type: 'block')
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
@invalidateAllBlockDecorationsDimensions = false
|
||||
|
||||
for markerId, decorations of visibleDecorationsByMarkerId
|
||||
for decoration in decorations when decoration.isType('block')
|
||||
@updateBlockDecorationState(decoration, true)
|
||||
|
||||
@invalidatedDimensionsByBlockDecoration.forEach (decoration) =>
|
||||
@updateBlockDecorationState(decoration, false)
|
||||
|
||||
for decorationId, decorationState of @state.content.blockDecorations
|
||||
continue if @blockDecorationsToRenderById[decorationId]
|
||||
continue if decorationState.screenRow is @mouseWheelScreenRow
|
||||
|
||||
delete @state.content.blockDecorations[decorationId]
|
||||
|
||||
updateBlockDecorationState: (decoration, isVisible) ->
|
||||
return if @blockDecorationsToRenderById[decoration.getId()]
|
||||
|
||||
screenRow = decoration.getMarker().getHeadScreenPosition().row
|
||||
if decoration.getProperties().position is "before"
|
||||
@precedingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@precedingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
else
|
||||
@followingBlockDecorationsByScreenRow[screenRow] ?= []
|
||||
@followingBlockDecorationsByScreenRow[screenRow].push(decoration)
|
||||
@state.content.blockDecorations[decoration.getId()] = {decoration, screenRow, isVisible}
|
||||
@blockDecorationsToRenderById[decoration.getId()] = true
|
||||
|
||||
updateLineDecorations: ->
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@@ -1136,7 +1214,7 @@ class TextEditorPresenter
|
||||
screenRange.end.column = 0
|
||||
|
||||
repositionRegionWithinTile: (region, tileStartRow) ->
|
||||
region.top += @scrollTop - tileStartRow * @lineHeight
|
||||
region.top += @scrollTop - @lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow)
|
||||
region.left += @scrollLeft
|
||||
|
||||
buildHighlightRegions: (screenRange) ->
|
||||
@@ -1206,6 +1284,73 @@ class TextEditorPresenter
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
setBlockDecorationDimensions: (decoration, width, height) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.resizeBlock(decoration.getId(), height)
|
||||
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
invalidateBlockDecorationDimensions: (decoration) ->
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
spliceBlockDecorationsInRange: (start, end, screenDelta) ->
|
||||
return if screenDelta is 0
|
||||
|
||||
oldExtent = end - start
|
||||
newExtent = end - start + screenDelta
|
||||
invalidatedBlockDecorationIds = @lineTopIndex.splice(start, oldExtent, newExtent)
|
||||
invalidatedBlockDecorationIds.forEach (id) =>
|
||||
decoration = @model.decorationForId(id)
|
||||
newScreenPosition = decoration.getMarker().getHeadScreenPosition()
|
||||
@lineTopIndex.moveBlock(id, newScreenPosition.row)
|
||||
@invalidatedDimensionsByBlockDecoration.add(decoration)
|
||||
|
||||
didAddBlockDecoration: (decoration) ->
|
||||
return if not decoration.isType('block') or @observedBlockDecorations.has(decoration)
|
||||
|
||||
didMoveDisposable = decoration.getMarker().bufferMarker.onDidChange (markerEvent) =>
|
||||
@didMoveBlockDecoration(decoration, markerEvent)
|
||||
|
||||
didDestroyDisposable = decoration.onDidDestroy =>
|
||||
@disposables.remove(didMoveDisposable)
|
||||
@disposables.remove(didDestroyDisposable)
|
||||
didMoveDisposable.dispose()
|
||||
didDestroyDisposable.dispose()
|
||||
@didDestroyBlockDecoration(decoration)
|
||||
|
||||
isAfter = decoration.getProperties().position is "after"
|
||||
@lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0, isAfter)
|
||||
|
||||
@observedBlockDecorations.add(decoration)
|
||||
@invalidateBlockDecorationDimensions(decoration)
|
||||
@disposables.add(didMoveDisposable)
|
||||
@disposables.add(didDestroyDisposable)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didMoveBlockDecoration: (decoration, markerEvent) ->
|
||||
# Don't move blocks after a text change, because we already splice on buffer
|
||||
# change.
|
||||
return if markerEvent.textChanged
|
||||
|
||||
@lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didDestroyBlockDecoration: (decoration) ->
|
||||
return unless @observedBlockDecorations.has(decoration)
|
||||
|
||||
@lineTopIndex.removeBlock(decoration.getId())
|
||||
@observedBlockDecorations.delete(decoration)
|
||||
@invalidatedDimensionsByBlockDecoration.delete(decoration)
|
||||
@shouldUpdateDecorations = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
observeCursor: (cursor) ->
|
||||
didChangePositionDisposable = cursor.onDidChangePosition =>
|
||||
@pauseCursorBlinking()
|
||||
@@ -1266,7 +1411,7 @@ class TextEditorPresenter
|
||||
@emitDidUpdateState()
|
||||
|
||||
didChangeFirstVisibleScreenRow: (screenRow) ->
|
||||
@setScrollTop(screenRow * @lineHeight)
|
||||
@setScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(screenRow))
|
||||
|
||||
getVerticalScrollMarginInPixels: ->
|
||||
Math.round(@model.getVerticalScrollMargin() * @lineHeight)
|
||||
@@ -1287,8 +1432,8 @@ class TextEditorPresenter
|
||||
|
||||
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
|
||||
|
||||
top = screenRange.start.row * @lineHeight
|
||||
bottom = (screenRange.end.row + 1) * @lineHeight
|
||||
top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row)
|
||||
bottom = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.end.row) + @lineHeight
|
||||
|
||||
if options?.center
|
||||
desiredScrollCenter = (top + bottom) / 2
|
||||
@@ -1360,7 +1505,7 @@ class TextEditorPresenter
|
||||
|
||||
restoreScrollTopIfNeeded: ->
|
||||
unless @scrollTop?
|
||||
@updateScrollTop(@model.getFirstVisibleScreenRow() * @lineHeight)
|
||||
@updateScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getFirstVisibleScreenRow()))
|
||||
|
||||
restoreScrollLeftIfNeeded: ->
|
||||
unless @scrollLeft?
|
||||
|
||||
@@ -1437,6 +1437,8 @@ class TextEditor extends Model
|
||||
# * __gutter__: A decoration that tracks a {TextEditorMarker} 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`.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
@@ -1456,11 +1458,14 @@ class TextEditor extends Model
|
||||
# property.
|
||||
# * `gutter` Tracks a {TextEditorMarker} 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.
|
||||
# * `class` This CSS class will be applied to the decorated line number,
|
||||
# line, highlight, or overlay.
|
||||
# * `item` (optional) An {HTMLElement} or a model {Object} with a
|
||||
# corresponding view registered. Only applicable to the `gutter` and
|
||||
# `overlay` types.
|
||||
# corresponding view registered. Only applicable to the `gutter`,
|
||||
# `overlay` and `block` types.
|
||||
# * `onlyHead` (optional) If `true`, the decoration will only be applied to
|
||||
# the head of the `TextEditorMarker`. Only applicable to the `line` and
|
||||
# `line-number` types.
|
||||
@@ -1470,9 +1475,10 @@ class TextEditor extends Model
|
||||
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
|
||||
# if the associated `TextEditorMarker` is non-empty. Only applicable to the
|
||||
# `gutter`, `line`, and `line-number` types.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay`,
|
||||
# controls where the overlay view is positioned relative to the `TextEditorMarker`.
|
||||
# Values can be `'head'` (the default), or `'tail'`.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay` and `block`,
|
||||
# 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.
|
||||
#
|
||||
# Returns a {Decoration} object
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
|
||||
Reference in New Issue
Block a user