diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 6cfc99d36..4022ea46b 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -169,38 +169,87 @@ describe "TextEditorPresenter", -> expect(stateFn(presenter).tiles[12]).toBeUndefined() - it "computes each tile's height and scrollTop based on block decorations' height", -> - presenter = buildPresenter(explicitHeight: 120, scrollTop: 0, lineHeight: 10, tileSize: 2) + 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 = editor.addBlockDecorationForScreenRow(0) - blockDecoration2 = editor.addBlockDecorationForScreenRow(3) - blockDecoration3 = editor.addBlockDecorationForScreenRow(5) - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 1) - presenter.setBlockDecorationDimensions(blockDecoration2, 0, 30) - presenter.setBlockDecorationDimensions(blockDecoration3, 0, 40) + blockDecoration1 = editor.addBlockDecorationForScreenRow(0) + blockDecoration2 = editor.addBlockDecorationForScreenRow(3) + blockDecoration3 = editor.addBlockDecorationForScreenRow(5) + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 1) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 30) + presenter.setBlockDecorationDimensions(blockDecoration3, 0, 40) - expect(stateFn(presenter).tiles[0].height).toBe(20 + 1) - expect(stateFn(presenter).tiles[0].top).toBe(0) - expect(stateFn(presenter).tiles[2].height).toBe(20 + 30) - expect(stateFn(presenter).tiles[2].top).toBe(20 + 1) - expect(stateFn(presenter).tiles[4].height).toBe(20 + 40) - expect(stateFn(presenter).tiles[4].top).toBe(20 + 30 + 20 + 1) - expect(stateFn(presenter).tiles[6].height).toBe(20) - expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30) + 20 + 1) - expect(stateFn(presenter).tiles[8]).toBeUndefined() + expect(stateFn(presenter).tiles[0].height).toBe(20 + 1) + expect(stateFn(presenter).tiles[0].top).toBe(0) + expect(stateFn(presenter).tiles[2].height).toBe(20 + 30) + expect(stateFn(presenter).tiles[2].top).toBe(20 + 1) + expect(stateFn(presenter).tiles[4].height).toBe(20 + 40) + expect(stateFn(presenter).tiles[4].top).toBe(20 + 30 + 20 + 1) + expect(stateFn(presenter).tiles[6].height).toBe(20) + expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30) + 20 + 1) + expect(stateFn(presenter).tiles[8]).toBeUndefined() - presenter.setScrollTop(21) + presenter.setScrollTop(21) - expect(stateFn(presenter).tiles[0]).toBeUndefined() - expect(stateFn(presenter).tiles[2].height).toBe(20 + 30) - expect(stateFn(presenter).tiles[2].top).toBe(0) - expect(stateFn(presenter).tiles[4].height).toBe(20 + 40) - expect(stateFn(presenter).tiles[4].top).toBe(30 + 20) - expect(stateFn(presenter).tiles[6].height).toBe(20) - expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30)) - expect(stateFn(presenter).tiles[8].height).toBe(20) - expect(stateFn(presenter).tiles[8].top).toBe((20 + 40) + (20 + 30) + 20) - expect(stateFn(presenter).tiles[10]).toBeUndefined() + expect(stateFn(presenter).tiles[0]).toBeUndefined() + expect(stateFn(presenter).tiles[2].height).toBe(20 + 30) + expect(stateFn(presenter).tiles[2].top).toBe(0) + expect(stateFn(presenter).tiles[4].height).toBe(20 + 40) + expect(stateFn(presenter).tiles[4].top).toBe(30 + 20) + expect(stateFn(presenter).tiles[6].height).toBe(20) + expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30)) + expect(stateFn(presenter).tiles[8].height).toBe(20) + expect(stateFn(presenter).tiles[8].top).toBe((20 + 40) + (20 + 30) + 20) + expect(stateFn(presenter).tiles[10]).toBeUndefined() + + it "works correctly when soft wrapping is enabled", -> + blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) + blockDecoration2 = editor.addBlockDecorationForScreenRow(4, null) + blockDecoration3 = editor.addBlockDecorationForScreenRow(8, null) + + 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) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 8409f33e0..46a575e55 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -29,8 +29,8 @@ class TextEditorPresenter @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} @observedBlockDecorations = new Set() - @invalidatedBlockDecorations = new Set() - @invalidateAllBlockDecorations = false + @invalidatedDimensionsByBlockDecoration = new Set() + @invalidateAllBlockDecorationsDimensions = false @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -130,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() @@ -139,10 +140,6 @@ class TextEditorPresenter @emitDidUpdateState() @disposables.add @model.onDidAddDecoration(@didAddBlockDecoration.bind(this)) - @disposables.add @model.buffer.onDidChange ({oldRange, newRange}) => - oldExtent = oldRange.getExtent() - newExtent = newRange.getExtent() - @lineTopIndex.splice(oldRange.start, oldExtent, newExtent) for decoration in @model.getDecorations({type: 'block'}) this.didAddBlockDecoration(decoration) @@ -990,7 +987,7 @@ class TextEditorPresenter @measurementsChanged() measurementsChanged: -> - @invalidateAllBlockDecorations = true + @invalidateAllBlockDecorationsDimensions = true @shouldUpdateDecorations = true @emitDidUpdateState() @@ -1048,16 +1045,16 @@ class TextEditorPresenter @blockDecorationsByScreenRow = {} visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1) - if @invalidateAllBlockDecorations + if @invalidateAllBlockDecorationsDimensions for decoration in @model.getDecorations(type: 'block') - @invalidatedBlockDecorations.add(decoration) - @invalidateAllBlockDecorations = false + @invalidatedDimensionsByBlockDecoration.add(decoration) + @invalidateAllBlockDecorationsDimensions = false for markerId, decorations of visibleDecorationsByMarkerId for decoration in decorations when decoration.isType('block') @updateBlockDecorationState(decoration, true) - @invalidatedBlockDecorations.forEach (decoration) => + @invalidatedDimensionsByBlockDecoration.forEach (decoration) => @updateBlockDecorationState(decoration, false) for decorationId, decorationState of @state.content.blockDecorations @@ -1270,15 +1267,25 @@ class TextEditorPresenter setBlockDecorationDimensions: (decoration, width, height) -> @lineTopIndex.resizeBlock(decoration.getId(), height) - @invalidatedBlockDecorations.delete(decoration) + @invalidatedDimensionsByBlockDecoration.delete(decoration) @shouldUpdateDecorations = true @emitDidUpdateState() invalidateBlockDecorationDimensions: (decoration) -> - @invalidatedBlockDecorations.add(decoration) + @invalidatedDimensionsByBlockDecoration.add(decoration) @shouldUpdateDecorations = true @emitDidUpdateState() + spliceBlockDecorationsInRange: (start, end, screenDelta) -> + return if screenDelta is 0 + + oldExtent = Point(end - start, Infinity) + newExtent = Point(end - start + screenDelta, 0) + invalidatedBlockDecorationIds = @lineTopIndex.splice(Point(start, 0), oldExtent, newExtent, true) + invalidatedBlockDecorationIds?.forEach (blockDecorationId) => + decoration = @model.decorationForId(blockDecorationId) + @lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition()) + didAddBlockDecoration: (decoration) -> return if not decoration.isType('block') or @observedBlockDecorations.has(decoration) @@ -1292,12 +1299,7 @@ class TextEditorPresenter didDestroyDisposable.dispose() @didDestroyBlockDecoration(decoration) - @lineTopIndex.insertBlock( - decoration.getId(), - decoration.getMarker().getHeadBufferPosition(), - true, - 0 - ) + @lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition(), true, 0) @observedBlockDecorations.add(decoration) @invalidateBlockDecorationDimensions(decoration) @@ -1311,7 +1313,7 @@ class TextEditorPresenter # change. return if markerEvent.textChanged - @lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadBufferPosition()) + @lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition()) @shouldUpdateDecorations = true @emitDidUpdateState() @@ -1320,7 +1322,7 @@ class TextEditorPresenter @lineTopIndex.removeBlock(decoration.getId()) @observedBlockDecorations.delete(decoration) - @invalidatedBlockDecorations.delete(decoration) + @invalidatedDimensionsByBlockDecoration.delete(decoration) @shouldUpdateDecorations = true @emitDidUpdateState()