From c8254566eff33918d433ba80171dab489ee7ad28 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Nov 2015 15:34:45 +0100 Subject: [PATCH 001/173] Change verticalScrollbar's height according to block decorations --- spec/text-editor-presenter-spec.coffee | 33 ++++++++++++++++++++++++ src/text-editor-presenter.coffee | 35 +++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 8dd34fde8..6f1cf3a70 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -486,6 +486,39 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 500) expect(presenter.getState().verticalScrollbar.scrollHeight).toBe 500 + it "updates when new block decorations are measured, changed or destroyed", -> + presenter = buildPresenter(scrollTop: 0, lineHeight: 10) + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 + + addBlockDecorationAtScreenRow = (screenRow) -> + editor.decorateMarker( + editor.markScreenPosition([screenRow, 0], invalidate: "never"), + type: "block", + item: document.createElement("div") + ) + + blockDecoration1 = addBlockDecorationAtScreenRow(0) + blockDecoration2 = addBlockDecorationAtScreenRow(3) + blockDecoration3 = addBlockDecorationAtScreenRow(7) + + presenter.setBlockDecorationSize(blockDecoration1, 0, 35.8) + presenter.setBlockDecorationSize(blockDecoration2, 0, 50.3) + presenter.setBlockDecorationSize(blockDecoration3, 0, 95.2) + + linesHeight = editor.getScreenLineCount() * 10 + blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2) + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight) + + presenter.setBlockDecorationSize(blockDecoration2, 0, 100.3) + + blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2) + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight) + + waitsForStateToUpdate presenter, -> blockDecoration3.destroy() + runs -> + blockDecorationsHeight = Math.round(35.8 + 100.3) + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight) + it "updates when the ::lineHeight changes", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expectStateUpdate presenter, -> presenter.setLineHeight(20) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 405e34548..6296e9331 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -28,6 +28,7 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} + @blockDecorationsDimensions = new Map @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -71,6 +72,7 @@ class TextEditorPresenter getPreMeasurementState: -> @updating = true + @updateBlockDecorations() if @shouldUpdateBlockDecorations @updateVerticalDimensions() @updateScrollbarDimensions() @@ -140,6 +142,7 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = false @shouldUpdateContentState = false @shouldUpdateDecorations = false + @shouldUpdateBlockDecorations = false @shouldUpdateLinesState = false @shouldUpdateTilesState = false @shouldUpdateCursorsState = false @@ -158,6 +161,7 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true + @shouldUpdateBlockDecorations = true @shouldUpdateLinesState = true @shouldUpdateTilesState = true @shouldUpdateCursorsState = true @@ -188,6 +192,8 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = true @shouldUpdateDecorations = true @shouldUpdateOverlaysState = true + @shouldUpdateBlockDecorations = true + @shouldUpdateVerticalScrollState = true @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() @@ -727,10 +733,19 @@ class TextEditorPresenter @scrollHeight = scrollHeight @updateScrollTop(@scrollTop) + getLinesHeight: -> + @lineHeight * @model.getScreenLineCount() + + getBlockDecorationsHeight: -> + sizes = Array.from(@blockDecorationsDimensions.values()) + sum = (a, b) -> a + b + height = sizes.map((size) -> size.height).reduce(sum, 0) + height + updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = @lineHeight * @model.getScreenLineCount() + @contentHeight = Math.round(@getLinesHeight() + @getBlockDecorationsHeight()) if @contentHeight isnt oldContentHeight @updateHeight() @@ -1364,6 +1379,24 @@ class TextEditorPresenter @emitDidUpdateState() + setBlockDecorationSize: (decoration, width, height) -> + @blockDecorationsDimensions.set(decoration.id, {width, height}) + + @shouldUpdateBlockDecorations = true + @shouldUpdateVerticalScrollState = true + @emitDidUpdateState() + + updateBlockDecorations: -> + blockDecorations = {} + for decoration in @model.getDecorations(type: "block") + blockDecorations[decoration.id] = decoration + + @blockDecorationsDimensions.forEach (value, key) => + unless blockDecorations.hasOwnProperty(key) + @blockDecorationsDimensions.delete(key) + + @shouldUpdateVerticalScrollState = true + observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => @shouldUpdateHiddenInputState = true if cursor.isLastCursor() From 89d9a2ce832f8cee0f35e5a6fd8bc588163127de Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Nov 2015 15:40:07 +0100 Subject: [PATCH 002/173] :art: Create block decorations in `TextEditor` This is just an experiment, although I like that we can hide some information (irrelevant to the user) behind a clean API. --- spec/text-editor-presenter-spec.coffee | 15 +++++---------- src/text-editor.coffee | 7 +++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 6f1cf3a70..e3dc9feaa 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -490,16 +490,11 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - addBlockDecorationAtScreenRow = (screenRow) -> - editor.decorateMarker( - editor.markScreenPosition([screenRow, 0], invalidate: "never"), - type: "block", - item: document.createElement("div") - ) - - blockDecoration1 = addBlockDecorationAtScreenRow(0) - blockDecoration2 = addBlockDecorationAtScreenRow(3) - blockDecoration3 = addBlockDecorationAtScreenRow(7) + # Setting `null` as the DOM element, as it doesn't really matter here. + # Maybe a signal that we should separate models from views? + blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) + blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) + blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) presenter.setBlockDecorationSize(blockDecoration1, 0, 35.8) presenter.setBlockDecorationSize(blockDecoration2, 0, 50.3) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index a151c9dba..f88c9149c 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1398,6 +1398,13 @@ class TextEditor extends Model Section: Decorations ### + addBlockDecorationForScreenRow: (screenRow, element) -> + @decorateMarker( + @markScreenPosition([screenRow, 0], invalidate: "never"), + type: "block", + element: element + ) + # Essential: Add a decoration that tracks a {TextEditorMarker}. When the # marker moves, is invalidated, or is destroyed, the decoration will be # updated to reflect the marker's state. From 30da4bdb0c66f8f9ae8b50a7a75028a1370c9fe2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Nov 2015 15:43:33 +0100 Subject: [PATCH 003/173] Ensure gutter scroll height takes block decorations into account --- spec/text-editor-presenter-spec.coffee | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index e3dc9feaa..1e8b3dbb9 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2874,6 +2874,34 @@ describe "TextEditorPresenter", -> customGutter.destroy() describe ".scrollHeight", -> + it "updates when new block decorations are measured, changed or destroyed", -> + presenter = buildPresenter(scrollTop: 0, lineHeight: 10) + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 + + # Setting `null` as the DOM element, as it doesn't really matter here. + # Maybe a signal that we should separate models from views? + blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) + blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) + blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) + + presenter.setBlockDecorationSize(blockDecoration1, 0, 35.8) + presenter.setBlockDecorationSize(blockDecoration2, 0, 50.3) + presenter.setBlockDecorationSize(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.setBlockDecorationSize(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 From 96863eef1cde237824bd55f618d64882ddfcd9aa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Nov 2015 15:44:59 +0100 Subject: [PATCH 004/173] Ensure content scrollHeight takes block decorations into account Seems like all these properties in the presenter's state share a common contract, which we should probably extract. It could also mean there's an underlying design problem, because we are testing two dimensions of the same behavior in a single spec. --- spec/text-editor-presenter-spec.coffee | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 1e8b3dbb9..a5d963bd3 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -679,6 +679,34 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.maxHeight).toBe(50) describe ".scrollHeight", -> + it "updates when new block decorations are measured, changed or destroyed", -> + presenter = buildPresenter(scrollTop: 0, lineHeight: 10) + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 + + # Setting `null` as the DOM element, as it doesn't really matter here. + # Maybe a signal that we should separate models from views? + blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) + blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) + blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) + + presenter.setBlockDecorationSize(blockDecoration1, 0, 35.8) + presenter.setBlockDecorationSize(blockDecoration2, 0, 50.3) + presenter.setBlockDecorationSize(blockDecoration3, 0, 95.2) + + linesHeight = editor.getScreenLineCount() * 10 + blockDecorationsHeight = Math.round(35.8 + 50.3 + 95.2) + expect(presenter.getState().content.scrollHeight).toBe(linesHeight + blockDecorationsHeight) + + presenter.setBlockDecorationSize(blockDecoration2, 0, 100.3) + + blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2) + expect(presenter.getState().content.scrollHeight).toBe(linesHeight + blockDecorationsHeight) + + waitsForStateToUpdate presenter, -> blockDecoration3.destroy() + runs -> + blockDecorationsHeight = Math.round(35.8 + 100.3) + expect(presenter.getState().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(presenter.getState().content.scrollHeight).toBe editor.getScreenLineCount() * 10 From 5ac7ffcf48c0a7c6a86fe83f0feafa018cd20e5b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Nov 2015 16:09:21 +0100 Subject: [PATCH 005/173] :art: Rename to ::blockDecorationsDimensionsById --- src/text-editor-presenter.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 6296e9331..cc5327f0e 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -28,7 +28,7 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} - @blockDecorationsDimensions = new Map + @blockDecorationsDimensionsById = new Map @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -737,7 +737,7 @@ class TextEditorPresenter @lineHeight * @model.getScreenLineCount() getBlockDecorationsHeight: -> - sizes = Array.from(@blockDecorationsDimensions.values()) + sizes = Array.from(@blockDecorationsDimensionsById.values()) sum = (a, b) -> a + b height = sizes.map((size) -> size.height).reduce(sum, 0) height @@ -1380,7 +1380,7 @@ class TextEditorPresenter @emitDidUpdateState() setBlockDecorationSize: (decoration, width, height) -> - @blockDecorationsDimensions.set(decoration.id, {width, height}) + @blockDecorationsDimensionsById.set(decoration.id, {width, height}) @shouldUpdateBlockDecorations = true @shouldUpdateVerticalScrollState = true @@ -1391,9 +1391,9 @@ class TextEditorPresenter for decoration in @model.getDecorations(type: "block") blockDecorations[decoration.id] = decoration - @blockDecorationsDimensions.forEach (value, key) => + @blockDecorationsDimensionsById.forEach (value, key) => unless blockDecorations.hasOwnProperty(key) - @blockDecorationsDimensions.delete(key) + @blockDecorationsDimensionsById.delete(key) @shouldUpdateVerticalScrollState = true From 02651b7a580ad7f8e102e5523dad103b829572b3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Nov 2015 16:10:08 +0100 Subject: [PATCH 006/173] :art: Rename to ::setBlockDecorationDimensions --- spec/text-editor-presenter-spec.coffee | 24 ++++++++++++------------ src/text-editor-presenter.coffee | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index a5d963bd3..010a9fc2d 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -496,15 +496,15 @@ describe "TextEditorPresenter", -> blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) - presenter.setBlockDecorationSize(blockDecoration1, 0, 35.8) - presenter.setBlockDecorationSize(blockDecoration2, 0, 50.3) - presenter.setBlockDecorationSize(blockDecoration3, 0, 95.2) + 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(presenter.getState().verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight) - presenter.setBlockDecorationSize(blockDecoration2, 0, 100.3) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3) blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2) expect(presenter.getState().verticalScrollbar.scrollHeight).toBe(linesHeight + blockDecorationsHeight) @@ -689,15 +689,15 @@ describe "TextEditorPresenter", -> blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) - presenter.setBlockDecorationSize(blockDecoration1, 0, 35.8) - presenter.setBlockDecorationSize(blockDecoration2, 0, 50.3) - presenter.setBlockDecorationSize(blockDecoration3, 0, 95.2) + 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(presenter.getState().content.scrollHeight).toBe(linesHeight + blockDecorationsHeight) - presenter.setBlockDecorationSize(blockDecoration2, 0, 100.3) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3) blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2) expect(presenter.getState().content.scrollHeight).toBe(linesHeight + blockDecorationsHeight) @@ -2912,15 +2912,15 @@ describe "TextEditorPresenter", -> blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) - presenter.setBlockDecorationSize(blockDecoration1, 0, 35.8) - presenter.setBlockDecorationSize(blockDecoration2, 0, 50.3) - presenter.setBlockDecorationSize(blockDecoration3, 0, 95.2) + 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.setBlockDecorationSize(blockDecoration2, 0, 100.3) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 100.3) blockDecorationsHeight = Math.round(35.8 + 100.3 + 95.2) expect(getStylesForGutterWithName(presenter, 'line-number').scrollHeight).toBe(linesHeight + blockDecorationsHeight) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index cc5327f0e..d38d3c98f 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1379,7 +1379,7 @@ class TextEditorPresenter @emitDidUpdateState() - setBlockDecorationSize: (decoration, width, height) -> + setBlockDecorationDimensions: (decoration, width, height) -> @blockDecorationsDimensionsById.set(decoration.id, {width, height}) @shouldUpdateBlockDecorations = true From 1b5fd1863021bdc9f0c790c0fb388f0490a2921e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 09:54:14 +0100 Subject: [PATCH 007/173] Use ::rowForPosition and ::positionForRow instead of simple math --- src/text-editor-presenter.coffee | 59 +++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index d38d3c98f..ab9def07e 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -28,7 +28,8 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} - @blockDecorationsDimensionsById = new Map + @blockDecorationsDimensionsById = {} + @blockDecorationsDimensionsByScreenRow = {} @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -426,7 +427,7 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 tile = @state.content.tiles[tileStartRow] ?= {} - tile.top = tileStartRow * @lineHeight - @scrollTop + tile.top = @positionForRow(tileStartRow) - @scrollTop tile.left = -@scrollLeft tile.height = @tileSize * @lineHeight tile.display = "block" @@ -692,19 +693,35 @@ class TextEditorPresenter return + rowForPosition: (position, floor = true) -> + top = 0 + for tileRow in [0..@model.getScreenLineCount()] by @tileSize + for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 + nextTop = top + @lineHeight + if floor + return row if nextTop > position + else + return row if top >= position + top = nextTop + @model.getScreenLineCount() + + positionForRow: (targetRow) -> + top = 0 + for tileRow in [0..@model.getScreenLineCount()] by @tileSize + for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 + return top if row is targetRow + top += @lineHeight + top + updateStartRow: -> return unless @scrollTop? and @lineHeight? - startRow = Math.floor(@scrollTop / @lineHeight) - @startRow = Math.max(0, startRow) + @startRow = Math.max(0, @rowForPosition(@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 = @rowForPosition(@scrollTop + @height + @lineHeight, false) updateRowsPerPage: -> rowsPerPage = Math.floor(@getClientHeight() / @lineHeight) @@ -736,16 +753,17 @@ class TextEditorPresenter getLinesHeight: -> @lineHeight * @model.getScreenLineCount() - getBlockDecorationsHeight: -> - sizes = Array.from(@blockDecorationsDimensionsById.values()) + getBlockDecorationsHeight: (blockDecorations) -> sum = (a, b) -> a + b - height = sizes.map((size) -> size.height).reduce(sum, 0) - height + Array.from(blockDecorations).map((size) -> size.height).reduce(sum, 0) updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round(@getLinesHeight() + @getBlockDecorationsHeight()) + allBlockDecorations = _.values(@blockDecorationsDimensionsById) + @contentHeight = Math.round( + @getLinesHeight() + @getBlockDecorationsHeight(allBlockDecorations) + ) if @contentHeight isnt oldContentHeight @updateHeight() @@ -1380,20 +1398,27 @@ class TextEditorPresenter @emitDidUpdateState() setBlockDecorationDimensions: (decoration, width, height) -> - @blockDecorationsDimensionsById.set(decoration.id, {width, height}) + screenRow = decoration.getMarker().getHeadScreenPosition().row + dimensions = {width, height} + + @blockDecorationsDimensionsByScreenRow[screenRow] ?= {} + @blockDecorationsDimensionsByScreenRow[screenRow][decoration.id] = dimensions + @blockDecorationsDimensionsById[decoration.id] = dimensions @shouldUpdateBlockDecorations = true @shouldUpdateVerticalScrollState = true @emitDidUpdateState() updateBlockDecorations: -> + # TODO: Move this inside `DisplayBuffer` blockDecorations = {} for decoration in @model.getDecorations(type: "block") blockDecorations[decoration.id] = decoration - @blockDecorationsDimensionsById.forEach (value, key) => - unless blockDecorations.hasOwnProperty(key) - @blockDecorationsDimensionsById.delete(key) + for screenRow, decorations of @blockDecorationsDimensionsByScreenRow + for decorationId of decorations when not blockDecorations.hasOwnProperty(decorationId) + delete @blockDecorationsDimensionsById[decorationId] + delete @blockDecorationsDimensionsByScreenRow[screenRow][decorationId] @shouldUpdateVerticalScrollState = true From a9cb1bda8d217ee5f4ea614487d8323a0fbd4cf9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 10:16:38 +0100 Subject: [PATCH 008/173] Use `::positionForRow` in `::updateTilesState` The algorithm is still super sloooow :snail:, but we'll fix that in a later commit. --- spec/text-editor-presenter-spec.coffee | 31 ++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 12 +++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 010a9fc2d..9edd8e9f8 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -168,6 +168,37 @@ 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) + + blockDecoration1 = editor.addBlockDecorationForScreenRow(3) + blockDecoration2 = editor.addBlockDecorationForScreenRow(5) + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 40) + + expect(stateFn(presenter).tiles[0].height).toBe(20) + 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) + expect(stateFn(presenter).tiles[4].height).toBe(20 + 40) + expect(stateFn(presenter).tiles[4].top).toBe(20 + 30 + 20) + expect(stateFn(presenter).tiles[6].height).toBe(20) + expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30) + 20) + expect(stateFn(presenter).tiles[8]).toBeUndefined() + + presenter.setScrollTop(20) + + 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 "includes state for all tiles if no external ::explicitHeight is assigned", -> presenter = buildPresenter(explicitHeight: null, tileSize: 2) expect(stateFn(presenter).tiles[0]).toBeDefined() diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ab9def07e..5cec15278 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -429,14 +429,14 @@ class TextEditorPresenter tile = @state.content.tiles[tileStartRow] ?= {} tile.top = @positionForRow(tileStartRow) - @scrollTop tile.left = -@scrollLeft - tile.height = @tileSize * @lineHeight + tile.height = @positionForRow(tileStartRow + @tileSize) - @positionForRow(tileStartRow) tile.display = "block" tile.zIndex = zIndex tile.highlights ?= {} gutterTile = @lineNumberGutter.tiles[tileStartRow] ?= {} - gutterTile.top = tileStartRow * @lineHeight - @scrollTop - gutterTile.height = @tileSize * @lineHeight + gutterTile.top = @positionForRow(tileStartRow) - @scrollTop + gutterTile.height = @positionForRow(tileStartRow + @tileSize) - @positionForRow(tileStartRow) gutterTile.display = "block" gutterTile.zIndex = zIndex @@ -697,7 +697,8 @@ class TextEditorPresenter top = 0 for tileRow in [0..@model.getScreenLineCount()] by @tileSize for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 - nextTop = top + @lineHeight + blockDecorationsForCurrentRow = _.values(@blockDecorationsDimensionsByScreenRow[row]) + nextTop = top + @lineHeight + @getBlockDecorationsHeight(blockDecorationsForCurrentRow) if floor return row if nextTop > position else @@ -710,7 +711,8 @@ class TextEditorPresenter for tileRow in [0..@model.getScreenLineCount()] by @tileSize for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 return top if row is targetRow - top += @lineHeight + blockDecorationsForNextRow = _.values(@blockDecorationsDimensionsByScreenRow[row + 1]) + top += @lineHeight + @getBlockDecorationsHeight(blockDecorationsForNextRow) top updateStartRow: -> From 6e2587bc8cfb0b96bd83d210e8bfd81a52a1fa3a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 10:30:43 +0100 Subject: [PATCH 009/173] :racehorse: Cache screen row height I am trying to defer the usage of fancy algorithms as much as possible. The current one is linear and should probably be changed, but it performs quite decently for the time being. Maybe with some more caching we could even avoid to implement a tree data structure? --- src/text-editor-presenter.coffee | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5cec15278..aea789416 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -30,6 +30,7 @@ class TextEditorPresenter @customGutterDecorationsByGutterName = {} @blockDecorationsDimensionsById = {} @blockDecorationsDimensionsByScreenRow = {} + @heightsByScreenRow = {} @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -426,17 +427,20 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 + top = @positionForRow(tileStartRow) + height = @positionForRow(tileStartRow + @tileSize) - top + tile = @state.content.tiles[tileStartRow] ?= {} - tile.top = @positionForRow(tileStartRow) - @scrollTop + tile.top = top - @scrollTop tile.left = -@scrollLeft - tile.height = @positionForRow(tileStartRow + @tileSize) - @positionForRow(tileStartRow) + tile.height = height tile.display = "block" tile.zIndex = zIndex tile.highlights ?= {} gutterTile = @lineNumberGutter.tiles[tileStartRow] ?= {} - gutterTile.top = @positionForRow(tileStartRow) - @scrollTop - gutterTile.height = @positionForRow(tileStartRow + @tileSize) - @positionForRow(tileStartRow) + gutterTile.top = top - @scrollTop + gutterTile.height = height gutterTile.display = "block" gutterTile.zIndex = zIndex @@ -693,12 +697,17 @@ class TextEditorPresenter return + getScreenRowHeight: (screenRow) -> + @heightsByScreenRow[screenRow] or @lineHeight + + setScreenRowHeight: (screenRow, height) -> + @heightsByScreenRow[screenRow] = height + rowForPosition: (position, floor = true) -> top = 0 for tileRow in [0..@model.getScreenLineCount()] by @tileSize for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 - blockDecorationsForCurrentRow = _.values(@blockDecorationsDimensionsByScreenRow[row]) - nextTop = top + @lineHeight + @getBlockDecorationsHeight(blockDecorationsForCurrentRow) + nextTop = top + @getScreenRowHeight(row) if floor return row if nextTop > position else @@ -711,8 +720,7 @@ class TextEditorPresenter for tileRow in [0..@model.getScreenLineCount()] by @tileSize for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 return top if row is targetRow - blockDecorationsForNextRow = _.values(@blockDecorationsDimensionsByScreenRow[row + 1]) - top += @lineHeight + @getBlockDecorationsHeight(blockDecorationsForNextRow) + top += @getScreenRowHeight(row + 1) top updateStartRow: -> @@ -1403,10 +1411,15 @@ class TextEditorPresenter screenRow = decoration.getMarker().getHeadScreenPosition().row dimensions = {width, height} - @blockDecorationsDimensionsByScreenRow[screenRow] ?= {} - @blockDecorationsDimensionsByScreenRow[screenRow][decoration.id] = dimensions + screenRowDecorations = @blockDecorationsDimensionsByScreenRow[screenRow] ?= {} + screenRowDecorations[decoration.id] = dimensions @blockDecorationsDimensionsById[decoration.id] = dimensions + @setScreenRowHeight( + screenRow, + @lineHeight + @getBlockDecorationsHeight(_.values(screenRowDecorations)) + ) + @shouldUpdateBlockDecorations = true @shouldUpdateVerticalScrollState = true @emitDidUpdateState() From 0b5638f7495814e9f4315f96d2d9470a287e39df Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 10:47:25 +0100 Subject: [PATCH 010/173] Make sure tile positions are computed correctly --- spec/text-editor-presenter-spec.coffee | 20 +++++++++++--------- src/text-editor-presenter.coffee | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 9edd8e9f8..ef8f8b2bd 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -171,22 +171,24 @@ describe "TextEditorPresenter", -> 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(3) - blockDecoration2 = editor.addBlockDecorationForScreenRow(5) - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30) - presenter.setBlockDecorationDimensions(blockDecoration2, 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) + 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) + 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) + 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) + expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30) + 20 + 1) expect(stateFn(presenter).tiles[8]).toBeUndefined() - presenter.setScrollTop(20) + presenter.setScrollTop(21) expect(stateFn(presenter).tiles[0]).toBeUndefined() expect(stateFn(presenter).tiles[2].height).toBe(20 + 30) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index aea789416..8738357b4 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -720,7 +720,7 @@ class TextEditorPresenter for tileRow in [0..@model.getScreenLineCount()] by @tileSize for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 return top if row is targetRow - top += @getScreenRowHeight(row + 1) + top += @getScreenRowHeight(row) top updateStartRow: -> From 1a8d7b486d644b0d9ea0914721077a8b9fc0f5a7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 12:10:48 +0100 Subject: [PATCH 011/173] Remove caching by screen rows Because we cannot use them as a cache key, because markers' position can change at any time. Performance-wise this is slow with many markers, as we need to do a lot of buffer-to-screen conversions. --- src/text-editor-presenter.coffee | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 8738357b4..4fd612ec5 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -74,7 +74,7 @@ class TextEditorPresenter getPreMeasurementState: -> @updating = true - @updateBlockDecorations() if @shouldUpdateBlockDecorations + @updateBlockDecorations() @updateVerticalDimensions() @updateScrollbarDimensions() @@ -144,7 +144,6 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = false @shouldUpdateContentState = false @shouldUpdateDecorations = false - @shouldUpdateBlockDecorations = false @shouldUpdateLinesState = false @shouldUpdateTilesState = false @shouldUpdateCursorsState = false @@ -163,7 +162,6 @@ class TextEditorPresenter @shouldUpdateHiddenInputState = true @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateBlockDecorations = true @shouldUpdateLinesState = true @shouldUpdateTilesState = true @shouldUpdateCursorsState = true @@ -194,7 +192,6 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = true @shouldUpdateDecorations = true @shouldUpdateOverlaysState = true - @shouldUpdateBlockDecorations = true @shouldUpdateVerticalScrollState = true @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() @@ -1410,32 +1407,27 @@ class TextEditorPresenter setBlockDecorationDimensions: (decoration, width, height) -> screenRow = decoration.getMarker().getHeadScreenPosition().row dimensions = {width, height} - - screenRowDecorations = @blockDecorationsDimensionsByScreenRow[screenRow] ?= {} - screenRowDecorations[decoration.id] = dimensions @blockDecorationsDimensionsById[decoration.id] = dimensions - @setScreenRowHeight( - screenRow, - @lineHeight + @getBlockDecorationsHeight(_.values(screenRowDecorations)) - ) - - @shouldUpdateBlockDecorations = true @shouldUpdateVerticalScrollState = true @emitDidUpdateState() updateBlockDecorations: -> - # TODO: Move this inside `DisplayBuffer` + @heightsByScreenRow = {} blockDecorations = {} + + # TODO: move into DisplayBuffer for decoration in @model.getDecorations(type: "block") blockDecorations[decoration.id] = decoration - for screenRow, decorations of @blockDecorationsDimensionsByScreenRow - for decorationId of decorations when not blockDecorations.hasOwnProperty(decorationId) + for decorationId of @blockDecorationsDimensionsById + unless blockDecorations.hasOwnProperty(decorationId) delete @blockDecorationsDimensionsById[decorationId] - delete @blockDecorationsDimensionsByScreenRow[screenRow][decorationId] - @shouldUpdateVerticalScrollState = true + for decorationId, decoration of blockDecorations + screenRow = decoration.getMarker().getHeadScreenPosition().row + @heightsByScreenRow[screenRow] ?= @lineHeight + @heightsByScreenRow[screenRow] += @blockDecorationsDimensionsById[decorationId].height observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => From e1e06580c1b3f12118ce4eff8fc331ace3e1604e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 12:47:47 +0100 Subject: [PATCH 012/173] Move position conversion in LinesYardstick --- spec/fake-lines-yardstick.coffee | 30 +++++++++++++++++++++----- src/lines-yardstick.coffee | 36 ++++++++++++++++++++++++-------- src/text-editor-presenter.coffee | 34 +++++++----------------------- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index 1872b8c65..781318f8c 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -31,7 +31,7 @@ class FakeLinesYardstick targetColumn = screenPosition.column baseCharacterWidth = @model.getDefaultCharWidth() - top = targetRow * @model.getLineHeightInPixels() + top = @topPixelPositionForRow(targetRow) left = 0 column = 0 @@ -60,17 +60,37 @@ class FakeLinesYardstick {top, left} - pixelRectForScreenRange: (screenRange) -> - lineHeight = @model.getLineHeightInPixels() + rowForTopPixelPosition: (position, floor = true) -> + top = 0 + for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() + tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) + for row in [tileStartRow...tileEndRow] by 1 + nextTop = top + @presenter.getScreenRowHeight(row) + if floor + return row if nextTop > position + else + return row if top >= position + top = nextTop + @model.getScreenLineCount() + topPixelPositionForRow: (targetRow) -> + top = 0 + for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() + tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) + for row in [tileStartRow...tileEndRow] by 1 + return top if row is targetRow + top += @presenter.getScreenRowHeight(row) + top + + pixelRectForScreenRange: (screenRange) -> if screenRange.end.row > screenRange.start.row top = @pixelPositionForScreenPosition(screenRange.start).top left = 0 - height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight + height = @topPixelPositionForRow(screenRange.end.row + 1) - top width = @presenter.getScrollWidth() else {top, left} = @pixelPositionForScreenPosition(screenRange.start, false) - height = lineHeight + height = @topPixelPositionForRow(screenRange.end.row + 1) - top width = @pixelPositionForScreenPosition(screenRange.end, false).left - left {top, left, width, height} diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 54ba6cf57..2febf8add 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -22,11 +22,9 @@ class LinesYardstick targetTop = pixelPosition.top targetLeft = pixelPosition.left defaultCharWidth = @model.getDefaultCharWidth() - row = Math.floor(targetTop / @model.getLineHeightInPixels()) - targetLeft = 0 if row < 0 + row = @rowForTopPixelPosition(targetTop) + targetLeft = 0 if targetTop < 0 targetLeft = Infinity if row > @model.getLastScreenRow() - row = Math.min(row, @model.getLastScreenRow()) - row = Math.max(0, row) @prepareScreenRowsForMeasurement([row]) unless measureVisibleLinesOnly @@ -92,7 +90,7 @@ class LinesYardstick @prepareScreenRowsForMeasurement([targetRow]) unless measureVisibleLinesOnly - top = targetRow * @model.getLineHeightInPixels() + top = @topPixelPositionForRow(targetRow) left = @leftPixelPositionForScreenPosition(targetRow, targetColumn) @clearScreenRowsForMeasurement() unless measureVisibleLinesOnly @@ -174,17 +172,37 @@ class LinesYardstick left + width - offset - pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) -> - lineHeight = @model.getLineHeightInPixels() + rowForTopPixelPosition: (position, floor = true) -> + top = 0 + for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() + tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) + for row in [tileStartRow...tileEndRow] by 1 + nextTop = top + @presenter.getScreenRowHeight(row) + if floor + return row if nextTop > position + else + return row if top >= position + top = nextTop + @model.getScreenLineCount() + topPixelPositionForRow: (targetRow) -> + top = 0 + for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() + tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) + for row in [tileStartRow...tileEndRow] by 1 + return top if row is targetRow + top += @presenter.getScreenRowHeight(row) + top + + pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) -> if screenRange.end.row > screenRange.start.row top = @pixelPositionForScreenPosition(screenRange.start, true, measureVisibleLinesOnly).top left = 0 - height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight + height = @topPixelPositionForRow(screenRange.end.row + 1) - top width = @presenter.getScrollWidth() else {top, left} = @pixelPositionForScreenPosition(screenRange.start, false, measureVisibleLinesOnly) - height = lineHeight + height = @topPixelPositionForRow(screenRange.end.row + 1) - top width = @pixelPositionForScreenPosition(screenRange.end, false, measureVisibleLinesOnly).left - left {top, left, width, height} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 4fd612ec5..041a117e1 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -374,6 +374,9 @@ class TextEditorPresenter getEndTileRow: -> @constrainRow(@tileForRow(@endRow)) + getTileSize: -> + @tileSize + isValidScreenRow: (screenRow) -> screenRow >= 0 and screenRow < @model.getScreenLineCount() @@ -424,8 +427,8 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 - top = @positionForRow(tileStartRow) - height = @positionForRow(tileStartRow + @tileSize) - top + top = @linesYardstick.topPixelPositionForRow(tileStartRow) + height = @linesYardstick.topPixelPositionForRow(tileStartRow + @tileSize) - top tile = @state.content.tiles[tileStartRow] ?= {} tile.top = top - @scrollTop @@ -697,38 +700,15 @@ class TextEditorPresenter getScreenRowHeight: (screenRow) -> @heightsByScreenRow[screenRow] or @lineHeight - setScreenRowHeight: (screenRow, height) -> - @heightsByScreenRow[screenRow] = height - - rowForPosition: (position, floor = true) -> - top = 0 - for tileRow in [0..@model.getScreenLineCount()] by @tileSize - for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 - nextTop = top + @getScreenRowHeight(row) - if floor - return row if nextTop > position - else - return row if top >= position - top = nextTop - @model.getScreenLineCount() - - positionForRow: (targetRow) -> - top = 0 - for tileRow in [0..@model.getScreenLineCount()] by @tileSize - for row in [tileRow...Math.min(tileRow + @tileSize, @model.getScreenLineCount())] by 1 - return top if row is targetRow - top += @getScreenRowHeight(row) - top - updateStartRow: -> return unless @scrollTop? and @lineHeight? - @startRow = Math.max(0, @rowForPosition(@scrollTop)) + @startRow = Math.max(0, @linesYardstick.rowForTopPixelPosition(@scrollTop)) updateEndRow: -> return unless @scrollTop? and @lineHeight? and @height? - @endRow = @rowForPosition(@scrollTop + @height + @lineHeight, false) + @endRow = @linesYardstick.rowForTopPixelPosition(@scrollTop + @height + @lineHeight, false) updateRowsPerPage: -> rowsPerPage = Math.floor(@getClientHeight() / @lineHeight) From 587f86261277b6c0428d041b5bd272c44ec9ee42 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 12:55:09 +0100 Subject: [PATCH 013/173] Use LinesYardstick consistently --- src/text-editor-presenter.coffee | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 041a117e1..ece1fe790 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -638,9 +638,11 @@ class TextEditorPresenter continue unless @gutterIsVisible(gutter) for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName] + top = @linesYardstick.topPixelPositionForRow(screenRange.start.row) + bottom = @linesYardstick.topPixelPositionForRow(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 @@ -1313,7 +1315,7 @@ class TextEditorPresenter screenRange.end.column = 0 repositionRegionWithinTile: (region, tileStartRow) -> - region.top += @scrollTop - tileStartRow * @lineHeight + region.top += @scrollTop - @linesYardstick.topPixelPositionForRow(tileStartRow) region.left += @scrollLeft buildHighlightRegions: (screenRange) -> @@ -1487,7 +1489,7 @@ class TextEditorPresenter @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> - @updateScrollTop(screenRow * @lineHeight) + @updateScrollTop(@linesYardstick.topPixelPositionForRow(screenRow)) getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) @@ -1508,8 +1510,8 @@ class TextEditorPresenter verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - top = screenRange.start.row * @lineHeight - bottom = (screenRange.end.row + 1) * @lineHeight + top = @linesYardstick.topPixelPositionForRow(screenRange.start.row) + bottom = @linesYardstick.topPixelPositionForRow(screenRange.end.row + 1) if options?.center desiredScrollCenter = (top + bottom) / 2 @@ -1581,7 +1583,7 @@ class TextEditorPresenter restoreScrollTopIfNeeded: -> unless @scrollTop? - @updateScrollTop(@model.getFirstVisibleScreenRow() * @lineHeight) + @updateScrollTop(@linesYardstick.topPixelPositionForRow(@model.getFirstVisibleScreenRow())) restoreScrollLeftIfNeeded: -> unless @scrollLeft? From 6611cf8353463968199a9c3db3e2a6c75d2065d1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 14:05:05 +0100 Subject: [PATCH 014/173] Force cursor to be tall as ::lineHeight --- src/text-editor-presenter.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ece1fe790..fd23d337a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -509,6 +509,7 @@ class TextEditorPresenter return unless cursor.isVisible() and @startRow <= screenRange.start.row < @endRow pixelRect = @pixelRectForScreenRange(screenRange) + pixelRect.height = @lineHeight pixelRect.width = Math.round(@baseCharacterWidth) if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect From 6365c869927b949c14f6e7e66de2f7d9da741b69 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 14:32:01 +0100 Subject: [PATCH 015/173] Include blockDecorations inside each line's state --- spec/text-editor-presenter-spec.coffee | 62 ++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 7 +++ 2 files changed, 69 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index ef8f8b2bd..615019ee4 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1270,6 +1270,68 @@ 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 "adds block decorations to the relevant line state objects, both initially and when decorations change", -> + blockDecoration1 = editor.addBlockDecorationForScreenRow(0) + presenter = buildPresenter() + blockDecoration2 = editor.addBlockDecorationForScreenRow(3) + blockDecoration3 = editor.addBlockDecorationForScreenRow(7) + + waitsForStateToUpdate presenter + runs -> + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual [blockDecoration1] + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual [blockDecoration2] + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual [blockDecoration3] + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toBeUndefined() + + waitsForStateToUpdate presenter, -> + blockDecoration1.getMarker().setHeadBufferPosition([1, 0]) + blockDecoration2.getMarker().setHeadBufferPosition([5, 0]) + blockDecoration3.getMarker().setHeadBufferPosition([9, 0]) + + runs -> + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual [blockDecoration1] + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [blockDecoration2] + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual [blockDecoration3] + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toBeUndefined() + + waitsForStateToUpdate presenter, -> + blockDecoration1.destroy() + blockDecoration3.destroy() + + runs -> + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [blockDecoration2] + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toBeUndefined() + 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') diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index fd23d337a..2cf5b0d2a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -477,6 +477,7 @@ class TextEditorPresenter lineState = tileState.lines[line.id] lineState.screenRow = screenRow lineState.decorationClasses = @lineDecorationClassesForRow(screenRow) + lineState.blockDecorations = @blockDecorationsByScreenRow[screenRow] else tileState.lines[line.id] = screenRow: screenRow @@ -493,6 +494,7 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) + blockDecorations: @blockDecorationsByScreenRow[screenRow] for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) @@ -1397,6 +1399,7 @@ class TextEditorPresenter updateBlockDecorations: -> @heightsByScreenRow = {} + @blockDecorationsByScreenRow = {} blockDecorations = {} # TODO: move into DisplayBuffer @@ -1409,6 +1412,10 @@ class TextEditorPresenter for decorationId, decoration of blockDecorations screenRow = decoration.getMarker().getHeadScreenPosition().row + @blockDecorationsByScreenRow[screenRow] ?= [] + @blockDecorationsByScreenRow[screenRow].push(decoration) + + continue unless @blockDecorationsDimensionsById.hasOwnProperty(decorationId) @heightsByScreenRow[screenRow] ?= @lineHeight @heightsByScreenRow[screenRow] += @blockDecorationsDimensionsById[decorationId].height From 6ad21307cce6ca86d9b1705a0e985f971f5801cc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 16:27:01 +0100 Subject: [PATCH 016/173] Provide blockDecorationsHeight for each line number --- spec/text-editor-presenter-spec.coffee | 65 ++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 3 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 615019ee4..8f7622f15 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2563,6 +2563,71 @@ describe "TextEditorPresenter", -> expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 5, softWrapped: false} expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 6, softWrapped: false} + describe ".blockDecorations", -> + it "adds block decorations' height to the relevant line number state objects, both initially and when decorations change", -> + blockDecoration1 = editor.addBlockDecorationForScreenRow(0) + presenter = buildPresenter() + blockDecoration2 = editor.addBlockDecorationForScreenRow(3) + blockDecoration3 = editor.addBlockDecorationForScreenRow(3) + blockDecoration4 = editor.addBlockDecorationForScreenRow(7) + + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) + presenter.setBlockDecorationDimensions(blockDecoration3, 0, 30) + presenter.setBlockDecorationDimensions(blockDecoration4, 0, 40) + + 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) + expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0) + expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0) + expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(0) + + 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) + expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(30) + expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0) + expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(0) + + 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) + expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0) + expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0) + expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(0) + 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') diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2cf5b0d2a..d078594a3 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -693,8 +693,9 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) + blockDecorationsHeight = @getScreenRowHeight(screenRow) - @lineHeight - 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 From 0159d5c31eb184483a85b8a96026662f6a88f1f9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Nov 2015 16:58:08 +0100 Subject: [PATCH 017/173] :art: --- spec/fake-lines-yardstick.coffee | 8 +------- src/lines-yardstick.coffee | 8 +------- src/text-editor-presenter.coffee | 14 +++++++------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index 781318f8c..952a9ddfc 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -74,13 +74,7 @@ class FakeLinesYardstick @model.getScreenLineCount() topPixelPositionForRow: (targetRow) -> - top = 0 - for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() - tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) - for row in [tileStartRow...tileEndRow] by 1 - return top if row is targetRow - top += @presenter.getScreenRowHeight(row) - top + @presenter.getScreenRowsHeight(0, targetRow) pixelRectForScreenRange: (screenRange) -> if screenRange.end.row > screenRange.start.row diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 2febf8add..442097537 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -186,13 +186,7 @@ class LinesYardstick @model.getScreenLineCount() topPixelPositionForRow: (targetRow) -> - top = 0 - for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() - tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) - for row in [tileStartRow...tileEndRow] by 1 - return top if row is targetRow - top += @presenter.getScreenRowHeight(row) - top + @presenter.getScreenRowsHeight(0, targetRow) pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) -> if screenRange.end.row > screenRange.start.row diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index d078594a3..d9f65f3cb 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -706,6 +706,12 @@ class TextEditorPresenter getScreenRowHeight: (screenRow) -> @heightsByScreenRow[screenRow] or @lineHeight + getScreenRowsHeight: (startRow, endRow) -> + height = 0 + for screenRow in [startRow...endRow] by 1 + height += @getScreenRowHeight(screenRow) + Math.round(height) + updateStartRow: -> return unless @scrollTop? and @lineHeight? @@ -746,17 +752,11 @@ class TextEditorPresenter getLinesHeight: -> @lineHeight * @model.getScreenLineCount() - getBlockDecorationsHeight: (blockDecorations) -> - sum = (a, b) -> a + b - Array.from(blockDecorations).map((size) -> size.height).reduce(sum, 0) - updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight allBlockDecorations = _.values(@blockDecorationsDimensionsById) - @contentHeight = Math.round( - @getLinesHeight() + @getBlockDecorationsHeight(allBlockDecorations) - ) + @contentHeight = @getScreenRowsHeight(0, @model.getScreenLineCount()) if @contentHeight isnt oldContentHeight @updateHeight() From ef851a822c4fbc51573a20698c930825ceaa96e9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Nov 2015 10:12:44 +0100 Subject: [PATCH 018/173] :art: Move block decoration related stuff into its own presenter --- src/block-decorations-presenter.js | 75 ++++++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 75 +++++++++++++++--------------- 2 files changed, 113 insertions(+), 37 deletions(-) create mode 100644 src/block-decorations-presenter.js diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js new file mode 100644 index 000000000..373444c45 --- /dev/null +++ b/src/block-decorations-presenter.js @@ -0,0 +1,75 @@ +/** @babel */ + +const {Emitter} = require('event-kit') + +module.exports = +class BlockDecorationsPresenter { + constructor (model) { + this.model = model + this.emitter = new Emitter() + this.blockDecorationsDimensionsById = new Map + this.blockDecorationsByScreenRow = new Map + this.heightsByScreenRow = new Map + } + + onDidUpdateState (callback) { + return this.emitter.on('did-update-state', callback) + } + + update () { + this.heightsByScreenRow.clear() + this.blockDecorationsByScreenRow.clear() + let blockDecorations = new Map + + // TODO: move into DisplayBuffer + for (let decoration of this.model.getDecorations({type: "block"})) { + blockDecorations.set(decoration.id, decoration) + } + + for (let [decorationId] of this.blockDecorationsDimensionsById) { + if (!blockDecorations.has(decorationId)) { + this.blockDecorationsDimensionsById.delete(decorationId) + } + } + + for (let [decorationId, decoration] of blockDecorations) { + let screenRow = decoration.getMarker().getHeadScreenPosition().row + this.addBlockDecorationToScreenRow(screenRow, decoration) + if (this.hasMeasuredBlockDecoration(decoration)) { + this.addHeightToScreenRow( + screenRow, + this.blockDecorationsDimensionsById.get(decorationId).height + ) + } + } + } + + setBlockDecorationDimensions (decoration, width, height) { + this.blockDecorationsDimensionsById.set(decoration.id, {width, height}) + this.emitter.emit('did-update-state') + } + + blockDecorationsHeightForScreenRow (screenRow) { + return Number(this.heightsByScreenRow.get(screenRow)) || 0 + } + + addHeightToScreenRow (screenRow, height) { + let previousHeight = this.blockDecorationsHeightForScreenRow(screenRow) + let newHeight = previousHeight + height + this.heightsByScreenRow.set(screenRow, newHeight) + } + + addBlockDecorationToScreenRow (screenRow, decoration) { + let decorations = this.blockDecorationsForScreenRow(screenRow) || [] + decorations.push(decoration) + this.blockDecorationsByScreenRow.set(screenRow, decorations) + } + + blockDecorationsForScreenRow (screenRow) { + return this.blockDecorationsByScreenRow.get(screenRow) + } + + hasMeasuredBlockDecoration (decoration) { + return this.blockDecorationsDimensionsById.has(decoration.id) + } +} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index d9f65f3cb..0e7a2c56d 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -2,6 +2,7 @@ {Point, Range} = require 'text-buffer' _ = require 'underscore-plus' Decoration = require './decoration' +BlockDecorationsPresenter = require './block-decorations-presenter' module.exports = class TextEditorPresenter @@ -28,9 +29,7 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} - @blockDecorationsDimensionsById = {} - @blockDecorationsDimensionsByScreenRow = {} - @heightsByScreenRow = {} + @blockDecorationsPresenter = new BlockDecorationsPresenter(@model) @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -74,7 +73,8 @@ class TextEditorPresenter getPreMeasurementState: -> @updating = true - @updateBlockDecorations() + @blockDecorationsPresenter.update() + @updateVerticalDimensions() @updateScrollbarDimensions() @@ -196,6 +196,35 @@ class TextEditorPresenter @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() + @disposables.add @blockDecorationsPresenter.onDidUpdateState => + @shouldUpdateVerticalScrollState = true + @emitDidUpdateState() + + # @disposables.add @ + + # @disposables.add @model.onDidAddDecoration (decoration) => + # return unless decoration.isType("block") + # + # didMoveDisposable = decoration.getMarker().onDidChange ({oldHeadScreenPosition, newHeadScreenPosition}) => + # # @blockDecorationsMoveOperations.add() + # # @moveBlockDecoration(decoration, oldHeadScreenPosition, newHeadScreenPosition) + # @emitDidUpdateState() + # didChangeDisposable = decoration.onDidChangeProperties (properties) => + # # @changePropertiesOperations.add() + # # @updateBlockDecoration(decoration) + # @emitDidUpdateState() + # didDestroyDisposable = decoration.onDidDestroy => + # didMoveDisposable.dispose() + # didChangeDisposable.dispose() + # didDestroyDisposable.dispose() + # + # # @destroyOperations.add() + # # @destroyBlockDecoration(decoration) + # @emitDidUpdateState() + # + # # @addBlockDecoration(decoration) + # @emitDidUpdateState() + @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) @disposables.add @model.onDidChangePlaceholderText => @shouldUpdateContentState = true @@ -477,7 +506,7 @@ class TextEditorPresenter lineState = tileState.lines[line.id] lineState.screenRow = screenRow lineState.decorationClasses = @lineDecorationClassesForRow(screenRow) - lineState.blockDecorations = @blockDecorationsByScreenRow[screenRow] + lineState.blockDecorations = this.blockDecorationsPresenter.blockDecorationsForScreenRow(screenRow) else tileState.lines[line.id] = screenRow: screenRow @@ -494,7 +523,7 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) - blockDecorations: @blockDecorationsByScreenRow[screenRow] + blockDecorations: this.blockDecorationsPresenter.blockDecorationsForScreenRow(screenRow) for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) @@ -693,7 +722,7 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - blockDecorationsHeight = @getScreenRowHeight(screenRow) - @lineHeight + blockDecorationsHeight = @blockDecorationsPresenter.blockDecorationsHeightForScreenRow(screenRow) tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight} visibleLineNumberIds[line.id] = true @@ -704,7 +733,7 @@ class TextEditorPresenter return getScreenRowHeight: (screenRow) -> - @heightsByScreenRow[screenRow] or @lineHeight + @lineHeight + @blockDecorationsPresenter.blockDecorationsHeightForScreenRow(screenRow) getScreenRowsHeight: (startRow, endRow) -> height = 0 @@ -755,7 +784,6 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - allBlockDecorations = _.values(@blockDecorationsDimensionsById) @contentHeight = @getScreenRowsHeight(0, @model.getScreenLineCount()) if @contentHeight isnt oldContentHeight @@ -1391,34 +1419,7 @@ class TextEditorPresenter @emitDidUpdateState() setBlockDecorationDimensions: (decoration, width, height) -> - screenRow = decoration.getMarker().getHeadScreenPosition().row - dimensions = {width, height} - @blockDecorationsDimensionsById[decoration.id] = dimensions - - @shouldUpdateVerticalScrollState = true - @emitDidUpdateState() - - updateBlockDecorations: -> - @heightsByScreenRow = {} - @blockDecorationsByScreenRow = {} - blockDecorations = {} - - # TODO: move into DisplayBuffer - for decoration in @model.getDecorations(type: "block") - blockDecorations[decoration.id] = decoration - - for decorationId of @blockDecorationsDimensionsById - unless blockDecorations.hasOwnProperty(decorationId) - delete @blockDecorationsDimensionsById[decorationId] - - for decorationId, decoration of blockDecorations - screenRow = decoration.getMarker().getHeadScreenPosition().row - @blockDecorationsByScreenRow[screenRow] ?= [] - @blockDecorationsByScreenRow[screenRow].push(decoration) - - continue unless @blockDecorationsDimensionsById.hasOwnProperty(decorationId) - @heightsByScreenRow[screenRow] ?= @lineHeight - @heightsByScreenRow[screenRow] += @blockDecorationsDimensionsById[decorationId].height + @blockDecorationsPresenter.setBlockDecorationDimensions(arguments...) observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => From 0419fb16a0524732d42c9446c4ce4160d2d65f64 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Nov 2015 10:19:53 +0100 Subject: [PATCH 019/173] :art: Improve names a bit --- src/block-decorations-presenter.js | 46 +++++++++++++++--------------- src/text-editor-presenter.coffee | 10 +++---- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 373444c45..c5697e7d4 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -7,8 +7,8 @@ class BlockDecorationsPresenter { constructor (model) { this.model = model this.emitter = new Emitter() - this.blockDecorationsDimensionsById = new Map - this.blockDecorationsByScreenRow = new Map + this.dimensionsByDecorationId = new Map + this.decorationsByScreenRow = new Map this.heightsByScreenRow = new Map } @@ -18,58 +18,58 @@ class BlockDecorationsPresenter { update () { this.heightsByScreenRow.clear() - this.blockDecorationsByScreenRow.clear() - let blockDecorations = new Map + this.decorationsByScreenRow.clear() + let decorations = new Map // TODO: move into DisplayBuffer for (let decoration of this.model.getDecorations({type: "block"})) { - blockDecorations.set(decoration.id, decoration) + decorations.set(decoration.id, decoration) } - for (let [decorationId] of this.blockDecorationsDimensionsById) { - if (!blockDecorations.has(decorationId)) { - this.blockDecorationsDimensionsById.delete(decorationId) + for (let [decorationId] of this.dimensionsByDecorationId) { + if (!decorations.has(decorationId)) { + this.dimensionsByDecorationId.delete(decorationId) } } - for (let [decorationId, decoration] of blockDecorations) { + for (let [decorationId, decoration] of decorations) { let screenRow = decoration.getMarker().getHeadScreenPosition().row - this.addBlockDecorationToScreenRow(screenRow, decoration) - if (this.hasMeasuredBlockDecoration(decoration)) { + this.addDecorationToScreenRow(screenRow, decoration) + if (this.hasMeasurementsForDecoration(decoration)) { this.addHeightToScreenRow( screenRow, - this.blockDecorationsDimensionsById.get(decorationId).height + this.dimensionsByDecorationId.get(decorationId).height ) } } } - setBlockDecorationDimensions (decoration, width, height) { - this.blockDecorationsDimensionsById.set(decoration.id, {width, height}) + setDimensionsForDecoration (decoration, width, height) { + this.dimensionsByDecorationId.set(decoration.id, {width, height}) this.emitter.emit('did-update-state') } - blockDecorationsHeightForScreenRow (screenRow) { + heightForScreenRow (screenRow) { return Number(this.heightsByScreenRow.get(screenRow)) || 0 } addHeightToScreenRow (screenRow, height) { - let previousHeight = this.blockDecorationsHeightForScreenRow(screenRow) + let previousHeight = this.heightForScreenRow(screenRow) let newHeight = previousHeight + height this.heightsByScreenRow.set(screenRow, newHeight) } - addBlockDecorationToScreenRow (screenRow, decoration) { - let decorations = this.blockDecorationsForScreenRow(screenRow) || [] + addDecorationToScreenRow (screenRow, decoration) { + let decorations = this.getDecorationsByScreenRow(screenRow) || [] decorations.push(decoration) - this.blockDecorationsByScreenRow.set(screenRow, decorations) + this.decorationsByScreenRow.set(screenRow, decorations) } - blockDecorationsForScreenRow (screenRow) { - return this.blockDecorationsByScreenRow.get(screenRow) + getDecorationsByScreenRow (screenRow) { + return this.decorationsByScreenRow.get(screenRow) } - hasMeasuredBlockDecoration (decoration) { - return this.blockDecorationsDimensionsById.has(decoration.id) + hasMeasurementsForDecoration (decoration) { + return this.dimensionsByDecorationId.has(decoration.id) } } diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 0e7a2c56d..153a78dbf 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -506,7 +506,7 @@ class TextEditorPresenter lineState = tileState.lines[line.id] lineState.screenRow = screenRow lineState.decorationClasses = @lineDecorationClassesForRow(screenRow) - lineState.blockDecorations = this.blockDecorationsPresenter.blockDecorationsForScreenRow(screenRow) + lineState.blockDecorations = this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow) else tileState.lines[line.id] = screenRow: screenRow @@ -523,7 +523,7 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) - blockDecorations: this.blockDecorationsPresenter.blockDecorationsForScreenRow(screenRow) + blockDecorations: this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow) for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) @@ -722,7 +722,7 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - blockDecorationsHeight = @blockDecorationsPresenter.blockDecorationsHeightForScreenRow(screenRow) + blockDecorationsHeight = @blockDecorationsPresenter.heightForScreenRow(screenRow) tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight} visibleLineNumberIds[line.id] = true @@ -733,7 +733,7 @@ class TextEditorPresenter return getScreenRowHeight: (screenRow) -> - @lineHeight + @blockDecorationsPresenter.blockDecorationsHeightForScreenRow(screenRow) + @lineHeight + @blockDecorationsPresenter.heightForScreenRow(screenRow) getScreenRowsHeight: (startRow, endRow) -> height = 0 @@ -1419,7 +1419,7 @@ class TextEditorPresenter @emitDidUpdateState() setBlockDecorationDimensions: (decoration, width, height) -> - @blockDecorationsPresenter.setBlockDecorationDimensions(arguments...) + @blockDecorationsPresenter.setDimensionsForDecoration(arguments...) observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => From b998e7f2d99a48bfeaaf294d6a63346b7b7ac4c5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Nov 2015 11:43:46 +0100 Subject: [PATCH 020/173] :racehorse: Incremental updates for block decorations --- spec/text-editor-presenter-spec.coffee | 64 ++++----- src/block-decorations-presenter.js | 171 +++++++++++++++++++------ src/decoration.coffee | 8 +- src/text-editor-presenter.coffee | 30 +---- 4 files changed, 176 insertions(+), 97 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 8f7622f15..58f832109 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1280,18 +1280,18 @@ describe "TextEditorPresenter", -> waitsForStateToUpdate presenter runs -> expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual [blockDecoration1] - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual [] expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual [blockDecoration2] - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual [] expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual [blockDecoration3] - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual [] waitsForStateToUpdate presenter, -> blockDecoration1.getMarker().setHeadBufferPosition([1, 0]) @@ -1299,38 +1299,38 @@ describe "TextEditorPresenter", -> blockDecoration3.getMarker().setHeadBufferPosition([9, 0]) runs -> - expect(lineStateForScreenRow(presenter, 0).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual [] expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual [blockDecoration1] - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual [] expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [blockDecoration2] - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual [] expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual [blockDecoration3] - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual [] waitsForStateToUpdate presenter, -> blockDecoration1.destroy() blockDecoration3.destroy() runs -> - expect(lineStateForScreenRow(presenter, 0).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual [] expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [blockDecoration2] - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toBeUndefined() - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toBeUndefined() + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual [] describe ".decorationClasses", -> it "adds decoration classes to the relevant line state objects, both initially and when decorations change", -> diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index c5697e7d4..3a669e2f9 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -1,75 +1,172 @@ /** @babel */ -const {Emitter} = require('event-kit') +const {CompositeDisposable, Emitter} = require('event-kit') module.exports = class BlockDecorationsPresenter { constructor (model) { this.model = model + this.disposables = new CompositeDisposable() this.emitter = new Emitter() - this.dimensionsByDecorationId = new Map this.decorationsByScreenRow = new Map - this.heightsByScreenRow = new Map + this.heightByScreenRow = new Map + this.screenRowByDecoration = new Map + this.dimensionsByDecoration = new Map + this.moveOperationsByDecoration = new Map + this.addOperationsByDecoration = new Map + this.changeOperationsByDecoration = new Map + this.firstUpdate = true + + this.observeModel() + } + + destroy () { + this.disposables.dispose() } onDidUpdateState (callback) { - return this.emitter.on('did-update-state', callback) + return this.emitter.on("did-update-state", callback) + } + + observeModel () { + this.disposables.add( + this.model.onDidAddDecoration((decoration) => this.observeDecoration(decoration)) + ) } update () { - this.heightsByScreenRow.clear() + if (this.firstUpdate) { + this.fullUpdate() + this.firstUpdate = false + } else { + this.incrementalUpdate() + } + } + + fullUpdate () { this.decorationsByScreenRow.clear() - let decorations = new Map + this.screenRowByDecoration.clear() + this.moveOperationsByDecoration.clear() + this.addOperationsByDecoration.clear() - // TODO: move into DisplayBuffer for (let decoration of this.model.getDecorations({type: "block"})) { - decorations.set(decoration.id, decoration) - } - - for (let [decorationId] of this.dimensionsByDecorationId) { - if (!decorations.has(decorationId)) { - this.dimensionsByDecorationId.delete(decorationId) - } - } - - for (let [decorationId, decoration] of decorations) { let screenRow = decoration.getMarker().getHeadScreenPosition().row this.addDecorationToScreenRow(screenRow, decoration) - if (this.hasMeasurementsForDecoration(decoration)) { - this.addHeightToScreenRow( - screenRow, - this.dimensionsByDecorationId.get(decorationId).height - ) - } + this.observeDecoration(decoration) } } + incrementalUpdate () { + for (let [changedDecoration] of this.changeOperationsByDecoration) { + let screenRow = changedDecoration.getMarker().getHeadScreenPosition().row + this.recalculateScreenRowHeight(screenRow) + } + + for (let [addedDecoration] of this.addOperationsByDecoration) { + let screenRow = addedDecoration.getMarker().getHeadScreenPosition().row + this.addDecorationToScreenRow(screenRow, addedDecoration) + } + + for (let [movedDecoration, moveOperations] of this.moveOperationsByDecoration) { + let {oldHeadScreenPosition} = moveOperations[0] + let {newHeadScreenPosition} = moveOperations[moveOperations.length - 1] + this.removeDecorationFromScreenRow(oldHeadScreenPosition.row, movedDecoration) + this.addDecorationToScreenRow(newHeadScreenPosition.row, movedDecoration) + } + + this.addOperationsByDecoration.clear() + this.moveOperationsByDecoration.clear() + this.changeOperationsByDecoration.clear() + } + setDimensionsForDecoration (decoration, width, height) { - this.dimensionsByDecorationId.set(decoration.id, {width, height}) - this.emitter.emit('did-update-state') + this.changeOperationsByDecoration.set(decoration, true) + this.dimensionsByDecoration.set(decoration, {width, height}) + this.emitter.emit("did-update-state") } heightForScreenRow (screenRow) { - return Number(this.heightsByScreenRow.get(screenRow)) || 0 - } - - addHeightToScreenRow (screenRow, height) { - let previousHeight = this.heightForScreenRow(screenRow) - let newHeight = previousHeight + height - this.heightsByScreenRow.set(screenRow, newHeight) + return this.heightByScreenRow.get(screenRow) || 0 } addDecorationToScreenRow (screenRow, decoration) { - let decorations = this.getDecorationsByScreenRow(screenRow) || [] - decorations.push(decoration) - this.decorationsByScreenRow.set(screenRow, decorations) + let decorations = this.getDecorationsByScreenRow(screenRow) + if (!decorations.has(decoration)) { + decorations.add(decoration) + this.screenRowByDecoration.set(decoration, screenRow) + this.recalculateScreenRowHeight(screenRow) + } + } + + removeDecorationFromScreenRow (screenRow, decoration) { + if (!Number.isInteger(screenRow) || !decoration) { + return + } + + let decorations = this.getDecorationsByScreenRow(screenRow) + if (decorations.has(decoration)) { + decorations.delete(decoration) + this.recalculateScreenRowHeight(screenRow) + } } getDecorationsByScreenRow (screenRow) { + if (!this.decorationsByScreenRow.has(screenRow)) { + this.decorationsByScreenRow.set(screenRow, new Set()) + } + return this.decorationsByScreenRow.get(screenRow) } - hasMeasurementsForDecoration (decoration) { - return this.dimensionsByDecorationId.has(decoration.id) + getDecorationDimensions (decoration) { + return this.dimensionsByDecoration.get(decoration) || {width: 0, height: 0} + } + + recalculateScreenRowHeight (screenRow) { + let height = 0 + for (let decoration of this.getDecorationsByScreenRow(screenRow)) { + height += this.getDecorationDimensions(decoration).height + } + this.heightByScreenRow.set(screenRow, height) + } + + observeDecoration (decoration) { + if (!decoration.isType("block")) { + return + } + + let didMoveDisposable = decoration.getMarker().onDidChange((markerEvent) => { + this.didMoveDecoration(decoration, markerEvent) + }) + + let didDestroyDisposable = decoration.onDidDestroy(() => { + didMoveDisposable.dispose() + didDestroyDisposable.dispose() + this.didDestroyDecoration(decoration) + }) + + this.didAddDecoration(decoration) + } + + didAddDecoration (decoration) { + this.addOperationsByDecoration.set(decoration, true) + this.emitter.emit("did-update-state") + } + + didMoveDecoration (decoration, markerEvent) { + let moveOperations = this.moveOperationsByDecoration.get(decoration) || [] + moveOperations.push(markerEvent) + this.moveOperationsByDecoration.set(decoration, moveOperations) + this.emitter.emit("did-update-state") + } + + didDestroyDecoration (decoration) { + this.moveOperationsByDecoration.delete(decoration) + this.addOperationsByDecoration.delete(decoration) + + this.removeDecorationFromScreenRow( + this.screenRowByDecoration.get(decoration), decoration + ) + this.emitter.emit("did-update-state") } } diff --git a/src/decoration.coffee b/src/decoration.coffee index f57d234d1..11e32236d 100644 --- a/src/decoration.coffee +++ b/src/decoration.coffee @@ -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: -> + "" + ### Section: Private methods ### diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 153a78dbf..f71858fac 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -46,6 +46,7 @@ class TextEditorPresenter getLinesYardstick: -> @linesYardstick destroy: -> + @blockDecorationsPresenter.destroy() @disposables.dispose() clearTimeout(@stoppedScrollingTimeoutId) if @stoppedScrollingTimeoutId? clearInterval(@reflowingInterval) if @reflowingInterval? @@ -200,31 +201,6 @@ class TextEditorPresenter @shouldUpdateVerticalScrollState = true @emitDidUpdateState() - # @disposables.add @ - - # @disposables.add @model.onDidAddDecoration (decoration) => - # return unless decoration.isType("block") - # - # didMoveDisposable = decoration.getMarker().onDidChange ({oldHeadScreenPosition, newHeadScreenPosition}) => - # # @blockDecorationsMoveOperations.add() - # # @moveBlockDecoration(decoration, oldHeadScreenPosition, newHeadScreenPosition) - # @emitDidUpdateState() - # didChangeDisposable = decoration.onDidChangeProperties (properties) => - # # @changePropertiesOperations.add() - # # @updateBlockDecoration(decoration) - # @emitDidUpdateState() - # didDestroyDisposable = decoration.onDidDestroy => - # didMoveDisposable.dispose() - # didChangeDisposable.dispose() - # didDestroyDisposable.dispose() - # - # # @destroyOperations.add() - # # @destroyBlockDecoration(decoration) - # @emitDidUpdateState() - # - # # @addBlockDecoration(decoration) - # @emitDidUpdateState() - @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) @disposables.add @model.onDidChangePlaceholderText => @shouldUpdateContentState = true @@ -506,7 +482,7 @@ class TextEditorPresenter lineState = tileState.lines[line.id] lineState.screenRow = screenRow lineState.decorationClasses = @lineDecorationClassesForRow(screenRow) - lineState.blockDecorations = this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow) + lineState.blockDecorations = Array.from(this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow)) else tileState.lines[line.id] = screenRow: screenRow @@ -523,7 +499,7 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) - blockDecorations: this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow) + blockDecorations: Array.from(this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow)) for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) From 539a5b0ae7e2779b84e47e1a0e681f2fa86c45b9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Nov 2015 14:26:52 +0100 Subject: [PATCH 021/173] :racehorse: Do as little pixel conversion as possible We desperately need a tree-based data structure. :cry: --- spec/fake-lines-yardstick.coffee | 16 +++++++++++++++- src/lines-yardstick.coffee | 16 +++++++++++++++- src/text-editor-presenter.coffee | 23 +++++++++++++---------- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index 952a9ddfc..9dfeb7da4 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -74,7 +74,21 @@ class FakeLinesYardstick @model.getScreenLineCount() topPixelPositionForRow: (targetRow) -> - @presenter.getScreenRowsHeight(0, targetRow) + top = 0 + for row in [0..targetRow] + return top if targetRow is row + top += @presenter.getScreenRowHeight(row) + top + + topPixelPositionForRows: (startRow, endRow, step) -> + results = {} + top = 0 + for tileStartRow in [0..endRow] by step + tileEndRow = Math.min(tileStartRow + step, @model.getScreenLineCount()) + results[tileStartRow] = top + for row in [tileStartRow...tileEndRow] by 1 + top += @presenter.getScreenRowHeight(row) + results pixelRectForScreenRange: (screenRange) -> if screenRange.end.row > screenRange.start.row diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 442097537..d827d7bce 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -186,7 +186,21 @@ class LinesYardstick @model.getScreenLineCount() topPixelPositionForRow: (targetRow) -> - @presenter.getScreenRowsHeight(0, targetRow) + top = 0 + for row in [0..targetRow] + return top if targetRow is row + top += @presenter.getScreenRowHeight(row) + top + + topPixelPositionForRows: (startRow, endRow, step) -> + results = {} + top = 0 + for tileStartRow in [0..endRow] by step + tileEndRow = Math.min(tileStartRow + step, @model.getScreenLineCount()) + results[tileStartRow] = top + for row in [tileStartRow...tileEndRow] by 1 + top += @presenter.getScreenRowHeight(row) + results pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) -> if screenRange.end.row > screenRange.start.row diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index f71858fac..869b7914b 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -421,7 +421,14 @@ class TextEditorPresenter screenRowIndex = screenRows.length - 1 zIndex = 0 + tilesPositions = @linesYardstick.topPixelPositionForRows( + @tileForRow(startRow), + @tileForRow(endRow) + @tileSize, + @tileSize + ) + for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize + tileEndRow = @constrainRow(tileStartRow + @tileSize) rowsWithinTile = [] while screenRowIndex >= 0 @@ -432,8 +439,8 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 - top = @linesYardstick.topPixelPositionForRow(tileStartRow) - height = @linesYardstick.topPixelPositionForRow(tileStartRow + @tileSize) - top + top = Math.round(tilesPositions[tileStartRow]) + height = Math.round(tilesPositions[tileEndRow] - top) tile = @state.content.tiles[tileStartRow] ?= {} tile.top = top - @scrollTop @@ -709,13 +716,7 @@ class TextEditorPresenter return getScreenRowHeight: (screenRow) -> - @lineHeight + @blockDecorationsPresenter.heightForScreenRow(screenRow) - - getScreenRowsHeight: (startRow, endRow) -> - height = 0 - for screenRow in [startRow...endRow] by 1 - height += @getScreenRowHeight(screenRow) - Math.round(height) + @lineHeight + @blockDecorationsPresenter.heightForScreenRow(screenRow) updateStartRow: -> return unless @scrollTop? and @lineHeight? @@ -760,7 +761,9 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = @getScreenRowsHeight(0, @model.getScreenLineCount()) + @contentHeight = Math.round( + @linesYardstick.topPixelPositionForRow(@model.getScreenLineCount()) + ) if @contentHeight isnt oldContentHeight @updateHeight() From 8a54a2c15bbb893675e1aa2bdbc86e55da14f098 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Nov 2015 14:44:58 +0100 Subject: [PATCH 022/173] Use a boolean in each line state object --- spec/text-editor-presenter-spec.coffee | 86 +++++++++++++------------- src/text-editor-presenter.coffee | 5 +- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 58f832109..b940fd06d 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1270,8 +1270,8 @@ 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 "adds block decorations to the relevant line state objects, both initially and when decorations change", -> + describe ".hasBlockDecorations", -> + it "is true when block decorations are present before a line, both initially and when decorations change", -> blockDecoration1 = editor.addBlockDecorationForScreenRow(0) presenter = buildPresenter() blockDecoration2 = editor.addBlockDecorationForScreenRow(3) @@ -1279,19 +1279,19 @@ describe "TextEditorPresenter", -> waitsForStateToUpdate presenter runs -> - expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual [blockDecoration1] - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual [blockDecoration2] - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual [blockDecoration3] - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 0).hasBlockDecorations).toBe(true) + expect(lineStateForScreenRow(presenter, 1).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 2).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 3).hasBlockDecorations).toBe(true) + expect(lineStateForScreenRow(presenter, 4).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 5).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 6).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 7).hasBlockDecorations).toBe(true) + expect(lineStateForScreenRow(presenter, 8).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 9).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 10).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 11).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 12).hasBlockDecorations).toBe(false) waitsForStateToUpdate presenter, -> blockDecoration1.getMarker().setHeadBufferPosition([1, 0]) @@ -1299,38 +1299,38 @@ describe "TextEditorPresenter", -> blockDecoration3.getMarker().setHeadBufferPosition([9, 0]) runs -> - expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual [blockDecoration1] - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [blockDecoration2] - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual [blockDecoration3] - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 0).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 1).hasBlockDecorations).toBe(true) + expect(lineStateForScreenRow(presenter, 2).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 3).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 4).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 5).hasBlockDecorations).toBe(true) + expect(lineStateForScreenRow(presenter, 6).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 7).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 8).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 9).hasBlockDecorations).toBe(true) + expect(lineStateForScreenRow(presenter, 10).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 11).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 12).hasBlockDecorations).toBe(false) waitsForStateToUpdate presenter, -> blockDecoration1.destroy() blockDecoration3.destroy() runs -> - expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual [blockDecoration2] - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual [] - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual [] + expect(lineStateForScreenRow(presenter, 0).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 1).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 2).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 3).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 4).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 5).hasBlockDecorations).toBe(true) + expect(lineStateForScreenRow(presenter, 6).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 7).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 8).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 9).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 10).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 11).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 12).hasBlockDecorations).toBe(false) describe ".decorationClasses", -> it "adds decoration classes to the relevant line state objects, both initially and when decorations change", -> @@ -2563,8 +2563,8 @@ describe "TextEditorPresenter", -> expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 5, softWrapped: false} expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 6, softWrapped: false} - describe ".blockDecorations", -> - it "adds block decorations' height to the relevant line number state objects, both initially and when decorations change", -> + 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 = editor.addBlockDecorationForScreenRow(0) presenter = buildPresenter() blockDecoration2 = editor.addBlockDecorationForScreenRow(3) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 869b7914b..0e802a0f7 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -485,11 +485,12 @@ class TextEditorPresenter throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}") visibleLineIds[line.id] = true + blockDecorations = this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow) if tileState.lines.hasOwnProperty(line.id) lineState = tileState.lines[line.id] lineState.screenRow = screenRow lineState.decorationClasses = @lineDecorationClassesForRow(screenRow) - lineState.blockDecorations = Array.from(this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow)) + lineState.hasBlockDecorations = blockDecorations.size isnt 0 else tileState.lines[line.id] = screenRow: screenRow @@ -506,7 +507,7 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) - blockDecorations: Array.from(this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow)) + hasBlockDecorations: blockDecorations.size isnt 0 for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) From 526a97562ec3c597f9fe58d69af24d48f6d81afc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Nov 2015 15:05:15 +0100 Subject: [PATCH 023/173] Include block decorations as a separate object on presenter's state --- spec/text-editor-presenter-spec.coffee | 66 ++++++++++++++++++++++++++ src/block-decorations-presenter.js | 12 +++-- src/text-editor-presenter.coffee | 10 +++- 3 files changed, 83 insertions(+), 5 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index b940fd06d..903c6ebf7 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2064,6 +2064,72 @@ describe "TextEditorPresenter", -> flashCount: 2 } + describe ".blockDecorations", -> + stateForBlockDecoration = (presenter, decoration) -> + presenter.getState().content.blockDecorations[decoration.id] + + it "contains state for block decorations, indicating the screen row they belong to both initially and when their markers move", -> + item = {} + blockDecoration1 = editor.addBlockDecorationForScreenRow(0, item) + blockDecoration2 = editor.addBlockDecorationForScreenRow(4, item) + blockDecoration3 = editor.addBlockDecorationForScreenRow(4, item) + blockDecoration4 = editor.addBlockDecorationForScreenRow(10, item) + presenter = buildPresenter(explicitHeight: 30, scrollTop: 0) + + expectValues stateForBlockDecoration(presenter, blockDecoration1), { + decoration: blockDecoration1 + screenRow: 0 + } + expectValues stateForBlockDecoration(presenter, blockDecoration2), { + decoration: blockDecoration2 + screenRow: 4 + } + expectValues stateForBlockDecoration(presenter, blockDecoration3), { + decoration: blockDecoration3 + screenRow: 4 + } + expectValues stateForBlockDecoration(presenter, blockDecoration4), { + decoration: blockDecoration4 + screenRow: 10 + } + + waitsForStateToUpdate presenter, -> + editor.getBuffer().insert([0, 0], 'Hello world \n\n\n\n') + + runs -> + expectValues stateForBlockDecoration(presenter, blockDecoration1), { + decoration: blockDecoration1 + screenRow: 4 + } + expectValues stateForBlockDecoration(presenter, blockDecoration2), { + decoration: blockDecoration2 + screenRow: 8 + } + expectValues stateForBlockDecoration(presenter, blockDecoration3), { + decoration: blockDecoration3 + screenRow: 8 + } + expectValues stateForBlockDecoration(presenter, blockDecoration4), { + decoration: blockDecoration4 + screenRow: 14 + } + + waitsForStateToUpdate presenter, -> + blockDecoration2.destroy() + blockDecoration4.destroy() + + runs -> + expectValues stateForBlockDecoration(presenter, blockDecoration1), { + decoration: blockDecoration1 + screenRow: 4 + } + expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined() + expectValues stateForBlockDecoration(presenter, blockDecoration3), { + decoration: blockDecoration3 + screenRow: 8 + } + expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() + describe ".overlays", -> [item] = [] stateForOverlay = (presenter, decoration) -> diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 3a669e2f9..762372b15 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -90,7 +90,7 @@ class BlockDecorationsPresenter { } addDecorationToScreenRow (screenRow, decoration) { - let decorations = this.getDecorationsByScreenRow(screenRow) + let decorations = this.decorationsForScreenRow(screenRow) if (!decorations.has(decoration)) { decorations.add(decoration) this.screenRowByDecoration.set(decoration, screenRow) @@ -103,14 +103,14 @@ class BlockDecorationsPresenter { return } - let decorations = this.getDecorationsByScreenRow(screenRow) + let decorations = this.decorationsForScreenRow(screenRow) if (decorations.has(decoration)) { decorations.delete(decoration) this.recalculateScreenRowHeight(screenRow) } } - getDecorationsByScreenRow (screenRow) { + decorationsForScreenRow (screenRow) { if (!this.decorationsByScreenRow.has(screenRow)) { this.decorationsByScreenRow.set(screenRow, new Set()) } @@ -118,13 +118,17 @@ class BlockDecorationsPresenter { return this.decorationsByScreenRow.get(screenRow) } + getAllDecorationsByScreenRow () { + return this.decorationsByScreenRow + } + getDecorationDimensions (decoration) { return this.dimensionsByDecoration.get(decoration) || {width: 0, height: 0} } recalculateScreenRowHeight (screenRow) { let height = 0 - for (let decoration of this.getDecorationsByScreenRow(screenRow)) { + for (let decoration of this.decorationsForScreenRow(screenRow)) { height += this.getDecorationDimensions(decoration).height } this.heightByScreenRow.set(screenRow, height) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 0e802a0f7..8fd0a790a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -76,6 +76,7 @@ class TextEditorPresenter @blockDecorationsPresenter.update() + @updateBlockDecorationsState() @updateVerticalDimensions() @updateScrollbarDimensions() @@ -485,7 +486,7 @@ class TextEditorPresenter throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}") visibleLineIds[line.id] = true - blockDecorations = this.blockDecorationsPresenter.getDecorationsByScreenRow(screenRow) + blockDecorations = this.blockDecorationsPresenter.decorationsForScreenRow(screenRow) if tileState.lines.hasOwnProperty(line.id) lineState = tileState.lines[line.id] lineState.screenRow = screenRow @@ -1205,6 +1206,13 @@ class TextEditorPresenter return unless 0 <= @startRow <= @endRow <= Infinity @decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1) + updateBlockDecorationsState: -> + @state.content.blockDecorations = {} + + @blockDecorationsPresenter.getAllDecorationsByScreenRow().forEach (decorations, screenRow) => + decorations.forEach (decoration) => + @state.content.blockDecorations[decoration.id] = {decoration, screenRow} + updateLineDecorations: -> @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} From da1fd69a1f2c4a60e961693ebfe6e49463950c0e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 30 Nov 2015 15:43:39 +0100 Subject: [PATCH 024/173] Start implementing BlockDecorationsComponent --- src/block-decorations-component.coffee | 45 ++++++++++++++++++++++++++ src/text-editor-component.coffee | 5 +++ src/text-editor.coffee | 4 +-- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/block-decorations-component.coffee diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee new file mode 100644 index 000000000..4f3bbae67 --- /dev/null +++ b/src/block-decorations-component.coffee @@ -0,0 +1,45 @@ +cloneObject = (object) -> + clone = {} + clone[key] = value for key, value of object + clone + +module.exports = +class BlockDecorationsComponent + constructor: (@views, @domElementPool) -> + @domNode = @domElementPool.buildElement("div") + @newState = null + @oldState = null + @blockDecorationNodesById = {} + + getDomNode: -> + @domNode + + updateSync: (state) -> + @newState = state.content + @oldState ?= {blockDecorations: {}} + + for id, blockDecorationState of @newState.blockDecorations + if @oldState.blockDecorations.hasOwnProperty(id) + @updateBlockDecorationNode(id) + else + @createAndAppendBlockDecorationNode(id) + + @oldState.blockDecorations[id] = cloneObject(blockDecorationState) + + createAndAppendBlockDecorationNode: (id) -> + blockDecorationState = @newState.blockDecorations[id] + blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item) + blockDecorationNode.classList.add("block-decoration-row-#{blockDecorationState.screenRow}") + + @domNode.appendChild(blockDecorationNode) + + @blockDecorationNodesById[id] = blockDecorationNode + + updateBlockDecorationNode: (id) -> + newBlockDecorationState = @newState.blockDecorations[id] + oldBlockDecorationState = @oldState.blockDecorations[id] + blockDecorationNode = @blockDecorationNodesById[id] + + if newBlockDecorationState.screenRow isnt oldBlockDecorationState.screenRow + blockDecorationNode.classList.remove("block-decoration-row-#{oldBlockDecorationState.screenRow}") + blockDecorationNode.classList.add("block-decoration-row-#{newBlockDecorationState.screenRow}") diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 430b0c0fd..b20e00751 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -13,6 +13,7 @@ ScrollbarCornerComponent = require './scrollbar-corner-component' OverlayManager = require './overlay-manager' DOMElementPool = require './dom-element-pool' LinesYardstick = require './lines-yardstick' +BlockDecorationsComponent = require './block-decorations-component' module.exports = class TextEditorComponent @@ -59,6 +60,7 @@ class TextEditorComponent @presenter.onDidUpdateState(@requestUpdate) @domElementPool = new DOMElementPool + @blockDecorationsComponent = new BlockDecorationsComponent(@views, @domElementPool) @domNode = document.createElement('div') if @useShadowDOM @@ -68,9 +70,11 @@ class TextEditorComponent insertionPoint.setAttribute('select', 'atom-overlay') @domNode.appendChild(insertionPoint) @overlayManager = new OverlayManager(@presenter, @hostElement, @views) + @hostElement.appendChild(@blockDecorationsComponent.getDomNode()) else @domNode.classList.add('editor-contents') @overlayManager = new OverlayManager(@presenter, @domNode, @views) + @domNode.appendChild(@blockDecorationsComponent.getDomNode()) @scrollViewNode = document.createElement('div') @scrollViewNode.classList.add('scroll-view') @@ -156,6 +160,7 @@ class TextEditorComponent @hiddenInputComponent.updateSync(@newState) @linesComponent.updateSync(@newState) + @blockDecorationsComponent.updateSync(@newState) @horizontalScrollbarComponent.updateSync(@newState) @verticalScrollbarComponent.updateSync(@newState) @scrollbarCornerComponent.updateSync(@newState) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index f88c9149c..136756538 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1398,11 +1398,11 @@ class TextEditor extends Model Section: Decorations ### - addBlockDecorationForScreenRow: (screenRow, element) -> + addBlockDecorationForScreenRow: (screenRow, item) -> @decorateMarker( @markScreenPosition([screenRow, 0], invalidate: "never"), type: "block", - element: element + item: item ) # Essential: Add a decoration that tracks a {TextEditorMarker}. When the From 47b16c513c9fdace8c82726276482ef93f5e0f3b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 1 Dec 2015 09:30:38 +0100 Subject: [PATCH 025/173] Make sure cursors are updated with respect to block decorations --- spec/fake-lines-yardstick.coffee | 5 ++++- spec/text-editor-presenter-spec.coffee | 29 ++++++++++++++++++++++++++ src/lines-yardstick.coffee | 5 ++++- src/text-editor-presenter.coffee | 14 ++++++++++++- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index 9dfeb7da4..e6d1f4f53 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -31,7 +31,7 @@ class FakeLinesYardstick targetColumn = screenPosition.column baseCharacterWidth = @model.getDefaultCharWidth() - top = @topPixelPositionForRow(targetRow) + top = @bottomPixelPositionForRow(targetRow) left = 0 column = 0 @@ -80,6 +80,9 @@ class FakeLinesYardstick top += @presenter.getScreenRowHeight(row) top + bottomPixelPositionForRow: (targetRow) -> + @topPixelPositionForRow(targetRow + 1) - @model.getLineHeightInPixels() + topPixelPositionForRows: (startRow, endRow, step) -> results = {} top = 0 diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 903c6ebf7..83f0d8236 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1527,6 +1527,35 @@ describe "TextEditorPresenter", -> presenter.setHorizontalScrollbarHeight(10) expect(presenter.getState().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 = editor.addBlockDecorationForScreenRow(0) + blockDecoration2 = editor.addBlockDecorationForScreenRow(1) + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) + + waitsForStateToUpdate presenter + 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() + it "updates when ::scrollTop changes", -> editor.setSelectedBufferRanges([ [[1, 2], [1, 2]], diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index d827d7bce..bb2c7faba 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -90,7 +90,7 @@ class LinesYardstick @prepareScreenRowsForMeasurement([targetRow]) unless measureVisibleLinesOnly - top = @topPixelPositionForRow(targetRow) + top = @bottomPixelPositionForRow(targetRow) left = @leftPixelPositionForScreenPosition(targetRow, targetColumn) @clearScreenRowsForMeasurement() unless measureVisibleLinesOnly @@ -192,6 +192,9 @@ class LinesYardstick top += @presenter.getScreenRowHeight(row) top + bottomPixelPositionForRow: (targetRow) -> + @topPixelPositionForRow(targetRow + 1) - @model.getLineHeightInPixels() + topPixelPositionForRows: (startRow, endRow, step) -> results = {} top = 0 diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 8fd0a790a..91ff18012 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -199,7 +199,19 @@ class TextEditorPresenter @emitDidUpdateState() @disposables.add @blockDecorationsPresenter.onDidUpdateState => + @shouldUpdateHeightState = true @shouldUpdateVerticalScrollState = true + @shouldUpdateHorizontalScrollState = true + @shouldUpdateScrollbarsState = true + @shouldUpdateContentState = true + @shouldUpdateDecorations = true + @shouldUpdateCursorsState = true + @shouldUpdateLinesState = true + @shouldUpdateLineNumberGutterState = true + @shouldUpdateLineNumbersState = true + @shouldUpdateGutterOrderState = true + @shouldUpdateCustomGutterDecorationState = true + @emitDidUpdateState() @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) @@ -1406,7 +1418,7 @@ class TextEditorPresenter @emitDidUpdateState() - setBlockDecorationDimensions: (decoration, width, height) -> + setBlockDecorationDimensions: -> @blockDecorationsPresenter.setDimensionsForDecoration(arguments...) observeCursor: (cursor) -> From d24290357a647c75e002d1626f17f39e5e6bfa9e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 1 Dec 2015 13:36:23 +0100 Subject: [PATCH 026/173] Implement block decorations in the components land --- spec/text-editor-component-spec.js | 104 +++++++++++++++++++++++++ src/block-decorations-component.coffee | 23 ++++-- src/line-numbers-tile-component.coffee | 7 +- src/lines-tile-component.coffee | 37 +++++++++ src/text-editor-component.coffee | 6 +- 5 files changed, 167 insertions(+), 10 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 609d20291..18ad030c0 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1651,6 +1651,110 @@ describe('TextEditorComponent', function () { }) }) + describe('block decorations rendering', function () { + function createBlockDecorationForScreenRowWith(screenRow, {className}) { + let item = document.createElement("div") + item.className = className || "" + let blockDecoration = editor.addBlockDecorationForScreenRow(screenRow, item) + return [item, blockDecoration] + } + + afterEach(function () { + atom.themes.removeStylesheet('test') + }) + + it("renders all the editor's block decorations, inserting them in the appropriate spots between lines", async function () { + wrapperNode.style.height = 9 * lineHeightInPixels + 'px' + component.measureDimensions() + await nextViewUpdatePromise() + + let [item1, blockDecoration1] = createBlockDecorationForScreenRowWith(0, {className: "decoration-1"}) + let [item2, blockDecoration2] = createBlockDecorationForScreenRowWith(2, {className: "decoration-2"}) + let [item3, blockDecoration3] = createBlockDecorationForScreenRowWith(4, {className: "decoration-3"}) + let [item4, blockDecoration4] = createBlockDecorationForScreenRowWith(7, {className: "decoration-4"}) + + 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; } + ` + + await nextViewUpdatePromise() + + 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 + "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")).toBe(item4) + + 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) + expect(item4.getBoundingClientRect().height).toBe(0) // hidden + + editor.setCursorScreenPosition([0, 0]) + editor.insertNewline() + blockDecoration1.destroy() + + await nextViewUpdatePromise() + + 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 + 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 + "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(item1.getBoundingClientRect().height).toBe(0) // hidden + expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) + expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) + expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 40 + 100) + + await nextViewUpdatePromise() + + atom.styles.addStyleSheet ` + atom-text-editor .decoration-2 { height: 60px !important; } + ` + + await nextViewUpdatePromise() + + 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 + 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 + "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(item1.getBoundingClientRect().height).toBe(0) // hidden + expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) + expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60) + expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 60 + 100) + }) + }) + describe('highlight decoration rendering', function () { let decoration, marker, scrollViewClientLeft diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index 4f3bbae67..0f9270660 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -5,19 +5,21 @@ cloneObject = (object) -> module.exports = class BlockDecorationsComponent - constructor: (@views, @domElementPool) -> - @domNode = @domElementPool.buildElement("div") + constructor: (@container, @views, @presenter, @domElementPool) -> @newState = null @oldState = null @blockDecorationNodesById = {} - getDomNode: -> - @domNode - updateSync: (state) -> @newState = state.content @oldState ?= {blockDecorations: {}} + 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) @@ -26,12 +28,21 @@ class BlockDecorationsComponent @oldState.blockDecorations[id] = cloneObject(blockDecorationState) + measureBlockDecorations: -> + for decorationId, blockDecorationNode of @blockDecorationNodesById + decoration = @newState.blockDecorations[decorationId].decoration + @presenter.setBlockDecorationDimensions( + decoration, + blockDecorationNode.offsetWidth, + blockDecorationNode.offsetHeight + ) + createAndAppendBlockDecorationNode: (id) -> blockDecorationState = @newState.blockDecorations[id] blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item) blockDecorationNode.classList.add("block-decoration-row-#{blockDecorationState.screenRow}") - @domNode.appendChild(blockDecorationNode) + @container.appendChild(blockDecorationNode) @blockDecorationNodesById[id] = blockDecorationNode diff --git a/src/line-numbers-tile-component.coffee b/src/line-numbers-tile-component.coffee index 32dbca0a2..30f13fff2 100644 --- a/src/line-numbers-tile-component.coffee +++ b/src/line-numbers-tile-component.coffee @@ -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? diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index 6b4ac80ba..f4b0dbab5 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -20,6 +20,7 @@ class LinesTileComponent @screenRowsByLineId = {} @lineIdsByScreenRow = {} @textNodesByLineId = {} + @insertionPointsByLineId = {} @domNode = @domElementPool.buildElement("div") @domNode.style.position = "absolute" @domNode.style.display = "block" @@ -80,6 +81,8 @@ class LinesTileComponent removeLineNode: (id) -> @domElementPool.freeElementAndDescendants(@lineNodesByLineId[id]) + @removeBlockDecorationInsertionPoint(id) + delete @lineNodesByLineId[id] delete @textNodesByLineId[id] delete @lineIdsByScreenRow[@screenRowsByLineId[id]] @@ -116,6 +119,31 @@ class LinesTileComponent else @domNode.appendChild(lineNode) + @insertBlockDecorationInsertionPoint(id) + + removeBlockDecorationInsertionPoint: (id) -> + if insertionPoint = @insertionPointsByLineId[id] + @domElementPool.freeElementAndDescendants(insertionPoint) + delete @insertionPointsByLineId[id] + + insertBlockDecorationInsertionPoint: (id) -> + {hasBlockDecorations} = @newTileState.lines[id] + + if hasBlockDecorations + lineNode = @lineNodesByLineId[id] + insertionPoint = @domElementPool.buildElement("content") + @domNode.insertBefore(insertionPoint, lineNode) + @insertionPointsByLineId[id] = insertionPoint + + @updateBlockDecorationInsertionPoint(id) + + updateBlockDecorationInsertionPoint: (id) -> + {screenRow} = @newTileState.lines[id] + + if insertionPoint = @insertionPointsByLineId[id] + insertionPoint.dataset.screenRow = screenRow + insertionPoint.setAttribute("select", ".block-decoration-row-#{screenRow}") + findNodeNextTo: (node) -> for nextNode, index in @domNode.children continue if index is 0 # skips highlights node @@ -336,7 +364,16 @@ class LinesTileComponent oldLineState.decorationClasses = newLineState.decorationClasses + if not oldLineState.hasBlockDecorations and newLineState.hasBlockDecorations + @insertBlockDecorationInsertionPoint(id) + else if oldLineState.hasBlockDecorations and not newLineState.hasBlockDecorations + @removeBlockDecorationInsertionPoint(id) + + oldLineState.hasBlockDecorations = newLineState.hasBlockDecorations + if newLineState.screenRow isnt oldLineState.screenRow + @updateBlockDecorationInsertionPoint(id) + lineNode.dataset.screenRow = newLineState.screenRow oldLineState.screenRow = newLineState.screenRow @lineIdsByScreenRow[newLineState.screenRow] = id diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index b20e00751..faf6a6715 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -60,7 +60,6 @@ class TextEditorComponent @presenter.onDidUpdateState(@requestUpdate) @domElementPool = new DOMElementPool - @blockDecorationsComponent = new BlockDecorationsComponent(@views, @domElementPool) @domNode = document.createElement('div') if @useShadowDOM @@ -70,11 +69,11 @@ class TextEditorComponent insertionPoint.setAttribute('select', 'atom-overlay') @domNode.appendChild(insertionPoint) @overlayManager = new OverlayManager(@presenter, @hostElement, @views) - @hostElement.appendChild(@blockDecorationsComponent.getDomNode()) + @blockDecorationsComponent = new BlockDecorationsComponent(@hostElement, @views, @presenter, @domElementPool) else @domNode.classList.add('editor-contents') @overlayManager = new OverlayManager(@presenter, @domNode, @views) - @domNode.appendChild(@blockDecorationsComponent.getDomNode()) + @blockDecorationsComponent = new BlockDecorationsComponent(@domNode, @views, @presenter, @domElementPool) @scrollViewNode = document.createElement('div') @scrollViewNode.classList.add('scroll-view') @@ -177,6 +176,7 @@ class TextEditorComponent readAfterUpdateSync: => @overlayManager?.measureOverlays() + @blockDecorationsComponent.measureBlockDecorations() mountGutterContainerComponent: -> @gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views}) From e0b1cabb2118c719b823ec56c0a006419d28b8d2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 1 Dec 2015 19:33:35 +0100 Subject: [PATCH 027/173] Implement a linear structure for block decoration coordinates We still cannot handle `::rowForTopPixelPosition` when the passed top position is in the middle of two rows and there's also a block decoration. We'll get there eventually. Also, the specs in this commit should serve as a good test suite for the future logarithmic data structure. --- spec/line-top-index-spec.js | 184 +++++++++++++++++++++++++++++++++++ src/linear-line-top-index.js | 90 +++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 spec/line-top-index-spec.js create mode 100644 src/linear-line-top-index.js diff --git a/spec/line-top-index-spec.js b/spec/line-top-index-spec.js new file mode 100644 index 000000000..9a9e33e3d --- /dev/null +++ b/spec/line-top-index-spec.js @@ -0,0 +1,184 @@ +/** @babel */ + +const LineTopIndex = require('../src/linear-line-top-index') + +describe("LineTopIndex", function () { + let lineTopIndex + + beforeEach(function () { + lineTopIndex = new LineTopIndex() + lineTopIndex.setDefaultLineHeight(10) + lineTopIndex.setMaxRow(12) + }) + + describe("::topPixelPositionForRow(row)", function () { + it("performs the simple math when there are no block decorations", function () { + expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) + expect(lineTopIndex.topPixelPositionForRow(4)).toBe(40) + expect(lineTopIndex.topPixelPositionForRow(5)).toBe(50) + expect(lineTopIndex.topPixelPositionForRow(12)).toBe(120) + expect(lineTopIndex.topPixelPositionForRow(13)).toBe(120) + expect(lineTopIndex.topPixelPositionForRow(14)).toBe(120) + + lineTopIndex.splice(0, 2, 3) + + expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) + expect(lineTopIndex.topPixelPositionForRow(4)).toBe(40) + expect(lineTopIndex.topPixelPositionForRow(5)).toBe(50) + expect(lineTopIndex.topPixelPositionForRow(12)).toBe(120) + expect(lineTopIndex.topPixelPositionForRow(13)).toBe(130) + expect(lineTopIndex.topPixelPositionForRow(14)).toBe(130) + }) + + it("takes into account inserted and removed blocks", function () { + let block1 = lineTopIndex.insertBlock(0, 10) + let block2 = lineTopIndex.insertBlock(3, 20) + let block3 = lineTopIndex.insertBlock(5, 20) + + expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) + expect(lineTopIndex.topPixelPositionForRow(1)).toBe(20) + expect(lineTopIndex.topPixelPositionForRow(2)).toBe(30) + expect(lineTopIndex.topPixelPositionForRow(3)).toBe(40) + expect(lineTopIndex.topPixelPositionForRow(4)).toBe(70) + expect(lineTopIndex.topPixelPositionForRow(5)).toBe(80) + expect(lineTopIndex.topPixelPositionForRow(6)).toBe(110) + + lineTopIndex.removeBlock(block1) + lineTopIndex.removeBlock(block3) + + expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) + expect(lineTopIndex.topPixelPositionForRow(1)).toBe(10) + expect(lineTopIndex.topPixelPositionForRow(2)).toBe(20) + expect(lineTopIndex.topPixelPositionForRow(3)).toBe(30) + expect(lineTopIndex.topPixelPositionForRow(4)).toBe(60) + expect(lineTopIndex.topPixelPositionForRow(5)).toBe(70) + expect(lineTopIndex.topPixelPositionForRow(6)).toBe(80) + }) + + it("moves blocks down/up when splicing regions", function () { + let block1 = lineTopIndex.insertBlock(3, 20) + let block2 = lineTopIndex.insertBlock(5, 30) + + lineTopIndex.splice(0, 0, 4) + + expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) + expect(lineTopIndex.topPixelPositionForRow(6)).toBe(60) + expect(lineTopIndex.topPixelPositionForRow(7)).toBe(70) + expect(lineTopIndex.topPixelPositionForRow(8)).toBe(100) + expect(lineTopIndex.topPixelPositionForRow(9)).toBe(110) + expect(lineTopIndex.topPixelPositionForRow(10)).toBe(150) + expect(lineTopIndex.topPixelPositionForRow(11)).toBe(160) + + lineTopIndex.splice(0, 6, 2) + + expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) + expect(lineTopIndex.topPixelPositionForRow(3)).toBe(30) + expect(lineTopIndex.topPixelPositionForRow(4)).toBe(60) + expect(lineTopIndex.topPixelPositionForRow(5)).toBe(70) + expect(lineTopIndex.topPixelPositionForRow(6)).toBe(110) + + lineTopIndex.splice(2, 4, 0) + + expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) + expect(lineTopIndex.topPixelPositionForRow(1)).toBe(10) + expect(lineTopIndex.topPixelPositionForRow(2)).toBe(20) + expect(lineTopIndex.topPixelPositionForRow(3)).toBe(80) + expect(lineTopIndex.topPixelPositionForRow(4)).toBe(90) + expect(lineTopIndex.topPixelPositionForRow(5)).toBe(100) + expect(lineTopIndex.topPixelPositionForRow(6)).toBe(110) + expect(lineTopIndex.topPixelPositionForRow(7)).toBe(120) + expect(lineTopIndex.topPixelPositionForRow(8)).toBe(130) + expect(lineTopIndex.topPixelPositionForRow(9)).toBe(130) + }) + }) + + describe("::rowForTopPixelPosition(top)", function () { + it("performs the simple math when there are no block decorations", function () { + expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(4) + expect(lineTopIndex.rowForTopPixelPosition(44)).toBe(4) + expect(lineTopIndex.rowForTopPixelPosition(46)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(50)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(120)).toBe(12) + expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(12) + expect(lineTopIndex.rowForTopPixelPosition(140)).toBe(12) + expect(lineTopIndex.rowForTopPixelPosition(145)).toBe(12) + + lineTopIndex.splice(0, 2, 3) + + expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(4) + expect(lineTopIndex.rowForTopPixelPosition(50)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(120)).toBe(12) + expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(13) + expect(lineTopIndex.rowForTopPixelPosition(140)).toBe(13) + }) + + it("takes into account inserted and removed blocks", function () { + let block1 = lineTopIndex.insertBlock(0, 10) + let block2 = lineTopIndex.insertBlock(3, 20) + let block3 = lineTopIndex.insertBlock(5, 20) + + expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(6)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(10)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(12)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(17)).toBe(1) + expect(lineTopIndex.rowForTopPixelPosition(20)).toBe(1) + expect(lineTopIndex.rowForTopPixelPosition(30)).toBe(2) + expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(3) + expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(4) + expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(90)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(100)).toBe(6) + expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) + + lineTopIndex.removeBlock(block1) + lineTopIndex.removeBlock(block3) + + expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(10)).toBe(1) + expect(lineTopIndex.rowForTopPixelPosition(20)).toBe(2) + expect(lineTopIndex.rowForTopPixelPosition(30)).toBe(3) + expect(lineTopIndex.rowForTopPixelPosition(60)).toBe(4) + expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(6) + }) + + it("moves blocks down/up when splicing regions", function () { + let block1 = lineTopIndex.insertBlock(3, 20) + let block2 = lineTopIndex.insertBlock(5, 30) + + lineTopIndex.splice(0, 0, 4) + + expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(60)).toBe(6) + expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(7) + expect(lineTopIndex.rowForTopPixelPosition(100)).toBe(8) + expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(9) + expect(lineTopIndex.rowForTopPixelPosition(150)).toBe(10) + expect(lineTopIndex.rowForTopPixelPosition(160)).toBe(11) + + lineTopIndex.splice(0, 6, 2) + + expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(30)).toBe(3) + expect(lineTopIndex.rowForTopPixelPosition(60)).toBe(4) + expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) + + lineTopIndex.splice(2, 4, 0) + + expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) + expect(lineTopIndex.rowForTopPixelPosition(10)).toBe(1) + expect(lineTopIndex.rowForTopPixelPosition(20)).toBe(2) + expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(3) + expect(lineTopIndex.rowForTopPixelPosition(90)).toBe(4) + expect(lineTopIndex.rowForTopPixelPosition(100)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) + expect(lineTopIndex.rowForTopPixelPosition(120)).toBe(7) + expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(8) + expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(8) + }) + }) +}) diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js new file mode 100644 index 000000000..638997d49 --- /dev/null +++ b/src/linear-line-top-index.js @@ -0,0 +1,90 @@ +/** @babel */ + +module.exports = +class LineTopIndex { + constructor () { + this.idCounter = 0 + this.blocks = [] + this.maxRow = 0 + this.defaultLineHeight = 0 + } + + setDefaultLineHeight (lineHeight) { + this.defaultLineHeight = lineHeight + } + + setMaxRow (maxRow) { + this.maxRow = maxRow + } + + insertBlock (row, height) { + let id = this.idCounter++ + this.blocks.push({id, row, height}) + this.blocks.sort((a, b) => a.row - b.row) + return id + } + + changeBlockHeight (id, height) { + let block = this.blocks.find((block) => block.id == id) + if (block) { + block.height = height + } + } + + removeBlock (id) { + let index = this.blocks.findIndex((block) => block.id == id) + if (index != -1) { + this.blocks.splice(index, 1) + } + } + + splice (startRow, oldExtent, newExtent) { + let blocksHeight = 0 + this.blocks.forEach(function (block) { + if (block.row >= startRow) { + if (block.row >= startRow + oldExtent) { + block.row += newExtent - oldExtent + } else { + block.row = startRow + newExtent + // invalidate marker? + } + + block.top = block.row * this.defaultLineHeight + blocksHeight + blocksHeight += block.height + } + }) + + this.setMaxRow(this.maxRow + newExtent - oldExtent) + } + + topPixelPositionForRow (row) { + row = Math.min(row, this.maxRow) + let linesHeight = row * this.defaultLineHeight + let blocksHeight = this.blocks.filter((block) => block.row < row).reduce((a, b) => a + b.height, 0) + return linesHeight + blocksHeight + } + + bottomPixelPositionForRow (row) { + return this.topPixelPositionForRow(row + 1) - this.defaultLineHeight + } + + rowForTopPixelPosition (top) { + let blocksHeight = 0 + let lastRow = 0 + let lastTop = 0 + for (let block of this.blocks) { + blocksHeight += block.height + let linesHeight = block.row * this.defaultLineHeight + if (blocksHeight + linesHeight > top) { + break + } else { + lastRow = block.row + lastTop = blocksHeight + linesHeight + } + } + + let remainingHeight = Math.max(0, top - lastTop) + let remainingRows = Math.round(remainingHeight / this.defaultLineHeight) + return Math.min(this.maxRow, lastRow + remainingRows) + } +} From 1e07b8df0553572369c4f8c2ea8249e646573be9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 09:13:06 +0100 Subject: [PATCH 028/173] Handle position between rows correctly --- spec/line-top-index-spec.js | 7 ++++++- src/linear-line-top-index.js | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/spec/line-top-index-spec.js b/spec/line-top-index-spec.js index 9a9e33e3d..f2c3f9ca5 100644 --- a/spec/line-top-index-spec.js +++ b/spec/line-top-index-spec.js @@ -130,8 +130,13 @@ describe("LineTopIndex", function () { expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(4) expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(5) expect(lineTopIndex.rowForTopPixelPosition(90)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(100)).toBe(6) + expect(lineTopIndex.rowForTopPixelPosition(95)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(105)).toBe(6) expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) + expect(lineTopIndex.rowForTopPixelPosition(160)).toBe(11) + expect(lineTopIndex.rowForTopPixelPosition(166)).toBe(12) + expect(lineTopIndex.rowForTopPixelPosition(170)).toBe(12) + expect(lineTopIndex.rowForTopPixelPosition(240)).toBe(12) lineTopIndex.removeBlock(block1) lineTopIndex.removeBlock(block3) diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 638997d49..7d84058d1 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -73,11 +73,16 @@ class LineTopIndex { let lastRow = 0 let lastTop = 0 for (let block of this.blocks) { - blocksHeight += block.height + let nextBlocksHeight = blocksHeight + block.height let linesHeight = block.row * this.defaultLineHeight - if (blocksHeight + linesHeight > top) { - break + if (nextBlocksHeight + linesHeight > top) { + while (lastRow < block.row && lastTop + this.defaultLineHeight / 2 < top) { + lastTop += this.defaultLineHeight + lastRow++ + } + return lastRow } else { + blocksHeight = nextBlocksHeight lastRow = block.row lastTop = blocksHeight + linesHeight } From 60b1d20667b6855b6b9ff27c5513fc738af02045 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 09:55:47 +0100 Subject: [PATCH 029/173] :fire: Remove old code --- src/linear-line-top-index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 7d84058d1..727c3b9af 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -39,7 +39,6 @@ class LineTopIndex { } splice (startRow, oldExtent, newExtent) { - let blocksHeight = 0 this.blocks.forEach(function (block) { if (block.row >= startRow) { if (block.row >= startRow + oldExtent) { @@ -48,9 +47,6 @@ class LineTopIndex { block.row = startRow + newExtent // invalidate marker? } - - block.top = block.row * this.defaultLineHeight + blocksHeight - blocksHeight += block.height } }) From 1f20ab517045396638ab3395e35511207f96cb40 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 14:49:36 +0100 Subject: [PATCH 030/173] Use LinearLineTopIndex in BlockDecorationsPresenter --- src/block-decorations-presenter.js | 150 ++++++++++++----------------- src/linear-line-top-index.js | 16 ++- src/text-editor-presenter.coffee | 7 +- 3 files changed, 78 insertions(+), 95 deletions(-) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 762372b15..4d787c25f 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -1,6 +1,7 @@ /** @babel */ const {CompositeDisposable, Emitter} = require('event-kit') +const LineTopIndex = require('./linear-line-top-index') module.exports = class BlockDecorationsPresenter { @@ -8,14 +9,11 @@ class BlockDecorationsPresenter { this.model = model this.disposables = new CompositeDisposable() this.emitter = new Emitter() - this.decorationsByScreenRow = new Map - this.heightByScreenRow = new Map - this.screenRowByDecoration = new Map - this.dimensionsByDecoration = new Map - this.moveOperationsByDecoration = new Map - this.addOperationsByDecoration = new Map - this.changeOperationsByDecoration = new Map this.firstUpdate = true + this.lineTopIndex = new LineTopIndex + this.blocksByDecoration = new Map + this.decorationsByBlock = new Map + this.observedDecorations = new Set this.observeModel() } @@ -28,10 +26,21 @@ class BlockDecorationsPresenter { return this.emitter.on("did-update-state", callback) } + setLineHeight (lineHeight) { + this.lineTopIndex.setDefaultLineHeight(lineHeight) + } + observeModel () { - this.disposables.add( - this.model.onDidAddDecoration((decoration) => this.observeDecoration(decoration)) - ) + this.lineTopIndex.setMaxRow(this.model.getScreenLineCount()) + this.lineTopIndex.setDefaultLineHeight(this.model.getLineHeightInPixels()) + this.disposables.add(this.model.onDidAddDecoration((decoration) => { + this.observeDecoration(decoration) + })) + this.disposables.add(this.model.onDidChange(({start, end, screenDelta}) => { + let oldExtent = end - start + let newExtent = Math.max(0, end - start + screenDelta) + this.lineTopIndex.splice(start, oldExtent, newExtent) + })) } update () { @@ -44,101 +53,57 @@ class BlockDecorationsPresenter { } fullUpdate () { - this.decorationsByScreenRow.clear() - this.screenRowByDecoration.clear() - this.moveOperationsByDecoration.clear() - this.addOperationsByDecoration.clear() - for (let decoration of this.model.getDecorations({type: "block"})) { - let screenRow = decoration.getMarker().getHeadScreenPosition().row - this.addDecorationToScreenRow(screenRow, decoration) this.observeDecoration(decoration) } } incrementalUpdate () { - for (let [changedDecoration] of this.changeOperationsByDecoration) { - let screenRow = changedDecoration.getMarker().getHeadScreenPosition().row - this.recalculateScreenRowHeight(screenRow) - } - - for (let [addedDecoration] of this.addOperationsByDecoration) { - let screenRow = addedDecoration.getMarker().getHeadScreenPosition().row - this.addDecorationToScreenRow(screenRow, addedDecoration) - } - - for (let [movedDecoration, moveOperations] of this.moveOperationsByDecoration) { - let {oldHeadScreenPosition} = moveOperations[0] - let {newHeadScreenPosition} = moveOperations[moveOperations.length - 1] - this.removeDecorationFromScreenRow(oldHeadScreenPosition.row, movedDecoration) - this.addDecorationToScreenRow(newHeadScreenPosition.row, movedDecoration) - } - - this.addOperationsByDecoration.clear() - this.moveOperationsByDecoration.clear() - this.changeOperationsByDecoration.clear() } setDimensionsForDecoration (decoration, width, height) { - this.changeOperationsByDecoration.set(decoration, true) - this.dimensionsByDecoration.set(decoration, {width, height}) + let block = this.blocksByDecoration.get(decoration) + if (block) { + this.lineTopIndex.resizeBlock(block, height) + } else { + this.observeDecoration(decoration) + block = this.blocksByDecoration.get(decoration) + this.lineTopIndex.resizeBlock(block, height) + } + this.emitter.emit("did-update-state") } heightForScreenRow (screenRow) { - return this.heightByScreenRow.get(screenRow) || 0 - } - - addDecorationToScreenRow (screenRow, decoration) { - let decorations = this.decorationsForScreenRow(screenRow) - if (!decorations.has(decoration)) { - decorations.add(decoration) - this.screenRowByDecoration.set(decoration, screenRow) - this.recalculateScreenRowHeight(screenRow) - } - } - - removeDecorationFromScreenRow (screenRow, decoration) { - if (!Number.isInteger(screenRow) || !decoration) { - return - } - - let decorations = this.decorationsForScreenRow(screenRow) - if (decorations.has(decoration)) { - decorations.delete(decoration) - this.recalculateScreenRowHeight(screenRow) - } + return this.lineTopIndex.bottomPixelPositionForRow(screenRow) - this.lineTopIndex.topPixelPositionForRow(screenRow) } decorationsForScreenRow (screenRow) { - if (!this.decorationsByScreenRow.has(screenRow)) { - this.decorationsByScreenRow.set(screenRow, new Set()) - } - - return this.decorationsByScreenRow.get(screenRow) + let blocks = this.lineTopIndex.allBlocks().filter((block) => block.row == screenRow) + return blocks.map((block) => this.decorationsByBlock.get(block.id)).filter((decoration) => decoration) } getAllDecorationsByScreenRow () { - return this.decorationsByScreenRow - } - - getDecorationDimensions (decoration) { - return this.dimensionsByDecoration.get(decoration) || {width: 0, height: 0} - } - - recalculateScreenRowHeight (screenRow) { - let height = 0 - for (let decoration of this.decorationsForScreenRow(screenRow)) { - height += this.getDecorationDimensions(decoration).height + let blocks = this.lineTopIndex.allBlocks() + let decorationsByScreenRow = new Map + for (let block of blocks) { + let decoration = this.decorationsByBlock.get(block.id) + if (decoration) { + let decorations = decorationsByScreenRow.get(block.row) || [] + decorations.push(decoration) + decorationsByScreenRow.set(block.row, decorations) + } } - this.heightByScreenRow.set(screenRow, height) + + return decorationsByScreenRow } observeDecoration (decoration) { - if (!decoration.isType("block")) { + if (!decoration.isType("block") || this.observedDecorations.has(decoration)) { return } + // TODO: change this with a "on manual did change" event. let didMoveDisposable = decoration.getMarker().onDidChange((markerEvent) => { this.didMoveDecoration(decoration, markerEvent) }) @@ -146,31 +111,36 @@ class BlockDecorationsPresenter { let didDestroyDisposable = decoration.onDidDestroy(() => { didMoveDisposable.dispose() didDestroyDisposable.dispose() + this.observedDecorations.delete(decoration) this.didDestroyDecoration(decoration) }) this.didAddDecoration(decoration) + this.observedDecorations.add(decoration) } didAddDecoration (decoration) { - this.addOperationsByDecoration.set(decoration, true) + let screenRow = decoration.getMarker().getHeadScreenPosition().row + let block = this.lineTopIndex.insertBlock(screenRow, 0) + this.decorationsByBlock.set(block, decoration) + this.blocksByDecoration.set(decoration, block) this.emitter.emit("did-update-state") } - didMoveDecoration (decoration, markerEvent) { - let moveOperations = this.moveOperationsByDecoration.get(decoration) || [] - moveOperations.push(markerEvent) - this.moveOperationsByDecoration.set(decoration, moveOperations) + didMoveDecoration (decoration, {oldHeadScreenPosition, newHeadScreenPosition}) { + let block = this.blocksByDecoration.get(decoration) + let newScreenRow = decoration.getMarker().getHeadScreenPosition().row + this.lineTopIndex.moveBlock(block, newScreenRow) this.emitter.emit("did-update-state") } didDestroyDecoration (decoration) { - this.moveOperationsByDecoration.delete(decoration) - this.addOperationsByDecoration.delete(decoration) - - this.removeDecorationFromScreenRow( - this.screenRowByDecoration.get(decoration), decoration - ) + let block = this.blocksByDecoration.get(decoration) + if (block) { + this.lineTopIndex.removeBlock(block) + this.blocksByDecoration.delete(decoration) + this.decorationsByBlock.delete(block) + } this.emitter.emit("did-update-state") } } diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 727c3b9af..1f4a62fb9 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -3,7 +3,7 @@ module.exports = class LineTopIndex { constructor () { - this.idCounter = 0 + this.idCounter = 1 this.blocks = [] this.maxRow = 0 this.defaultLineHeight = 0 @@ -24,13 +24,21 @@ class LineTopIndex { return id } - changeBlockHeight (id, height) { + resizeBlock (id, height) { let block = this.blocks.find((block) => block.id == id) if (block) { block.height = height } } + moveBlock (id, newRow) { + let block = this.blocks.find((block) => block.id == id) + if (block) { + block.row = newRow + this.blocks.sort((a, b) => a.row - b.row) + } + } + removeBlock (id) { let index = this.blocks.findIndex((block) => block.id == id) if (index != -1) { @@ -38,6 +46,10 @@ class LineTopIndex { } } + allBlocks () { + return this.blocks + } + splice (startRow, oldExtent, newExtent) { this.blocks.forEach(function (block) { if (block.row >= startRow) { diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 91ff18012..41209d128 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -503,7 +503,7 @@ class TextEditorPresenter lineState = tileState.lines[line.id] lineState.screenRow = screenRow lineState.decorationClasses = @lineDecorationClassesForRow(screenRow) - lineState.hasBlockDecorations = blockDecorations.size isnt 0 + lineState.hasBlockDecorations = blockDecorations.length isnt 0 else tileState.lines[line.id] = screenRow: screenRow @@ -520,7 +520,7 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) - hasBlockDecorations: blockDecorations.size isnt 0 + hasBlockDecorations: blockDecorations.length isnt 0 for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) @@ -1140,6 +1140,7 @@ class TextEditorPresenter @lineHeight = lineHeight @restoreScrollTopIfNeeded() @model.setLineHeightInPixels(lineHeight) + @blockDecorationsPresenter.setLineHeight(lineHeight) @shouldUpdateHeightState = true @shouldUpdateHorizontalScrollState = true @shouldUpdateVerticalScrollState = true @@ -1222,7 +1223,7 @@ class TextEditorPresenter @state.content.blockDecorations = {} @blockDecorationsPresenter.getAllDecorationsByScreenRow().forEach (decorations, screenRow) => - decorations.forEach (decoration) => + for decoration in decorations @state.content.blockDecorations[decoration.id] = {decoration, screenRow} updateLineDecorations: -> From f30e4ccc9de3a437d030ea74388bb04c0f6b69b7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 16:01:55 +0100 Subject: [PATCH 031/173] Use the new LineTopIndex in TextEditorPresenter --- spec/fake-lines-yardstick.coffee | 41 +++--------------------- spec/text-editor-component-spec.js | 12 +++---- spec/text-editor-presenter-spec.coffee | 5 ++- src/block-decorations-presenter.js | 4 +-- src/linear-line-top-index.js | 15 +++++++-- src/lines-yardstick.coffee | 43 +++----------------------- src/text-editor-component.coffee | 6 ++-- src/text-editor-presenter.coffee | 41 ++++++++++-------------- 8 files changed, 54 insertions(+), 113 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index e6d1f4f53..3d2ebe7d5 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -2,7 +2,7 @@ module.exports = class FakeLinesYardstick - constructor: (@model, @presenter) -> + constructor: (@model, @presenter, @lineTopIndex) -> @characterWidthsByScope = {} prepareScreenRowsForMeasurement: -> @@ -31,7 +31,7 @@ class FakeLinesYardstick targetColumn = screenPosition.column baseCharacterWidth = @model.getDefaultCharWidth() - top = @bottomPixelPositionForRow(targetRow) + top = @lineTopIndex.bottomPixelPositionForRow(targetRow) left = 0 column = 0 @@ -60,48 +60,15 @@ class FakeLinesYardstick {top, left} - rowForTopPixelPosition: (position, floor = true) -> - top = 0 - for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() - tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) - for row in [tileStartRow...tileEndRow] by 1 - nextTop = top + @presenter.getScreenRowHeight(row) - if floor - return row if nextTop > position - else - return row if top >= position - top = nextTop - @model.getScreenLineCount() - - topPixelPositionForRow: (targetRow) -> - top = 0 - for row in [0..targetRow] - return top if targetRow is row - top += @presenter.getScreenRowHeight(row) - top - - bottomPixelPositionForRow: (targetRow) -> - @topPixelPositionForRow(targetRow + 1) - @model.getLineHeightInPixels() - - topPixelPositionForRows: (startRow, endRow, step) -> - results = {} - top = 0 - for tileStartRow in [0..endRow] by step - tileEndRow = Math.min(tileStartRow + step, @model.getScreenLineCount()) - results[tileStartRow] = top - for row in [tileStartRow...tileEndRow] by 1 - top += @presenter.getScreenRowHeight(row) - results - pixelRectForScreenRange: (screenRange) -> if screenRange.end.row > screenRange.start.row top = @pixelPositionForScreenPosition(screenRange.start).top left = 0 - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @presenter.getScrollWidth() else {top, left} = @pixelPositionForScreenPosition(screenRange.start, false) - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @pixelPositionForScreenPosition(screenRange.end, false).left - left {top, left, width, height} diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 18ad030c0..85d404b1e 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1707,13 +1707,13 @@ describe('TextEditorComponent', function () { await nextViewUpdatePromise() - expect(component.getDomNode().querySelectorAll(".line").length).toBe(9) + 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 + "px") + 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)`) expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull() @@ -1724,7 +1724,7 @@ describe('TextEditorComponent', function () { expect(item1.getBoundingClientRect().height).toBe(0) // hidden expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) - expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 40 + 100) + expect(item4.getBoundingClientRect().height).toBe(0) // hidden await nextViewUpdatePromise() @@ -1734,13 +1734,13 @@ describe('TextEditorComponent', function () { await nextViewUpdatePromise() - expect(component.getDomNode().querySelectorAll(".line").length).toBe(9) + 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 + "px") + 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)`) expect(component.getTopmostDOMNode().querySelector(".decoration-1")).toBeNull() @@ -1751,7 +1751,7 @@ describe('TextEditorComponent', function () { expect(item1.getBoundingClientRect().height).toBe(0) // hidden expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60) - expect(item4.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 8 + 60 + 100) + expect(item4.getBoundingClientRect().height).toBe(0) // hidden }) }) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 83f0d8236..0e80ed113 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -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 '../src/linear-line-top-index' describe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. @@ -26,12 +27,14 @@ describe "TextEditorPresenter", -> buffer.destroy() buildPresenterWithoutMeasurements = (params={}) -> + lineTopIndex = new LineTopIndex _.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, presenter, lineTopIndex)) presenter buildPresenter = (params={}) -> diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 4d787c25f..80f4de6f4 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -5,12 +5,12 @@ const LineTopIndex = require('./linear-line-top-index') module.exports = class BlockDecorationsPresenter { - constructor (model) { + constructor (model, lineTopIndex) { this.model = model this.disposables = new CompositeDisposable() this.emitter = new Emitter() this.firstUpdate = true - this.lineTopIndex = new LineTopIndex + this.lineTopIndex = lineTopIndex this.blocksByDecoration = new Map this.decorationsByBlock = new Map this.observedDecorations = new Set diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 1f4a62fb9..7b6103f55 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -76,7 +76,7 @@ class LineTopIndex { return this.topPixelPositionForRow(row + 1) - this.defaultLineHeight } - rowForTopPixelPosition (top) { + rowForTopPixelPosition (top, roundingStrategy='round') { let blocksHeight = 0 let lastRow = 0 let lastTop = 0 @@ -97,7 +97,16 @@ class LineTopIndex { } let remainingHeight = Math.max(0, top - lastTop) - let remainingRows = Math.round(remainingHeight / this.defaultLineHeight) - return Math.min(this.maxRow, lastRow + remainingRows) + let remainingRows = Math.min(this.maxRow, lastRow + remainingHeight / this.defaultLineHeight) + switch (roundingStrategy) { + case "round": + return Math.round(remainingRows) + case "floor": + return Math.floor(remainingRows) + case "ceil": + return Math.ceil(remainingRows) + default: + throw new Error(`Cannot use '${roundingStrategy}' as a rounding strategy!`) + } } } diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index bb2c7faba..c1faa6399 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -3,7 +3,7 @@ TokenIterator = require './token-iterator' module.exports = class LinesYardstick - constructor: (@model, @presenter, @lineNodesProvider, grammarRegistry) -> + constructor: (@model, @presenter, @lineNodesProvider, @lineTopIndex, grammarRegistry) -> @tokenIterator = new TokenIterator({grammarRegistry}) @rangeForMeasurement = document.createRange() @invalidateCache() @@ -22,7 +22,7 @@ class LinesYardstick targetTop = pixelPosition.top targetLeft = pixelPosition.left defaultCharWidth = @model.getDefaultCharWidth() - row = @rowForTopPixelPosition(targetTop) + row = @lineTopIndex.rowForTopPixelPosition(targetTop, 'floor') targetLeft = 0 if targetTop < 0 targetLeft = Infinity if row > @model.getLastScreenRow() @@ -90,7 +90,7 @@ class LinesYardstick @prepareScreenRowsForMeasurement([targetRow]) unless measureVisibleLinesOnly - top = @bottomPixelPositionForRow(targetRow) + top = @lineTopIndex.bottomPixelPositionForRow(targetRow) left = @leftPixelPositionForScreenPosition(targetRow, targetColumn) @clearScreenRowsForMeasurement() unless measureVisibleLinesOnly @@ -172,48 +172,15 @@ class LinesYardstick left + width - offset - rowForTopPixelPosition: (position, floor = true) -> - top = 0 - for tileStartRow in [0..@model.getScreenLineCount()] by @presenter.getTileSize() - tileEndRow = Math.min(tileStartRow + @presenter.getTileSize(), @model.getScreenLineCount()) - for row in [tileStartRow...tileEndRow] by 1 - nextTop = top + @presenter.getScreenRowHeight(row) - if floor - return row if nextTop > position - else - return row if top >= position - top = nextTop - @model.getScreenLineCount() - - topPixelPositionForRow: (targetRow) -> - top = 0 - for row in [0..targetRow] - return top if targetRow is row - top += @presenter.getScreenRowHeight(row) - top - - bottomPixelPositionForRow: (targetRow) -> - @topPixelPositionForRow(targetRow + 1) - @model.getLineHeightInPixels() - - topPixelPositionForRows: (startRow, endRow, step) -> - results = {} - top = 0 - for tileStartRow in [0..endRow] by step - tileEndRow = Math.min(tileStartRow + step, @model.getScreenLineCount()) - results[tileStartRow] = top - for row in [tileStartRow...tileEndRow] by 1 - top += @presenter.getScreenRowHeight(row) - results - pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) -> if screenRange.end.row > screenRange.start.row top = @pixelPositionForScreenPosition(screenRange.start, true, measureVisibleLinesOnly).top left = 0 - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @presenter.getScrollWidth() else {top, left} = @pixelPositionForScreenPosition(screenRange.start, false, measureVisibleLinesOnly) - height = @topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top width = @pixelPositionForScreenPosition(screenRange.end, false, measureVisibleLinesOnly).left - left {top, left, width, height} diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index faf6a6715..7fc51b9a4 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -14,6 +14,7 @@ OverlayManager = require './overlay-manager' DOMElementPool = require './dom-element-pool' LinesYardstick = require './lines-yardstick' BlockDecorationsComponent = require './block-decorations-component' +LineTopIndex = require './linear-line-top-index' module.exports = class TextEditorComponent @@ -49,6 +50,7 @@ class TextEditorComponent @observeConfig() @setScrollSensitivity(@config.get('editor.scrollSensitivity')) + lineTopIndex = new LineTopIndex(@editor) @presenter = new TextEditorPresenter model: @editor tileSize: tileSize @@ -56,11 +58,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') @@ -85,7 +87,7 @@ class TextEditorComponent @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars}) @scrollViewNode.appendChild(@linesComponent.getDomNode()) - @linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, @grammars) + @linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, lineTopIndex, @grammars) @presenter.setLinesYardstick(@linesYardstick) @horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll}) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 41209d128..29428c1d4 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -14,7 +14,7 @@ class TextEditorPresenter minimumReflowInterval: 200 constructor: (params) -> - {@model, @config} = params + {@model, @config, @lineTopIndex} = params {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params {@contentFrameWidth} = params @@ -29,7 +29,7 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} - @blockDecorationsPresenter = new BlockDecorationsPresenter(@model) + @blockDecorationsPresenter = new BlockDecorationsPresenter(@model, @lineTopIndex) @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -434,12 +434,6 @@ class TextEditorPresenter screenRowIndex = screenRows.length - 1 zIndex = 0 - tilesPositions = @linesYardstick.topPixelPositionForRows( - @tileForRow(startRow), - @tileForRow(endRow) + @tileSize, - @tileSize - ) - for tileStartRow in [@tileForRow(endRow)..@tileForRow(startRow)] by -@tileSize tileEndRow = @constrainRow(tileStartRow + @tileSize) rowsWithinTile = [] @@ -452,8 +446,9 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 - top = Math.round(tilesPositions[tileStartRow]) - height = Math.round(tilesPositions[tileEndRow] - top) + top = Math.round(@lineTopIndex.topPixelPositionForRow(tileStartRow)) + bottom = Math.round(@lineTopIndex.topPixelPositionForRow(tileEndRow)) + height = bottom - top tile = @state.content.tiles[tileStartRow] ?= {} tile.top = top - @scrollTop @@ -667,8 +662,8 @@ class TextEditorPresenter continue unless @gutterIsVisible(gutter) for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName] - top = @linesYardstick.topPixelPositionForRow(screenRange.start.row) - bottom = @linesYardstick.topPixelPositionForRow(screenRange.end.row + 1) + top = @lineTopIndex.topPixelPositionForRow(screenRange.start.row) + bottom = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) @customGutterDecorations[gutterName][decorationId] = top: top height: bottom - top @@ -735,12 +730,12 @@ class TextEditorPresenter updateStartRow: -> return unless @scrollTop? and @lineHeight? - @startRow = Math.max(0, @linesYardstick.rowForTopPixelPosition(@scrollTop)) + @startRow = Math.max(0, @lineTopIndex.rowForTopPixelPosition(@scrollTop, "floor")) updateEndRow: -> return unless @scrollTop? and @lineHeight? and @height? - @endRow = @linesYardstick.rowForTopPixelPosition(@scrollTop + @height + @lineHeight, false) + @endRow = @lineTopIndex.rowForTopPixelPosition(@scrollTop + @height + @lineHeight, 'ceil') updateRowsPerPage: -> rowsPerPage = Math.floor(@getClientHeight() / @lineHeight) @@ -775,9 +770,7 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round( - @linesYardstick.topPixelPositionForRow(@model.getScreenLineCount()) - ) + @contentHeight = Math.round(@lineTopIndex.topPixelPositionForRow(Infinity)) if @contentHeight isnt oldContentHeight @updateHeight() @@ -1138,9 +1131,9 @@ class TextEditorPresenter setLineHeight: (lineHeight) -> unless @lineHeight is lineHeight @lineHeight = lineHeight + @model.setLineHeightInPixels(@lineHeight) + @lineTopIndex.setDefaultLineHeight(@lineHeight) @restoreScrollTopIfNeeded() - @model.setLineHeightInPixels(lineHeight) - @blockDecorationsPresenter.setLineHeight(lineHeight) @shouldUpdateHeightState = true @shouldUpdateHorizontalScrollState = true @shouldUpdateVerticalScrollState = true @@ -1348,7 +1341,7 @@ class TextEditorPresenter screenRange.end.column = 0 repositionRegionWithinTile: (region, tileStartRow) -> - region.top += @scrollTop - @linesYardstick.topPixelPositionForRow(tileStartRow) + region.top += @scrollTop - @lineTopIndex.topPixelPositionForRow(tileStartRow) region.left += @scrollLeft buildHighlightRegions: (screenRange) -> @@ -1500,7 +1493,7 @@ class TextEditorPresenter @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> - @updateScrollTop(@linesYardstick.topPixelPositionForRow(screenRow)) + @updateScrollTop(@lineTopIndex.topPixelPositionForRow(screenRow)) getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) @@ -1521,8 +1514,8 @@ class TextEditorPresenter verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - top = @linesYardstick.topPixelPositionForRow(screenRange.start.row) - bottom = @linesYardstick.topPixelPositionForRow(screenRange.end.row + 1) + top = @lineTopIndex.topPixelPositionForRow(screenRange.start.row) + bottom = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) if options?.center desiredScrollCenter = (top + bottom) / 2 @@ -1594,7 +1587,7 @@ class TextEditorPresenter restoreScrollTopIfNeeded: -> unless @scrollTop? - @updateScrollTop(@linesYardstick.topPixelPositionForRow(@model.getFirstVisibleScreenRow())) + @updateScrollTop(@lineTopIndex.topPixelPositionForRow(@model.getFirstVisibleScreenRow())) restoreScrollLeftIfNeeded: -> unless @scrollLeft? From 5bcdcbeef6bb7e60f2c00119778bcd642eb78bbd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 16:13:44 +0100 Subject: [PATCH 032/173] :art: --- spec/text-editor-presenter-spec.coffee | 10 +++++----- src/linear-line-top-index.js | 6 ++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 0e80ed113..9b6f5b78e 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -191,17 +191,17 @@ describe "TextEditorPresenter", -> expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30) + 20 + 1) expect(stateFn(presenter).tiles[8]).toBeUndefined() - presenter.setScrollTop(21) + presenter.setScrollTop(22) 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[2].top).toBe(-1) expect(stateFn(presenter).tiles[4].height).toBe(20 + 40) - expect(stateFn(presenter).tiles[4].top).toBe(30 + 20) + expect(stateFn(presenter).tiles[4].top).toBe(30 + 20 - 1) expect(stateFn(presenter).tiles[6].height).toBe(20) - expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30)) + expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30) - 1) expect(stateFn(presenter).tiles[8].height).toBe(20) - expect(stateFn(presenter).tiles[8].top).toBe((20 + 40) + (20 + 30) + 20) + expect(stateFn(presenter).tiles[8].top).toBe((20 + 40) + (20 + 30) + 20 - 1) expect(stateFn(presenter).tiles[10]).toBeUndefined() it "includes state for all tiles if no external ::explicitHeight is assigned", -> diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 7b6103f55..84a3bdf04 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -76,7 +76,7 @@ class LineTopIndex { return this.topPixelPositionForRow(row + 1) - this.defaultLineHeight } - rowForTopPixelPosition (top, roundingStrategy='round') { + rowForTopPixelPosition (top, roundingStrategy='floor') { let blocksHeight = 0 let lastRow = 0 let lastTop = 0 @@ -84,7 +84,7 @@ class LineTopIndex { let nextBlocksHeight = blocksHeight + block.height let linesHeight = block.row * this.defaultLineHeight if (nextBlocksHeight + linesHeight > top) { - while (lastRow < block.row && lastTop + this.defaultLineHeight / 2 < top) { + while (lastRow < block.row && lastTop + this.defaultLineHeight < top) { lastTop += this.defaultLineHeight lastRow++ } @@ -99,8 +99,6 @@ class LineTopIndex { let remainingHeight = Math.max(0, top - lastTop) let remainingRows = Math.min(this.maxRow, lastRow + remainingHeight / this.defaultLineHeight) switch (roundingStrategy) { - case "round": - return Math.round(remainingRows) case "floor": return Math.floor(remainingRows) case "ceil": From e10fdc234bd3600afcae1b3d4babc6c8a0806cba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 16:26:10 +0100 Subject: [PATCH 033/173] :bug: Coordinate conversion is hard --- spec/line-top-index-spec.js | 8 ++++---- spec/text-editor-presenter-spec.coffee | 10 +++++----- src/linear-line-top-index.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/line-top-index-spec.js b/spec/line-top-index-spec.js index f2c3f9ca5..62d3e1839 100644 --- a/spec/line-top-index-spec.js +++ b/spec/line-top-index-spec.js @@ -97,7 +97,7 @@ describe("LineTopIndex", function () { expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(4) expect(lineTopIndex.rowForTopPixelPosition(44)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(46)).toBe(5) + expect(lineTopIndex.rowForTopPixelPosition(46)).toBe(4) expect(lineTopIndex.rowForTopPixelPosition(50)).toBe(5) expect(lineTopIndex.rowForTopPixelPosition(120)).toBe(12) expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(12) @@ -123,7 +123,7 @@ describe("LineTopIndex", function () { expect(lineTopIndex.rowForTopPixelPosition(6)).toBe(0) expect(lineTopIndex.rowForTopPixelPosition(10)).toBe(0) expect(lineTopIndex.rowForTopPixelPosition(12)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(17)).toBe(1) + expect(lineTopIndex.rowForTopPixelPosition(17)).toBe(0) expect(lineTopIndex.rowForTopPixelPosition(20)).toBe(1) expect(lineTopIndex.rowForTopPixelPosition(30)).toBe(2) expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(3) @@ -131,10 +131,10 @@ describe("LineTopIndex", function () { expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(5) expect(lineTopIndex.rowForTopPixelPosition(90)).toBe(5) expect(lineTopIndex.rowForTopPixelPosition(95)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(105)).toBe(6) + expect(lineTopIndex.rowForTopPixelPosition(106)).toBe(5) expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) expect(lineTopIndex.rowForTopPixelPosition(160)).toBe(11) - expect(lineTopIndex.rowForTopPixelPosition(166)).toBe(12) + expect(lineTopIndex.rowForTopPixelPosition(166)).toBe(11) expect(lineTopIndex.rowForTopPixelPosition(170)).toBe(12) expect(lineTopIndex.rowForTopPixelPosition(240)).toBe(12) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 9b6f5b78e..0e80ed113 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -191,17 +191,17 @@ describe "TextEditorPresenter", -> expect(stateFn(presenter).tiles[6].top).toBe((20 + 40) + (20 + 30) + 20 + 1) expect(stateFn(presenter).tiles[8]).toBeUndefined() - presenter.setScrollTop(22) + 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(-1) + 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 - 1) + 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) - 1) + 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 - 1) + expect(stateFn(presenter).tiles[8].top).toBe((20 + 40) + (20 + 30) + 20) expect(stateFn(presenter).tiles[10]).toBeUndefined() it "includes state for all tiles if no external ::explicitHeight is assigned", -> diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 84a3bdf04..1c7fa0503 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -84,7 +84,7 @@ class LineTopIndex { let nextBlocksHeight = blocksHeight + block.height let linesHeight = block.row * this.defaultLineHeight if (nextBlocksHeight + linesHeight > top) { - while (lastRow < block.row && lastTop + this.defaultLineHeight < top) { + while (lastRow < block.row && lastTop + this.defaultLineHeight <= top) { lastTop += this.defaultLineHeight lastRow++ } From 5228471bc52da9575653e411de9cbda7e9f5ce19 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 17:04:33 +0100 Subject: [PATCH 034/173] Write failing spec for measuring invisible elements --- spec/text-editor-component-spec.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 85d404b1e..3b8409469 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1699,7 +1699,8 @@ describe('TextEditorComponent', function () { 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) - expect(item4.getBoundingClientRect().height).toBe(0) // hidden + expect(item4.getBoundingClientRect().top).toBe(0) // hidden + expect(item4.getBoundingClientRect().height).toBe(120) editor.setCursorScreenPosition([0, 0]) editor.insertNewline() @@ -1713,7 +1714,7 @@ describe('TextEditorComponent', function () { 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() + "px") + expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + "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() @@ -1721,10 +1722,11 @@ describe('TextEditorComponent', function () { expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3) expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBe(item4) - expect(item1.getBoundingClientRect().height).toBe(0) // hidden + expect(item1.getBoundingClientRect().height).toBe(0) // deleted expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) - expect(item4.getBoundingClientRect().height).toBe(0) // hidden + expect(item4.getBoundingClientRect().top).toBe(0) // hidden + expect(item4.getBoundingClientRect().height).toBe(120) await nextViewUpdatePromise() @@ -1740,7 +1742,7 @@ describe('TextEditorComponent', function () { 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() + "px") + expect(component.tileNodesForLines()[2].style.height).toBe(TILE_SIZE * editor.getLineHeightInPixels() + 120 + "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() @@ -1748,10 +1750,11 @@ describe('TextEditorComponent', function () { expect(component.getTopmostDOMNode().querySelector(".decoration-3")).toBe(item3) expect(component.getTopmostDOMNode().querySelector(".decoration-4")).toBe(item4) - expect(item1.getBoundingClientRect().height).toBe(0) // hidden + expect(item1.getBoundingClientRect().height).toBe(0) // deleted expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60) - expect(item4.getBoundingClientRect().height).toBe(0) // hidden + expect(item4.getBoundingClientRect().top).toBe(0) // hidden + expect(item4.getBoundingClientRect().height).toBe(120) // hidden }) }) From 87c8694d01111bbfe04c85395bdbff5621781c13 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 17:21:12 +0100 Subject: [PATCH 035/173] Use ::bottomPixelPositionForRow to scroll logically --- src/text-editor-presenter.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 29428c1d4..44cf14794 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1514,8 +1514,8 @@ class TextEditorPresenter verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - top = @lineTopIndex.topPixelPositionForRow(screenRange.start.row) - bottom = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) + top = @lineTopIndex.bottomPixelPositionForRow(screenRange.start.row) + bottom = @lineTopIndex.bottomPixelPositionForRow(screenRange.end.row + 1) if options?.center desiredScrollCenter = (top + bottom) / 2 From 9ef3ecf378734ba541476cbe070f58742ac205f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 18:34:57 +0100 Subject: [PATCH 036/173] Handle off-screen measurements properly --- spec/text-editor-presenter-spec.coffee | 26 +++++++++++++++++++++++++- src/block-decorations-component.coffee | 14 ++++++++++++++ src/text-editor-component.coffee | 2 ++ src/text-editor-presenter.coffee | 6 ++++-- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 0e80ed113..be8ce974e 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2106,23 +2106,27 @@ describe "TextEditorPresenter", -> blockDecoration2 = editor.addBlockDecorationForScreenRow(4, item) blockDecoration3 = editor.addBlockDecorationForScreenRow(4, item) blockDecoration4 = editor.addBlockDecorationForScreenRow(10, item) - presenter = buildPresenter(explicitHeight: 30, scrollTop: 0) + 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 } waitsForStateToUpdate presenter, -> @@ -2132,36 +2136,56 @@ describe "TextEditorPresenter", -> expectValues stateForBlockDecoration(presenter, blockDecoration1), { decoration: blockDecoration1 screenRow: 4 + isVisible: true } expectValues stateForBlockDecoration(presenter, blockDecoration2), { decoration: blockDecoration2 screenRow: 8 + isVisible: false } expectValues stateForBlockDecoration(presenter, blockDecoration3), { decoration: blockDecoration3 screenRow: 8 + isVisible: false } expectValues stateForBlockDecoration(presenter, blockDecoration4), { decoration: blockDecoration4 screenRow: 14 + isVisible: false } waitsForStateToUpdate presenter, -> blockDecoration2.destroy() blockDecoration4.destroy() + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) runs -> expectValues stateForBlockDecoration(presenter, blockDecoration1), { decoration: blockDecoration1 screenRow: 4 + isVisible: true } expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined() expectValues stateForBlockDecoration(presenter, blockDecoration3), { decoration: blockDecoration3 screenRow: 8 + isVisible: false } expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() + presenter.setScrollTop(80) + + expectValues stateForBlockDecoration(presenter, blockDecoration1), { + decoration: blockDecoration1 + screenRow: 4 + isVisible: false + } + expectValues stateForBlockDecoration(presenter, blockDecoration3), { + decoration: blockDecoration3 + screenRow: 8 + isVisible: true + } + describe ".overlays", -> [item] = [] stateForOverlay = (presenter, decoration) -> diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index 0f9270660..b214fd584 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -9,6 +9,13 @@ class BlockDecorationsComponent @newState = null @oldState = null @blockDecorationNodesById = {} + @domNode = @domElementPool.buildElement("content") + @domNode.setAttribute("select", ".atom--invisible-block-decoration") + @domNode.style.visibility = "hidden" + @domNode.style.position = "absolute" + + getDomNode: -> + @domNode updateSync: (state) -> @newState = state.content @@ -41,6 +48,8 @@ class BlockDecorationsComponent blockDecorationState = @newState.blockDecorations[id] blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item) blockDecorationNode.classList.add("block-decoration-row-#{blockDecorationState.screenRow}") + unless blockDecorationState.isVisible + blockDecorationNode.classList.add("atom--invisible-block-decoration") @container.appendChild(blockDecorationNode) @@ -51,6 +60,11 @@ class BlockDecorationsComponent oldBlockDecorationState = @oldState.blockDecorations[id] blockDecorationNode = @blockDecorationNodesById[id] + if newBlockDecorationState.isVisible and not oldBlockDecorationState.isVisible + blockDecorationNode.classList.remove("atom--invisible-block-decoration") + else if not newBlockDecorationState.isVisible and oldBlockDecorationState.isVisible + blockDecorationNode.classList.add("atom--invisible-block-decoration") + if newBlockDecorationState.screenRow isnt oldBlockDecorationState.screenRow blockDecorationNode.classList.remove("block-decoration-row-#{oldBlockDecorationState.screenRow}") blockDecorationNode.classList.add("block-decoration-row-#{newBlockDecorationState.screenRow}") diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 7fc51b9a4..e5e63c371 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -87,6 +87,8 @@ class TextEditorComponent @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars}) @scrollViewNode.appendChild(@linesComponent.getDomNode()) + @scrollViewNode.appendChild(@blockDecorationsComponent.getDomNode()) + @linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, lineTopIndex, @grammars) @presenter.setLinesYardstick(@linesYardstick) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 44cf14794..54ffc041f 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -76,7 +76,6 @@ class TextEditorPresenter @blockDecorationsPresenter.update() - @updateBlockDecorationsState() @updateVerticalDimensions() @updateScrollbarDimensions() @@ -88,6 +87,8 @@ class TextEditorPresenter @updateCommonGutterState() @updateReflowState() + @updateBlockDecorationsState() + if @shouldUpdateDecorations @fetchDecorations() @updateLineDecorations() @@ -1217,7 +1218,8 @@ class TextEditorPresenter @blockDecorationsPresenter.getAllDecorationsByScreenRow().forEach (decorations, screenRow) => for decoration in decorations - @state.content.blockDecorations[decoration.id] = {decoration, screenRow} + isVisible = @getStartTileRow() <= screenRow < @getEndTileRow() + @tileSize + @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} updateLineDecorations: -> @lineDecorationsByScreenRow = {} From fcb8a13f4a934de1c58eb9fe6338971f4e7f1442 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 2 Dec 2015 19:11:51 +0100 Subject: [PATCH 037/173] Use buffer marker events to avoid conversions --- src/block-decorations-presenter.js | 33 +++++++++++++----------------- src/linear-line-top-index.js | 5 +++++ src/text-editor-presenter.coffee | 5 +---- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 80f4de6f4..31571f57a 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -45,22 +45,13 @@ class BlockDecorationsPresenter { update () { if (this.firstUpdate) { - this.fullUpdate() + for (let decoration of this.model.getDecorations({type: "block"})) { + this.observeDecoration(decoration) + } this.firstUpdate = false - } else { - this.incrementalUpdate() } } - fullUpdate () { - for (let decoration of this.model.getDecorations({type: "block"})) { - this.observeDecoration(decoration) - } - } - - incrementalUpdate () { - } - setDimensionsForDecoration (decoration, width, height) { let block = this.blocksByDecoration.get(decoration) if (block) { @@ -74,10 +65,6 @@ class BlockDecorationsPresenter { this.emitter.emit("did-update-state") } - heightForScreenRow (screenRow) { - return this.lineTopIndex.bottomPixelPositionForRow(screenRow) - this.lineTopIndex.topPixelPositionForRow(screenRow) - } - decorationsForScreenRow (screenRow) { let blocks = this.lineTopIndex.allBlocks().filter((block) => block.row == screenRow) return blocks.map((block) => this.decorationsByBlock.get(block.id)).filter((decoration) => decoration) @@ -103,18 +90,21 @@ class BlockDecorationsPresenter { return } - // TODO: change this with a "on manual did change" event. - let didMoveDisposable = decoration.getMarker().onDidChange((markerEvent) => { + let didMoveDisposable = decoration.getMarker().bufferMarker.onDidChange((markerEvent) => { this.didMoveDecoration(decoration, markerEvent) }) let didDestroyDisposable = decoration.onDidDestroy(() => { + this.disposables.remove(didMoveDisposable) + this.disposables.remove(didDestroyDisposable) didMoveDisposable.dispose() didDestroyDisposable.dispose() this.observedDecorations.delete(decoration) this.didDestroyDecoration(decoration) }) + this.disposables.add(didMoveDisposable) + this.disposables.add(didDestroyDisposable) this.didAddDecoration(decoration) this.observedDecorations.add(decoration) } @@ -127,7 +117,12 @@ class BlockDecorationsPresenter { this.emitter.emit("did-update-state") } - didMoveDecoration (decoration, {oldHeadScreenPosition, newHeadScreenPosition}) { + didMoveDecoration (decoration, {textChanged}) { + if (textChanged) { + // No need to move blocks because of a text change, because we already splice on buffer change. + return + } + let block = this.blocksByDecoration.get(decoration) let newScreenRow = decoration.getMarker().getHeadScreenPosition().row this.lineTopIndex.moveBlock(block, newScreenRow) diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 1c7fa0503..82778933f 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -50,6 +50,11 @@ class LineTopIndex { return this.blocks } + blocksHeightForRow (row) { + let blocksForRow = this.blocks.filter((block) => block.row == row) + return blocksForRow.reduce((a, b) => a + b.height, 0) + } + splice (startRow, oldExtent, newExtent) { this.blocks.forEach(function (block) { if (block.row >= startRow) { diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 54ffc041f..72d1cec54 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -715,7 +715,7 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - blockDecorationsHeight = @blockDecorationsPresenter.heightForScreenRow(screenRow) + blockDecorationsHeight = @lineTopIndex.blocksHeightForRow(screenRow) tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight} visibleLineNumberIds[line.id] = true @@ -725,9 +725,6 @@ class TextEditorPresenter return - getScreenRowHeight: (screenRow) -> - @lineHeight + @blockDecorationsPresenter.heightForScreenRow(screenRow) - updateStartRow: -> return unless @scrollTop? and @lineHeight? From 937116a2808771b8a6b43f8017463b136bffcb2d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 11:26:45 +0100 Subject: [PATCH 038/173] Render only visible and yet-to-be-measured block decorations --- spec/text-editor-presenter-spec.coffee | 101 ++++++++++++------------- src/block-decorations-component.coffee | 4 +- src/block-decorations-presenter.js | 15 +++- src/text-editor-component.coffee | 3 + src/text-editor-element.coffee | 3 + src/text-editor-presenter.coffee | 11 ++- 6 files changed, 78 insertions(+), 59 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index be8ce974e..52d2e2bc0 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2129,62 +2129,61 @@ describe "TextEditorPresenter", -> isVisible: false } - waitsForStateToUpdate presenter, -> - editor.getBuffer().insert([0, 0], 'Hello world \n\n\n\n') + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) + presenter.setBlockDecorationDimensions(blockDecoration4, 0, 20) - runs -> - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 4 - isVisible: true - } - expectValues stateForBlockDecoration(presenter, blockDecoration2), { - decoration: blockDecoration2 - screenRow: 8 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 8 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration4), { - decoration: blockDecoration4 - screenRow: 14 - isVisible: false - } + 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() - waitsForStateToUpdate presenter, -> - blockDecoration2.destroy() - blockDecoration4.destroy() - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) + presenter.setScrollTop(90) - runs -> - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 4 - isVisible: true - } - expect(stateForBlockDecoration(presenter, blockDecoration2)).toBeUndefined() - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 8 - isVisible: false - } - expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() + expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + expectValues stateForBlockDecoration(presenter, blockDecoration2), { + decoration: blockDecoration2 + screenRow: 4 + isVisible: false + } + expectValues stateForBlockDecoration(presenter, blockDecoration3), { + decoration: blockDecoration3 + screenRow: 4 + isVisible: false + } + expectValues stateForBlockDecoration(presenter, blockDecoration4), { + decoration: blockDecoration4 + screenRow: 10 + isVisible: true + } - presenter.setScrollTop(80) + presenter.invalidateBlockDecorationDimensions(blockDecoration1) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) + presenter.setBlockDecorationDimensions(blockDecoration3, 0, 10) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { - decoration: blockDecoration1 - screenRow: 4 - isVisible: false - } - expectValues stateForBlockDecoration(presenter, blockDecoration3), { - decoration: blockDecoration3 - screenRow: 8 - isVisible: true - } + 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 + } describe ".overlays", -> [item] = [] diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index b214fd584..50ac52fc7 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -60,9 +60,9 @@ class BlockDecorationsComponent oldBlockDecorationState = @oldState.blockDecorations[id] blockDecorationNode = @blockDecorationNodesById[id] - if newBlockDecorationState.isVisible and not oldBlockDecorationState.isVisible + if newBlockDecorationState.isVisible blockDecorationNode.classList.remove("atom--invisible-block-decoration") - else if not newBlockDecorationState.isVisible and oldBlockDecorationState.isVisible + else blockDecorationNode.classList.add("atom--invisible-block-decoration") if newBlockDecorationState.screenRow isnt oldBlockDecorationState.screenRow diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 31571f57a..a5306a7ef 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -14,6 +14,7 @@ class BlockDecorationsPresenter { this.blocksByDecoration = new Map this.decorationsByBlock = new Map this.observedDecorations = new Set + this.measuredDecorations = new Set this.observeModel() } @@ -62,6 +63,12 @@ class BlockDecorationsPresenter { this.lineTopIndex.resizeBlock(block, height) } + this.measuredDecorations.add(decoration) + this.emitter.emit("did-update-state") + } + + invalidateDimensionsForDecoration (decoration) { + this.measuredDecorations.delete(decoration) this.emitter.emit("did-update-state") } @@ -70,14 +77,16 @@ class BlockDecorationsPresenter { return blocks.map((block) => this.decorationsByBlock.get(block.id)).filter((decoration) => decoration) } - getAllDecorationsByScreenRow () { + decorationsForScreenRowRange (startRow, endRow) { let blocks = this.lineTopIndex.allBlocks() let decorationsByScreenRow = new Map for (let block of blocks) { let decoration = this.decorationsByBlock.get(block.id) - if (decoration) { + let hasntMeasuredDecoration = !this.measuredDecorations.has(decoration) + let isVisible = startRow <= block.row && block.row < endRow + if (decoration && (isVisible || hasntMeasuredDecoration)) { let decorations = decorationsByScreenRow.get(block.row) || [] - decorations.push(decoration) + decorations.push({decoration, isVisible}) decorationsByScreenRow.set(block.row, decorations) } } diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index e5e63c371..afe46c705 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -456,6 +456,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 diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 1a55eb002..6722f51df 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -347,4 +347,7 @@ class TextEditorElement extends HTMLElement getHeight: -> @offsetHeight + invalidateBlockDecorationDimensions: -> + @component.invalidateBlockDecorationDimensions(arguments...) + module.exports = TextEditorElement = document.registerElement 'atom-text-editor', prototype: TextEditorElement.prototype diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 72d1cec54..b2bc16ca9 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1213,9 +1213,11 @@ class TextEditorPresenter updateBlockDecorationsState: -> @state.content.blockDecorations = {} - @blockDecorationsPresenter.getAllDecorationsByScreenRow().forEach (decorations, screenRow) => - for decoration in decorations - isVisible = @getStartTileRow() <= screenRow < @getEndTileRow() + @tileSize + startRow = @getStartTileRow() + endRow = @getEndTileRow() + @tileSize + decorations = @blockDecorationsPresenter.decorationsForScreenRowRange(startRow, endRow) + decorations.forEach (decorations, screenRow) => + for {decoration, isVisible} in decorations @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} updateLineDecorations: -> @@ -1414,6 +1416,9 @@ class TextEditorPresenter setBlockDecorationDimensions: -> @blockDecorationsPresenter.setDimensionsForDecoration(arguments...) + invalidateBlockDecorationDimensions: -> + @blockDecorationsPresenter.invalidateDimensionsForDecoration(arguments...) + observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => @shouldUpdateHiddenInputState = true if cursor.isLastCursor() From f22bd5d0aebbb1cd51596a058762342468833342 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 11:52:10 +0100 Subject: [PATCH 039/173] :racehorse: Use ids instead of classes --- spec/text-editor-presenter-spec.coffee | 84 +++++++++++++------------- src/block-decorations-component.coffee | 7 +-- src/lines-tile-component.coffee | 6 +- src/text-editor-presenter.coffee | 6 +- 4 files changed, 50 insertions(+), 53 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 52d2e2bc0..f28dade69 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1273,8 +1273,8 @@ 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 ".hasBlockDecorations", -> - it "is true when block decorations are present before a line, both initially and when decorations change", -> + describe ".blockDecorations", -> + it "contains all block decorations that are present before a line, both initially and when decorations change", -> blockDecoration1 = editor.addBlockDecorationForScreenRow(0) presenter = buildPresenter() blockDecoration2 = editor.addBlockDecorationForScreenRow(3) @@ -1282,58 +1282,58 @@ describe "TextEditorPresenter", -> waitsForStateToUpdate presenter runs -> - expect(lineStateForScreenRow(presenter, 0).hasBlockDecorations).toBe(true) - expect(lineStateForScreenRow(presenter, 1).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 2).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 3).hasBlockDecorations).toBe(true) - expect(lineStateForScreenRow(presenter, 4).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 5).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 6).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 7).hasBlockDecorations).toBe(true) - expect(lineStateForScreenRow(presenter, 8).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 9).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 10).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 11).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 12).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual([blockDecoration1]) + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([blockDecoration2]) + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([blockDecoration3]) + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) waitsForStateToUpdate presenter, -> blockDecoration1.getMarker().setHeadBufferPosition([1, 0]) - blockDecoration2.getMarker().setHeadBufferPosition([5, 0]) + blockDecoration2.getMarker().setHeadBufferPosition([9, 0]) blockDecoration3.getMarker().setHeadBufferPosition([9, 0]) runs -> - expect(lineStateForScreenRow(presenter, 0).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 1).hasBlockDecorations).toBe(true) - expect(lineStateForScreenRow(presenter, 2).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 3).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 4).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 5).hasBlockDecorations).toBe(true) - expect(lineStateForScreenRow(presenter, 6).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 7).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 8).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 9).hasBlockDecorations).toBe(true) - expect(lineStateForScreenRow(presenter, 10).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 11).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 12).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([blockDecoration1]) + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([blockDecoration3, blockDecoration2]) + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) waitsForStateToUpdate presenter, -> blockDecoration1.destroy() blockDecoration3.destroy() runs -> - expect(lineStateForScreenRow(presenter, 0).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 1).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 2).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 3).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 4).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 5).hasBlockDecorations).toBe(true) - expect(lineStateForScreenRow(presenter, 6).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 7).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 8).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 9).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 10).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 11).hasBlockDecorations).toBe(false) - expect(lineStateForScreenRow(presenter, 12).hasBlockDecorations).toBe(false) + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([blockDecoration2]) + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) describe ".decorationClasses", -> it "adds decoration classes to the relevant line state objects, both initially and when decorations change", -> diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index 50ac52fc7..2a8574ef6 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -47,7 +47,7 @@ class BlockDecorationsComponent createAndAppendBlockDecorationNode: (id) -> blockDecorationState = @newState.blockDecorations[id] blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item) - blockDecorationNode.classList.add("block-decoration-row-#{blockDecorationState.screenRow}") + blockDecorationNode.id = "atom--block-decoration-#{id}" unless blockDecorationState.isVisible blockDecorationNode.classList.add("atom--invisible-block-decoration") @@ -57,14 +57,9 @@ class BlockDecorationsComponent 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 newBlockDecorationState.screenRow isnt oldBlockDecorationState.screenRow - blockDecorationNode.classList.remove("block-decoration-row-#{oldBlockDecorationState.screenRow}") - blockDecorationNode.classList.add("block-decoration-row-#{newBlockDecorationState.screenRow}") diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index f4b0dbab5..f447c3352 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -138,11 +138,11 @@ class LinesTileComponent @updateBlockDecorationInsertionPoint(id) updateBlockDecorationInsertionPoint: (id) -> - {screenRow} = @newTileState.lines[id] - + {blockDecorations, screenRow} = @newTileState.lines[id] + elementsIds = blockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',') if insertionPoint = @insertionPointsByLineId[id] insertionPoint.dataset.screenRow = screenRow - insertionPoint.setAttribute("select", ".block-decoration-row-#{screenRow}") + insertionPoint.setAttribute("select", elementsIds) findNodeNextTo: (node) -> for nextNode, index in @domNode.children diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index b2bc16ca9..637a3a011 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -499,7 +499,8 @@ class TextEditorPresenter lineState = tileState.lines[line.id] lineState.screenRow = screenRow lineState.decorationClasses = @lineDecorationClassesForRow(screenRow) - lineState.hasBlockDecorations = blockDecorations.length isnt 0 + lineState.blockDecorations = blockDecorations + lineState.hasBlockDecorations = blockDecorations.length > 0 else tileState.lines[line.id] = screenRow: screenRow @@ -516,7 +517,8 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) - hasBlockDecorations: blockDecorations.length isnt 0 + blockDecorations: blockDecorations + hasBlockDecorations: blockDecorations.length > 0 for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) From e75263b5e08a524eb2096c6e8462426562daaa80 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 13:07:14 +0100 Subject: [PATCH 040/173] :fire: --- src/text-editor-presenter.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 637a3a011..dcae0966c 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -195,7 +195,6 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = true @shouldUpdateDecorations = true @shouldUpdateOverlaysState = true - @shouldUpdateVerticalScrollState = true @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() @@ -212,7 +211,6 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = true @shouldUpdateGutterOrderState = true @shouldUpdateCustomGutterDecorationState = true - @emitDidUpdateState() @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) From 47644ee48738bf13dc3b35f30580b35225843e63 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 13:09:10 +0100 Subject: [PATCH 041/173] More :fire: --- src/text-editor-presenter.coffee | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index dcae0966c..2a869045f 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -391,9 +391,6 @@ class TextEditorPresenter getEndTileRow: -> @constrainRow(@tileForRow(@endRow)) - getTileSize: -> - @tileSize - isValidScreenRow: (screenRow) -> screenRow >= 0 and screenRow < @model.getScreenLineCount() @@ -533,7 +530,6 @@ class TextEditorPresenter return unless cursor.isVisible() and @startRow <= screenRange.start.row < @endRow pixelRect = @pixelRectForScreenRange(screenRange) - pixelRect.height = @lineHeight pixelRect.width = Math.round(@baseCharacterWidth) if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect @@ -762,9 +758,6 @@ class TextEditorPresenter @scrollHeight = scrollHeight @updateScrollTop(@scrollTop) - getLinesHeight: -> - @lineHeight * @model.getScreenLineCount() - updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight From e23af02606ef6ed6e575c0344ea2027e97a11ce2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 13:11:41 +0100 Subject: [PATCH 042/173] Fix linting errors --- src/block-decorations-presenter.js | 28 ++++++++++++++-------------- src/linear-line-top-index.js | 14 +++++++------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index a5306a7ef..276fbb401 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -11,10 +11,10 @@ class BlockDecorationsPresenter { this.emitter = new Emitter() this.firstUpdate = true this.lineTopIndex = lineTopIndex - this.blocksByDecoration = new Map - this.decorationsByBlock = new Map - this.observedDecorations = new Set - this.measuredDecorations = new Set + this.blocksByDecoration = new Map() + this.decorationsByBlock = new Map() + this.observedDecorations = new Set() + this.measuredDecorations = new Set() this.observeModel() } @@ -24,7 +24,7 @@ class BlockDecorationsPresenter { } onDidUpdateState (callback) { - return this.emitter.on("did-update-state", callback) + return this.emitter.on('did-update-state', callback) } setLineHeight (lineHeight) { @@ -46,7 +46,7 @@ class BlockDecorationsPresenter { update () { if (this.firstUpdate) { - for (let decoration of this.model.getDecorations({type: "block"})) { + for (let decoration of this.model.getDecorations({type: 'block'})) { this.observeDecoration(decoration) } this.firstUpdate = false @@ -64,22 +64,22 @@ class BlockDecorationsPresenter { } this.measuredDecorations.add(decoration) - this.emitter.emit("did-update-state") + this.emitter.emit('did-update-state') } invalidateDimensionsForDecoration (decoration) { this.measuredDecorations.delete(decoration) - this.emitter.emit("did-update-state") + this.emitter.emit('did-update-state') } decorationsForScreenRow (screenRow) { - let blocks = this.lineTopIndex.allBlocks().filter((block) => block.row == screenRow) + let blocks = this.lineTopIndex.allBlocks().filter((block) => block.row === screenRow) return blocks.map((block) => this.decorationsByBlock.get(block.id)).filter((decoration) => decoration) } decorationsForScreenRowRange (startRow, endRow) { let blocks = this.lineTopIndex.allBlocks() - let decorationsByScreenRow = new Map + let decorationsByScreenRow = new Map() for (let block of blocks) { let decoration = this.decorationsByBlock.get(block.id) let hasntMeasuredDecoration = !this.measuredDecorations.has(decoration) @@ -95,7 +95,7 @@ class BlockDecorationsPresenter { } observeDecoration (decoration) { - if (!decoration.isType("block") || this.observedDecorations.has(decoration)) { + if (!decoration.isType('block') || this.observedDecorations.has(decoration)) { return } @@ -123,7 +123,7 @@ class BlockDecorationsPresenter { let block = this.lineTopIndex.insertBlock(screenRow, 0) this.decorationsByBlock.set(block, decoration) this.blocksByDecoration.set(decoration, block) - this.emitter.emit("did-update-state") + this.emitter.emit('did-update-state') } didMoveDecoration (decoration, {textChanged}) { @@ -135,7 +135,7 @@ class BlockDecorationsPresenter { let block = this.blocksByDecoration.get(decoration) let newScreenRow = decoration.getMarker().getHeadScreenPosition().row this.lineTopIndex.moveBlock(block, newScreenRow) - this.emitter.emit("did-update-state") + this.emitter.emit('did-update-state') } didDestroyDecoration (decoration) { @@ -145,6 +145,6 @@ class BlockDecorationsPresenter { this.blocksByDecoration.delete(decoration) this.decorationsByBlock.delete(block) } - this.emitter.emit("did-update-state") + this.emitter.emit('did-update-state') } } diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 82778933f..a930b4a38 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -25,14 +25,14 @@ class LineTopIndex { } resizeBlock (id, height) { - let block = this.blocks.find((block) => block.id == id) + let block = this.blocks.find((block) => block.id === id) if (block) { block.height = height } } moveBlock (id, newRow) { - let block = this.blocks.find((block) => block.id == id) + let block = this.blocks.find((block) => block.id === id) if (block) { block.row = newRow this.blocks.sort((a, b) => a.row - b.row) @@ -40,8 +40,8 @@ class LineTopIndex { } removeBlock (id) { - let index = this.blocks.findIndex((block) => block.id == id) - if (index != -1) { + let index = this.blocks.findIndex((block) => block.id === id) + if (index !== -1) { this.blocks.splice(index, 1) } } @@ -51,7 +51,7 @@ class LineTopIndex { } blocksHeightForRow (row) { - let blocksForRow = this.blocks.filter((block) => block.row == row) + let blocksForRow = this.blocks.filter((block) => block.row === row) return blocksForRow.reduce((a, b) => a + b.height, 0) } @@ -104,9 +104,9 @@ class LineTopIndex { let remainingHeight = Math.max(0, top - lastTop) let remainingRows = Math.min(this.maxRow, lastRow + remainingHeight / this.defaultLineHeight) switch (roundingStrategy) { - case "floor": + case 'floor': return Math.floor(remainingRows) - case "ceil": + case 'ceil': return Math.ceil(remainingRows) default: throw new Error(`Cannot use '${roundingStrategy}' as a rounding strategy!`) From 5f6f99259e39b349bccdac46e2418cb8203beb02 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 15:05:14 +0100 Subject: [PATCH 043/173] Ensure custom gutters work properly --- spec/text-editor-presenter-spec.coffee | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index f28dade69..1dce76e7c 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2978,6 +2978,54 @@ 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 = editor.addBlockDecorationForScreenRow(0) + presenter.setBlockDecorationDimensions(blockDecoration1, 0, 3) + # block decoration between decoration1 and decoration2 + blockDecoration2 = editor.addBlockDecorationForScreenRow(3) + presenter.setBlockDecorationDimensions(blockDecoration2, 0, 5) + # block decoration between decoration2 and decoration3 + blockDecoration3 = editor.addBlockDecorationForScreenRow(10) + presenter.setBlockDecorationDimensions(blockDecoration3, 0, 7) + + decorationState = getContentForGutterWithName(presenter, 'test-gutter') + expect(decorationState[decoration1.id].top).toBe lineHeight * marker1.getScreenRange().start.row + expect(decorationState[decoration1.id].height).toBe lineHeight * marker1.getScreenRange().getRowCount() + 3 + 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 + 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 + 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 + 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 + 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 + 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) From da42fc74ed1596706e6aec5a10fe77dbd6f0c8c4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 15:05:41 +0100 Subject: [PATCH 044/173] :fire: :lipstick: --- src/block-decorations-presenter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 276fbb401..f1a05da85 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -1,7 +1,6 @@ /** @babel */ const {CompositeDisposable, Emitter} = require('event-kit') -const LineTopIndex = require('./linear-line-top-index') module.exports = class BlockDecorationsPresenter { From e4655c62e4b9d8d1bd33dba61088d1cadb77635c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 15:07:54 +0100 Subject: [PATCH 045/173] :green_heart: Fix false negative --- spec/text-editor-presenter-spec.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 1dce76e7c..1ae540005 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -910,7 +910,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.scrollTop).toBe 13 it "scrolls down automatically when the model is changed", -> - presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20) + presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 10) editor.setText("") editor.insertNewline() @@ -919,6 +919,9 @@ describe "TextEditorPresenter", -> editor.insertNewline() expect(presenter.getState().content.scrollTop).toBe(10) + editor.insertNewline() + expect(presenter.getState().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) From f6688b6d712142d17d6cd0c0504973e17408f703 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 15:24:55 +0100 Subject: [PATCH 046/173] Don't use babel when not needed --- src/block-decorations-presenter.js | 25 ++++++++++++------------- src/linear-line-top-index.js | 5 +++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index f1a05da85..9964f3eb9 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -1,13 +1,13 @@ -/** @babel */ +'use strict' -const {CompositeDisposable, Emitter} = require('event-kit') +const EventKit = require('event-kit') module.exports = class BlockDecorationsPresenter { constructor (model, lineTopIndex) { this.model = model - this.disposables = new CompositeDisposable() - this.emitter = new Emitter() + this.disposables = new EventKit.CompositeDisposable() + this.emitter = new EventKit.Emitter() this.firstUpdate = true this.lineTopIndex = lineTopIndex this.blocksByDecoration = new Map() @@ -33,13 +33,12 @@ class BlockDecorationsPresenter { observeModel () { this.lineTopIndex.setMaxRow(this.model.getScreenLineCount()) this.lineTopIndex.setDefaultLineHeight(this.model.getLineHeightInPixels()) - this.disposables.add(this.model.onDidAddDecoration((decoration) => { - this.observeDecoration(decoration) - })) - this.disposables.add(this.model.onDidChange(({start, end, screenDelta}) => { - let oldExtent = end - start - let newExtent = Math.max(0, end - start + screenDelta) - this.lineTopIndex.splice(start, oldExtent, newExtent) + + this.disposables.add(this.model.onDidAddDecoration(this.observeDecoration.bind(this))) + this.disposables.add(this.model.onDidChange((changeEvent) => { + let oldExtent = changeEvent.end - changeEvent.start + let newExtent = Math.max(0, changeEvent.end - changeEvent.start + changeEvent.screenDelta) + this.lineTopIndex.splice(changeEvent.start, oldExtent, newExtent) })) } @@ -125,8 +124,8 @@ class BlockDecorationsPresenter { this.emitter.emit('did-update-state') } - didMoveDecoration (decoration, {textChanged}) { - if (textChanged) { + didMoveDecoration (decoration, markerEvent) { + if (markerEvent.textChanged) { // No need to move blocks because of a text change, because we already splice on buffer change. return } diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index a930b4a38..3fe501ec9 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -1,4 +1,4 @@ -/** @babel */ +'use strict' module.exports = class LineTopIndex { @@ -81,7 +81,8 @@ class LineTopIndex { return this.topPixelPositionForRow(row + 1) - this.defaultLineHeight } - rowForTopPixelPosition (top, roundingStrategy='floor') { + rowForTopPixelPosition (top, strategy) { + const roundingStrategy = strategy || 'floor' let blocksHeight = 0 let lastRow = 0 let lastTop = 0 From 555d77afa650d04fa2173c4ad55a05e9277ffba4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 15:45:47 +0100 Subject: [PATCH 047/173] Do not remove invisible decorations on ::mouseWheelScreenRow --- spec/text-editor-presenter-spec.coffee | 19 +++++++++++++++++++ src/block-decorations-presenter.js | 5 +++-- src/text-editor-presenter.coffee | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 1ae540005..fa2f1ea4f 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2103,6 +2103,25 @@ describe "TextEditorPresenter", -> stateForBlockDecoration = (presenter, decoration) -> presenter.getState().content.blockDecorations[decoration.id] + it "contains state for measured block decorations that are not visible when they are on ::mouseWheelScreenRow", -> + blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) + presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0, stoppedScrollingDelay: 200) + presenter.getState() # 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 "contains state for block decorations, indicating the screen row they belong to both initially and when their markers move", -> item = {} blockDecoration1 = editor.addBlockDecorationForScreenRow(0, item) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 9964f3eb9..51944e584 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -75,13 +75,14 @@ class BlockDecorationsPresenter { return blocks.map((block) => this.decorationsByBlock.get(block.id)).filter((decoration) => decoration) } - decorationsForScreenRowRange (startRow, endRow) { + decorationsForScreenRowRange (startRow, endRow, mouseWheelScreenRow) { let blocks = this.lineTopIndex.allBlocks() let decorationsByScreenRow = new Map() for (let block of blocks) { let decoration = this.decorationsByBlock.get(block.id) let hasntMeasuredDecoration = !this.measuredDecorations.has(decoration) - let isVisible = startRow <= block.row && block.row < endRow + let isWithinVisibleRange = startRow <= block.row && block.row < endRow + let isVisible = isWithinVisibleRange || block.row === mouseWheelScreenRow if (decoration && (isVisible || hasntMeasuredDecoration)) { let decorations = decorationsByScreenRow.get(block.row) || [] decorations.push({decoration, isVisible}) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2a869045f..78bc67a08 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1208,7 +1208,7 @@ class TextEditorPresenter startRow = @getStartTileRow() endRow = @getEndTileRow() + @tileSize - decorations = @blockDecorationsPresenter.decorationsForScreenRowRange(startRow, endRow) + decorations = @blockDecorationsPresenter.decorationsForScreenRowRange(startRow, endRow, @mouseWheelScreenRow) decorations.forEach (decorations, screenRow) => for {decoration, isVisible} in decorations @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} From 14b126ace0a187d09f9dc57e9ecc0f0226daac3f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 16:06:36 +0100 Subject: [PATCH 048/173] :art: --- src/lines-tile-component.coffee | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index f447c3352..6c676dfc2 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -138,11 +138,18 @@ class LinesTileComponent @updateBlockDecorationInsertionPoint(id) updateBlockDecorationInsertionPoint: (id) -> - {blockDecorations, screenRow} = @newTileState.lines[id] - elementsIds = blockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',') - if insertionPoint = @insertionPointsByLineId[id] + oldLineState = @oldTileState.lines[id] + newLineState = @newTileState.lines[id] + insertionPoint = @insertionPointsByLineId[id] + return unless insertionPoint? + + if newLineState.screenRow isnt oldLineState.screenRow insertionPoint.dataset.screenRow = screenRow - insertionPoint.setAttribute("select", elementsIds) + + blockDecorationsSelector = newLineState.blockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',') + if blockDecorationsSelector isnt oldLineState.blockDecorationsSelector + insertionPoint.setAttribute("select", blockDecorationsSelector) + oldLineState.blockDecorationsSelector = blockDecorationsSelector findNodeNextTo: (node) -> for nextNode, index in @domNode.children @@ -369,16 +376,16 @@ class LinesTileComponent else if oldLineState.hasBlockDecorations and not newLineState.hasBlockDecorations @removeBlockDecorationInsertionPoint(id) - oldLineState.hasBlockDecorations = newLineState.hasBlockDecorations - if newLineState.screenRow isnt oldLineState.screenRow - @updateBlockDecorationInsertionPoint(id) - lineNode.dataset.screenRow = newLineState.screenRow - oldLineState.screenRow = newLineState.screenRow @lineIdsByScreenRow[newLineState.screenRow] = id @screenRowsByLineId[id] = newLineState.screenRow + @updateBlockDecorationInsertionPoint(id) + + oldLineState.screenRow = newLineState.screenRow + oldLineState.hasBlockDecorations = newLineState.hasBlockDecorations + lineNodeForScreenRow: (screenRow) -> @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] From 36103a024ac2c89359ce57afc74bef40d6e78a43 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 16:25:24 +0100 Subject: [PATCH 049/173] Make sure to add screen row to block decoration nodes --- spec/text-editor-component-spec.js | 31 ++++++++++++++++++++++++++ src/block-decorations-component.coffee | 13 ++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 3b8409469..f4afad9ca 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -3540,6 +3540,37 @@ 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.addBlockDecorationForScreenRow(0, 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' diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index 2a8574ef6..c154b85c6 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -31,10 +31,9 @@ class BlockDecorationsComponent if @oldState.blockDecorations.hasOwnProperty(id) @updateBlockDecorationNode(id) else + @oldState.blockDecorations[id] = {} @createAndAppendBlockDecorationNode(id) - @oldState.blockDecorations[id] = cloneObject(blockDecorationState) - measureBlockDecorations: -> for decorationId, blockDecorationNode of @blockDecorationNodesById decoration = @newState.blockDecorations[decorationId].decoration @@ -48,18 +47,20 @@ class BlockDecorationsComponent blockDecorationState = @newState.blockDecorations[id] blockDecorationNode = @views.getView(blockDecorationState.decoration.getProperties().item) blockDecorationNode.id = "atom--block-decoration-#{id}" - unless blockDecorationState.isVisible - blockDecorationNode.classList.add("atom--invisible-block-decoration") - @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 From cc4344735e42faddfd7db47af5f5c9e337f63085 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 16:30:15 +0100 Subject: [PATCH 050/173] :green_heart: Fix specs --- spec/lines-yardstick-spec.coffee | 5 +++-- spec/text-editor-component-spec.js | 25 +++++++++---------------- src/block-decorations-presenter.js | 5 +++++ src/lines-tile-component.coffee | 2 +- src/text-editor-component.coffee | 20 ++++++++++---------- src/text-editor-presenter.coffee | 9 +++++++-- 6 files changed, 35 insertions(+), 31 deletions(-) diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index ae85a0e9d..fbf5c53bf 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -1,4 +1,5 @@ -LinesYardstick = require "../src/lines-yardstick" +LinesYardstick = require '../src/lines-yardstick' +LineTopIndex = require '../src/linear-line-top-index' {toArray} = require 'underscore-plus' describe "LinesYardstick", -> @@ -56,7 +57,7 @@ describe "LinesYardstick", -> textNodes editor.setLineHeightInPixels(14) - linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider, atom.grammars) + linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider, new LineTopIndex(), atom.grammars) afterEach -> lineNode.remove() for lineNode in createdLineNodes diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index f4afad9ca..d214201fa 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1663,7 +1663,7 @@ describe('TextEditorComponent', function () { atom.themes.removeStylesheet('test') }) - it("renders all the editor's block decorations, inserting them in the appropriate spots between lines", async function () { + it("renders visible and yet-to-be-measured block decorations, inserting them in the appropriate spots between lines and refreshing them when needed", async function () { wrapperNode.style.height = 9 * lineHeightInPixels + 'px' component.measureDimensions() await nextViewUpdatePromise() @@ -1680,7 +1680,7 @@ describe('TextEditorComponent', function () { atom-text-editor .decoration-4 { width: 30px; height: 120px; } ` - await nextViewUpdatePromise() + await nextAnimationFramePromise() expect(component.getDomNode().querySelectorAll(".line").length).toBe(7) @@ -1694,19 +1694,17 @@ describe('TextEditorComponent', function () { 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")).toBe(item4) + expect(component.getTopmostDOMNode().querySelector(".decoration-4")).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) - expect(item4.getBoundingClientRect().top).toBe(0) // hidden - expect(item4.getBoundingClientRect().height).toBe(120) editor.setCursorScreenPosition([0, 0]) editor.insertNewline() blockDecoration1.destroy() - await nextViewUpdatePromise() + await nextAnimationFramePromise() expect(component.getDomNode().querySelectorAll(".line").length).toBe(7) @@ -1720,21 +1718,19 @@ describe('TextEditorComponent', function () { 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-4")).toBeNull() - expect(item1.getBoundingClientRect().height).toBe(0) // deleted expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) - expect(item4.getBoundingClientRect().top).toBe(0) // hidden - expect(item4.getBoundingClientRect().height).toBe(120) - await nextViewUpdatePromise() + await nextAnimationFramePromise() atom.styles.addStyleSheet ` atom-text-editor .decoration-2 { height: 60px !important; } ` - await nextViewUpdatePromise() + 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) @@ -1748,13 +1744,10 @@ describe('TextEditorComponent', function () { 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-4")).toBeNull() - expect(item1.getBoundingClientRect().height).toBe(0) // deleted expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60) - expect(item4.getBoundingClientRect().top).toBe(0) // hidden - expect(item4.getBoundingClientRect().height).toBe(120) // hidden }) }) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 51944e584..bfe08dd63 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -70,6 +70,11 @@ class BlockDecorationsPresenter { this.emitter.emit('did-update-state') } + measurementsChanged () { + this.measuredDecorations.clear() + this.emitter.emit('did-update-state') + } + decorationsForScreenRow (screenRow) { let blocks = this.lineTopIndex.allBlocks().filter((block) => block.row === screenRow) return blocks.map((block) => this.decorationsByBlock.get(block.id)).filter((decoration) => decoration) diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index 6c676dfc2..73e88aa32 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -144,7 +144,7 @@ class LinesTileComponent return unless insertionPoint? if newLineState.screenRow isnt oldLineState.screenRow - insertionPoint.dataset.screenRow = screenRow + insertionPoint.dataset.screenRow = newLineState.screenRow blockDecorationsSelector = newLineState.blockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',') if blockDecorationsSelector isnt oldLineState.blockDecorationsSelector diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index afe46c705..c35139d7c 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -283,13 +283,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? @@ -576,7 +576,7 @@ class TextEditorComponent handleStylingChange: => @sampleFontStyling() @sampleBackgroundColors() - @invalidateCharacterWidths() + @invalidateMeasurements() handleDragUntilMouseUp: (dragHandler) -> dragging = false @@ -730,7 +730,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) @@ -840,7 +840,7 @@ class TextEditorComponent setFontSize: (fontSize) -> @getTopmostDOMNode().style.fontSize = fontSize + 'px' @sampleFontStyling() - @invalidateCharacterWidths() + @invalidateMeasurements() getFontFamily: -> getComputedStyle(@getTopmostDOMNode()).fontFamily @@ -848,16 +848,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) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 78bc67a08..5b1f01b35 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1152,9 +1152,12 @@ class TextEditorPresenter @koreanCharWidth = koreanCharWidth @model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) @restoreScrollLeftIfNeeded() - @characterWidthsChanged() + @measurementsChanged() - characterWidthsChanged: -> + measurementsChanged: -> + @blockDecorationsPresenter.measurementsChanged() + + @shouldUpdateHeightState = true @shouldUpdateHorizontalScrollState = true @shouldUpdateVerticalScrollState = true @shouldUpdateScrollbarsState = true @@ -1162,8 +1165,10 @@ class TextEditorPresenter @shouldUpdateContentState = true @shouldUpdateDecorations = true @shouldUpdateLinesState = true + @shouldUpdateLineNumbersState = true @shouldUpdateCursorsState = true @shouldUpdateOverlaysState = true + @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() From f2a02215199d9c41b6a10a45f228775d7957948c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 3 Dec 2015 17:42:21 +0100 Subject: [PATCH 051/173] Make sure block decorations are always in the right spot --- spec/text-editor-component-spec.js | 2 +- spec/text-editor-presenter-spec.coffee | 39 ++++++++++++++++++++++---- src/lines-tile-component.coffee | 4 +-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index d214201fa..2c090cd4d 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1663,7 +1663,7 @@ describe('TextEditorComponent', function () { atom.themes.removeStylesheet('test') }) - it("renders visible and yet-to-be-measured block decorations, inserting them in the appropriate spots between lines and refreshing them when needed", async function () { + it("renders visible and yet-to-be-measured block decorations, inserting them between the appropriate lines and refreshing them as needed", async function () { wrapperNode.style.height = 9 * lineHeightInPixels + 'px' component.measureDimensions() await nextViewUpdatePromise() diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index fa2f1ea4f..a07c3446b 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1320,11 +1320,11 @@ describe "TextEditorPresenter", -> expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) waitsForStateToUpdate presenter, -> - blockDecoration1.destroy() blockDecoration3.destroy() + blockDecoration1.getMarker().setHeadBufferPosition([0, 0]) runs -> - expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual([blockDecoration1]) expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([]) expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([]) @@ -1338,6 +1338,25 @@ describe "TextEditorPresenter", -> expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) + waitsForStateToUpdate presenter, -> + editor.setCursorBufferPosition([0, 0]) + editor.insertNewline() + + runs -> + expect(lineStateForScreenRow(presenter, 0).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([blockDecoration1]) + expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([blockDecoration2]) + expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) + expect(lineStateForScreenRow(presenter, 12).blockDecorations).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') @@ -1551,10 +1570,11 @@ describe "TextEditorPresenter", -> blockDecoration1 = editor.addBlockDecorationForScreenRow(0) blockDecoration2 = editor.addBlockDecorationForScreenRow(1) - presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30) - presenter.setBlockDecorationDimensions(blockDecoration2, 0, 10) - waitsForStateToUpdate presenter + 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} @@ -1562,6 +1582,15 @@ describe "TextEditorPresenter", -> 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]], diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index 73e88aa32..24c05dc2e 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -127,14 +127,14 @@ class LinesTileComponent delete @insertionPointsByLineId[id] insertBlockDecorationInsertionPoint: (id) -> - {hasBlockDecorations} = @newTileState.lines[id] + {hasBlockDecorations, screenRow} = @newTileState.lines[id] if hasBlockDecorations lineNode = @lineNodesByLineId[id] insertionPoint = @domElementPool.buildElement("content") @domNode.insertBefore(insertionPoint, lineNode) @insertionPointsByLineId[id] = insertionPoint - + insertionPoint.dataset.screenRow = screenRow @updateBlockDecorationInsertionPoint(id) updateBlockDecorationInsertionPoint: (id) -> From 16525047f1f9f46a14c6a4197619fd916d6d6f7a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Dec 2015 12:49:59 +0100 Subject: [PATCH 052/173] :green_heart: Fix component and presenter specs --- spec/text-editor-component-spec.js | 23 +++++++++++------------ spec/text-editor-presenter-spec.coffee | 12 ++++++------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 2c090cd4d..a645493e4 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1673,13 +1673,13 @@ describe('TextEditorComponent', function () { let [item3, blockDecoration3] = createBlockDecorationForScreenRowWith(4, {className: "decoration-3"}) let [item4, blockDecoration4] = createBlockDecorationForScreenRowWith(7, {className: "decoration-4"}) - 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.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; }`, + {context: 'atom-text-editor'} + ) await nextAnimationFramePromise() expect(component.getDomNode().querySelectorAll(".line").length).toBe(7) @@ -1723,11 +1723,10 @@ describe('TextEditorComponent', function () { expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) - await nextAnimationFramePromise() - - atom.styles.addStyleSheet ` - atom-text-editor .decoration-2 { height: 60px !important; } - ` + atom.styles.addStyleSheet( + `atom-text-editor .decoration-2 { height: 60px !important; }`, + {context: 'atom-text-editor'} + ) await nextAnimationFramePromise() // causes the DOM to update and to retrieve new styles await nextAnimationFramePromise() // applies the changes diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index a07c3446b..885640e5b 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -403,7 +403,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20) - presenter.characterWidthsChanged() + presenter.measurementsChanged() expect(presenter.getState().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", -> @@ -676,7 +676,7 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20) - presenter.characterWidthsChanged() + presenter.measurementsChanged() expect(presenter.getState().hiddenInput.width).toBe 20 it "is 2px at the end of lines", -> @@ -818,7 +818,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20) - presenter.characterWidthsChanged() + presenter.measurementsChanged() expect(presenter.getState().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", -> @@ -1665,12 +1665,12 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.getLinesYardstick().setScopedCharacterWidth(['source.js', 'storage.modifier.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.modifier.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", -> @@ -2016,7 +2016,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}] } From 14d8ecefdde4d9c2dacb3cfdd9efa13751b24637 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Dec 2015 12:54:20 +0100 Subject: [PATCH 053/173] :green_heart: Fix LinesYardstick specs --- spec/lines-yardstick-spec.coffee | 5 ++++- src/lines-yardstick.coffee | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index fbf5c53bf..44267699e 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -57,7 +57,10 @@ describe "LinesYardstick", -> textNodes editor.setLineHeightInPixels(14) - linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider, new LineTopIndex(), atom.grammars) + lineTopIndex = new LineTopIndex() + lineTopIndex.setDefaultLineHeight(editor.getLineHeightInPixels()) + lineTopIndex.setMaxRow(editor.getScreenLineCount()) + linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider, lineTopIndex, atom.grammars) afterEach -> lineNode.remove() for lineNode in createdLineNodes diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index c1faa6399..847ebfd46 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -25,6 +25,7 @@ class LinesYardstick row = @lineTopIndex.rowForTopPixelPosition(targetTop, 'floor') targetLeft = 0 if targetTop < 0 targetLeft = Infinity if row > @model.getLastScreenRow() + row = Math.min(row, @model.getLastScreenRow()) @prepareScreenRowsForMeasurement([row]) unless measureVisibleLinesOnly From 7554f71f7451f6e2143ce6b1d61eaefb5d435717 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Dec 2015 14:21:23 +0100 Subject: [PATCH 054/173] Make sure screen row is set correctly on --- spec/text-editor-component-spec.js | 52 +++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index a645493e4..1ae914d35 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1724,7 +1724,7 @@ describe('TextEditorComponent', function () { expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) atom.styles.addStyleSheet( - `atom-text-editor .decoration-2 { height: 60px !important; }`, + 'atom-text-editor .decoration-2 { height: 60px !important; }', {context: 'atom-text-editor'} ) @@ -1748,6 +1748,56 @@ describe('TextEditorComponent', function () { expect(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 60) }) + + it("correctly sets screen rows on elements, both initially and when decorations move", async function () { + wrapperNode.style.height = 9 * lineHeightInPixels + 'px' + component.measureDimensions() + await nextViewUpdatePromise() + + let [item, blockDecoration] = createBlockDecorationForScreenRowWith(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") + }) }) describe('highlight decoration rendering', function () { From b6b2958e67055420e11a7dc09b327006cec7806a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Dec 2015 15:33:18 +0100 Subject: [PATCH 055/173] :memo: TextEditor::addBlockDecorationForScreenRow --- src/text-editor.coffee | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 136756538..27fd75c22 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1398,11 +1398,16 @@ class TextEditor extends Model Section: Decorations ### + # Experimental: Mark and add a block decoration to the specified screen row. + # + # * `screenRow` A {Number} representing the screen row where to add the block decoration. + # * `item` A {ViewRegistry::getView}-compatible object to render. + # + # Returns a {Decoration} object. addBlockDecorationForScreenRow: (screenRow, item) -> @decorateMarker( @markScreenPosition([screenRow, 0], invalidate: "never"), - type: "block", - item: item + type: "block", item: item ) # Essential: Add a decoration that tracks a {TextEditorMarker}. When the From cb4c27757a0914b31abad050bac6ba71ea6de4fa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Dec 2015 15:35:53 +0100 Subject: [PATCH 056/173] :memo: TextEditorElement::invalidateBlockDecorationDimensions --- src/text-editor-element.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 6722f51df..380417163 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -347,6 +347,12 @@ 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...) From c578f221bf7be484d726f59c9f05b60edd337f8d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 4 Dec 2015 15:42:49 +0100 Subject: [PATCH 057/173] :white_check_mark: Test ::invalidateBlockDecorationDimensions --- spec/text-editor-component-spec.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index ab9583f31..86f924116 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1724,7 +1724,7 @@ describe('TextEditorComponent', function () { expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 40) atom.styles.addStyleSheet( - 'atom-text-editor .decoration-2 { height: 60px !important; }', + 'atom-text-editor .decoration-2 { height: 60px; }', {context: 'atom-text-editor'} ) @@ -1747,6 +1747,28 @@ describe('TextEditorComponent', function () { 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(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 + 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 + "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(item2.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 3) + expect(item3.getBoundingClientRect().top).toBe(editor.getLineHeightInPixels() * 5 + 20) }) it("correctly sets screen rows on elements, both initially and when decorations move", async function () { From cfb30c795dfaf9b35da0bdab76aeed08c5853cd5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 14 Dec 2015 15:04:54 +0100 Subject: [PATCH 058/173] Use an updated version of LineTopIndex --- spec/fake-lines-yardstick.coffee | 10 +- spec/line-top-index-spec.js | 189 ------------------------- spec/lines-yardstick-spec.coffee | 7 +- spec/text-editor-presenter-spec.coffee | 15 +- src/block-decorations-presenter.js | 19 +-- src/linear-line-top-index.js | 33 ++--- src/lines-yardstick.coffee | 12 +- src/text-editor-component.coffee | 5 +- src/text-editor-presenter.coffee | 24 ++-- 9 files changed, 56 insertions(+), 258 deletions(-) delete mode 100644 spec/line-top-index-spec.js diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index 3d2ebe7d5..a8a624970 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -31,7 +31,7 @@ class FakeLinesYardstick targetColumn = screenPosition.column baseCharacterWidth = @model.getDefaultCharWidth() - top = @lineTopIndex.bottomPixelPositionForRow(targetRow) + top = @lineTopIndex.pixelPositionForRow(targetRow) left = 0 column = 0 @@ -61,14 +61,14 @@ class FakeLinesYardstick {top, left} pixelRectForScreenRange: (screenRange) -> + top = @lineTopIndex.pixelPositionForRow(screenRange.start.row) if screenRange.end.row > screenRange.start.row - top = @pixelPositionForScreenPosition(screenRange.start).top left = 0 - height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.pixelPositionForRow(screenRange.end.row) - top + @model.getLineHeightInPixels() width = @presenter.getScrollWidth() else - {top, left} = @pixelPositionForScreenPosition(screenRange.start, false) - height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top + {left} = @pixelPositionForScreenPosition(screenRange.start, false) + height = @lineTopIndex.pixelPositionForRow(screenRange.end.row) - top + @model.getLineHeightInPixels() width = @pixelPositionForScreenPosition(screenRange.end, false).left - left {top, left, width, height} diff --git a/spec/line-top-index-spec.js b/spec/line-top-index-spec.js deleted file mode 100644 index 62d3e1839..000000000 --- a/spec/line-top-index-spec.js +++ /dev/null @@ -1,189 +0,0 @@ -/** @babel */ - -const LineTopIndex = require('../src/linear-line-top-index') - -describe("LineTopIndex", function () { - let lineTopIndex - - beforeEach(function () { - lineTopIndex = new LineTopIndex() - lineTopIndex.setDefaultLineHeight(10) - lineTopIndex.setMaxRow(12) - }) - - describe("::topPixelPositionForRow(row)", function () { - it("performs the simple math when there are no block decorations", function () { - expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) - expect(lineTopIndex.topPixelPositionForRow(4)).toBe(40) - expect(lineTopIndex.topPixelPositionForRow(5)).toBe(50) - expect(lineTopIndex.topPixelPositionForRow(12)).toBe(120) - expect(lineTopIndex.topPixelPositionForRow(13)).toBe(120) - expect(lineTopIndex.topPixelPositionForRow(14)).toBe(120) - - lineTopIndex.splice(0, 2, 3) - - expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) - expect(lineTopIndex.topPixelPositionForRow(4)).toBe(40) - expect(lineTopIndex.topPixelPositionForRow(5)).toBe(50) - expect(lineTopIndex.topPixelPositionForRow(12)).toBe(120) - expect(lineTopIndex.topPixelPositionForRow(13)).toBe(130) - expect(lineTopIndex.topPixelPositionForRow(14)).toBe(130) - }) - - it("takes into account inserted and removed blocks", function () { - let block1 = lineTopIndex.insertBlock(0, 10) - let block2 = lineTopIndex.insertBlock(3, 20) - let block3 = lineTopIndex.insertBlock(5, 20) - - expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) - expect(lineTopIndex.topPixelPositionForRow(1)).toBe(20) - expect(lineTopIndex.topPixelPositionForRow(2)).toBe(30) - expect(lineTopIndex.topPixelPositionForRow(3)).toBe(40) - expect(lineTopIndex.topPixelPositionForRow(4)).toBe(70) - expect(lineTopIndex.topPixelPositionForRow(5)).toBe(80) - expect(lineTopIndex.topPixelPositionForRow(6)).toBe(110) - - lineTopIndex.removeBlock(block1) - lineTopIndex.removeBlock(block3) - - expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) - expect(lineTopIndex.topPixelPositionForRow(1)).toBe(10) - expect(lineTopIndex.topPixelPositionForRow(2)).toBe(20) - expect(lineTopIndex.topPixelPositionForRow(3)).toBe(30) - expect(lineTopIndex.topPixelPositionForRow(4)).toBe(60) - expect(lineTopIndex.topPixelPositionForRow(5)).toBe(70) - expect(lineTopIndex.topPixelPositionForRow(6)).toBe(80) - }) - - it("moves blocks down/up when splicing regions", function () { - let block1 = lineTopIndex.insertBlock(3, 20) - let block2 = lineTopIndex.insertBlock(5, 30) - - lineTopIndex.splice(0, 0, 4) - - expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) - expect(lineTopIndex.topPixelPositionForRow(6)).toBe(60) - expect(lineTopIndex.topPixelPositionForRow(7)).toBe(70) - expect(lineTopIndex.topPixelPositionForRow(8)).toBe(100) - expect(lineTopIndex.topPixelPositionForRow(9)).toBe(110) - expect(lineTopIndex.topPixelPositionForRow(10)).toBe(150) - expect(lineTopIndex.topPixelPositionForRow(11)).toBe(160) - - lineTopIndex.splice(0, 6, 2) - - expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) - expect(lineTopIndex.topPixelPositionForRow(3)).toBe(30) - expect(lineTopIndex.topPixelPositionForRow(4)).toBe(60) - expect(lineTopIndex.topPixelPositionForRow(5)).toBe(70) - expect(lineTopIndex.topPixelPositionForRow(6)).toBe(110) - - lineTopIndex.splice(2, 4, 0) - - expect(lineTopIndex.topPixelPositionForRow(0)).toBe(0) - expect(lineTopIndex.topPixelPositionForRow(1)).toBe(10) - expect(lineTopIndex.topPixelPositionForRow(2)).toBe(20) - expect(lineTopIndex.topPixelPositionForRow(3)).toBe(80) - expect(lineTopIndex.topPixelPositionForRow(4)).toBe(90) - expect(lineTopIndex.topPixelPositionForRow(5)).toBe(100) - expect(lineTopIndex.topPixelPositionForRow(6)).toBe(110) - expect(lineTopIndex.topPixelPositionForRow(7)).toBe(120) - expect(lineTopIndex.topPixelPositionForRow(8)).toBe(130) - expect(lineTopIndex.topPixelPositionForRow(9)).toBe(130) - }) - }) - - describe("::rowForTopPixelPosition(top)", function () { - it("performs the simple math when there are no block decorations", function () { - expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(44)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(46)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(50)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(120)).toBe(12) - expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(12) - expect(lineTopIndex.rowForTopPixelPosition(140)).toBe(12) - expect(lineTopIndex.rowForTopPixelPosition(145)).toBe(12) - - lineTopIndex.splice(0, 2, 3) - - expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(50)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(120)).toBe(12) - expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(13) - expect(lineTopIndex.rowForTopPixelPosition(140)).toBe(13) - }) - - it("takes into account inserted and removed blocks", function () { - let block1 = lineTopIndex.insertBlock(0, 10) - let block2 = lineTopIndex.insertBlock(3, 20) - let block3 = lineTopIndex.insertBlock(5, 20) - - expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(6)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(10)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(12)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(17)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(20)).toBe(1) - expect(lineTopIndex.rowForTopPixelPosition(30)).toBe(2) - expect(lineTopIndex.rowForTopPixelPosition(40)).toBe(3) - expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(90)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(95)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(106)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) - expect(lineTopIndex.rowForTopPixelPosition(160)).toBe(11) - expect(lineTopIndex.rowForTopPixelPosition(166)).toBe(11) - expect(lineTopIndex.rowForTopPixelPosition(170)).toBe(12) - expect(lineTopIndex.rowForTopPixelPosition(240)).toBe(12) - - lineTopIndex.removeBlock(block1) - lineTopIndex.removeBlock(block3) - - expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(10)).toBe(1) - expect(lineTopIndex.rowForTopPixelPosition(20)).toBe(2) - expect(lineTopIndex.rowForTopPixelPosition(30)).toBe(3) - expect(lineTopIndex.rowForTopPixelPosition(60)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(6) - }) - - it("moves blocks down/up when splicing regions", function () { - let block1 = lineTopIndex.insertBlock(3, 20) - let block2 = lineTopIndex.insertBlock(5, 30) - - lineTopIndex.splice(0, 0, 4) - - expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(60)).toBe(6) - expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(7) - expect(lineTopIndex.rowForTopPixelPosition(100)).toBe(8) - expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(9) - expect(lineTopIndex.rowForTopPixelPosition(150)).toBe(10) - expect(lineTopIndex.rowForTopPixelPosition(160)).toBe(11) - - lineTopIndex.splice(0, 6, 2) - - expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(30)).toBe(3) - expect(lineTopIndex.rowForTopPixelPosition(60)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(70)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) - - lineTopIndex.splice(2, 4, 0) - - expect(lineTopIndex.rowForTopPixelPosition(0)).toBe(0) - expect(lineTopIndex.rowForTopPixelPosition(10)).toBe(1) - expect(lineTopIndex.rowForTopPixelPosition(20)).toBe(2) - expect(lineTopIndex.rowForTopPixelPosition(80)).toBe(3) - expect(lineTopIndex.rowForTopPixelPosition(90)).toBe(4) - expect(lineTopIndex.rowForTopPixelPosition(100)).toBe(5) - expect(lineTopIndex.rowForTopPixelPosition(110)).toBe(6) - expect(lineTopIndex.rowForTopPixelPosition(120)).toBe(7) - expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(8) - expect(lineTopIndex.rowForTopPixelPosition(130)).toBe(8) - }) - }) -}) diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index 44267699e..791385d64 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -57,9 +57,10 @@ describe "LinesYardstick", -> textNodes editor.setLineHeightInPixels(14) - lineTopIndex = new LineTopIndex() - lineTopIndex.setDefaultLineHeight(editor.getLineHeightInPixels()) - lineTopIndex.setMaxRow(editor.getScreenLineCount()) + lineTopIndex = new LineTopIndex({ + maxRow: editor.getScreenLineCount(), + defaultLineHeight: editor.getLineHeightInPixels() + }) linesYardstick = new LinesYardstick(editor, mockPresenter, mockLineNodesProvider, lineTopIndex, atom.grammars) afterEach -> diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index fdcb8a418..87c32475d 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -27,7 +27,10 @@ describe "TextEditorPresenter", -> buffer.destroy() buildPresenterWithoutMeasurements = (params={}) -> - lineTopIndex = new LineTopIndex + lineTopIndex = new LineTopIndex({ + maxRow: editor.getScreenLineCount(), + defaultLineHeight: editor.getLineHeightInPixels() + }) _.defaults params, model: editor config: atom.config @@ -526,8 +529,6 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - # Setting `null` as the DOM element, as it doesn't really matter here. - # Maybe a signal that we should separate models from views? blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) @@ -719,8 +720,6 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - # Setting `null` as the DOM element, as it doesn't really matter here. - # Maybe a signal that we should separate models from views? blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) @@ -3041,8 +3040,8 @@ describe "TextEditorPresenter", -> presenter.setBlockDecorationDimensions(blockDecoration3, 0, 7) decorationState = getContentForGutterWithName(presenter, 'test-gutter') - expect(decorationState[decoration1.id].top).toBe lineHeight * marker1.getScreenRange().start.row - expect(decorationState[decoration1.id].height).toBe lineHeight * marker1.getScreenRange().getRowCount() + 3 + 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 @@ -3286,8 +3285,6 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - # Setting `null` as the DOM element, as it doesn't really matter here. - # Maybe a signal that we should separate models from views? blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index bfe08dd63..7f7258226 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -31,9 +31,6 @@ class BlockDecorationsPresenter { } observeModel () { - this.lineTopIndex.setMaxRow(this.model.getScreenLineCount()) - this.lineTopIndex.setDefaultLineHeight(this.model.getLineHeightInPixels()) - this.disposables.add(this.model.onDidAddDecoration(this.observeDecoration.bind(this))) this.disposables.add(this.model.onDidChange((changeEvent) => { let oldExtent = changeEvent.end - changeEvent.start @@ -54,11 +51,10 @@ class BlockDecorationsPresenter { setDimensionsForDecoration (decoration, width, height) { let block = this.blocksByDecoration.get(decoration) if (block) { - this.lineTopIndex.resizeBlock(block, height) + this.lineTopIndex.resizeBlock(decoration.getMarker().id, height) } else { this.observeDecoration(decoration) - block = this.blocksByDecoration.get(decoration) - this.lineTopIndex.resizeBlock(block, height) + this.lineTopIndex.resizeBlock(decoration.getMarker().id, height) } this.measuredDecorations.add(decoration) @@ -124,9 +120,9 @@ class BlockDecorationsPresenter { didAddDecoration (decoration) { let screenRow = decoration.getMarker().getHeadScreenPosition().row - let block = this.lineTopIndex.insertBlock(screenRow, 0) - this.decorationsByBlock.set(block, decoration) - this.blocksByDecoration.set(decoration, block) + this.lineTopIndex.insertBlock(decoration.getMarker().id, screenRow, 0) + this.decorationsByBlock.set(decoration.getMarker().id, decoration) + this.blocksByDecoration.set(decoration, decoration.getMarker().id) this.emitter.emit('did-update-state') } @@ -136,16 +132,15 @@ class BlockDecorationsPresenter { return } - let block = this.blocksByDecoration.get(decoration) let newScreenRow = decoration.getMarker().getHeadScreenPosition().row - this.lineTopIndex.moveBlock(block, newScreenRow) + this.lineTopIndex.moveBlock(decoration.getMarker().id, newScreenRow) this.emitter.emit('did-update-state') } didDestroyDecoration (decoration) { let block = this.blocksByDecoration.get(decoration) if (block) { - this.lineTopIndex.removeBlock(block) + this.lineTopIndex.removeBlock(decoration.getMarker().id) this.blocksByDecoration.delete(decoration) this.decorationsByBlock.delete(block) } diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js index 3fe501ec9..aad947dd8 100644 --- a/src/linear-line-top-index.js +++ b/src/linear-line-top-index.js @@ -1,27 +1,23 @@ -'use strict' +"use babel" -module.exports = -class LineTopIndex { - constructor () { - this.idCounter = 1 +export default class LineTopIndex { + constructor (params = {}) { this.blocks = [] - this.maxRow = 0 - this.defaultLineHeight = 0 + this.maxRow = params.maxRow || 0 + this.setDefaultLineHeight(params.defaultLineHeight || 0) } setDefaultLineHeight (lineHeight) { this.defaultLineHeight = lineHeight } - setMaxRow (maxRow) { - this.maxRow = maxRow + getMaxRow () { + return this.maxRow } - insertBlock (row, height) { - let id = this.idCounter++ + insertBlock (id, row, height) { this.blocks.push({id, row, height}) this.blocks.sort((a, b) => a.row - b.row) - return id } resizeBlock (id, height) { @@ -62,26 +58,21 @@ class LineTopIndex { block.row += newExtent - oldExtent } else { block.row = startRow + newExtent - // invalidate marker? } } }) - this.setMaxRow(this.maxRow + newExtent - oldExtent) + this.maxRow = this.maxRow + newExtent - oldExtent } - topPixelPositionForRow (row) { + pixelPositionForRow (row) { row = Math.min(row, this.maxRow) let linesHeight = row * this.defaultLineHeight - let blocksHeight = this.blocks.filter((block) => block.row < row).reduce((a, b) => a + b.height, 0) + let blocksHeight = this.blocks.filter((block) => block.row <= row).reduce((a, b) => a + b.height, 0) return linesHeight + blocksHeight } - bottomPixelPositionForRow (row) { - return this.topPixelPositionForRow(row + 1) - this.defaultLineHeight - } - - rowForTopPixelPosition (top, strategy) { + rowForPixelPosition (top, strategy) { const roundingStrategy = strategy || 'floor' let blocksHeight = 0 let lastRow = 0 diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 847ebfd46..e6454989a 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -22,7 +22,7 @@ class LinesYardstick targetTop = pixelPosition.top targetLeft = pixelPosition.left defaultCharWidth = @model.getDefaultCharWidth() - row = @lineTopIndex.rowForTopPixelPosition(targetTop, 'floor') + row = @lineTopIndex.rowForPixelPosition(targetTop, 'floor') targetLeft = 0 if targetTop < 0 targetLeft = Infinity if row > @model.getLastScreenRow() row = Math.min(row, @model.getLastScreenRow()) @@ -91,7 +91,7 @@ class LinesYardstick @prepareScreenRowsForMeasurement([targetRow]) unless measureVisibleLinesOnly - top = @lineTopIndex.bottomPixelPositionForRow(targetRow) + top = @lineTopIndex.pixelPositionForRow(targetRow) left = @leftPixelPositionForScreenPosition(targetRow, targetColumn) @clearScreenRowsForMeasurement() unless measureVisibleLinesOnly @@ -174,14 +174,14 @@ class LinesYardstick left + width - offset pixelRectForScreenRange: (screenRange, measureVisibleLinesOnly) -> + top = @lineTopIndex.pixelPositionForRow(screenRange.start.row) if screenRange.end.row > screenRange.start.row - top = @pixelPositionForScreenPosition(screenRange.start, true, measureVisibleLinesOnly).top left = 0 - height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top + height = @lineTopIndex.pixelPositionForRow(screenRange.end.row) - top + @model.getLineHeightInPixels() width = @presenter.getScrollWidth() else - {top, left} = @pixelPositionForScreenPosition(screenRange.start, false, measureVisibleLinesOnly) - height = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) - top + {left} = @pixelPositionForScreenPosition(screenRange.start, false, measureVisibleLinesOnly) + height = @lineTopIndex.pixelPositionForRow(screenRange.end.row) - top + @model.getLineHeightInPixels() width = @pixelPositionForScreenPosition(screenRange.end, false, measureVisibleLinesOnly).left - left {top, left, width, height} diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index c35139d7c..3e644a084 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -50,7 +50,10 @@ class TextEditorComponent @observeConfig() @setScrollSensitivity(@config.get('editor.scrollSensitivity')) - lineTopIndex = new LineTopIndex(@editor) + lineTopIndex = new LineTopIndex({ + maxRow: @editor.getScreenLineCount(), + defaultLineHeight: @editor.getLineHeightInPixels() + }) @presenter = new TextEditorPresenter model: @editor tileSize: tileSize diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5b1f01b35..ca05ea36a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -442,8 +442,8 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 - top = Math.round(@lineTopIndex.topPixelPositionForRow(tileStartRow)) - bottom = Math.round(@lineTopIndex.topPixelPositionForRow(tileEndRow)) + top = Math.round(@lineTopIndex.pixelPositionForRow(tileStartRow - 1) + @lineHeight) + bottom = Math.round(@lineTopIndex.pixelPositionForRow(tileEndRow - 1) + @lineHeight) height = bottom - top tile = @state.content.tiles[tileStartRow] ?= {} @@ -659,8 +659,8 @@ class TextEditorPresenter continue unless @gutterIsVisible(gutter) for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName] - top = @lineTopIndex.topPixelPositionForRow(screenRange.start.row) - bottom = @lineTopIndex.topPixelPositionForRow(screenRange.end.row + 1) + top = @lineTopIndex.pixelPositionForRow(screenRange.start.row) + bottom = @lineTopIndex.pixelPositionForRow(screenRange.end.row) + @lineHeight @customGutterDecorations[gutterName][decorationId] = top: top height: bottom - top @@ -724,12 +724,12 @@ class TextEditorPresenter updateStartRow: -> return unless @scrollTop? and @lineHeight? - @startRow = Math.max(0, @lineTopIndex.rowForTopPixelPosition(@scrollTop, "floor")) + @startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop, "floor")) updateEndRow: -> return unless @scrollTop? and @lineHeight? and @height? - @endRow = @lineTopIndex.rowForTopPixelPosition(@scrollTop + @height + @lineHeight, 'ceil') + @endRow = @lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight, 'ceil') updateRowsPerPage: -> rowsPerPage = Math.floor(@getClientHeight() / @lineHeight) @@ -761,7 +761,7 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round(@lineTopIndex.topPixelPositionForRow(Infinity)) + @contentHeight = Math.round(@lineTopIndex.pixelPositionForRow(Infinity)) if @contentHeight isnt oldContentHeight @updateHeight() @@ -1340,7 +1340,7 @@ class TextEditorPresenter screenRange.end.column = 0 repositionRegionWithinTile: (region, tileStartRow) -> - region.top += @scrollTop - @lineTopIndex.topPixelPositionForRow(tileStartRow) + region.top += @scrollTop - (@lineTopIndex.pixelPositionForRow(tileStartRow - 1) + @lineHeight) region.left += @scrollLeft buildHighlightRegions: (screenRange) -> @@ -1495,7 +1495,7 @@ class TextEditorPresenter @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> - @updateScrollTop(@lineTopIndex.topPixelPositionForRow(screenRow)) + @updateScrollTop(@lineTopIndex.pixelPositionForRow(screenRow)) getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) @@ -1516,8 +1516,8 @@ class TextEditorPresenter verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - top = @lineTopIndex.bottomPixelPositionForRow(screenRange.start.row) - bottom = @lineTopIndex.bottomPixelPositionForRow(screenRange.end.row + 1) + top = @lineTopIndex.pixelPositionForRow(screenRange.start.row) + bottom = @lineTopIndex.pixelPositionForRow(screenRange.end.row) + @lineHeight if options?.center desiredScrollCenter = (top + bottom) / 2 @@ -1589,7 +1589,7 @@ class TextEditorPresenter restoreScrollTopIfNeeded: -> unless @scrollTop? - @updateScrollTop(@lineTopIndex.topPixelPositionForRow(@model.getFirstVisibleScreenRow())) + @updateScrollTop(@lineTopIndex.pixelPositionForRow(@model.getFirstVisibleScreenRow())) restoreScrollLeftIfNeeded: -> unless @scrollLeft? From 4b6a218bb91ff2338c14782714d88ad080b00e71 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 14 Dec 2015 15:53:01 +0100 Subject: [PATCH 059/173] Start to move stuff back into TextEditorPresenter --- spec/text-editor-presenter-spec.coffee | 24 ++++++--- src/block-decorations-presenter.js | 74 ++++++-------------------- src/text-editor-presenter.coffee | 28 +++++----- 3 files changed, 49 insertions(+), 77 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 87c32475d..1d1b214ec 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1313,7 +1313,7 @@ describe "TextEditorPresenter", -> expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([]) expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([blockDecoration3, blockDecoration2]) + expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([blockDecoration2, blockDecoration3]) expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([]) expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) @@ -2166,12 +2166,12 @@ describe "TextEditorPresenter", -> expectValues stateForBlockDecoration(presenter, blockDecoration2), { decoration: blockDecoration2 screenRow: 4 - isVisible: true + isVisible: false } expectValues stateForBlockDecoration(presenter, blockDecoration3), { decoration: blockDecoration3 screenRow: 4 - isVisible: true + isVisible: false } expectValues stateForBlockDecoration(presenter, blockDecoration4), { decoration: blockDecoration4 @@ -2212,16 +2212,23 @@ describe "TextEditorPresenter", -> screenRow: 4 isVisible: false } - expectValues stateForBlockDecoration(presenter, blockDecoration4), { - decoration: blockDecoration4 - screenRow: 10 - isVisible: true - } + expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() 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() + expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() + + presenter.setScrollTop(140) + expectValues stateForBlockDecoration(presenter, blockDecoration1), { decoration: blockDecoration1 screenRow: 0 @@ -2235,6 +2242,7 @@ describe "TextEditorPresenter", -> isVisible: true } + describe ".overlays", -> [item] = [] stateForOverlay = (presenter, decoration) -> diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 7f7258226..076cfea12 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -8,10 +8,7 @@ class BlockDecorationsPresenter { this.model = model this.disposables = new EventKit.CompositeDisposable() this.emitter = new EventKit.Emitter() - this.firstUpdate = true this.lineTopIndex = lineTopIndex - this.blocksByDecoration = new Map() - this.decorationsByBlock = new Map() this.observedDecorations = new Set() this.measuredDecorations = new Set() @@ -31,30 +28,24 @@ class BlockDecorationsPresenter { } observeModel () { - this.disposables.add(this.model.onDidAddDecoration(this.observeDecoration.bind(this))) + this.disposables.add(this.model.onDidAddDecoration(this.didAddDecoration.bind(this))) this.disposables.add(this.model.onDidChange((changeEvent) => { let oldExtent = changeEvent.end - changeEvent.start let newExtent = Math.max(0, changeEvent.end - changeEvent.start + changeEvent.screenDelta) this.lineTopIndex.splice(changeEvent.start, oldExtent, newExtent) })) - } - update () { - if (this.firstUpdate) { - for (let decoration of this.model.getDecorations({type: 'block'})) { - this.observeDecoration(decoration) - } - this.firstUpdate = false + for (let decoration of this.model.getDecorations({type: 'block'})) { + this.didAddDecoration(decoration) } } setDimensionsForDecoration (decoration, width, height) { - let block = this.blocksByDecoration.get(decoration) - if (block) { - this.lineTopIndex.resizeBlock(decoration.getMarker().id, height) + if (this.observedDecorations.has(decoration)) { + this.lineTopIndex.resizeBlock(decoration.getId(), height) } else { - this.observeDecoration(decoration) - this.lineTopIndex.resizeBlock(decoration.getMarker().id, height) + this.didAddDecoration(decoration) + this.lineTopIndex.resizeBlock(decoration.getId(), height) } this.measuredDecorations.add(decoration) @@ -71,30 +62,7 @@ class BlockDecorationsPresenter { this.emitter.emit('did-update-state') } - decorationsForScreenRow (screenRow) { - let blocks = this.lineTopIndex.allBlocks().filter((block) => block.row === screenRow) - return blocks.map((block) => this.decorationsByBlock.get(block.id)).filter((decoration) => decoration) - } - - decorationsForScreenRowRange (startRow, endRow, mouseWheelScreenRow) { - let blocks = this.lineTopIndex.allBlocks() - let decorationsByScreenRow = new Map() - for (let block of blocks) { - let decoration = this.decorationsByBlock.get(block.id) - let hasntMeasuredDecoration = !this.measuredDecorations.has(decoration) - let isWithinVisibleRange = startRow <= block.row && block.row < endRow - let isVisible = isWithinVisibleRange || block.row === mouseWheelScreenRow - if (decoration && (isVisible || hasntMeasuredDecoration)) { - let decorations = decorationsByScreenRow.get(block.row) || [] - decorations.push({decoration, isVisible}) - decorationsByScreenRow.set(block.row, decorations) - } - } - - return decorationsByScreenRow - } - - observeDecoration (decoration) { + didAddDecoration (decoration) { if (!decoration.isType('block') || this.observedDecorations.has(decoration)) { return } @@ -108,21 +76,15 @@ class BlockDecorationsPresenter { this.disposables.remove(didDestroyDisposable) didMoveDisposable.dispose() didDestroyDisposable.dispose() - this.observedDecorations.delete(decoration) this.didDestroyDecoration(decoration) }) + let screenRow = decoration.getMarker().getHeadScreenPosition().row + this.lineTopIndex.insertBlock(decoration.getId(), screenRow, 0) + + this.observedDecorations.add(decoration) this.disposables.add(didMoveDisposable) this.disposables.add(didDestroyDisposable) - this.didAddDecoration(decoration) - this.observedDecorations.add(decoration) - } - - didAddDecoration (decoration) { - let screenRow = decoration.getMarker().getHeadScreenPosition().row - this.lineTopIndex.insertBlock(decoration.getMarker().id, screenRow, 0) - this.decorationsByBlock.set(decoration.getMarker().id, decoration) - this.blocksByDecoration.set(decoration, decoration.getMarker().id) this.emitter.emit('did-update-state') } @@ -133,17 +95,15 @@ class BlockDecorationsPresenter { } let newScreenRow = decoration.getMarker().getHeadScreenPosition().row - this.lineTopIndex.moveBlock(decoration.getMarker().id, newScreenRow) + this.lineTopIndex.moveBlock(decoration.getId(), newScreenRow) this.emitter.emit('did-update-state') } didDestroyDecoration (decoration) { - let block = this.blocksByDecoration.get(decoration) - if (block) { - this.lineTopIndex.removeBlock(decoration.getMarker().id) - this.blocksByDecoration.delete(decoration) - this.decorationsByBlock.delete(block) + if (this.observedDecorations.has(decoration)) { + this.lineTopIndex.removeBlock(decoration.getId()) + this.observedDecorations.delete(decoration) + this.emitter.emit('did-update-state') } - this.emitter.emit('did-update-state') } } diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ca05ea36a..5a0630296 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -74,8 +74,6 @@ class TextEditorPresenter getPreMeasurementState: -> @updating = true - @blockDecorationsPresenter.update() - @updateVerticalDimensions() @updateScrollbarDimensions() @@ -87,11 +85,10 @@ class TextEditorPresenter @updateCommonGutterState() @updateReflowState() - @updateBlockDecorationsState() - if @shouldUpdateDecorations @fetchDecorations() @updateLineDecorations() + @updateBlockDecorations() if @shouldUpdateLinesState or @shouldUpdateLineNumbersState @updateTilesState() @@ -489,7 +486,7 @@ class TextEditorPresenter throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}") visibleLineIds[line.id] = true - blockDecorations = this.blockDecorationsPresenter.decorationsForScreenRow(screenRow) + blockDecorations = @blockDecorationsByScreenRow[screenRow] ? [] if tileState.lines.hasOwnProperty(line.id) lineState = tileState.lines[line.id] lineState.screenRow = screenRow @@ -943,6 +940,7 @@ class TextEditorPresenter @shouldUpdateLinesState = true @shouldUpdateLineNumbersState = true @shouldUpdateCustomGutterDecorationState = true + @shouldUpdateDecorations = true @emitDidUpdateState() @@ -1208,15 +1206,21 @@ class TextEditorPresenter return unless 0 <= @startRow <= @endRow <= Infinity @decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1) - updateBlockDecorationsState: -> + updateBlockDecorations: -> @state.content.blockDecorations = {} + @blockDecorationsByScreenRow = {} - startRow = @getStartTileRow() - endRow = @getEndTileRow() + @tileSize - decorations = @blockDecorationsPresenter.decorationsForScreenRowRange(startRow, endRow, @mouseWheelScreenRow) - decorations.forEach (decorations, screenRow) => - for {decoration, isVisible} in decorations - @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} + for decoration in @model.getDecorations({type: "block"}) + screenRow = decoration.getMarker().getHeadScreenPosition().row + @updateBlockDecorationState(decoration, screenRow) + @blockDecorationsByScreenRow[screenRow] ?= [] + @blockDecorationsByScreenRow[screenRow].push(decoration) + + updateBlockDecorationState: (decoration, screenRow) -> + hasntMeasuredDecoration = !@blockDecorationsPresenter.measuredDecorations.has(decoration) + isVisible = @startRow <= screenRow < @endRow || screenRow is @mouseWheelScreenRow + if isVisible or hasntMeasuredDecoration + @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} updateLineDecorations: -> @lineDecorationsByScreenRow = {} From d227d8b64611a2333468e9c861b6ae5fb66b2150 Mon Sep 17 00:00:00 2001 From: Chen Shen Date: Wed, 2 Dec 2015 16:57:23 -0800 Subject: [PATCH 060/173] Add config to disable autoupdate, revert previous build option --- build/Gruntfile.coffee | 3 +- build/tasks/build-task.coffee | 1 - build/tasks/disable-autoupdate-task.coffee | 12 -------- src/browser/application-menu.coffee | 9 ++---- src/browser/atom-application.coffee | 3 +- src/browser/auto-update-manager.coffee | 33 ++++++++++++++-------- src/config-schema.coffee | 4 +++ src/config.coffee | 2 +- 8 files changed, 31 insertions(+), 36 deletions(-) delete mode 100644 build/tasks/disable-autoupdate-task.coffee diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 26d9c2f42..e4bb656c1 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -36,7 +36,6 @@ module.exports = (grunt) -> buildDir = grunt.option('build-dir') buildDir ?= path.join(os.tmpdir(), 'atom-build') buildDir = path.resolve(buildDir) - disableAutoUpdate = grunt.option('no-auto-update') ? false channel = grunt.option('channel') releasableBranches = ['stable', 'beta'] @@ -179,7 +178,7 @@ module.exports = (grunt) -> pkg: grunt.file.readJSON('package.json') atom: { - appName, channel, metadata, disableAutoUpdate, + appName, channel, metadata, appFileName, apmFileName, appDir, buildDir, contentsDir, installDir, shellAppDir, symbolsDir, } diff --git a/build/tasks/build-task.coffee b/build/tasks/build-task.coffee index 213aa0da4..a86c7c1f4 100644 --- a/build/tasks/build-task.coffee +++ b/build/tasks/build-task.coffee @@ -186,5 +186,4 @@ module.exports = (grunt) -> dependencies = ['compile', 'generate-license:save', 'generate-module-cache', 'compile-packages-slug'] dependencies.push('copy-info-plist') if process.platform is 'darwin' dependencies.push('set-exe-icon') if process.platform is 'win32' - dependencies.push('disable-autoupdate') if grunt.config.get('atom.disableAutoUpdate') grunt.task.run(dependencies...) diff --git a/build/tasks/disable-autoupdate-task.coffee b/build/tasks/disable-autoupdate-task.coffee deleted file mode 100644 index 7517543da..000000000 --- a/build/tasks/disable-autoupdate-task.coffee +++ /dev/null @@ -1,12 +0,0 @@ -fs = require 'fs' -path = require 'path' - -module.exports = (grunt) -> - - grunt.registerTask 'disable-autoupdate', 'Set up disableAutoUpdate field in package.json file', -> - appDir = fs.realpathSync(grunt.config.get('atom.appDir')) - - metadata = grunt.file.readJSON(path.join(appDir, 'package.json')) - metadata._disableAutoUpdate = grunt.config.get('atom.disableAutoUpdate') - - grunt.file.write(path.join(appDir, 'package.json'), JSON.stringify(metadata)) diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index 27b9df8e1..74da80e43 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -103,8 +103,6 @@ class ApplicationMenu downloadingUpdateItem.visible = false installUpdateItem.visible = false - return if @autoUpdateManager.isDisabled() - switch state when 'idle', 'error', 'no-update-available' checkForUpdateItem.visible = true @@ -119,9 +117,10 @@ class ApplicationMenu # # Returns an Array of menu item Objects. getDefaultTemplate: -> - template = [ + [ label: "Atom" submenu: [ + {label: "Check for Update", metadata: {autoUpdate: true}} {label: 'Reload', accelerator: 'Command+R', click: => @focusedWindow()?.reload()} {label: 'Close Window', accelerator: 'Command+Shift+W', click: => @focusedWindow()?.close()} {label: 'Toggle Dev Tools', accelerator: 'Command+Alt+I', click: => @focusedWindow()?.toggleDevTools()} @@ -129,10 +128,6 @@ class ApplicationMenu ] ] - # Add `Check for Update` button if autoUpdateManager is enabled. - template[0].submenu.unshift({label: "Check for Update", metadata: {autoUpdate: true}}) unless @autoUpdateManager.isDisabled() - template - focusedWindow: -> _.find global.atomApplication.windows, (atomWindow) -> atomWindow.isFocused() diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index e720597e3..d8b2257b7 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -74,8 +74,7 @@ class AtomApplication @pidsToOpenWindows = {} @windows = [] - disableAutoUpdate = require(path.join(@resourcePath, 'package.json'))._disableAutoUpdate ? false - @autoUpdateManager = new AutoUpdateManager(@version, options.test, disableAutoUpdate) + @autoUpdateManager = new AutoUpdateManager(@version, options.test, @resourcePath) @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index 55ab2462b..00c00fedd 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -1,5 +1,6 @@ autoUpdater = null _ = require 'underscore-plus' +Config = require '../config' {EventEmitter} = require 'events' path = require 'path' @@ -15,10 +16,13 @@ module.exports = class AutoUpdateManager _.extend @prototype, EventEmitter.prototype - constructor: (@version, @testMode, @disabled) -> + constructor: (@version, @testMode, resourcePath) -> @state = IdleState @iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png') @feedUrl = "https://atom.io/api/updates?version=#{@version}" + @config = new Config({configDirPath: process.env.ATOM_HOME, resourcePath, enablePersistence: true}) + @config.setSchema null, {type: 'object', properties: _.clone(require('../config-schema'))} + @config.load() process.nextTick => @setupAutoUpdater() setupAutoUpdater: -> @@ -46,9 +50,13 @@ class AutoUpdateManager @setState(UpdateAvailableState) @emitUpdateAvailableEvent(@getWindows()...) - # Only check for updates periodically if enabled and running in release - # version. - @scheduleUpdateCheck() unless /\w{7}/.test(@version) or @disabled + @config.onDidChange 'core.enableAutoupdate', ({newValue}) => + if newValue + @scheduleUpdateCheck() + else + @cancelScheduledUpdateCheck() + + @scheduleUpdateCheck() if @config.get 'core.enableAutoupdate' switch process.platform when 'win32' @@ -56,9 +64,6 @@ class AutoUpdateManager when 'linux' @setState(UnsupportedState) - isDisabled: -> - @disabled - emitUpdateAvailableEvent: (windows...) -> return unless @releaseVersion? for atomWindow in windows @@ -74,10 +79,16 @@ class AutoUpdateManager @state scheduleUpdateCheck: -> - checkForUpdates = => @check(hidePopups: true) - fourHours = 1000 * 60 * 60 * 4 - setInterval(checkForUpdates, fourHours) - checkForUpdates() + unless @checkForUpdatesIntervalID + checkForUpdates = => @check(hidePopups: true) + fourHours = 1000 * 60 * 60 * 4 + @checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours) + checkForUpdates() + + cancelScheduledUpdateCheck: -> + if @checkForUpdatesIntervalID + clearInterval(@checkForUpdatesIntervalID) + @checkForUpdatesIntervalID = null check: ({hidePopups}={}) -> unless hidePopups diff --git a/src/config-schema.coffee b/src/config-schema.coffee index d9c0c1d21..f1e5e8bbd 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -104,6 +104,10 @@ module.exports = description: 'Automatically open an empty editor on startup.' type: 'boolean' default: true + enableAutoupdate: + description: 'Automatically update Atom when new release is available.' + type: 'boolean' + default: true editor: type: 'object' diff --git a/src/config.coffee b/src/config.coffee index 2e4387732..c680cf415 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -820,7 +820,7 @@ class Config @watchSubscription = null notifyFailure: (errorMessage, detail) -> - @notificationManager.addError(errorMessage, {detail, dismissable: true}) + @notificationManager.addError(errorMessage, {detail, dismissable: true}) if @notificationManager save: -> return if @shouldNotAccessFileSystem() From ea3736aa703708e94a0b2e41e8e1ece3e991480d Mon Sep 17 00:00:00 2001 From: Chen Shen Date: Fri, 11 Dec 2015 10:36:01 -0800 Subject: [PATCH 061/173] address comments --- src/browser/auto-update-manager.coffee | 8 +++++--- src/config-schema.coffee | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index 00c00fedd..612702a67 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -50,13 +50,13 @@ class AutoUpdateManager @setState(UpdateAvailableState) @emitUpdateAvailableEvent(@getWindows()...) - @config.onDidChange 'core.enableAutoupdate', ({newValue}) => + @config.onDidChange 'core.enableAutoUpdate', ({newValue}) => if newValue @scheduleUpdateCheck() else @cancelScheduledUpdateCheck() - @scheduleUpdateCheck() if @config.get 'core.enableAutoupdate' + @scheduleUpdateCheck() if @config.get 'core.enableAutoUpdate' switch process.platform when 'win32' @@ -79,7 +79,9 @@ class AutoUpdateManager @state scheduleUpdateCheck: -> - unless @checkForUpdatesIntervalID + # Only schedule update check periodically if running in release version and + # and there is no existing scheduled update check. + unless /\w{7}/.test(@version) or @checkForUpdatesIntervalID checkForUpdates = => @check(hidePopups: true) fourHours = 1000 * 60 * 60 * 4 @checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours) diff --git a/src/config-schema.coffee b/src/config-schema.coffee index f1e5e8bbd..31dd980bb 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -104,8 +104,8 @@ module.exports = description: 'Automatically open an empty editor on startup.' type: 'boolean' default: true - enableAutoupdate: - description: 'Automatically update Atom when new release is available.' + enableAutoUpdate: + description: 'Automatically update Atom when a new release is available.' type: 'boolean' default: true From 6c0643b4faa559e349d425eeb8527cf91cddb87a Mon Sep 17 00:00:00 2001 From: Chen Shen Date: Tue, 15 Dec 2015 14:04:09 -0800 Subject: [PATCH 062/173] address comments --- src/browser/auto-update-manager.coffee | 4 ++-- src/config-schema.coffee | 2 +- src/config.coffee | 13 +++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index 612702a67..6a008d44d 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -50,13 +50,13 @@ class AutoUpdateManager @setState(UpdateAvailableState) @emitUpdateAvailableEvent(@getWindows()...) - @config.onDidChange 'core.enableAutoUpdate', ({newValue}) => + @config.onDidChange 'core.automaticallyUpdate', ({newValue}) => if newValue @scheduleUpdateCheck() else @cancelScheduledUpdateCheck() - @scheduleUpdateCheck() if @config.get 'core.enableAutoUpdate' + @scheduleUpdateCheck() if @config.get 'core.automaticallyUpdate' switch process.platform when 'win32' diff --git a/src/config-schema.coffee b/src/config-schema.coffee index 31dd980bb..88e00c71d 100644 --- a/src/config-schema.coffee +++ b/src/config-schema.coffee @@ -104,7 +104,7 @@ module.exports = description: 'Automatically open an empty editor on startup.' type: 'boolean' default: true - enableAutoUpdate: + automaticallyUpdate: description: 'Automatically update Atom when a new release is available.' type: 'boolean' default: true diff --git a/src/config.coffee b/src/config.coffee index c680cf415..2a883a57d 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -779,9 +779,14 @@ class Config loadUserConfig: -> return if @shouldNotAccessFileSystem() - unless fs.existsSync(@configFilePath) - fs.makeTreeSync(path.dirname(@configFilePath)) - CSON.writeFileSync(@configFilePath, {}) + try + unless fs.existsSync(@configFilePath) + fs.makeTreeSync(path.dirname(@configFilePath)) + CSON.writeFileSync(@configFilePath, {}) + catch error + @configFileHasErrors = true + @notifyFailure("Failed to initialize `#{path.basename(@configFilePath)}`", error.stack) + return try unless @savePending @@ -820,7 +825,7 @@ class Config @watchSubscription = null notifyFailure: (errorMessage, detail) -> - @notificationManager.addError(errorMessage, {detail, dismissable: true}) if @notificationManager + @notificationManager?.addError(errorMessage, {detail, dismissable: true}) save: -> return if @shouldNotAccessFileSystem() From 3256c8b5039302ca15ae55cbdde73cc1a2a6934f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 10:06:04 +0100 Subject: [PATCH 063/173] :green_heart: Adjust TextEditorComponent specs for block decorations --- spec/text-editor-component-spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 86f924116..488c6df3a 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1664,7 +1664,7 @@ describe('TextEditorComponent', function () { }) it("renders visible and yet-to-be-measured block decorations, inserting them between the appropriate lines and refreshing them as needed", async function () { - wrapperNode.style.height = 9 * lineHeightInPixels + 'px' + wrapperNode.style.height = 13 * lineHeightInPixels + 'px' component.measureDimensions() await nextViewUpdatePromise() @@ -1706,7 +1706,7 @@ describe('TextEditorComponent', function () { await nextAnimationFramePromise() - expect(component.getDomNode().querySelectorAll(".line").length).toBe(7) + 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)") @@ -1731,7 +1731,7 @@ describe('TextEditorComponent', function () { 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.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)") @@ -1753,7 +1753,7 @@ describe('TextEditorComponent', function () { await nextAnimationFramePromise() await nextAnimationFramePromise() - expect(component.getDomNode().querySelectorAll(".line").length).toBe(7) + 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)") From 6a403e441eb943454f2d1aba0a46dd965a168b28 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 10:41:21 +0100 Subject: [PATCH 064/173] Start integrating tree-based LineTopIndex --- package.json | 1 + spec/lines-yardstick-spec.coffee | 2 +- spec/text-editor-presenter-spec.coffee | 2 +- src/block-decorations-presenter.js | 14 ++-- src/linear-line-top-index.js | 107 ------------------------- src/text-editor-component.coffee | 2 +- src/text-editor-presenter.coffee | 12 ++- 7 files changed, 18 insertions(+), 122 deletions(-) delete mode 100644 src/linear-line-top-index.js diff --git a/package.json b/package.json index 21e960477..63953b1b6 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "jquery": "^2.1.1", "key-path-helpers": "^0.4.0", "less-cache": "0.22", + "line-top-index": "0.1.0", "marked": "^0.3.4", "normalize-package-data": "^2.0.0", "nslog": "^3", diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index 0120b4ef4..ceae2a847 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -1,5 +1,5 @@ LinesYardstick = require '../src/lines-yardstick' -LineTopIndex = require '../src/linear-line-top-index' +LineTopIndex = require 'line-top-index' {toArray} = require 'underscore-plus' describe "LinesYardstick", -> diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 0014d8cce..9bd85c7a9 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -5,7 +5,7 @@ TextBuffer = require 'text-buffer' TextEditor = require '../src/text-editor' TextEditorPresenter = require '../src/text-editor-presenter' FakeLinesYardstick = require './fake-lines-yardstick' -LineTopIndex = require '../src/linear-line-top-index' +LineTopIndex = require 'line-top-index' describe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js index 076cfea12..870d49dcd 100644 --- a/src/block-decorations-presenter.js +++ b/src/block-decorations-presenter.js @@ -29,10 +29,10 @@ class BlockDecorationsPresenter { observeModel () { this.disposables.add(this.model.onDidAddDecoration(this.didAddDecoration.bind(this))) - this.disposables.add(this.model.onDidChange((changeEvent) => { - let oldExtent = changeEvent.end - changeEvent.start - let newExtent = Math.max(0, changeEvent.end - changeEvent.start + changeEvent.screenDelta) - this.lineTopIndex.splice(changeEvent.start, oldExtent, newExtent) + this.disposables.add(this.model.buffer.onDidChange((changeEvent) => { + let oldExtent = changeEvent.oldRange.getExtent() + let newExtent = changeEvent.newRange.getExtent() + this.lineTopIndex.splice(changeEvent.oldRange.start, oldExtent, newExtent) })) for (let decoration of this.model.getDecorations({type: 'block'})) { @@ -79,8 +79,7 @@ class BlockDecorationsPresenter { this.didDestroyDecoration(decoration) }) - let screenRow = decoration.getMarker().getHeadScreenPosition().row - this.lineTopIndex.insertBlock(decoration.getId(), screenRow, 0) + this.lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadBufferPosition(), true, 0) this.observedDecorations.add(decoration) this.disposables.add(didMoveDisposable) @@ -94,8 +93,7 @@ class BlockDecorationsPresenter { return } - let newScreenRow = decoration.getMarker().getHeadScreenPosition().row - this.lineTopIndex.moveBlock(decoration.getId(), newScreenRow) + this.lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadBufferPosition()) this.emitter.emit('did-update-state') } diff --git a/src/linear-line-top-index.js b/src/linear-line-top-index.js deleted file mode 100644 index aad947dd8..000000000 --- a/src/linear-line-top-index.js +++ /dev/null @@ -1,107 +0,0 @@ -"use babel" - -export default class LineTopIndex { - constructor (params = {}) { - this.blocks = [] - this.maxRow = params.maxRow || 0 - this.setDefaultLineHeight(params.defaultLineHeight || 0) - } - - setDefaultLineHeight (lineHeight) { - this.defaultLineHeight = lineHeight - } - - getMaxRow () { - return this.maxRow - } - - insertBlock (id, row, height) { - this.blocks.push({id, row, height}) - this.blocks.sort((a, b) => a.row - b.row) - } - - resizeBlock (id, height) { - let block = this.blocks.find((block) => block.id === id) - if (block) { - block.height = height - } - } - - moveBlock (id, newRow) { - let block = this.blocks.find((block) => block.id === id) - if (block) { - block.row = newRow - this.blocks.sort((a, b) => a.row - b.row) - } - } - - removeBlock (id) { - let index = this.blocks.findIndex((block) => block.id === id) - if (index !== -1) { - this.blocks.splice(index, 1) - } - } - - allBlocks () { - return this.blocks - } - - blocksHeightForRow (row) { - let blocksForRow = this.blocks.filter((block) => block.row === row) - return blocksForRow.reduce((a, b) => a + b.height, 0) - } - - splice (startRow, oldExtent, newExtent) { - this.blocks.forEach(function (block) { - if (block.row >= startRow) { - if (block.row >= startRow + oldExtent) { - block.row += newExtent - oldExtent - } else { - block.row = startRow + newExtent - } - } - }) - - this.maxRow = this.maxRow + newExtent - oldExtent - } - - pixelPositionForRow (row) { - row = Math.min(row, this.maxRow) - let linesHeight = row * this.defaultLineHeight - let blocksHeight = this.blocks.filter((block) => block.row <= row).reduce((a, b) => a + b.height, 0) - return linesHeight + blocksHeight - } - - rowForPixelPosition (top, strategy) { - const roundingStrategy = strategy || 'floor' - let blocksHeight = 0 - let lastRow = 0 - let lastTop = 0 - for (let block of this.blocks) { - let nextBlocksHeight = blocksHeight + block.height - let linesHeight = block.row * this.defaultLineHeight - if (nextBlocksHeight + linesHeight > top) { - while (lastRow < block.row && lastTop + this.defaultLineHeight <= top) { - lastTop += this.defaultLineHeight - lastRow++ - } - return lastRow - } else { - blocksHeight = nextBlocksHeight - lastRow = block.row - lastTop = blocksHeight + linesHeight - } - } - - let remainingHeight = Math.max(0, top - lastTop) - let remainingRows = Math.min(this.maxRow, lastRow + remainingHeight / this.defaultLineHeight) - switch (roundingStrategy) { - case 'floor': - return Math.floor(remainingRows) - case 'ceil': - return Math.ceil(remainingRows) - default: - throw new Error(`Cannot use '${roundingStrategy}' as a rounding strategy!`) - } - } -} diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 531269366..266102cdd 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -14,7 +14,7 @@ OverlayManager = require './overlay-manager' DOMElementPool = require './dom-element-pool' LinesYardstick = require './lines-yardstick' BlockDecorationsComponent = require './block-decorations-component' -LineTopIndex = require './linear-line-top-index' +LineTopIndex = require 'line-top-index' module.exports = class TextEditorComponent diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index de98aa038..6c90c4f27 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -617,7 +617,8 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - blockDecorationsHeight = @lineTopIndex.blocksHeightForRow(screenRow) + previousRowBottomPixelPosition = @lineTopIndex.pixelPositionForRow(screenRow - 1) + @lineHeight + blockDecorationsHeight = @lineTopIndex.pixelPositionForRow(screenRow) - previousRowBottomPixelPosition tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight} visibleLineNumberIds[line.id] = true @@ -630,12 +631,15 @@ class TextEditorPresenter updateStartRow: -> return unless @scrollTop? and @lineHeight? - @startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop, "floor")) + @startRow = Math.max(0, @lineTopIndex.rowForPixelPosition(@scrollTop)) updateEndRow: -> return unless @scrollTop? and @lineHeight? and @height? - @endRow = @lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight, 'ceil') + @endRow = Math.min( + @model.getScreenLineCount(), + @lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight) + ) updateRowsPerPage: -> rowsPerPage = Math.floor(@getClientHeight() / @lineHeight) @@ -667,7 +671,7 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round(@lineTopIndex.pixelPositionForRow(Infinity)) + @contentHeight = Math.round(@lineTopIndex.pixelPositionForRow(@model.getScreenLineCount())) if @contentHeight isnt oldContentHeight @updateHeight() From 877eea3bd0d2779fa60ee756e2ff66145ed52c84 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 11:07:10 +0100 Subject: [PATCH 065/173] Finish integrating LineTopIndex --- spec/text-editor-component-spec.js | 9 +++++---- spec/text-editor-presenter-spec.coffee | 23 ++++++++--------------- src/text-editor-presenter.coffee | 6 ++++-- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 488c6df3a..2a686cf0c 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1664,7 +1664,7 @@ describe('TextEditorComponent', function () { }) it("renders visible and yet-to-be-measured block decorations, inserting them between the appropriate lines and refreshing them as needed", async function () { - wrapperNode.style.height = 13 * lineHeightInPixels + 'px' + wrapperNode.style.height = 9 * lineHeightInPixels + 'px' component.measureDimensions() await nextViewUpdatePromise() @@ -1706,7 +1706,7 @@ describe('TextEditorComponent', function () { await nextAnimationFramePromise() - expect(component.getDomNode().querySelectorAll(".line").length).toBe(9) + 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)") @@ -1731,7 +1731,7 @@ describe('TextEditorComponent', function () { await nextAnimationFramePromise() // causes the DOM to update and to retrieve new styles await nextAnimationFramePromise() // applies the changes - expect(component.getDomNode().querySelectorAll(".line").length).toBe(9) + 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)") @@ -1765,10 +1765,11 @@ describe('TextEditorComponent', function () { 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-4")).toBe(item4) 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) }) it("correctly sets screen rows on elements, both initially and when decorations move", async function () { diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 9bd85c7a9..98285c7b2 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2150,12 +2150,12 @@ describe "TextEditorPresenter", -> expectValues stateForBlockDecoration(presenter, blockDecoration2), { decoration: blockDecoration2 screenRow: 4 - isVisible: false + isVisible: true } expectValues stateForBlockDecoration(presenter, blockDecoration3), { decoration: blockDecoration3 screenRow: 4 - isVisible: false + isVisible: true } expectValues stateForBlockDecoration(presenter, blockDecoration4), { decoration: blockDecoration4 @@ -2196,23 +2196,16 @@ describe "TextEditorPresenter", -> screenRow: 4 isVisible: false } - expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() + 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() - expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() - - presenter.setScrollTop(140) - expectValues stateForBlockDecoration(presenter, blockDecoration1), { decoration: blockDecoration1 screenRow: 0 @@ -2226,7 +2219,6 @@ describe "TextEditorPresenter", -> isVisible: true } - describe ".overlays", -> [item] = [] stateForOverlay = (presenter, decoration) -> @@ -3091,6 +3083,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() diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 6c90c4f27..0b8bfbbc9 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -638,7 +638,7 @@ class TextEditorPresenter @endRow = Math.min( @model.getScreenLineCount(), - @lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight) + @lineTopIndex.rowForPixelPosition(@scrollTop + @height + (2 * @lineHeight - 1)) ) updateRowsPerPage: -> @@ -1059,8 +1059,10 @@ class TextEditorPresenter @blockDecorationsByScreenRow[screenRow].push(decoration) updateBlockDecorationState: (decoration, screenRow) -> + startRow = @getStartTileRow() + endRow = @getEndTileRow() + @tileSize hasntMeasuredDecoration = !@blockDecorationsPresenter.measuredDecorations.has(decoration) - isVisible = @startRow <= screenRow < @endRow || screenRow is @mouseWheelScreenRow + isVisible = startRow <= screenRow < endRow || screenRow is @mouseWheelScreenRow if isVisible or hasntMeasuredDecoration @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} From da412e92f8832d6397c0210fd91a0192857a0857 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 11:54:10 +0100 Subject: [PATCH 066/173] :fire: Remove BlockDecorationsPresenter --- src/block-decorations-presenter.js | 107 ----------------------------- src/text-editor-presenter.coffee | 88 +++++++++++++++++------- 2 files changed, 65 insertions(+), 130 deletions(-) delete mode 100644 src/block-decorations-presenter.js diff --git a/src/block-decorations-presenter.js b/src/block-decorations-presenter.js deleted file mode 100644 index 870d49dcd..000000000 --- a/src/block-decorations-presenter.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict' - -const EventKit = require('event-kit') - -module.exports = -class BlockDecorationsPresenter { - constructor (model, lineTopIndex) { - this.model = model - this.disposables = new EventKit.CompositeDisposable() - this.emitter = new EventKit.Emitter() - this.lineTopIndex = lineTopIndex - this.observedDecorations = new Set() - this.measuredDecorations = new Set() - - this.observeModel() - } - - destroy () { - this.disposables.dispose() - } - - onDidUpdateState (callback) { - return this.emitter.on('did-update-state', callback) - } - - setLineHeight (lineHeight) { - this.lineTopIndex.setDefaultLineHeight(lineHeight) - } - - observeModel () { - this.disposables.add(this.model.onDidAddDecoration(this.didAddDecoration.bind(this))) - this.disposables.add(this.model.buffer.onDidChange((changeEvent) => { - let oldExtent = changeEvent.oldRange.getExtent() - let newExtent = changeEvent.newRange.getExtent() - this.lineTopIndex.splice(changeEvent.oldRange.start, oldExtent, newExtent) - })) - - for (let decoration of this.model.getDecorations({type: 'block'})) { - this.didAddDecoration(decoration) - } - } - - setDimensionsForDecoration (decoration, width, height) { - if (this.observedDecorations.has(decoration)) { - this.lineTopIndex.resizeBlock(decoration.getId(), height) - } else { - this.didAddDecoration(decoration) - this.lineTopIndex.resizeBlock(decoration.getId(), height) - } - - this.measuredDecorations.add(decoration) - this.emitter.emit('did-update-state') - } - - invalidateDimensionsForDecoration (decoration) { - this.measuredDecorations.delete(decoration) - this.emitter.emit('did-update-state') - } - - measurementsChanged () { - this.measuredDecorations.clear() - this.emitter.emit('did-update-state') - } - - didAddDecoration (decoration) { - if (!decoration.isType('block') || this.observedDecorations.has(decoration)) { - return - } - - let didMoveDisposable = decoration.getMarker().bufferMarker.onDidChange((markerEvent) => { - this.didMoveDecoration(decoration, markerEvent) - }) - - let didDestroyDisposable = decoration.onDidDestroy(() => { - this.disposables.remove(didMoveDisposable) - this.disposables.remove(didDestroyDisposable) - didMoveDisposable.dispose() - didDestroyDisposable.dispose() - this.didDestroyDecoration(decoration) - }) - - this.lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadBufferPosition(), true, 0) - - this.observedDecorations.add(decoration) - this.disposables.add(didMoveDisposable) - this.disposables.add(didDestroyDisposable) - this.emitter.emit('did-update-state') - } - - didMoveDecoration (decoration, markerEvent) { - if (markerEvent.textChanged) { - // No need to move blocks because of a text change, because we already splice on buffer change. - return - } - - this.lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadBufferPosition()) - this.emitter.emit('did-update-state') - } - - didDestroyDecoration (decoration) { - if (this.observedDecorations.has(decoration)) { - this.lineTopIndex.removeBlock(decoration.getId()) - this.observedDecorations.delete(decoration) - this.emitter.emit('did-update-state') - } - } -} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 0b8bfbbc9..f7804f910 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -2,7 +2,6 @@ {Point, Range} = require 'text-buffer' _ = require 'underscore-plus' Decoration = require './decoration' -BlockDecorationsPresenter = require './block-decorations-presenter' module.exports = class TextEditorPresenter @@ -29,7 +28,8 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} - @blockDecorationsPresenter = new BlockDecorationsPresenter(@model, @lineTopIndex) + @observedBlockDecorations = new Set() + @measuredBlockDecorations = new Set() @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -46,7 +46,6 @@ class TextEditorPresenter getLinesYardstick: -> @linesYardstick destroy: -> - @blockDecorationsPresenter.destroy() @disposables.dispose() clearTimeout(@stoppedScrollingTimeoutId) if @stoppedScrollingTimeoutId? clearInterval(@reflowingInterval) if @reflowingInterval? @@ -138,20 +137,14 @@ class TextEditorPresenter @shouldUpdateDecorations = true @emitDidUpdateState() - @disposables.add @blockDecorationsPresenter.onDidUpdateState => - @shouldUpdateHeightState = true - @shouldUpdateVerticalScrollState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateScrollbarsState = true - @shouldUpdateContentState = true - @shouldUpdateDecorations = true - @shouldUpdateCursorsState = true - @shouldUpdateLinesState = true - @shouldUpdateLineNumberGutterState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateGutterOrderState = true - @shouldUpdateCustomGutterDecorationState = true - @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) @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) @disposables.add @model.onDidChangePlaceholderText(@emitDidUpdateState.bind(this)) @@ -995,7 +988,7 @@ class TextEditorPresenter @measurementsChanged() measurementsChanged: -> - @blockDecorationsPresenter.measurementsChanged() + @measuredBlockDecorations.clear() @shouldUpdateDecorations = true @emitDidUpdateState() @@ -1061,7 +1054,7 @@ class TextEditorPresenter updateBlockDecorationState: (decoration, screenRow) -> startRow = @getStartTileRow() endRow = @getEndTileRow() + @tileSize - hasntMeasuredDecoration = !@blockDecorationsPresenter.measuredDecorations.has(decoration) + hasntMeasuredDecoration = !@measuredBlockDecorations.has(decoration) isVisible = startRow <= screenRow < endRow || screenRow is @mouseWheelScreenRow if isVisible or hasntMeasuredDecoration @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} @@ -1258,11 +1251,60 @@ class TextEditorPresenter @emitDidUpdateState() - setBlockDecorationDimensions: -> - @blockDecorationsPresenter.setDimensionsForDecoration(arguments...) + setBlockDecorationDimensions: (decoration, width, height) -> + @lineTopIndex.resizeBlock(decoration.getId(), height) - invalidateBlockDecorationDimensions: -> - @blockDecorationsPresenter.invalidateDimensionsForDecoration(arguments...) + @measuredBlockDecorations.add(decoration) + @shouldUpdateDecorations = true + @emitDidUpdateState() + + invalidateBlockDecorationDimensions: (decoration) -> + @measuredBlockDecorations.delete(decoration) + @shouldUpdateDecorations = true + @emitDidUpdateState() + + 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) + + @lineTopIndex.insertBlock( + decoration.getId(), + decoration.getMarker().getHeadBufferPosition(), + true, + 0 + ) + + @observedBlockDecorations.add(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().getHeadBufferPosition()) + @shouldUpdateDecorations = true + @emitDidUpdateState() + + didDestroyBlockDecoration: (decoration) -> + return unless @observedBlockDecorations.has(decoration) + + @lineTopIndex.removeBlock(decoration.getId()) + @observedBlockDecorations.delete(decoration) + @shouldUpdateDecorations = true + @emitDidUpdateState() observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => From 907dc661ec46256d02375655eb92f0738fcb4efc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 12:43:27 +0100 Subject: [PATCH 067/173] :racehorse: Make updating block decorations incremental --- src/text-editor-presenter.coffee | 52 +++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index f7804f910..8409f33e0 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -29,7 +29,8 @@ class TextEditorPresenter @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterName = {} @observedBlockDecorations = new Set() - @measuredBlockDecorations = new Set() + @invalidatedBlockDecorations = new Set() + @invalidateAllBlockDecorations = false @screenRowsToMeasure = [] @transferMeasurementsToModel() @transferMeasurementsFromModel() @@ -204,6 +205,7 @@ class TextEditorPresenter highlights: {} overlays: {} cursors: {} + blockDecorations: {} gutters: [] # Shared state that is copied into ``@state.gutters`. @sharedGutterStyles = {} @@ -988,7 +990,7 @@ class TextEditorPresenter @measurementsChanged() measurementsChanged: -> - @measuredBlockDecorations.clear() + @invalidateAllBlockDecorations = true @shouldUpdateDecorations = true @emitDidUpdateState() @@ -1042,22 +1044,36 @@ class TextEditorPresenter @decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1) updateBlockDecorations: -> - @state.content.blockDecorations = {} + @blockDecorationsToRenderById = {} @blockDecorationsByScreenRow = {} + visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1) - for decoration in @model.getDecorations({type: "block"}) - screenRow = decoration.getMarker().getHeadScreenPosition().row - @updateBlockDecorationState(decoration, screenRow) - @blockDecorationsByScreenRow[screenRow] ?= [] - @blockDecorationsByScreenRow[screenRow].push(decoration) + if @invalidateAllBlockDecorations + for decoration in @model.getDecorations(type: 'block') + @invalidatedBlockDecorations.add(decoration) + @invalidateAllBlockDecorations = false - updateBlockDecorationState: (decoration, screenRow) -> - startRow = @getStartTileRow() - endRow = @getEndTileRow() + @tileSize - hasntMeasuredDecoration = !@measuredBlockDecorations.has(decoration) - isVisible = startRow <= screenRow < endRow || screenRow is @mouseWheelScreenRow - if isVisible or hasntMeasuredDecoration - @state.content.blockDecorations[decoration.id] = {decoration, screenRow, isVisible} + for markerId, decorations of visibleDecorationsByMarkerId + for decoration in decorations when decoration.isType('block') + @updateBlockDecorationState(decoration, true) + + @invalidatedBlockDecorations.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 + @blockDecorationsByScreenRow[screenRow] ?= [] + @blockDecorationsByScreenRow[screenRow].push(decoration) + @state.content.blockDecorations[decoration.getId()] = {decoration, screenRow, isVisible} + @blockDecorationsToRenderById[decoration.getId()] = true updateLineDecorations: -> @lineDecorationsByScreenRow = {} @@ -1254,12 +1270,12 @@ class TextEditorPresenter setBlockDecorationDimensions: (decoration, width, height) -> @lineTopIndex.resizeBlock(decoration.getId(), height) - @measuredBlockDecorations.add(decoration) + @invalidatedBlockDecorations.delete(decoration) @shouldUpdateDecorations = true @emitDidUpdateState() invalidateBlockDecorationDimensions: (decoration) -> - @measuredBlockDecorations.delete(decoration) + @invalidatedBlockDecorations.add(decoration) @shouldUpdateDecorations = true @emitDidUpdateState() @@ -1284,6 +1300,7 @@ class TextEditorPresenter ) @observedBlockDecorations.add(decoration) + @invalidateBlockDecorationDimensions(decoration) @disposables.add(didMoveDisposable) @disposables.add(didDestroyDisposable) @shouldUpdateDecorations = true @@ -1303,6 +1320,7 @@ class TextEditorPresenter @lineTopIndex.removeBlock(decoration.getId()) @observedBlockDecorations.delete(decoration) + @invalidatedBlockDecorations.delete(decoration) @shouldUpdateDecorations = true @emitDidUpdateState() From 087dc3b4fd6348675e136e06a36db70751dbe7bc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 15:18:04 +0100 Subject: [PATCH 068/173] Back to green specs :metal: --- spec/text-editor-presenter-spec.coffee | 2 +- src/lines-yardstick.coffee | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 98285c7b2..36395feed 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -893,7 +893,7 @@ describe "TextEditorPresenter", -> expect(getState(presenter).content.scrollTop).toBe 13 it "scrolls down automatically when the model is changed", -> - presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 10) + presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20) editor.setText("") editor.insertNewline() diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 6bc691639..3be276412 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -20,10 +20,11 @@ class LinesYardstick targetTop = pixelPosition.top targetLeft = pixelPosition.left defaultCharWidth = @model.getDefaultCharWidth() - row = @lineTopIndex.rowForPixelPosition(targetTop, 'floor') + 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) line = @model.tokenizedLineForScreenRow(row) lineNode = @lineNodesProvider.lineNodeForLineIdAndScreenRow(line?.id, row) From db9f67b9a515e5779bd62263e022eb9b610b6349 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 15:41:46 +0100 Subject: [PATCH 069/173] :fire: Remove maxRow parameter --- spec/lines-yardstick-spec.coffee | 1 - spec/text-editor-presenter-spec.coffee | 1 - src/text-editor-component.coffee | 1 - 3 files changed, 3 deletions(-) diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index ceae2a847..c6b8b2d76 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -46,7 +46,6 @@ describe "LinesYardstick", -> editor.setLineHeightInPixels(14) lineTopIndex = new LineTopIndex({ - maxRow: editor.getScreenLineCount(), defaultLineHeight: editor.getLineHeightInPixels() }) linesYardstick = new LinesYardstick(editor, mockLineNodesProvider, lineTopIndex, atom.grammars) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 36395feed..6cfc99d36 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -32,7 +32,6 @@ describe "TextEditorPresenter", -> buildPresenterWithoutMeasurements = (params={}) -> lineTopIndex = new LineTopIndex({ - maxRow: editor.getScreenLineCount(), defaultLineHeight: editor.getLineHeightInPixels() }) _.defaults params, diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 266102cdd..0edc267db 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -51,7 +51,6 @@ class TextEditorComponent @setScrollSensitivity(@config.get('editor.scrollSensitivity')) lineTopIndex = new LineTopIndex({ - maxRow: @editor.getScreenLineCount(), defaultLineHeight: @editor.getLineHeightInPixels() }) @presenter = new TextEditorPresenter From 8710089cb733290bd94ef631e58fca61a7091487 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 18 Dec 2015 15:43:23 +0100 Subject: [PATCH 070/173] Support only Shadow DOM enabled editors --- src/text-editor-component.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 0edc267db..1d2c0bcdc 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -77,7 +77,6 @@ class TextEditorComponent else @domNode.classList.add('editor-contents') @overlayManager = new OverlayManager(@presenter, @domNode, @views) - @blockDecorationsComponent = new BlockDecorationsComponent(@domNode, @views, @presenter, @domElementPool) @scrollViewNode = document.createElement('div') @scrollViewNode.classList.add('scroll-view') @@ -89,7 +88,8 @@ class TextEditorComponent @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars}) @scrollViewNode.appendChild(@linesComponent.getDomNode()) - @scrollViewNode.appendChild(@blockDecorationsComponent.getDomNode()) + if @blockDecorationsComponent? + @scrollViewNode.appendChild(@blockDecorationsComponent.getDomNode()) @linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex, @grammars) @presenter.setLinesYardstick(@linesYardstick) @@ -167,7 +167,7 @@ class TextEditorComponent @hiddenInputComponent.updateSync(@newState) @linesComponent.updateSync(@newState) - @blockDecorationsComponent.updateSync(@newState) + @blockDecorationsComponent?.updateSync(@newState) @horizontalScrollbarComponent.updateSync(@newState) @verticalScrollbarComponent.updateSync(@newState) @scrollbarCornerComponent.updateSync(@newState) @@ -187,7 +187,7 @@ class TextEditorComponent readAfterUpdateSync: => @overlayManager?.measureOverlays() - @blockDecorationsComponent.measureBlockDecorations() + @blockDecorationsComponent?.measureBlockDecorations() mountGutterContainerComponent: -> @gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views}) From 5fa9d3bc40a841904caa1f67d5adbdafef902bcb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 11:21:45 +0100 Subject: [PATCH 071/173] Splice LineTopIndex when DisplayBuffer changes We invalidate whole screen lines accordingly to `DisplayBuffer`, so that we can catch if there was any screen-only transformation and move block decorations accordingly. --- spec/text-editor-presenter-spec.coffee | 105 ++++++++++++++++++------- src/text-editor-presenter.coffee | 46 +++++------ 2 files changed, 101 insertions(+), 50 deletions(-) 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() From 5e0863c119d9f22f2621807c33edc625dd858cb5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 12:02:05 +0100 Subject: [PATCH 072/173] :white_check_mark: Write specs for moving markers manually --- spec/text-editor-presenter-spec.coffee | 48 +++++++++++++++++--------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 4022ea46b..199075197 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -180,27 +180,40 @@ describe "TextEditorPresenter", -> 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[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) + 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) expect(stateFn(presenter).tiles[8]).toBeUndefined() 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[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) + 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 - 21) + expect(stateFn(presenter).tiles[8].height).toBe(2 * 10) + expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 1 + 30 + 40 - 21) + expect(stateFn(presenter).tiles[10]).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) + 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 - 21) + expect(stateFn(presenter).tiles[8].height).toBe(2 * 10) + expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 1 + 30 + 40 - 21) expect(stateFn(presenter).tiles[10]).toBeUndefined() it "works correctly when soft wrapping is enabled", -> @@ -2231,6 +2244,7 @@ describe "TextEditorPresenter", -> } expect(stateForBlockDecoration(presenter, blockDecoration4)).toBeUndefined() + blockDecoration3.getMarker().setHeadScreenPosition([5, 0]) presenter.setScrollTop(90) expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() @@ -2241,7 +2255,7 @@ describe "TextEditorPresenter", -> } expectValues stateForBlockDecoration(presenter, blockDecoration3), { decoration: blockDecoration3 - screenRow: 4 + screenRow: 5 isVisible: false } expectValues stateForBlockDecoration(presenter, blockDecoration4), { From 07234c510935dd2e92a5a9a4e1f16625cfe5298d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 12:50:58 +0100 Subject: [PATCH 073/173] Resize all block decorations when width changes --- spec/text-editor-presenter-spec.coffee | 43 ++++++++++++++++++++++++++ src/block-decorations-component.coffee | 7 +++-- src/text-editor-component.coffee | 2 +- src/text-editor-presenter.coffee | 5 +++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 199075197..035cddcc9 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2195,6 +2195,49 @@ describe "TextEditorPresenter", -> expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + it "invalidates all block decorations when content frame width, window size or bounding client rect change", -> + blockDecoration1 = editor.addBlockDecorationForScreenRow(11, null) + 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 block decorations, indicating the screen row they belong to both initially and when their markers move", -> item = {} blockDecoration1 = editor.addBlockDecorationForScreenRow(0, item) diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index c154b85c6..e9d80f2aa 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -12,14 +12,17 @@ class BlockDecorationsComponent @domNode = @domElementPool.buildElement("content") @domNode.setAttribute("select", ".atom--invisible-block-decoration") @domNode.style.visibility = "hidden" - @domNode.style.position = "absolute" getDomNode: -> @domNode updateSync: (state) -> @newState = state.content - @oldState ?= {blockDecorations: {}} + @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) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 1d2c0bcdc..1fcb21123 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -89,7 +89,7 @@ class TextEditorComponent @scrollViewNode.appendChild(@linesComponent.getDomNode()) if @blockDecorationsComponent? - @scrollViewNode.appendChild(@blockDecorationsComponent.getDomNode()) + @linesComponent.getDomNode().appendChild(@blockDecorationsComponent.getDomNode()) @linesYardstick = new LinesYardstick(@editor, @linesComponent, lineTopIndex, @grammars) @presenter.setLinesYardstick(@linesYardstick) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 46a575e55..f11027b22 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -921,12 +921,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) -> @@ -940,6 +943,8 @@ class TextEditorPresenter if @windowWidth isnt width or @windowHeight isnt height @windowWidth = width @windowHeight = height + @invalidateAllBlockDecorationsDimensions = true + @shouldUpdateDecorations = true @emitDidUpdateState() From dfb095b75471d5f555c0b7b13c745f1bb6d93423 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 13:00:47 +0100 Subject: [PATCH 074/173] :fire: Remove TextEditor.prototype.addBlockDecorationForScreenRow --- spec/text-editor-component-spec.js | 10 +++- spec/text-editor-presenter-spec.coffee | 72 ++++++++++++++------------ src/text-editor.coffee | 18 ++----- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 2a686cf0c..fb874a7aa 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1655,7 +1655,10 @@ describe('TextEditorComponent', function () { function createBlockDecorationForScreenRowWith(screenRow, {className}) { let item = document.createElement("div") item.className = className || "" - let blockDecoration = editor.addBlockDecorationForScreenRow(screenRow, item) + let blockDecoration = editor.decorateMarker( + editor.markScreenPosition([screenRow, 0], invalidate: "never"), + type: "block", item: item + ) return [item, blockDecoration] } @@ -3616,7 +3619,10 @@ describe('TextEditorComponent', function () { item.style.width = "30px" item.style.height = "30px" item.className = "decoration-1" - editor.addBlockDecorationForScreenRow(0, item) + editor.decorateMarker( + editor.markScreenPosition([0, 0], invalidate: "never"), + type: "block", item: item + ) await nextViewUpdatePromise() diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 035cddcc9..5a4e8c916 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -30,6 +30,12 @@ describe "TextEditorPresenter", -> presenter.getPreMeasurementState() presenter.getPostMeasurementState() + addBlockDecorationForScreenRow = (screenRow, item) -> + editor.decorateMarker( + editor.markScreenPosition([screenRow, 0], invalidate: "never"), + type: "block", item: item + ) + buildPresenterWithoutMeasurements = (params={}) -> lineTopIndex = new LineTopIndex({ defaultLineHeight: editor.getLineHeightInPixels() @@ -173,9 +179,9 @@ describe "TextEditorPresenter", -> 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) + blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration2 = addBlockDecorationForScreenRow(3) + blockDecoration3 = addBlockDecorationForScreenRow(5) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 1) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 30) presenter.setBlockDecorationDimensions(blockDecoration3, 0, 40) @@ -217,9 +223,9 @@ describe "TextEditorPresenter", -> 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) + blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration2 = addBlockDecorationForScreenRow(4) + blockDecoration3 = addBlockDecorationForScreenRow(8) presenter = buildPresenter(explicitHeight: 330, lineHeight: 10, tileSize: 2, baseCharacterWidth: 5) @@ -586,9 +592,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) - blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) - blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) + blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration2 = addBlockDecorationForScreenRow(3) + blockDecoration3 = addBlockDecorationForScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3) @@ -760,9 +766,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) - blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) - blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) + blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration2 = addBlockDecorationForScreenRow(3) + blockDecoration3 = addBlockDecorationForScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3) @@ -1322,10 +1328,10 @@ describe "TextEditorPresenter", -> describe ".blockDecorations", -> it "contains all block decorations that are present before a line, both initially and when decorations change", -> - blockDecoration1 = editor.addBlockDecorationForScreenRow(0) + blockDecoration1 = addBlockDecorationForScreenRow(0) presenter = buildPresenter() - blockDecoration2 = editor.addBlockDecorationForScreenRow(3) - blockDecoration3 = editor.addBlockDecorationForScreenRow(7) + blockDecoration2 = addBlockDecorationForScreenRow(3) + blockDecoration3 = addBlockDecorationForScreenRow(7) waitsForStateToUpdate presenter runs -> @@ -1612,8 +1618,8 @@ describe "TextEditorPresenter", -> 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 = editor.addBlockDecorationForScreenRow(0) - blockDecoration2 = editor.addBlockDecorationForScreenRow(1) + blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration2 = addBlockDecorationForScreenRow(1) waitsForStateToUpdate presenter, -> presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30) @@ -2177,7 +2183,7 @@ describe "TextEditorPresenter", -> getState(presenter).content.blockDecorations[decoration.id] it "contains state for measured block decorations that are not visible when they are on ::mouseWheelScreenRow", -> - blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) + blockDecoration1 = addBlockDecorationForScreenRow(0) presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0, stoppedScrollingDelay: 200) getState(presenter) # flush pending state presenter.setBlockDecorationDimensions(blockDecoration1, 0, 0) @@ -2196,7 +2202,7 @@ describe "TextEditorPresenter", -> expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() it "invalidates all block decorations when content frame width, window size or bounding client rect change", -> - blockDecoration1 = editor.addBlockDecorationForScreenRow(11, null) + blockDecoration1 = addBlockDecorationForScreenRow(11) presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) expectValues stateForBlockDecoration(presenter, blockDecoration1), { @@ -2240,10 +2246,10 @@ describe "TextEditorPresenter", -> it "contains state for block decorations, indicating the screen row they belong to both initially and when their markers move", -> item = {} - blockDecoration1 = editor.addBlockDecorationForScreenRow(0, item) - blockDecoration2 = editor.addBlockDecorationForScreenRow(4, item) - blockDecoration3 = editor.addBlockDecorationForScreenRow(4, item) - blockDecoration4 = editor.addBlockDecorationForScreenRow(10, item) + blockDecoration1 = addBlockDecorationForScreenRow(0, item) + blockDecoration2 = addBlockDecorationForScreenRow(4, item) + blockDecoration3 = addBlockDecorationForScreenRow(4, item) + blockDecoration4 = addBlockDecorationForScreenRow(10, item) presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) expectValues stateForBlockDecoration(presenter, blockDecoration1), { @@ -2825,11 +2831,11 @@ describe "TextEditorPresenter", -> 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 = editor.addBlockDecorationForScreenRow(0) + blockDecoration1 = addBlockDecorationForScreenRow(0) presenter = buildPresenter() - blockDecoration2 = editor.addBlockDecorationForScreenRow(3) - blockDecoration3 = editor.addBlockDecorationForScreenRow(3) - blockDecoration4 = editor.addBlockDecorationForScreenRow(7) + blockDecoration2 = addBlockDecorationForScreenRow(3) + blockDecoration3 = addBlockDecorationForScreenRow(3) + blockDecoration4 = addBlockDecorationForScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) @@ -3119,13 +3125,13 @@ describe "TextEditorPresenter", -> it "updates when block decorations are added, changed or removed", -> # block decoration before decoration1 - blockDecoration1 = editor.addBlockDecorationForScreenRow(0) + blockDecoration1 = addBlockDecorationForScreenRow(0) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 3) # block decoration between decoration1 and decoration2 - blockDecoration2 = editor.addBlockDecorationForScreenRow(3) + blockDecoration2 = addBlockDecorationForScreenRow(3) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 5) # block decoration between decoration2 and decoration3 - blockDecoration3 = editor.addBlockDecorationForScreenRow(10) + blockDecoration3 = addBlockDecorationForScreenRow(10) presenter.setBlockDecorationDimensions(blockDecoration3, 0, 7) decorationState = getContentForGutterWithName(presenter, 'test-gutter') @@ -3375,9 +3381,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - blockDecoration1 = editor.addBlockDecorationForScreenRow(0, null) - blockDecoration2 = editor.addBlockDecorationForScreenRow(3, null) - blockDecoration3 = editor.addBlockDecorationForScreenRow(7, null) + blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration2 = addBlockDecorationForScreenRow(3) + blockDecoration3 = addBlockDecorationForScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index ed50f9e79..325e4a304 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1398,18 +1398,6 @@ class TextEditor extends Model Section: Decorations ### - # Experimental: Mark and add a block decoration to the specified screen row. - # - # * `screenRow` A {Number} representing the screen row where to add the block decoration. - # * `item` A {ViewRegistry::getView}-compatible object to render. - # - # Returns a {Decoration} object. - addBlockDecorationForScreenRow: (screenRow, item) -> - @decorateMarker( - @markScreenPosition([screenRow, 0], invalidate: "never"), - type: "block", item: item - ) - # Essential: Add a decoration that tracks a {TextEditorMarker}. When the # marker moves, is invalidated, or is destroyed, the decoration will be # updated to reflect the marker's state. @@ -1435,6 +1423,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__: A decoration that lies between the {TextEditorMarker} row and + # the previous one. # # ## Arguments # @@ -1457,8 +1447,8 @@ class TextEditor extends Model # * `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. From 5dfecf39ab5eb3f28f763d4b3755a74c01e33dd0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 13:18:36 +0100 Subject: [PATCH 075/173] Take margin top and margin bottom into account --- spec/text-editor-component-spec.js | 30 ++++++++++++++++++++++---- src/block-decorations-component.coffee | 5 ++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index fb874a7aa..041eca4cb 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1656,8 +1656,8 @@ describe('TextEditorComponent', function () { let item = document.createElement("div") item.className = className || "" let blockDecoration = editor.decorateMarker( - editor.markScreenPosition([screenRow, 0], invalidate: "never"), - type: "block", item: item + editor.markScreenPosition([screenRow, 0], {invalidate: "never"}), + {type: "block", item: item} ) return [item, blockDecoration] } @@ -1824,6 +1824,28 @@ describe('TextEditorComponent', function () { expect(component.lineNodeForScreenRow(1).dataset.screenRow).toBe("1") expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2") }) + + it('does not render highlights for off-screen lines until they come on-screen', async function () { + wrapperNode.style.height = 9 * lineHeightInPixels + 'px' + component.measureDimensions() + await nextViewUpdatePromise() + + let [item, blockDecoration] = createBlockDecorationForScreenRowWith(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 () { @@ -3620,8 +3642,8 @@ describe('TextEditorComponent', function () { item.style.height = "30px" item.className = "decoration-1" editor.decorateMarker( - editor.markScreenPosition([0, 0], invalidate: "never"), - type: "block", item: item + editor.markScreenPosition([0, 0], {invalidate: "never"}), + {type: "block", item: item} ) await nextViewUpdatePromise() diff --git a/src/block-decorations-component.coffee b/src/block-decorations-component.coffee index e9d80f2aa..0cfa7974f 100644 --- a/src/block-decorations-component.coffee +++ b/src/block-decorations-component.coffee @@ -39,11 +39,14 @@ class BlockDecorationsComponent 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 + blockDecorationNode.offsetHeight + marginTop + marginBottom ) createAndAppendBlockDecorationNode: (id) -> From 1534aad414fb14f07bd9589a868512b7c897aac1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 14:38:30 +0100 Subject: [PATCH 076/173] :arrow_up: line-top-index --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63953b1b6..99b62519e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "jquery": "^2.1.1", "key-path-helpers": "^0.4.0", "less-cache": "0.22", - "line-top-index": "0.1.0", + "line-top-index": "0.1.1", "marked": "^0.3.4", "normalize-package-data": "^2.0.0", "nslog": "^3", From 7543bcbdc17c42a863ad1e8ca544975ddee91c05 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 14:59:25 +0100 Subject: [PATCH 077/173] :art: Rearrange code a bit --- src/text-editor-presenter.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index f11027b22..9a29ec922 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1286,10 +1286,10 @@ class TextEditorPresenter 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()) + invalidatedBlockDecorationIds = @lineTopIndex.splice(Point(start, 0), oldExtent, newExtent) + invalidatedBlockDecorationIds.forEach (id) => + newScreenPosition = @model.decorationForId(id).getMarker().getHeadScreenPosition() + @lineTopIndex.moveBlock(id, newScreenPosition) didAddBlockDecoration: (decoration) -> return if not decoration.isType('block') or @observedBlockDecorations.has(decoration) From eab70d9a9537fd1939792c6b20ddf618b155de50 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 15:38:34 +0100 Subject: [PATCH 078/173] Invalidate spliced block decorations' dimensions --- spec/text-editor-presenter-spec.coffee | 45 ++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 4 ++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 5a4e8c916..b604a7022 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2201,6 +2201,51 @@ describe "TextEditorPresenter", -> expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() + it "invalidates block decorations that intersect a change in the buffer", -> + blockDecoration1 = addBlockDecorationForScreenRow(9) + blockDecoration2 = addBlockDecorationForScreenRow(10) + blockDecoration3 = addBlockDecorationForScreenRow(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 = addBlockDecorationForScreenRow(11) presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 9a29ec922..73848bdb3 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1288,8 +1288,10 @@ class TextEditorPresenter newExtent = Point(end - start + screenDelta, 0) invalidatedBlockDecorationIds = @lineTopIndex.splice(Point(start, 0), oldExtent, newExtent) invalidatedBlockDecorationIds.forEach (id) => - newScreenPosition = @model.decorationForId(id).getMarker().getHeadScreenPosition() + decoration = @model.decorationForId(id) + newScreenPosition = decoration.getMarker().getHeadScreenPosition() @lineTopIndex.moveBlock(id, newScreenPosition) + @invalidatedDimensionsByBlockDecoration.add(decoration) didAddBlockDecoration: (decoration) -> return if not decoration.isType('block') or @observedBlockDecorations.has(decoration) From a706a77fd99a4b20356b9a0c78b11f5a6b662dd9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 16:06:56 +0100 Subject: [PATCH 079/173] Add time-grunt to benchmark each task time --- build/Gruntfile.coffee | 2 ++ build/package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index 26d9c2f42..63afc2fd7 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -14,6 +14,8 @@ _ = require 'underscore-plus' packageJson = require '../package.json' module.exports = (grunt) -> + require('time-grunt')(grunt) + grunt.loadNpmTasks('grunt-babel') grunt.loadNpmTasks('grunt-coffeelint') grunt.loadNpmTasks('grunt-lesslint') diff --git a/build/package.json b/build/package.json index fd7d29d80..d5c780c08 100644 --- a/build/package.json +++ b/build/package.json @@ -37,6 +37,7 @@ "runas": "^3.1", "tello": "1.0.5", "temp": "~0.8.1", + "time-grunt": "1.2.2", "underscore-plus": "1.x", "unzip": "~0.1.9", "vm-compatibility-layer": "~0.1.0", From 4e0b2c0c21e24c3d8685e0cb16735dc291c39ef2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 19 Dec 2015 16:20:27 +0100 Subject: [PATCH 080/173] Print package/modules (un)installation times --- script/bootstrap | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/script/bootstrap b/script/bootstrap index 8314b9cb0..5f9241a3d 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -5,8 +5,16 @@ var verifyRequirements = require('./utils/verify-requirements'); var safeExec = require('./utils/child-process-wrapper.js').safeExec; var path = require('path'); +var t0, t1 + // Executes an array of commands one by one. function executeCommands(commands, done, index) { + if (index != undefined) { + t1 = Date.now() + console.log("=> Took " + (t1 - t0) + "ms."); + console.log(); + } + index = (index == undefined ? 0 : index); if (index < commands.length) { var command = commands[index]; @@ -17,6 +25,7 @@ function executeCommands(commands, done, index) { options = command.options; command = command.command; } + t0 = Date.now() safeExec(command, options, executeCommands.bind(this, commands, done, index + 1)); } else @@ -96,7 +105,10 @@ function bootstrap() { message: 'Installing apm...', options: apmInstallOptions }, - apmPath + ' clean' + apmFlags, + { + command: apmPath + ' clean' + apmFlags, + message: 'Deleting old packages...' + }, moduleInstallCommand, dedupeApmCommand + ' ' + packagesToDedupe.join(' '), ]; From d5e0f21389122cbe82bce158947ad591ebbe369d Mon Sep 17 00:00:00 2001 From: Tunghsiao Liu Date: Mon, 21 Dec 2015 12:44:18 +0800 Subject: [PATCH 081/173] Prevent committing `blob-store` --- dot-atom/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/dot-atom/.gitignore b/dot-atom/.gitignore index 81af3f689..569a62297 100644 --- a/dot-atom/.gitignore +++ b/dot-atom/.gitignore @@ -1,5 +1,6 @@ storage compile-cache dev +blob-store .npm .node-gyp From 754552cb476d60da7045748465b3651d736552ac Mon Sep 17 00:00:00 2001 From: Tunghsiao Liu Date: Mon, 21 Dec 2015 12:45:21 +0800 Subject: [PATCH 082/173] Reorder ignored items for .atom --- dot-atom/.gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dot-atom/.gitignore b/dot-atom/.gitignore index 569a62297..e5c80ce23 100644 --- a/dot-atom/.gitignore +++ b/dot-atom/.gitignore @@ -1,6 +1,6 @@ -storage +blob-store compile-cache dev -blob-store -.npm +storage .node-gyp +.npm From b7c4655c28e54f6db7ef8dbefd53bec41a11a22c Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 21 Dec 2015 17:52:31 -0500 Subject: [PATCH 083/173] :arrow_up: language-javascript@0.105.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd661ccd4..d9abfe276 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "language-html": "0.43.1", "language-hyperlink": "0.16.0", "language-java": "0.17.0", - "language-javascript": "0.104.0", + "language-javascript": "0.105.0", "language-json": "0.17.2", "language-less": "0.29.0", "language-make": "0.21.0", From 5d85ea18ae48c9c9fd54931630a99a4d44962a9e Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 21 Dec 2015 20:08:36 -0500 Subject: [PATCH 084/173] :arrow_up: language-php@0.36.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9abfe276..2ac4df46b 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "language-mustache": "0.13.0", "language-objective-c": "0.15.1", "language-perl": "0.32.0", - "language-php": "0.34.0", + "language-php": "0.36.0", "language-property-list": "0.8.0", "language-python": "0.42.1", "language-ruby": "0.65.0", From 7024e000a81c1e6fb05150da9013a84abadd270e Mon Sep 17 00:00:00 2001 From: joshaber Date: Wed, 30 Dec 2015 14:32:55 -0500 Subject: [PATCH 085/173] Provide some defaults. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise we’ll get: TypeError: Cannot read property 'pathToOpen' of undefined when called from ‘application:new-file’. --- src/browser/atom-application.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index e720597e3..c8b417386 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -370,7 +370,7 @@ class AtomApplication # :safeMode - Boolean to control the opened window's safe mode. # :profileStartup - Boolean to control creating a profile of the startup time. # :window - {AtomWindow} to open file paths in. - openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window}) -> + openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window} = {}) -> @openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window}) # Public: Opens multiple paths, in existing windows if possible. From 5c14d3e99b795f045bbdbe56cd6b9adc334ca386 Mon Sep 17 00:00:00 2001 From: joshaber Date: Wed, 30 Dec 2015 14:33:23 -0500 Subject: [PATCH 086/173] Don't fail if `event` is undefined. Unclear why this is undefined, but apparently it is. --- src/browser/atom-application.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index c8b417386..04c2d3824 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -229,7 +229,7 @@ class AtomApplication @openUrl({urlToOpen, @devMode, @safeMode}) app.on 'activate-with-no-open-windows', (event) => - event.preventDefault() + event?.preventDefault() @emit('application:new-window') # A request from the associated render process to open a new render process. From a63e70148d9a5a6d32b445c158fe07db189b46ba Mon Sep 17 00:00:00 2001 From: joshaber Date: Wed, 30 Dec 2015 17:33:45 -0500 Subject: [PATCH 087/173] Cascade new windows on OS X Fixes #10057. --- src/browser/atom-application.coffee | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index e720597e3..a77591159 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -167,7 +167,7 @@ class AtomApplication safeMode: @focusedWindow()?.safeMode @on 'application:quit', -> app.quit() - @on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings())) + @on 'application:new-window', -> @openPath(getLoadSettings()) @on 'application:new-file', -> (@focusedWindow() ? this).openPath() @on 'application:open', -> @promptForPathToOpen('all', getLoadSettings()) @on 'application:open-file', -> @promptForPathToOpen('file', getLoadSettings()) @@ -360,6 +360,15 @@ class AtomApplication focusedWindow: -> _.find @windows, (atomWindow) -> atomWindow.isFocused() + # Get the dimensions for opening a new window by cascading as appropriate. + getDimensionsForNewWindow: -> + dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions() + # On OS X, new windows cascade down and to the right. + if dimensions? and process.platform is 'darwin' + dimensions.x += 20 + dimensions.y += 20 + dimensions + # Public: Opens a single path, in an existing window if possible. # # options - @@ -417,6 +426,7 @@ class AtomApplication windowInitializationScript ?= require.resolve('../initialize-application-window') resourcePath ?= @resourcePath + windowDimensions ?= @getDimensionsForNewWindow() openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup}) if pidToKillWhenClosed? From 9e7cb15c2c056f99fb1f228758ef2e846549178f Mon Sep 17 00:00:00 2001 From: joshaber Date: Fri, 1 Jan 2016 12:05:42 -0500 Subject: [PATCH 088/173] Offset on Windows too. --- src/browser/atom-application.coffee | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index a77591159..b91adabac 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -360,13 +360,18 @@ class AtomApplication focusedWindow: -> _.find @windows, (atomWindow) -> atomWindow.isFocused() - # Get the dimensions for opening a new window by cascading as appropriate. + # Get the dimensions for opening a new window by cascading as appropriate to + # the platform. getDimensionsForNewWindow: -> - dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions() - # On OS X, new windows cascade down and to the right. - if dimensions? and process.platform is 'darwin' - dimensions.x += 20 - dimensions.y += 20 + offsetByPlatform = + darwin: 22 + win32: 26 + + dimensions = @windows[@windows.length - 1]?.getDimensions() + offset = offsetByPlatform[process.platform] + if dimensions? and offset? + dimensions.x += offset + dimensions.y += offset dimensions # Public: Opens a single path, in an existing window if possible. From dcd860ae6fb4249ad20536b75618c9590425cb1d Mon Sep 17 00:00:00 2001 From: joshaber Date: Fri, 1 Jan 2016 12:18:48 -0500 Subject: [PATCH 089/173] Use the last focused window instead of the last opened window. --- src/browser/atom-application.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index b91adabac..fe21ae107 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -367,7 +367,7 @@ class AtomApplication darwin: 22 win32: 26 - dimensions = @windows[@windows.length - 1]?.getDimensions() + dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions() offset = offsetByPlatform[process.platform] if dimensions? and offset? dimensions.x += offset From b25e7f025ca028006a2bfb58ea3d4e443e0aaa22 Mon Sep 17 00:00:00 2001 From: joshaber Date: Fri, 1 Jan 2016 14:46:16 -0500 Subject: [PATCH 090/173] :arrow_up: git-utils@4.0.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ac4df46b..927af044e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "fs-plus": "^2.8.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", - "git-utils": "^4.0.7", + "git-utils": "^4.0.8", "grim": "1.5.0", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", From b4035547c27beb24dda28d2d6ba127448c24eae2 Mon Sep 17 00:00:00 2001 From: joshaber Date: Fri, 1 Jan 2016 17:05:50 -0500 Subject: [PATCH 091/173] Use .getStatusForPaths. --- src/git-repository.coffee | 6 +++++- src/repository-status-handler.coffee | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/git-repository.coffee b/src/git-repository.coffee index 1663f9ad4..ee27f87a5 100644 --- a/src/git-repository.coffee +++ b/src/git-repository.coffee @@ -463,8 +463,12 @@ class GitRepository refreshStatus: -> @handlerPath ?= require.resolve('./repository-status-handler') + relativeProjectPaths = @project?.getPaths() + .map (path) => @relativize(path) + .filter (path) -> path.length > 0 + @statusTask?.terminate() - @statusTask = Task.once @handlerPath, @getPath(), ({statuses, upstream, branch, submodules}) => + @statusTask = Task.once @handlerPath, @getPath(), relativeProjectPaths, ({statuses, upstream, branch, submodules}) => statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream) and _.isEqual(branch, @branch) and diff --git a/src/repository-status-handler.coffee b/src/repository-status-handler.coffee index d0763fd5a..2fda9a335 100644 --- a/src/repository-status-handler.coffee +++ b/src/repository-status-handler.coffee @@ -1,7 +1,7 @@ Git = require 'git-utils' path = require 'path' -module.exports = (repoPath) -> +module.exports = (repoPath, paths = []) -> repo = Git.open(repoPath) upstream = {} @@ -12,7 +12,8 @@ module.exports = (repoPath) -> if repo? # Statuses in main repo workingDirectoryPath = repo.getWorkingDirectory() - for filePath, status of repo.getStatus() + repoStatus = (if paths.length > 0 then repo.getStatusForPaths(paths) else repo.getStatus()) + for filePath, status of repoStatus statuses[filePath] = status # Statuses in submodules From c1d00716ee59e144624462e4cb5335e7604cca54 Mon Sep 17 00:00:00 2001 From: Leonard Lamprecht Date: Sat, 2 Jan 2016 18:36:06 +0100 Subject: [PATCH 092/173] Combine certain items into a submenu --- menus/darwin.cson | 15 ++++++++++----- menus/linux.cson | 15 ++++++++++----- menus/win32.cson | 15 ++++++++++----- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/menus/darwin.cson b/menus/darwin.cson index 52b7a5bc8..6737adf46 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -11,11 +11,16 @@ { 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' } + { + label: 'Customization', + submenu: [ + { 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' } diff --git a/menus/linux.cson b/menus/linux.cson index fa831b4a4..416f16831 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -83,11 +83,16 @@ } { 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' } + { + label: 'Customization', + submenu: [ + { 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' } ] } diff --git a/menus/win32.cson b/menus/win32.cson index 04da3d388..82921d2a3 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -10,11 +10,16 @@ { 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' } + { + label: 'Customization', + submenu: [ + { 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: '&Save', command: 'core:save' } { label: 'Save &As…', command: 'core:save-as' } From 876928f7f6bfd3670496f4e4dc91d94aab9540b8 Mon Sep 17 00:00:00 2001 From: Kevin J Date: Sat, 2 Jan 2016 16:05:21 -0500 Subject: [PATCH 093/173] Update function documentation for scrollToScreenPosition --- src/text-editor.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 1435aef19..91b1409a4 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -3037,7 +3037,7 @@ class TextEditor extends Model # Essential: Scrolls the editor to the given screen position. # - # * `screenPosition` An object that represents a buffer position. It can be either + # * `screenPosition` An object that represents a screen position. It can be either # an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point} # * `options` (optional) {Object} # * `center` Center the editor around the position if possible. (default: false) From c1c67b682ccb7a7a71b404ad9ba4a4694d2c4f11 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 3 Jan 2016 18:58:51 -0500 Subject: [PATCH 094/173] :arrow_up: language-ruby@0.66.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 927af044e..73ba84ba7 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,7 @@ "language-php": "0.36.0", "language-property-list": "0.8.0", "language-python": "0.42.1", - "language-ruby": "0.65.0", + "language-ruby": "0.66.0", "language-ruby-on-rails": "0.24.0", "language-sass": "0.45.0", "language-shellscript": "0.21.0", From 31030275a8cda213d2021486f5299da0eba8e22d Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 3 Jan 2016 18:59:11 -0500 Subject: [PATCH 095/173] :arrow_up: language-ruby-on-rails@0.25.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73ba84ba7..0ad2ffae9 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "language-property-list": "0.8.0", "language-python": "0.42.1", "language-ruby": "0.66.0", - "language-ruby-on-rails": "0.24.0", + "language-ruby-on-rails": "0.25.0", "language-sass": "0.45.0", "language-shellscript": "0.21.0", "language-source": "0.9.0", From ab3cb1778a74f8e06ae31664e07927bdfca2652f Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 4 Jan 2016 09:40:54 -0500 Subject: [PATCH 096/173] :arrow_up: language-ruby@0.67.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0ad2ffae9..f6c38b9c8 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,7 @@ "language-php": "0.36.0", "language-property-list": "0.8.0", "language-python": "0.42.1", - "language-ruby": "0.66.0", + "language-ruby": "0.67.0", "language-ruby-on-rails": "0.25.0", "language-sass": "0.45.0", "language-shellscript": "0.21.0", From 6ee36b14b1774583f7d5305ea74edc945f0a3fa7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 Jan 2016 09:58:19 -0800 Subject: [PATCH 097/173] Prepare 1.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5710de92d..959be0793 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.3.2", + "version": "1.3.3", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From ba24364a89c2a0427e8fda270af768d54d667a35 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 Jan 2016 09:58:29 -0800 Subject: [PATCH 098/173] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 959be0793..1cb27543a 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.0.4", + "text-buffer": "8.0.4-hotfix.1", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" From a962bba3a1299a7e27955c3c63dd3a16af4418ce Mon Sep 17 00:00:00 2001 From: joshaber Date: Mon, 4 Jan 2016 16:24:47 -0500 Subject: [PATCH 099/173] :arrow_up: git-utils@4.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f6c38b9c8..591e1a2a0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "fs-plus": "^2.8.0", "fstream": "0.1.24", "fuzzaldrin": "^2.1", - "git-utils": "^4.0.8", + "git-utils": "^4.1.0", "grim": "1.5.0", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", From 9f6fbacddd8d4fb51e24b3b79d9a709d1bdaa0ad Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 4 Jan 2016 16:32:38 -0700 Subject: [PATCH 100/173] =?UTF-8?q?Don=E2=80=99t=20clip=20screen=20positio?= =?UTF-8?q?ns=20in=20yardstick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By clipping positions only in the TextEditorComponent, we can ensure that we check for the presence of a rendered line for the clipped row value. --- spec/fake-lines-yardstick.coffee | 3 +- spec/lines-yardstick-spec.coffee | 51 ++++++++++++++++---------------- src/lines-yardstick.coffee | 5 +--- src/text-editor-component.coffee | 7 +++-- src/text-editor-presenter.coffee | 22 +++++++------- 5 files changed, 45 insertions(+), 43 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index da5f8327e..a2568b959 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -19,9 +19,8 @@ class FakeLinesYardstick setScopedCharacterWidth: (scopeNames, character, width) -> @getScopedCharacterWidths(scopeNames)[character] = width - pixelPositionForScreenPosition: (screenPosition, clip=true) -> + pixelPositionForScreenPosition: (screenPosition) -> screenPosition = Point.fromObject(screenPosition) - screenPosition = @model.clipScreenPosition(screenPosition) if clip targetRow = screenPosition.row targetColumn = screenPosition.column diff --git a/spec/lines-yardstick-spec.coffee b/spec/lines-yardstick-spec.coffee index 74f5fca6a..3520d6935 100644 --- a/spec/lines-yardstick-spec.coffee +++ b/spec/lines-yardstick-spec.coffee @@ -1,5 +1,6 @@ LinesYardstick = require "../src/lines-yardstick" {toArray} = require 'underscore-plus' +{Point} = require 'text-buffer' describe "LinesYardstick", -> [editor, mockLineNodesProvider, createdLineNodes, linesYardstick, buildLineNode] = [] @@ -62,12 +63,12 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([0, 0])).toEqual({left: 0, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([0, 1])).toEqual({left: 7, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([0, 5])).toEqual({left: 37.78125, top: 0}) - expect(linesYardstick.pixelPositionForScreenPosition([1, 6])).toEqual({left: 43.171875, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([1, 9])).toEqual({left: 72.171875, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, Infinity])).toEqual({left: 287.859375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 37.78125, top: 0}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43.171875, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72.171875, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28}) it "reuses already computed pixel positions unless it is invalidated", -> atom.styles.addStyleSheet """ @@ -77,9 +78,9 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70}) atom.styles.addStyleSheet """ * { @@ -87,15 +88,15 @@ describe "LinesYardstick", -> } """ - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 19.203125, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 57.609375, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 95.609375, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 19.203125, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 57.609375, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 95.609375, top: 70}) linesYardstick.invalidateCache() - expect(linesYardstick.pixelPositionForScreenPosition([1, 2])).toEqual({left: 24, top: 14}) - expect(linesYardstick.pixelPositionForScreenPosition([2, 6])).toEqual({left: 72, top: 28}) - expect(linesYardstick.pixelPositionForScreenPosition([5, 10])).toEqual({left: 120, top: 70}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 2))).toEqual({left: 24, top: 14}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(2, 6))).toEqual({left: 72, top: 28}) + expect(linesYardstick.pixelPositionForScreenPosition(Point(5, 10))).toEqual({left: 120, top: 70}) it "correctly handles RTL characters", -> atom.styles.addStyleSheet """ @@ -106,13 +107,13 @@ describe "LinesYardstick", -> """ editor.setText("السلام عليكم") - expect(linesYardstick.pixelPositionForScreenPosition([0, 0]).left).toBe 0 - expect(linesYardstick.pixelPositionForScreenPosition([0, 1]).left).toBe 8 - expect(linesYardstick.pixelPositionForScreenPosition([0, 2]).left).toBe 16 - expect(linesYardstick.pixelPositionForScreenPosition([0, 5]).left).toBe 33 - expect(linesYardstick.pixelPositionForScreenPosition([0, 7]).left).toBe 50 - expect(linesYardstick.pixelPositionForScreenPosition([0, 9]).left).toBe 67 - expect(linesYardstick.pixelPositionForScreenPosition([0, 11]).left).toBe 84 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0)).left).toBe 0 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1)).left).toBe 8 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 2)).left).toBe 16 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5)).left).toBe 33 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 7)).left).toBe 50 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 9)).left).toBe 67 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 11)).left).toBe 84 it "doesn't report a width greater than 0 when the character to measure is at the beginning of a text node", -> # This spec documents what seems to be a bug in Chromium, because we'd @@ -137,9 +138,9 @@ describe "LinesYardstick", -> editor.setText(text) - expect(linesYardstick.pixelPositionForScreenPosition([0, 35]).left).toBe 230.90625 - expect(linesYardstick.pixelPositionForScreenPosition([0, 36]).left).toBe 237.5 - expect(linesYardstick.pixelPositionForScreenPosition([0, 37]).left).toBe 244.09375 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5 + expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375 describe "::screenPositionForPixelPosition(pixelPosition)", -> it "converts pixel positions to screen positions", -> diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index bd8219e81..9edbbe17a 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -77,10 +77,7 @@ class LinesYardstick else Point(row, column) - pixelPositionForScreenPosition: (screenPosition, clip=true) -> - screenPosition = Point.fromObject(screenPosition) - screenPosition = @model.clipScreenPosition(screenPosition) if clip - + pixelPositionForScreenPosition: (screenPosition) -> targetRow = screenPosition.row targetColumn = screenPosition.column diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 6a84c8dac..03644241b 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -434,12 +434,15 @@ class TextEditorComponent getVisibleRowRange: -> @presenter.getVisibleRowRange() - pixelPositionForScreenPosition: (screenPosition, clip) -> + pixelPositionForScreenPosition: (screenPosition, clip=true) -> + screenPosition = Point.fromObject(screenPosition) + screenPosition = @editor.clipScreenPosition(screenPosition) if clip + unless @presenter.isRowVisible(screenPosition.row) @presenter.setScreenRowsToMeasure([screenPosition.row]) @updateSyncPreMeasurement() - pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip) + pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition) @presenter.clearScreenRowsToMeasure() pixelPosition diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 1912f4c2e..6841cf70e 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -433,7 +433,7 @@ class TextEditorPresenter else screenPosition = decoration.getMarker().getHeadScreenPosition() - pixelPosition = @pixelPositionForScreenPosition(screenPosition, true) + pixelPosition = @pixelPositionForScreenPosition(screenPosition) top = pixelPosition.top + @lineHeight left = pixelPosition.left + @gutterWidth @@ -649,8 +649,10 @@ class TextEditorPresenter updateHorizontalDimensions: -> if @baseCharacterWidth? oldContentWidth = @contentWidth - clip = @model.tokenizedLineForScreenRow(@model.getLongestScreenRow())?.isSoftWrapped() - @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), @model.getMaxScreenLineLength()], clip).left + rightmostPosition = Point(@model.getLongestScreenRow(), @model.getMaxScreenLineLength()) + if @model.tokenizedLineForScreenRow(rightmostPosition.row)?.isSoftWrapped() + rightmostPosition = @model.clipScreenPosition(rightmostPosition) + @contentWidth = @pixelPositionForScreenPosition(rightmostPosition).left @contentWidth += @scrollLeft @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width @@ -966,9 +968,9 @@ class TextEditorPresenter hasPixelPositionRequirements: -> @lineHeight? and @baseCharacterWidth? - pixelPositionForScreenPosition: (screenPosition, clip=true) -> + pixelPositionForScreenPosition: (screenPosition) -> position = - @linesYardstick.pixelPositionForScreenPosition(screenPosition, clip, true) + @linesYardstick.pixelPositionForScreenPosition(screenPosition) position.top -= @getScrollTop() position.left -= @getScrollLeft() @@ -987,14 +989,14 @@ class TextEditorPresenter lineHeight = @model.getLineHeightInPixels() if screenRange.end.row > screenRange.start.row - top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, true).top + top = @linesYardstick.pixelPositionForScreenPosition(screenRange.start).top left = 0 height = (screenRange.end.row - screenRange.start.row + 1) * lineHeight width = @getScrollWidth() else - {top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start, false) + {top, left} = @linesYardstick.pixelPositionForScreenPosition(screenRange.start) height = lineHeight - width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end, false).left - left + width = @linesYardstick.pixelPositionForScreenPosition(screenRange.end).left - left {top, left, width, height} @@ -1139,8 +1141,8 @@ class TextEditorPresenter buildHighlightRegions: (screenRange) -> lineHeightInPixels = @lineHeight - startPixelPosition = @pixelPositionForScreenPosition(screenRange.start, false) - endPixelPosition = @pixelPositionForScreenPosition(screenRange.end, false) + startPixelPosition = @pixelPositionForScreenPosition(screenRange.start) + endPixelPosition = @pixelPositionForScreenPosition(screenRange.end) spannedRows = screenRange.end.row - screenRange.start.row + 1 regions = [] From 66697036770043d67d834829ecac71f9fa1cfe36 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 4 Jan 2016 16:40:56 -0700 Subject: [PATCH 101/173] =?UTF-8?q?Force=20a=20sync=20render=20when=20meas?= =?UTF-8?q?uring=20if=20we=20don=E2=80=99t=20have=20a=20DOM=20node=20for?= =?UTF-8?q?=20a=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/text-editor-component-spec.js | 7 +++++++ src/text-editor-component.coffee | 2 ++ src/text-editor-presenter.coffee | 3 +++ 3 files changed, 12 insertions(+) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index d5e9f5425..0546926e3 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -4567,6 +4567,13 @@ describe('TextEditorComponent', function () { }) }) + describe('::pixelPositionForScreenPosition()', () => { + it('returns the correct horizontal position, even if it is on a row that has not yet been rendered (regression)', () => { + editor.setTextInBufferRange([[5, 0], [6, 0]], 'hello world\n') + expect(wrapperNode.pixelPositionForScreenPosition([5, Infinity]).left).toBeGreaterThan(0) + }) + }) + describe('middle mouse paste on Linux', function () { let originalPlatform diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 03644241b..d97d82d04 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -440,6 +440,8 @@ class TextEditorComponent unless @presenter.isRowVisible(screenPosition.row) @presenter.setScreenRowsToMeasure([screenPosition.row]) + + unless @linesComponent.lineNodeForLineIdAndScreenRow(@presenter.lineIdForScreenRow(screenPosition.row), screenPosition.row)? @updateSyncPreMeasurement() pixelPosition = @linesYardstick.pixelPositionForScreenPosition(screenPosition) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 6841cf70e..40ea95514 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1377,3 +1377,6 @@ class TextEditorPresenter isRowVisible: (row) -> @startRow <= row < @endRow + + lineIdForScreenRow: (screenRow) -> + @model.tokenizedLineForScreenRow(screenRow)?.id From f774e1ce2171a40eac45b8b453ce156a68cb367c Mon Sep 17 00:00:00 2001 From: Chen Shen Date: Mon, 4 Jan 2016 16:55:44 -0800 Subject: [PATCH 102/173] add unittest --- spec/config-spec.coffee | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/config-spec.coffee b/spec/config-spec.coffee index eab2f6f04..c431fea5f 100644 --- a/spec/config-spec.coffee +++ b/spec/config-spec.coffee @@ -872,6 +872,26 @@ describe "Config", -> atom.config.loadUserConfig() expect(atom.config.get("foo.bar")).toBe "baz" + describe "when the config file fails to load", -> + addErrorHandler = null + + beforeEach -> + atom.notifications.onDidAddNotification addErrorHandler = jasmine.createSpy() + spyOn(fs, "existsSync").andCallFake -> + error = new Error() + error.code = 'EPERM' + throw error + + it "creates a notification and does not try to save later changes to disk", -> + load = -> atom.config.loadUserConfig() + expect(load).not.toThrow() + expect(addErrorHandler.callCount).toBe 1 + + atom.config.set("foo.bar", "baz") + advanceClock(100) + expect(atom.config.save).not.toHaveBeenCalled() + expect(atom.config.get("foo.bar")).toBe "baz" + describe ".observeUserConfig()", -> updatedHandler = null From 38e331691bcda4503b14ea1321147b87465027f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Machist=C3=A9=20N=2E=20Quintana?= Date: Mon, 4 Jan 2016 20:42:23 -0500 Subject: [PATCH 103/173] :arrow_up: apm@1.6.0 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index b8dda21ea..2e6b0b8ea 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "1.5.0" + "atom-package-manager": "1.6.0" } } From 4f0218c0a297c42eacb8b527b50cbdb8aa06281f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 Jan 2016 10:35:33 +0100 Subject: [PATCH 104/173] :memo: Fix mistyped spec description --- spec/text-editor-component-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 041eca4cb..99fd1bc54 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1825,7 +1825,7 @@ describe('TextEditorComponent', function () { expect(component.lineNodeForScreenRow(2).dataset.screenRow).toBe("2") }) - it('does not render highlights for off-screen lines until they come on-screen', async function () { + it('measures block decorations taking into account both top and bottom margins', async function () { wrapperNode.style.height = 9 * lineHeightInPixels + 'px' component.measureDimensions() await nextViewUpdatePromise() From 0d2801812757739afb41ae698c151a6e410c5585 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 Jan 2016 10:39:01 +0100 Subject: [PATCH 105/173] :fire: Remove redundant setup in specs --- spec/text-editor-component-spec.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 99fd1bc54..7717ea55d 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1662,15 +1662,17 @@ describe('TextEditorComponent', function () { return [item, blockDecoration] } + beforeEach(async function () { + wrapperNode.style.height = 9 * 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 () { - wrapperNode.style.height = 9 * lineHeightInPixels + 'px' - component.measureDimensions() - await nextViewUpdatePromise() - let [item1, blockDecoration1] = createBlockDecorationForScreenRowWith(0, {className: "decoration-1"}) let [item2, blockDecoration2] = createBlockDecorationForScreenRowWith(2, {className: "decoration-2"}) let [item3, blockDecoration3] = createBlockDecorationForScreenRowWith(4, {className: "decoration-3"}) @@ -1776,10 +1778,6 @@ describe('TextEditorComponent', function () { }) it("correctly sets screen rows on elements, both initially and when decorations move", async function () { - wrapperNode.style.height = 9 * lineHeightInPixels + 'px' - component.measureDimensions() - await nextViewUpdatePromise() - let [item, blockDecoration] = createBlockDecorationForScreenRowWith(0, {className: "decoration-1"}) atom.styles.addStyleSheet( 'atom-text-editor .decoration-1 { width: 30px; height: 80px; }', @@ -1826,10 +1824,6 @@ describe('TextEditorComponent', function () { }) it('measures block decorations taking into account both top and bottom margins', async function () { - wrapperNode.style.height = 9 * lineHeightInPixels + 'px' - component.measureDimensions() - await nextViewUpdatePromise() - let [item, blockDecoration] = createBlockDecorationForScreenRowWith(0, {className: "decoration-1"}) atom.styles.addStyleSheet( 'atom-text-editor .decoration-1 { width: 30px; height: 30px; margin-top: 10px; margin-bottom: 5px; }', From c7a7f0c6dd9c897c1a2f36edb35647e42410f6d5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 Jan 2016 10:56:29 +0100 Subject: [PATCH 106/173] :bug: Ignore setting dimensions for destroyed decorations --- spec/text-editor-presenter-spec.coffee | 9 +++++++++ src/text-editor-presenter.coffee | 2 ++ 2 files changed, 11 insertions(+) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index b604a7022..18e8d313b 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2375,6 +2375,15 @@ describe "TextEditorPresenter", -> isVisible: true } + it "doesn't throw an error when setting the dimensions for a destroyed decoration", -> + blockDecoration = addBlockDecorationForScreenRow(0) + presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) + blockDecoration.destroy() + + presenter.setBlockDecorationDimensions(blockDecoration, 30, 30) + + expect(getState(presenter).content.blockDecorations).toEqual({}) + describe ".overlays", -> [item] = [] stateForOverlay = (presenter, decoration) -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 73848bdb3..77de7e8d3 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1270,6 +1270,8 @@ class TextEditorPresenter @emitDidUpdateState() setBlockDecorationDimensions: (decoration, width, height) -> + return unless @observedBlockDecorations.has(decoration) + @lineTopIndex.resizeBlock(decoration.getId(), height) @invalidatedDimensionsByBlockDecoration.delete(decoration) From 1376afe17eb109d5c0ac2e93d575082671263cde Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 Jan 2016 10:57:32 +0100 Subject: [PATCH 107/173] :art: --- spec/text-editor-presenter-spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 18e8d313b..b65253a4d 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2377,9 +2377,9 @@ describe "TextEditorPresenter", -> it "doesn't throw an error when setting the dimensions for a destroyed decoration", -> blockDecoration = addBlockDecorationForScreenRow(0) - presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) - blockDecoration.destroy() + presenter = buildPresenter() + blockDecoration.destroy() presenter.setBlockDecorationDimensions(blockDecoration, 30, 30) expect(getState(presenter).content.blockDecorations).toEqual({}) From 94b77d099844c84903b338a6465cc54017a9a62c Mon Sep 17 00:00:00 2001 From: joshaber Date: Tue, 5 Jan 2016 11:59:59 -0500 Subject: [PATCH 108/173] Separate the offset determination from the dimensions. --- src/browser/atom-application.coffee | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index f6934761a..a5a535b93 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -360,15 +360,18 @@ class AtomApplication focusedWindow: -> _.find @windows, (atomWindow) -> atomWindow.isFocused() - # Get the dimensions for opening a new window by cascading as appropriate to - # the platform. - getDimensionsForNewWindow: -> + # Get the platform-specific window offset for new windows. + getWindowOffsetForCurrentPlatform: -> offsetByPlatform = darwin: 22 win32: 26 + offsetByPlatform[process.platform] ? 0 + # Get the dimensions for opening a new window by cascading as appropriate to + # the platform. + getDimensionsForNewWindow: -> dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions() - offset = offsetByPlatform[process.platform] + offset = @getWindowOffsetForCurrentPlatform() if dimensions? and offset? dimensions.x += offset dimensions.y += offset From 4ddadcc13580046dacc77b9b7af7198bc199e314 Mon Sep 17 00:00:00 2001 From: joshaber Date: Tue, 5 Jan 2016 12:00:08 -0500 Subject: [PATCH 109/173] Integration test for new window offset. --- spec/integration/helpers/start-atom.coffee | 3 +++ spec/integration/startup-spec.coffee | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee index b8768f532..8290a554e 100644 --- a/spec/integration/helpers/start-atom.coffee +++ b/spec/integration/helpers/start-atom.coffee @@ -127,6 +127,9 @@ buildAtomClient = (args, env) -> .execute -> require("remote").require("app").emit("before-quit") .call(done) + .addCommand "getWindowPosition", (cb) -> + @execute((-> atom.getPosition()), cb) + module.exports = (args, env, fn) -> [chromedriver, chromedriverLogs, chromedriverExit] = [] diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 799c7685f..374078111 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -181,6 +181,25 @@ describe "Starting Atom", -> , 5000) .waitForPaneItemCount(1, 5000) + it "opens a new window offset from the other window", -> + runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> + win0Position = null + win1Position = null + client + .waitForWindowCount(1, 10000) + .getWindowPosition() + .then ({value}) -> win0Position = value + .waitForNewWindow(-> + @startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome) + , 5000) + .waitForWindowCount(2, 10000) + .getWindowPosition() + .then ({value}) -> win1Position = value + .then -> + offset = atom.getWindowOffsetForCurrentPlatform() + expect(win0Position.x).toEqual(win1Position.x + offset) + expect(win0Position.y).toEqual(win1Position.y + offset) + it "doesn't open a new window if openEmptyEditorOnStart is disabled", -> configPath = path.join(atomHome, 'config.cson') config = CSON.readFileSync(configPath) From c8c8bc941e7c0ae73c2e6e5d7c16ed98d0221188 Mon Sep 17 00:00:00 2001 From: joshaber Date: Tue, 5 Jan 2016 12:27:59 -0500 Subject: [PATCH 110/173] Revert "Integration test for new window offset." This reverts commit 4ddadcc13580046dacc77b9b7af7198bc199e314. --- spec/integration/helpers/start-atom.coffee | 3 --- spec/integration/startup-spec.coffee | 19 ------------------- 2 files changed, 22 deletions(-) diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee index 8290a554e..b8768f532 100644 --- a/spec/integration/helpers/start-atom.coffee +++ b/spec/integration/helpers/start-atom.coffee @@ -127,9 +127,6 @@ buildAtomClient = (args, env) -> .execute -> require("remote").require("app").emit("before-quit") .call(done) - .addCommand "getWindowPosition", (cb) -> - @execute((-> atom.getPosition()), cb) - module.exports = (args, env, fn) -> [chromedriver, chromedriverLogs, chromedriverExit] = [] diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 374078111..799c7685f 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -181,25 +181,6 @@ describe "Starting Atom", -> , 5000) .waitForPaneItemCount(1, 5000) - it "opens a new window offset from the other window", -> - runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> - win0Position = null - win1Position = null - client - .waitForWindowCount(1, 10000) - .getWindowPosition() - .then ({value}) -> win0Position = value - .waitForNewWindow(-> - @startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome) - , 5000) - .waitForWindowCount(2, 10000) - .getWindowPosition() - .then ({value}) -> win1Position = value - .then -> - offset = atom.getWindowOffsetForCurrentPlatform() - expect(win0Position.x).toEqual(win1Position.x + offset) - expect(win0Position.y).toEqual(win1Position.y + offset) - it "doesn't open a new window if openEmptyEditorOnStart is disabled", -> configPath = path.join(atomHome, 'config.cson') config = CSON.readFileSync(configPath) From 2a29e7d1f4fc63519b06c5095c4f5e59fc4fe911 Mon Sep 17 00:00:00 2001 From: joshaber Date: Tue, 5 Jan 2016 12:48:34 -0500 Subject: [PATCH 111/173] Revert "Revert "Integration test for new window offset."" This reverts commit c8c8bc941e7c0ae73c2e6e5d7c16ed98d0221188. --- spec/integration/helpers/start-atom.coffee | 3 +++ spec/integration/startup-spec.coffee | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee index b8768f532..8290a554e 100644 --- a/spec/integration/helpers/start-atom.coffee +++ b/spec/integration/helpers/start-atom.coffee @@ -127,6 +127,9 @@ buildAtomClient = (args, env) -> .execute -> require("remote").require("app").emit("before-quit") .call(done) + .addCommand "getWindowPosition", (cb) -> + @execute((-> atom.getPosition()), cb) + module.exports = (args, env, fn) -> [chromedriver, chromedriverLogs, chromedriverExit] = [] diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 799c7685f..374078111 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -181,6 +181,25 @@ describe "Starting Atom", -> , 5000) .waitForPaneItemCount(1, 5000) + it "opens a new window offset from the other window", -> + runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> + win0Position = null + win1Position = null + client + .waitForWindowCount(1, 10000) + .getWindowPosition() + .then ({value}) -> win0Position = value + .waitForNewWindow(-> + @startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome) + , 5000) + .waitForWindowCount(2, 10000) + .getWindowPosition() + .then ({value}) -> win1Position = value + .then -> + offset = atom.getWindowOffsetForCurrentPlatform() + expect(win0Position.x).toEqual(win1Position.x + offset) + expect(win0Position.y).toEqual(win1Position.y + offset) + it "doesn't open a new window if openEmptyEditorOnStart is disabled", -> configPath = path.join(atomHome, 'config.cson') config = CSON.readFileSync(configPath) From f64878208f3eb47fff1e80dfe236782e9ca19129 Mon Sep 17 00:00:00 2001 From: joshaber Date: Tue, 5 Jan 2016 14:07:50 -0500 Subject: [PATCH 112/173] Really don't need this to be its own command. --- spec/integration/helpers/start-atom.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/integration/helpers/start-atom.coffee b/spec/integration/helpers/start-atom.coffee index 8290a554e..b8768f532 100644 --- a/spec/integration/helpers/start-atom.coffee +++ b/spec/integration/helpers/start-atom.coffee @@ -127,9 +127,6 @@ buildAtomClient = (args, env) -> .execute -> require("remote").require("app").emit("before-quit") .call(done) - .addCommand "getWindowPosition", (cb) -> - @execute((-> atom.getPosition()), cb) - module.exports = (args, env, fn) -> [chromedriver, chromedriverLogs, chromedriverExit] = [] From 740e8c660a714b2d52d5688feb2d59dfd90e66de Mon Sep 17 00:00:00 2001 From: joshaber Date: Tue, 5 Jan 2016 14:08:09 -0500 Subject: [PATCH 113/173] Put it with its friends. --- spec/integration/startup-spec.coffee | 37 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index 374078111..d66bcb077 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -125,6 +125,24 @@ describe "Starting Atom", -> .treeViewRootDirectories() .then ({value}) -> expect(value).toEqual([otherTempDirPath]) + it "opens the new window offset from the other window", -> + runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> + win0Position = null + win1Position = null + client + .waitForWindowCount(1, 10000) + .execute -> atom.getPosition() + .then ({value}) -> win0Position = value + .waitForNewWindow(-> + @startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome) + , 5000) + .waitForWindowCount(2, 10000) + .execute -> atom.getPosition() + .then ({value}) -> win1Position = value + .then -> + expect(win0Position.x).toEqual(win1Position.x + 22) + expect(win0Position.y).toEqual(win1Position.y + 22) + describe "reopening a directory that was previously opened", -> it "remembers the state of the window", -> runAtom [tempDirPath], {ATOM_HOME: atomHome}, (client) -> @@ -181,25 +199,6 @@ describe "Starting Atom", -> , 5000) .waitForPaneItemCount(1, 5000) - it "opens a new window offset from the other window", -> - runAtom [path.join(tempDirPath, "new-file")], {ATOM_HOME: atomHome}, (client) -> - win0Position = null - win1Position = null - client - .waitForWindowCount(1, 10000) - .getWindowPosition() - .then ({value}) -> win0Position = value - .waitForNewWindow(-> - @startAnotherAtom([path.join(temp.mkdirSync("a-third-dir"), "a-file")], ATOM_HOME: atomHome) - , 5000) - .waitForWindowCount(2, 10000) - .getWindowPosition() - .then ({value}) -> win1Position = value - .then -> - offset = atom.getWindowOffsetForCurrentPlatform() - expect(win0Position.x).toEqual(win1Position.x + offset) - expect(win0Position.y).toEqual(win1Position.y + offset) - it "doesn't open a new window if openEmptyEditorOnStart is disabled", -> configPath = path.join(atomHome, 'config.cson') config = CSON.readFileSync(configPath) From d224efb00d0b01a073fd75f6523731f5b5567225 Mon Sep 17 00:00:00 2001 From: joshaber Date: Tue, 5 Jan 2016 14:40:09 -0500 Subject: [PATCH 114/173] Just test the x coordinate, I suppose. --- spec/integration/startup-spec.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/integration/startup-spec.coffee b/spec/integration/startup-spec.coffee index d66bcb077..da4e08ae2 100644 --- a/spec/integration/startup-spec.coffee +++ b/spec/integration/startup-spec.coffee @@ -140,8 +140,11 @@ describe "Starting Atom", -> .execute -> atom.getPosition() .then ({value}) -> win1Position = value .then -> - expect(win0Position.x).toEqual(win1Position.x + 22) - expect(win0Position.y).toEqual(win1Position.y + 22) + expect(win1Position.x).toBeGreaterThan(win0Position.x) + # Ideally we'd test the y coordinate too, but if the window's + # already as tall as it can be, then OS X won't move it down outside + # the screen. + # expect(win1Position.y).toBeGreaterThan(win0Position.y) describe "reopening a directory that was previously opened", -> it "remembers the state of the window", -> From 3b8375d2b4163261e119481d0b5b63acdc07d727 Mon Sep 17 00:00:00 2001 From: Valerii Iatsko Date: Wed, 6 Jan 2016 00:05:58 +0100 Subject: [PATCH 115/173] This change gives custom repositoryProviders more priority - this wil allow us to override base git repository implementation --- src/project.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project.coffee b/src/project.coffee index d59c041cb..714511f9a 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -288,7 +288,7 @@ class Project extends Model 'atom.repository-provider', '^0.1.0', (provider) => - @repositoryProviders.push(provider) + @repositoryProviders.unshift(provider) @setPaths(@getPaths()) if null in @repositories new Disposable => @repositoryProviders.splice(@repositoryProviders.indexOf(provider), 1) From 972d62f9d2fffd8d32d7ad01bc2a73c4cfab6599 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 5 Jan 2016 17:23:32 -0800 Subject: [PATCH 116/173] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 591e1a2a0..d6eefcc7c 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.1.3", + "text-buffer": "8.1.4", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" From 8603ceb7e808e28e76d8c17a6fc923e1e5cda00c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 5 Jan 2016 11:08:30 +0100 Subject: [PATCH 117/173] Make spec more comprehensive --- spec/text-editor-presenter-spec.coffee | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index b65253a4d..99c56f1fc 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2289,7 +2289,7 @@ describe "TextEditorPresenter", -> presenter.setBlockDecorationDimensions(blockDecoration1, 0, 20) expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() - it "contains state for block decorations, indicating the screen row they belong to both initially and when their markers move", -> + it "contains state for on-screen and unmeasured block decorations, both initially and when they are updated or destroyed", -> item = {} blockDecoration1 = addBlockDecorationForScreenRow(0, item) blockDecoration2 = addBlockDecorationForScreenRow(4, item) @@ -2375,6 +2375,17 @@ describe "TextEditorPresenter", -> 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 = addBlockDecorationForScreenRow(0) presenter = buildPresenter() From 9efb3328bc0b252fc792f687377eb5d4b0f018a9 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 6 Jan 2016 18:20:46 -0800 Subject: [PATCH 118/173] Add helper to map error codes to friendly messages --- src/pane.coffee | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/pane.coffee b/src/pane.coffee index 412fc5251..c494297cf 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -721,30 +721,27 @@ class Pane extends Model message = "#{message} '#{itemPath}'" if itemPath @notificationManager.addWarning(message, options) - if error.code is 'EISDIR' or error.message?.endsWith?('is a directory') + customMessage = @getMessageForErrorCode(error.code) + if customMessage? + addWarningWithPath("Unable to save file: #{customMessage}") + else if error.code is 'EISDIR' or error.message?.endsWith?('is a directory') @notificationManager.addWarning("Unable to save file: #{error.message}") - else if error.code is 'EACCES' - addWarningWithPath('Unable to save file: Permission denied') else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST', 'ELOOP', 'EAGAIN'] addWarningWithPath('Unable to save file', detail: error.message) - else if error.code is 'EROFS' - addWarningWithPath('Unable to save file: Read-only file system') - else if error.code is 'ENOSPC' - addWarningWithPath('Unable to save file: No space left on device') - else if error.code is 'ENXIO' - addWarningWithPath('Unable to save file: No such device or address') - else if error.code is 'ENOTSUP' - addWarningWithPath('Unable to save file: Operation not supported on socket') - else if error.code is 'EIO' - addWarningWithPath('Unable to save file: I/O error writing file') - else if error.code is 'EINTR' - addWarningWithPath('Unable to save file: Interrupted system call') - else if error.code is 'ECONNRESET' - addWarningWithPath('Unable to save file: Connection reset') - else if error.code is 'ESPIPE' - addWarningWithPath('Unable to save file: Invalid seek') else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message) fileName = errorMatch[1] @notificationManager.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to") else throw error + + getMessageForErrorCode: (errorCode) -> + switch errorCode + when 'EACCES' then 'Permission denied' + when 'ECONNRESET' then 'Connection reset' + when 'EINTR' then 'Interrupted system call' + when 'EIO' then 'I/O error writing file' + when 'ENOSPC' then 'No space left on device' + when 'ENOTSUP' then 'Operation not supported on socket' + when 'ENXIO' then 'No such device or address' + when 'EROFS' then 'Read-only file system' + when 'ESPIPE' then 'Invalid seek' From 3bcb062ab0d8c6b9992dbcbee856e46608a0dbc1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 6 Jan 2016 18:23:25 -0800 Subject: [PATCH 119/173] Add friendly message for ETIMEDOUT --- src/pane.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pane.coffee b/src/pane.coffee index c494297cf..c006e29fe 100644 --- a/src/pane.coffee +++ b/src/pane.coffee @@ -745,3 +745,4 @@ class Pane extends Model when 'ENXIO' then 'No such device or address' when 'EROFS' then 'Read-only file system' when 'ESPIPE' then 'Invalid seek' + when 'ETIMEDOUT' then 'Connection timed out' From 41c8792a7d19b7cd4fbceb4cc7e4e1d99c51363a Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 6 Jan 2016 18:27:18 -0800 Subject: [PATCH 120/173] Handle EAGAIN errors on open --- src/workspace.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspace.coffee b/src/workspace.coffee index f64f58ee0..07e69cdcf 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -475,7 +475,7 @@ class Workspace extends Model when 'EACCES' @notificationManager.addWarning("Permission denied '#{error.path}'") return Promise.resolve() - when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR' + when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL', 'EMFILE', 'ENOTDIR', 'EAGAIN' @notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message) return Promise.resolve() else From 4d7af57db317a3ded916cb54fa49f2389d74df66 Mon Sep 17 00:00:00 2001 From: Leonard Lamprecht Date: Thu, 7 Jan 2016 11:51:54 +0100 Subject: [PATCH 121/173] Move to top level, add separator and ellipses --- menus/darwin.cson | 16 ++++++---------- menus/linux.cson | 16 ++++++---------- menus/win32.cson | 16 ++++++---------- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/menus/darwin.cson b/menus/darwin.cson index 6737adf46..b2f496685 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -11,16 +11,12 @@ { label: 'Downloading Update', enabled: false, visible: false} { type: 'separator' } { label: 'Preferences…', command: 'application:show-settings' } - { - label: 'Customization', - submenu: [ - { 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: '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' } diff --git a/menus/linux.cson b/menus/linux.cson index 416f16831..4e444ea4e 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -83,16 +83,12 @@ } { type: 'separator' } { label: '&Preferences', command: 'application:show-settings' } - { - label: 'Customization', - submenu: [ - { 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' } ] } diff --git a/menus/win32.cson b/menus/win32.cson index 82921d2a3..db8565cc6 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -10,16 +10,12 @@ { label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' } { type: 'separator' } { label: 'Se&ttings', command: 'application:show-settings' } - { - label: 'Customization', - submenu: [ - { 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' } From ee564845d6ae36418a79b9c29354b5b61ae4f2e3 Mon Sep 17 00:00:00 2001 From: Lee DohM Date: Thu, 7 Jan 2016 14:19:27 -0800 Subject: [PATCH 122/173] Default to using standard GitHub API token during build --- build/Gruntfile.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Gruntfile.coffee b/build/Gruntfile.coffee index f210ccff0..60d5916b8 100644 --- a/build/Gruntfile.coffee +++ b/build/Gruntfile.coffee @@ -256,7 +256,7 @@ module.exports = (grunt) -> outputDir: 'electron' downloadDir: electronDownloadDir rebuild: true # rebuild native modules after electron is updated - token: process.env.ATOM_ACCESS_TOKEN + token: process.env.ATOM_ACCESS_TOKEN ? 'da809a6077bb1b0aa7c5623f7b2d5f1fec2faae4' 'create-windows-installer': installer: From 26a2fe0f442c9f9d454b0dad8f1caa7126f483ba Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 8 Jan 2016 09:22:02 +0900 Subject: [PATCH 123/173] :arrow_up: one-dark/light-syntax@v1.1.2 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d6eefcc7c..bef892234 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,8 @@ "base16-tomorrow-dark-theme": "1.1.0", "base16-tomorrow-light-theme": "1.1.1", "one-dark-ui": "1.1.9", - "one-dark-syntax": "1.1.1", - "one-light-syntax": "1.1.1", + "one-dark-syntax": "1.1.2", + "one-light-syntax": "1.1.2", "one-light-ui": "1.1.9", "solarized-dark-syntax": "0.39.0", "solarized-light-syntax": "0.23.0", From 9e8507f2b673070b377859bb43be910cc67f2a98 Mon Sep 17 00:00:00 2001 From: simurai Date: Fri, 8 Jan 2016 09:24:08 +0900 Subject: [PATCH 124/173] Reorder one-light-ui so it's after one-dark-ui. Handy since they get updated together. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bef892234..eaad0b69a 100644 --- a/package.json +++ b/package.json @@ -65,9 +65,9 @@ "base16-tomorrow-dark-theme": "1.1.0", "base16-tomorrow-light-theme": "1.1.1", "one-dark-ui": "1.1.9", + "one-light-ui": "1.1.9", "one-dark-syntax": "1.1.2", "one-light-syntax": "1.1.2", - "one-light-ui": "1.1.9", "solarized-dark-syntax": "0.39.0", "solarized-light-syntax": "0.23.0", "about": "1.1.0", From de043956bb2c1812c40033278ed66771ede69783 Mon Sep 17 00:00:00 2001 From: Lee DohM Date: Thu, 7 Jan 2016 18:51:26 -0800 Subject: [PATCH 125/173] Relocate and rename 'View > Reload' to prevent confusion --- menus/darwin.cson | 2 +- menus/linux.cson | 2 +- menus/win32.cson | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/menus/darwin.cson b/menus/darwin.cson index 52b7a5bc8..31a2b8db6 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -139,7 +139,6 @@ { label: 'View' submenu: [ - { label: 'Reload', command: 'window:reload' } { label: 'Toggle Full Screen', command: 'window:toggle-full-screen' } { label: 'Panes' @@ -165,6 +164,7 @@ submenu: [ { label: 'Open In Dev Mode…', command: 'application:open-dev' } { label: 'Run Package Specs', command: 'window:run-package-specs' } + { label: 'Reload Window', command: 'window:reload' } { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/menus/linux.cson b/menus/linux.cson index fa831b4a4..8e9b22cc4 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -95,7 +95,6 @@ { label: '&View' submenu: [ - { label: '&Reload', command: 'window:reload' } { label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' } { label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' } { @@ -122,6 +121,7 @@ submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } + { label: '&Reload Window', command: 'window:reload' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/menus/win32.cson b/menus/win32.cson index 04da3d388..edc6a9c6c 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -94,7 +94,6 @@ { label: '&View' submenu: [ - { label: '&Reload', command: 'window:reload' } { label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' } { label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' } { @@ -121,6 +120,7 @@ submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } { label: 'Run Package &Specs', command: 'window:run-package-specs' } + { label: '&Reload Window', command: 'window:reload' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } From ad76d3396b48a1737e46f5b046d126a653df167a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 26 Nov 2015 16:09:40 +0800 Subject: [PATCH 126/173] :arrow_up: electron@0.34.5 Fix #9584. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1cb27543a..965c2010e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/atom/atom/issues" }, "license": "MIT", - "electronVersion": "0.34.3", + "electronVersion": "0.34.5", "dependencies": { "async": "0.2.6", "atom-keymap": "^6.1.1", From e9bb1f9220a07c3e723d6c801ca80265e4f92fa7 Mon Sep 17 00:00:00 2001 From: Lee DohM Date: Thu, 7 Jan 2016 19:31:03 -0800 Subject: [PATCH 127/173] Sort alphabetically --- menus/darwin.cson | 2 +- menus/linux.cson | 2 +- menus/win32.cson | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/menus/darwin.cson b/menus/darwin.cson index 31a2b8db6..0195bde71 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -163,8 +163,8 @@ label: 'Developer' submenu: [ { label: 'Open In Dev Mode…', command: 'application:open-dev' } - { label: 'Run Package Specs', command: 'window:run-package-specs' } { label: 'Reload Window', command: 'window:reload' } + { label: 'Run Package Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/menus/linux.cson b/menus/linux.cson index 8e9b22cc4..9f251d67f 100644 --- a/menus/linux.cson +++ b/menus/linux.cson @@ -120,8 +120,8 @@ label: 'Developer' submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } - { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: '&Reload Window', command: 'window:reload' } + { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } diff --git a/menus/win32.cson b/menus/win32.cson index edc6a9c6c..ad0461898 100644 --- a/menus/win32.cson +++ b/menus/win32.cson @@ -119,8 +119,8 @@ label: 'Developer' submenu: [ { label: 'Open In &Dev Mode…', command: 'application:open-dev' } - { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: '&Reload Window', command: 'window:reload' } + { label: 'Run Package &Specs', command: 'window:run-package-specs' } { label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' } ] } From 8f1c49cb5c7a79156b634d5d6a19523b56f5f5c9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 20 Nov 2015 16:55:38 -0800 Subject: [PATCH 128/173] Merge pull request #9636 from DouweM/unique-title Use unique text editor title in window and tab titles --- .../sample-theme-2/src/js/plugin/main.js | 0 spec/text-editor-spec.coffee | 23 +++++-- spec/workspace-spec.coffee | 6 +- src/text-editor.coffee | 66 +++++++++++-------- src/workspace.coffee | 2 +- 5 files changed, 58 insertions(+), 39 deletions(-) create mode 100644 spec/fixtures/testdir/sample-theme-2/src/js/plugin/main.js diff --git a/spec/fixtures/testdir/sample-theme-2/src/js/plugin/main.js b/spec/fixtures/testdir/sample-theme-2/src/js/plugin/main.js new file mode 100644 index 000000000..e69de29bb diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 39740ebd2..0cee8215a 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -168,7 +168,7 @@ describe "TextEditor", -> buffer.setPath(undefined) expect(editor.getLongTitle()).toBe 'untitled' - it "returns / when opened files has identical file names", -> + it "returns ' — ' when opened files have identical file names", -> editor1 = null editor2 = null waitsForPromise -> @@ -177,10 +177,10 @@ describe "TextEditor", -> atom.workspace.open(path.join('sample-theme-2', 'readme')).then (o) -> editor2 = o runs -> - expect(editor1.getLongTitle()).toBe 'sample-theme-1/readme' - expect(editor2.getLongTitle()).toBe 'sample-theme-2/readme' + expect(editor1.getLongTitle()).toBe "readme \u2014 sample-theme-1" + expect(editor2.getLongTitle()).toBe "readme \u2014 sample-theme-2" - it "or returns /.../ when opened files has identical file names", -> + it "returns ' — ' when opened files have identical file and dir names", -> editor1 = null editor2 = null waitsForPromise -> @@ -189,9 +189,20 @@ describe "TextEditor", -> atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) -> editor2 = o runs -> - expect(editor1.getLongTitle()).toBe 'sample-theme-1/.../main.js' - expect(editor2.getLongTitle()).toBe 'sample-theme-2/.../main.js' + expect(editor1.getLongTitle()).toBe "main.js \u2014 sample-theme-1/src/js" + expect(editor2.getLongTitle()).toBe "main.js \u2014 sample-theme-2/src/js" + it "returns ' — ' when opened files have identical file and same parent dir name", -> + editor1 = null + editor2 = null + waitsForPromise -> + atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'main.js')).then (o) -> + editor1 = o + atom.workspace.open(path.join('sample-theme-2', 'src', 'js', 'plugin', 'main.js')).then (o) -> + editor2 = o + runs -> + expect(editor1.getLongTitle()).toBe "main.js \u2014 js" + expect(editor2.getLongTitle()).toBe "main.js \u2014 js/plugin" it "notifies ::onDidChangeTitle observers when the underlying buffer path changes", -> observed = [] diff --git a/spec/workspace-spec.coffee b/spec/workspace-spec.coffee index 5d7343540..71713d83d 100644 --- a/spec/workspace-spec.coffee +++ b/spec/workspace-spec.coffee @@ -76,7 +76,7 @@ describe "Workspace", -> expect(editor4.getCursorScreenPosition()).toEqual [2, 4] expect(atom.workspace.getActiveTextEditor().getPath()).toBe editor3.getPath() - expect(document.title).toBe "#{path.basename(editor3.getPath())} - #{atom.project.getPaths()[0]} - Atom" + expect(document.title).toBe "#{path.basename(editor3.getLongTitle())} - #{atom.project.getPaths()[0]} - Atom" describe "where there are no open panes or editors", -> it "constructs the view with no open editors", -> @@ -776,8 +776,8 @@ describe "Workspace", -> applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom) }) workspace2.deserialize(atom.workspace.serialize(), atom.deserializers) - item = atom.workspace.getActivePaneItem() - expect(document.title).toBe "#{item.getTitle()} - #{atom.project.getPaths()[0]} - Atom" + item = workspace2.getActivePaneItem() + expect(document.title).toBe "#{item.getLongTitle()} - #{atom.project.getPaths()[0]} - Atom" workspace2.destroy() describe "document edited status", -> diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 29d56c6c8..a151c9dba 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -581,10 +581,7 @@ class TextEditor extends Model # # Returns a {String}. getTitle: -> - if sessionPath = @getPath() - path.basename(sessionPath) - else - 'untitled' + @getFileName() ? 'untitled' # Essential: Get unique title for display in other parts of the UI, such as # the window title. @@ -593,41 +590,52 @@ class TextEditor extends Model # If the editor's buffer is saved, its unique title is formatted as one # of the following, # * "" when it is the only editing buffer with this file name. - # * "/.../", where the "..." may be omitted - # if the the direct parent directory is already different. + # * " — " when other buffers have this file name. # # Returns a {String} getLongTitle: -> - if sessionPath = @getPath() - title = @getTitle() + if @getPath() + fileName = @getFileName() - # find text editors with identical file name. - paths = [] + allPathSegments = [] for textEditor in atom.workspace.getTextEditors() when textEditor isnt this - if textEditor.getTitle() is title - paths.push(textEditor.getPath()) - if paths.length is 0 - return title - fileName = path.basename(sessionPath) + if textEditor.getFileName() is fileName + allPathSegments.push(textEditor.getDirectoryPath().split(path.sep)) - # find the first directory in all these paths that is unique - nLevel = 0 - while (_.some(paths, (apath) -> path.basename(apath) is path.basename(sessionPath))) - sessionPath = path.dirname(sessionPath) - paths = _.map(paths, (apath) -> path.dirname(apath)) - nLevel += 1 + if allPathSegments.length is 0 + return fileName - directory = path.basename sessionPath - if nLevel > 1 - path.join(directory, "...", fileName) - else - path.join(directory, fileName) + ourPathSegments = @getDirectoryPath().split(path.sep) + allPathSegments.push ourPathSegments + + loop + firstSegment = ourPathSegments[0] + + commonBase = _.all(allPathSegments, (pathSegments) -> pathSegments.length > 1 and pathSegments[0] is firstSegment) + if commonBase + pathSegments.shift() for pathSegments in allPathSegments + else + break + + "#{fileName} \u2014 #{path.join(pathSegments...)}" else 'untitled' # Essential: Returns the {String} path of this editor's text buffer. getPath: -> @buffer.getPath() + getFileName: -> + if fullPath = @getPath() + path.basename(fullPath) + else + null + + getDirectoryPath: -> + if fullPath = @getPath() + path.dirname(fullPath) + else + null + # Extended: Returns the {String} character set encoding of this editor's text # buffer. getEncoding: -> @buffer.getEncoding() @@ -678,16 +686,16 @@ class TextEditor extends Model getSaveDialogOptions: -> {} checkoutHeadRevision: -> - if filePath = this.getPath() + if @getPath() checkoutHead = => - @project.repositoryForDirectory(new Directory(path.dirname(filePath))) + @project.repositoryForDirectory(new Directory(@getDirectoryPath())) .then (repository) => repository?.checkoutHeadForEditor(this) if @config.get('editor.confirmCheckoutHeadRevision') @applicationDelegate.confirm message: 'Confirm Checkout HEAD Revision' - detailedMessage: "Are you sure you want to discard all changes to \"#{path.basename(filePath)}\" since the last Git commit?" + detailedMessage: "Are you sure you want to discard all changes to \"#{@getFileName()}\" since the last Git commit?" buttons: OK: checkoutHead Cancel: null diff --git a/src/workspace.coffee b/src/workspace.coffee index 396008201..6088055d1 100644 --- a/src/workspace.coffee +++ b/src/workspace.coffee @@ -155,7 +155,7 @@ class Workspace extends Model projectPaths = @project.getPaths() ? [] if item = @getActivePaneItem() itemPath = item.getPath?() - itemTitle = item.getTitle?() + itemTitle = item.getLongTitle?() ? item.getTitle?() projectPath = _.find projectPaths, (projectPath) -> itemPath is projectPath or itemPath?.startsWith(projectPath + path.sep) itemTitle ?= "untitled" From f527504402452aad70210e767abcae37981aa3e2 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 7 Jan 2016 22:49:30 -0500 Subject: [PATCH 129/173] :arrow_up: language-python@0.43.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eaad0b69a..f9d41fc64 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "language-perl": "0.32.0", "language-php": "0.36.0", "language-property-list": "0.8.0", - "language-python": "0.42.1", + "language-python": "0.43.0", "language-ruby": "0.67.0", "language-ruby-on-rails": "0.25.0", "language-sass": "0.45.0", From 0bb8e21c04378f3c46fd0181c136b4136e714746 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 7 Jan 2016 22:49:51 -0500 Subject: [PATCH 130/173] :arrow_up: language-json@0.17.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f9d41fc64..8f8b24b9c 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "language-hyperlink": "0.16.0", "language-java": "0.17.0", "language-javascript": "0.105.0", - "language-json": "0.17.2", + "language-json": "0.17.3", "language-less": "0.29.0", "language-make": "0.21.0", "language-mustache": "0.13.0", From d00164dc3c5235eda3780ec76203bbdd4d73aaaa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 8 Jan 2016 11:05:55 +0100 Subject: [PATCH 131/173] Deal with rows instead of points in LineTopIndex --- src/text-editor-presenter.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 77de7e8d3..d81261ace 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1286,13 +1286,13 @@ class TextEditorPresenter 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) + 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) + @lineTopIndex.moveBlock(id, newScreenPosition.row) @invalidatedDimensionsByBlockDecoration.add(decoration) didAddBlockDecoration: (decoration) -> @@ -1308,7 +1308,7 @@ class TextEditorPresenter didDestroyDisposable.dispose() @didDestroyBlockDecoration(decoration) - @lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition(), true, 0) + @lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0) @observedBlockDecorations.add(decoration) @invalidateBlockDecorationDimensions(decoration) @@ -1322,7 +1322,7 @@ class TextEditorPresenter # change. return if markerEvent.textChanged - @lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition()) + @lineTopIndex.moveBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row) @shouldUpdateDecorations = true @emitDidUpdateState() From 1a13913168ef7f9fb614003434aa4fdf51b0f157 Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Fri, 8 Jan 2016 12:00:24 -0500 Subject: [PATCH 132/173] Use OS X Human Interface Guidelines compliant menu order Fixes https://github.com/atom/atom/issues/10302 --- menus/darwin.cson | 52 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/menus/darwin.cson b/menus/darwin.cson index 0195bde71..23f4b6144 100644 --- a/menus/darwin.cson +++ b/menus/darwin.cson @@ -110,32 +110,6 @@ ] } - { - label: 'Selection' - submenu: [ - { label: 'Add Selection Above', command: 'editor:add-selection-above' } - { label: 'Add Selection Below', command: 'editor:add-selection-below' } - { label: 'Single Selection', command: 'editor:consolidate-selections'} - { label: 'Split into Lines', command: 'editor:split-selections-into-lines'} - { type: 'separator' } - { label: 'Select to Top', command: 'core:select-to-top' } - { label: 'Select to Bottom', command: 'core:select-to-bottom' } - { type: 'separator' } - { label: 'Select Line', command: 'editor:select-line' } - { label: 'Select Word', command: 'editor:select-word' } - { label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' } - { label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' } - { label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' } - { label: 'Select to End of Word', command: 'editor:select-to-end-of-word' } - { label: 'Select to End of Line', command: 'editor:select-to-end-of-line' } - ] - } - - { - label: 'Find' - submenu: [] - } - { label: 'View' submenu: [ @@ -177,6 +151,32 @@ ] } + { + label: 'Selection' + submenu: [ + { label: 'Add Selection Above', command: 'editor:add-selection-above' } + { label: 'Add Selection Below', command: 'editor:add-selection-below' } + { label: 'Single Selection', command: 'editor:consolidate-selections'} + { label: 'Split into Lines', command: 'editor:split-selections-into-lines'} + { type: 'separator' } + { label: 'Select to Top', command: 'core:select-to-top' } + { label: 'Select to Bottom', command: 'core:select-to-bottom' } + { type: 'separator' } + { label: 'Select Line', command: 'editor:select-line' } + { label: 'Select Word', command: 'editor:select-word' } + { label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' } + { label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' } + { label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' } + { label: 'Select to End of Word', command: 'editor:select-to-end-of-word' } + { label: 'Select to End of Line', command: 'editor:select-to-end-of-line' } + ] + } + + { + label: 'Find' + submenu: [] + } + { label: 'Packages' submenu: [] From 5738b14eda476e90c85aaf67128df5baf7cbff23 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Fri, 8 Jan 2016 13:39:46 -0800 Subject: [PATCH 133/173] :art: --- spec/text-editor-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 686bb6868..ce84d2c50 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -5815,7 +5815,7 @@ describe "TextEditor", -> expect(editor1.isPending()).toBe true expect(editor.isPending()).toBe false # By default pending status is false - it "invokes ::onDidTerminatePendingState observers if pending status is removed", -> + it "invokes ::onDidTerminatePendingState observers if pending status is terminated", -> events = [] editor1.onDidTerminatePendingState (event) -> events.push(event) editor1.terminatePendingState() From 235e489f6e357075072a790ae449f431710b3f0f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 9 Jan 2016 04:20:30 -0800 Subject: [PATCH 134/173] Fix F11 keymaps Function key keymaps don't work when they feature upperface 'F' characters. They still tend to work because they typically have an application menu item which is associated with it and falled back upon. The main difference is that the originalEvent (KeyboardEvent) is not retained when doing this fallback as Atom thinks that the menu item was pressed instead of the keymap. Fixes #10287 --- keymaps/linux.cson | 2 +- keymaps/win32.cson | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/keymaps/linux.cson b/keymaps/linux.cson index 28f43a8fc..536a1d75d 100644 --- a/keymaps/linux.cson +++ b/keymaps/linux.cson @@ -13,7 +13,7 @@ 'ctrl-alt-o': 'application:add-project-folder' 'ctrl-shift-pageup': 'pane:move-item-left' 'ctrl-shift-pagedown': 'pane:move-item-right' - 'F11': 'window:toggle-full-screen' + 'f11': 'window:toggle-full-screen' # Sublime Parity 'ctrl-,': 'application:show-settings' diff --git a/keymaps/win32.cson b/keymaps/win32.cson index f052a64fc..cb291f493 100644 --- a/keymaps/win32.cson +++ b/keymaps/win32.cson @@ -19,7 +19,7 @@ 'ctrl-alt-o': 'application:add-project-folder' 'ctrl-shift-left': 'pane:move-item-left' 'ctrl-shift-right': 'pane:move-item-right' - 'F11': 'window:toggle-full-screen' + 'f11': 'window:toggle-full-screen' # Sublime Parity 'ctrl-,': 'application:show-settings' From d4c8770176efe0698dedeab7e943a76fd8eb698f Mon Sep 17 00:00:00 2001 From: Yuya Tanaka Date: Sun, 10 Jan 2016 19:35:34 +0900 Subject: [PATCH 135/173] Add copy project path command to text-editor --- src/register-default-commands.coffee | 3 ++- src/text-editor.coffee | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/register-default-commands.coffee b/src/register-default-commands.coffee index 159ea1abc..b4649f70e 100644 --- a/src/register-default-commands.coffee +++ b/src/register-default-commands.coffee @@ -169,7 +169,8 @@ module.exports = ({commandRegistry, commandInstaller, config}) -> 'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7) 'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8) 'editor:log-cursor-scope': -> @logCursorScope() - 'editor:copy-path': -> @copyPathToClipboard() + 'editor:copy-path': -> @copyPathToClipboard(false) + 'editor:copy-project-path': -> @copyPathToClipboard(true) 'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide')) 'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers')) 'editor:scroll-to-cursor': -> @scrollToCursorPosition() diff --git a/src/text-editor.coffee b/src/text-editor.coffee index ae2645567..983669e28 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -666,8 +666,9 @@ class TextEditor extends Model isPending: -> Boolean(@pending) # Copies the current file path to the native clipboard. - copyPathToClipboard: -> + copyPathToClipboard: (relative = false) -> if filePath = @getPath() + filePath = atom.project.relativize(filePath) if relative @clipboard.write(filePath) ### From 6b44562a22f197815f656639b6cb36d36423c030 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Sun, 10 Jan 2016 23:11:41 +0100 Subject: [PATCH 136/173] :arrow_up: language-go@0.42.0 This version fixes #10272 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 965c2010e..9c74d5c9b 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "language-css": "0.35.0", "language-gfm": "0.81.0", "language-git": "0.10.0", - "language-go": "0.40.0", + "language-go": "0.42.0", "language-html": "0.42.0", "language-hyperlink": "0.15.0", "language-java": "0.16.1", From 35f262f4117d6d4cbe33ec336d905781ef79af83 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 10 Jan 2016 20:41:43 -0500 Subject: [PATCH 137/173] :arrow_up: language-html@0.44.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f8b24b9c..76a351866 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "language-gfm": "0.82.0", "language-git": "0.11.0", "language-go": "0.41.0", - "language-html": "0.43.1", + "language-html": "0.44.0", "language-hyperlink": "0.16.0", "language-java": "0.17.0", "language-javascript": "0.105.0", From d48f3aaed8d750b07a8d6c1da582a4309ff2ff0f Mon Sep 17 00:00:00 2001 From: Daniel Hengeveld Date: Mon, 11 Jan 2016 10:06:01 -0500 Subject: [PATCH 138/173] :arrow_up: settings-view@0.232.1 refs https://github.com/atom/settings-view/pull/698 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76a351866..8fd36fd8a 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "open-on-github": "0.40.0", "package-generator": "0.41.0", "release-notes": "0.53.0", - "settings-view": "0.232.1", + "settings-view": "0.232.2", "snippets": "1.0.1", "spell-check": "0.63.0", "status-bar": "0.80.0", From 9e6136470e30842fe57db21eb2819215657d673a Mon Sep 17 00:00:00 2001 From: Josh Abernathy Date: Mon, 11 Jan 2016 12:17:08 -0500 Subject: [PATCH 139/173] :fire: CHANGELOG.md Having this file still here is confusing. Do we use it, or do we use https://github.com/benogle/pr-changelog? Let's apply :fire: if we're not gonna keep it up to date. /cc @mnquintana --- CHANGELOG.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e36b3f59e..8823bd9cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1 @@ See https://atom.io/releases - -## 1.4.0 - -* Switching encoding is now fast also with large files. -* Fixed an issue where disabling and re-enabling a package caused custom keymaps to be overridden. -* Fixed restoring untitled editors on restart. The new behavior never prompts to save new/changed files when closing a window or quitting Atom. - -## 1.3.0 - -* The tree-view now sorts directory entries more naturally, in a locale-sensitive way. -* Lines can now be moved up and down with multiple cursors. -* Improved the performance of marker-dependent code paths such as spell-check and find and replace. -* Fixed copying and pasting in native input fields. -* By default, windows with no pane items are now closed via the `core:close` command. The previous behavior can be restored via the `Close Empty Windows` option in settings. -* Fixed an issue where characters were inserted when toggling the settings view on some keyboard layouts. -* Modules can now temporarily override `Error.prepareStackTrace`. There is also an `Error.prototype.getRawStack()` method if you just need access to the raw v8 trace structure. -* Fixed a problem that caused blurry fonts on monitors that have a slightly higher resolution than 96 DPI. From ff0a62833f3302a1656b89686c25a28532f1fa6f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 11 Jan 2016 11:26:31 -0700 Subject: [PATCH 140/173] Pin jQuery to 2.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fd36fd8a..06f4d2bfe 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "grim": "1.5.0", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", - "jquery": "^2.1.1", + "jquery": "2.1.4", "key-path-helpers": "^0.4.0", "less-cache": "0.22", "marked": "^0.3.4", From 339489997b7fbecfd9ea6340f0f0d2de7768daf5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 11 Jan 2016 11:31:31 -0700 Subject: [PATCH 141/173] :arrow_up: tree-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06f4d2bfe..e3a4637a7 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "symbols-view": "0.110.1", "tabs": "0.88.0", "timecop": "0.33.0", - "tree-view": "0.198.0", + "tree-view": "0.198.1", "update-package-dependencies": "0.10.0", "welcome": "0.33.0", "whitespace": "0.32.1", From 2acb9c1ee885f0b47cc8e87085bf3c44748acf69 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 11 Jan 2016 11:31:38 -0700 Subject: [PATCH 142/173] :arrow_up: settings-view --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3a4637a7..d781d82ac 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "open-on-github": "0.40.0", "package-generator": "0.41.0", "release-notes": "0.53.0", - "settings-view": "0.232.2", + "settings-view": "0.232.3", "snippets": "1.0.1", "spell-check": "0.63.0", "status-bar": "0.80.0", From e7f15d06e4632eeee2d51cf827921a1c0a058b28 Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Sun, 10 Jan 2016 09:32:51 -0500 Subject: [PATCH 143/173] :white_check_mark: Add failing spec for disabling an already disabled package --- spec/package-manager-spec.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index e6848ef03..3099684b1 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -1026,6 +1026,16 @@ describe "PackageManager", -> expect(atom.packages.enablePackage("this-doesnt-exist")).toBeNull() expect(console.warn.callCount).toBe 1 + fit "does not disable an already disabled package", -> + packageName = 'package-with-main' + atom.config.pushAtKeyPath('core.disabledPackages', packageName) + atom.packages.observeDisabledPackages() + expect(atom.config.get('core.disabledPackages')).toContain packageName + + atom.packages.disablePackage(packageName) + packagesDisabled = atom.config.get('core.disabledPackages').filter((pack) -> pack is packageName) + expect(packagesDisabled.length).toEqual 1 + describe "with themes", -> didChangeActiveThemesHandler = null From 78543a7a34d0197576849de26217f7dccf895d6b Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Sun, 10 Jan 2016 09:38:12 -0500 Subject: [PATCH 144/173] Only disable a package if it isn't already disabled --- src/package-manager.coffee | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 6772178af..1ecdc5448 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -199,7 +199,10 @@ class PackageManager # Returns the {Package} that was disabled or null if it isn't loaded. disablePackage: (name) -> pack = @loadPackage(name) - pack?.disable() + + unless @isPackageDisabled(name) + pack?.disable() + pack # Public: Is the package with the given name disabled? From 088b32f9d5f583724391bb748d2a5f294f3d9d13 Mon Sep 17 00:00:00 2001 From: Machiste Quintana Date: Sun, 10 Jan 2016 09:39:09 -0500 Subject: [PATCH 145/173] Unfocus spec --- spec/package-manager-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 3099684b1..46d1d11ee 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -1026,7 +1026,7 @@ describe "PackageManager", -> expect(atom.packages.enablePackage("this-doesnt-exist")).toBeNull() expect(console.warn.callCount).toBe 1 - fit "does not disable an already disabled package", -> + it "does not disable an already disabled package", -> packageName = 'package-with-main' atom.config.pushAtKeyPath('core.disabledPackages', packageName) atom.packages.observeDisabledPackages() From 731e782f0de5c91d4a42ba530c8cf6d020e24161 Mon Sep 17 00:00:00 2001 From: joshaber Date: Mon, 11 Jan 2016 16:08:07 -0500 Subject: [PATCH 146/173] RIP release-notes. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index d781d82ac..5540a3e86 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "notifications": "0.62.1", "open-on-github": "0.40.0", "package-generator": "0.41.0", - "release-notes": "0.53.0", "settings-view": "0.232.3", "snippets": "1.0.1", "spell-check": "0.63.0", From 378f6aaabbe7220aff0a5864fb8e9c4e4b510de1 Mon Sep 17 00:00:00 2001 From: joshaber Date: Mon, 11 Jan 2016 16:08:24 -0500 Subject: [PATCH 147/173] :arrow_up: about@1.2.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5540a3e86..cfb6fa2c0 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "one-light-syntax": "1.1.2", "solarized-dark-syntax": "0.39.0", "solarized-light-syntax": "0.23.0", - "about": "1.1.0", + "about": "1.2.0", "archive-view": "0.61.0", "autocomplete-atom-api": "0.9.2", "autocomplete-css": "0.11.0", From ced74029f8442554397f274a95783af60b812907 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Mon, 11 Jan 2016 16:25:57 -0500 Subject: [PATCH 148/173] :arrow_up: language-ruby@0.67.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d781d82ac..de0275f10 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,7 @@ "language-php": "0.36.0", "language-property-list": "0.8.0", "language-python": "0.43.0", - "language-ruby": "0.67.0", + "language-ruby": "0.67.1", "language-ruby-on-rails": "0.25.0", "language-sass": "0.45.0", "language-shellscript": "0.21.0", From b01e97be59a019a384471a08bbf6b8dc98aff275 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 11 Jan 2016 14:01:55 -0800 Subject: [PATCH 149/173] Explicitly guard against decorating destroyed markers Signed-off-by: Nathan Sobo --- spec/display-buffer-spec.coffee | 7 +++++++ src/display-buffer.coffee | 1 + 2 files changed, 8 insertions(+) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 8c4adca44..0246008a4 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1253,6 +1253,13 @@ describe "DisplayBuffer", -> decoration.destroy() expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined() + it "does not allow destroyed markers to be decorated", -> + marker.destroy() + expect(-> + displayBuffer.decorateMarker(marker, {type: 'overlay', item: document.createElement('div')}) + ).toThrow("Cannot decorate a destroyed marker") + expect(displayBuffer.getOverlayDecorations()).toEqual [] + describe "when a decoration is updated via Decoration::update()", -> it "emits an 'updated' event containing the new and old params", -> decoration.onDidChangeProperties updatedSpy = jasmine.createSpy() diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index f5a7bd853..8b95656f9 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -812,6 +812,7 @@ class DisplayBuffer extends Model decorationsState decorateMarker: (marker, decorationParams) -> + throw new Error("Cannot decorate a destroyed marker") if marker.isDestroyed() marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id) decoration = new Decoration(marker, this, decorationParams) @decorationsByMarkerId[marker.id] ?= [] From eaab862e64b143d11de4be3490aee11cc64ee470 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 11 Jan 2016 15:39:53 -0800 Subject: [PATCH 150/173] :arrow_up: spell-check --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de0275f10..e3ac314bb 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "release-notes": "0.53.0", "settings-view": "0.232.3", "snippets": "1.0.1", - "spell-check": "0.63.0", + "spell-check": "0.64.0", "status-bar": "0.80.0", "styleguide": "0.45.0", "symbols-view": "0.110.1", From 389d3d6760c5f76d50cf316bf91eecd5ad2be57a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 11 Jan 2016 15:49:13 -0800 Subject: [PATCH 151/173] :arrow_up: spell-check --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3ac314bb..6ac1c9dce 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "release-notes": "0.53.0", "settings-view": "0.232.3", "snippets": "1.0.1", - "spell-check": "0.64.0", + "spell-check": "0.65.0", "status-bar": "0.80.0", "styleguide": "0.45.0", "symbols-view": "0.110.1", From 53950f6687ca9e0babf9d9c22e46badd8589c3e7 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Tue, 12 Jan 2016 10:14:40 -0500 Subject: [PATCH 152/173] :arrow_up: language-ruby@0.68.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ac1c9dce..08e5e0171 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,7 @@ "language-php": "0.36.0", "language-property-list": "0.8.0", "language-python": "0.43.0", - "language-ruby": "0.67.1", + "language-ruby": "0.68.0", "language-ruby-on-rails": "0.25.0", "language-sass": "0.45.0", "language-shellscript": "0.21.0", From 63220f7ead3926c5c42ab92cc1e6265d48b87147 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Jan 2016 10:54:56 -0700 Subject: [PATCH 153/173] Add presenter specs for block decorations *after* a row --- spec/text-editor-presenter-spec.coffee | 126 +++++++++++++------------ src/text-editor-presenter.coffee | 16 ++-- 2 files changed, 76 insertions(+), 66 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 99c56f1fc..252e6031a 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -30,10 +30,16 @@ describe "TextEditorPresenter", -> presenter.getPreMeasurementState() presenter.getPostMeasurementState() - addBlockDecorationForScreenRow = (screenRow, item) -> + addBlockDecorationBeforeScreenRow = (screenRow, item) -> editor.decorateMarker( editor.markScreenPosition([screenRow, 0], invalidate: "never"), - type: "block", item: item + 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={}) -> @@ -179,21 +185,23 @@ describe "TextEditorPresenter", -> it "computes each tile's height and scrollTop based on block decorations' height", -> presenter = buildPresenter(explicitHeight: 120, scrollTop: 0, lineHeight: 10, tileSize: 2) - blockDecoration1 = addBlockDecorationForScreenRow(0) - blockDecoration2 = addBlockDecorationForScreenRow(3) - blockDecoration3 = addBlockDecorationForScreenRow(5) + 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) + 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) + expect(stateFn(presenter).tiles[6].top).toBe(6 * 10 + 1 + 30 + 40 + 50) expect(stateFn(presenter).tiles[8]).toBeUndefined() presenter.setScrollTop(21) @@ -201,31 +209,27 @@ describe "TextEditorPresenter", -> 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) + 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 - 21) - expect(stateFn(presenter).tiles[8].height).toBe(2 * 10) - expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 1 + 30 + 40 - 21) - expect(stateFn(presenter).tiles[10]).toBeUndefined() + 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) + 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 - 21) - expect(stateFn(presenter).tiles[8].height).toBe(2 * 10) - expect(stateFn(presenter).tiles[8].top).toBe(8 * 10 + 1 + 30 + 40 - 21) - expect(stateFn(presenter).tiles[10]).toBeUndefined() + 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 = addBlockDecorationForScreenRow(0) - blockDecoration2 = addBlockDecorationForScreenRow(4) - blockDecoration3 = addBlockDecorationForScreenRow(8) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) + blockDecoration2 = addBlockDecorationBeforeScreenRow(4) + blockDecoration3 = addBlockDecorationBeforeScreenRow(8) presenter = buildPresenter(explicitHeight: 330, lineHeight: 10, tileSize: 2, baseCharacterWidth: 5) @@ -592,9 +596,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - blockDecoration1 = addBlockDecorationForScreenRow(0) - blockDecoration2 = addBlockDecorationForScreenRow(3) - blockDecoration3 = addBlockDecorationForScreenRow(7) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) + blockDecoration2 = addBlockDecorationBeforeScreenRow(3) + blockDecoration3 = addBlockDecorationBeforeScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3) @@ -766,9 +770,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - blockDecoration1 = addBlockDecorationForScreenRow(0) - blockDecoration2 = addBlockDecorationForScreenRow(3) - blockDecoration3 = addBlockDecorationForScreenRow(7) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) + blockDecoration2 = addBlockDecorationBeforeScreenRow(3) + blockDecoration3 = addBlockDecorationBeforeScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3) @@ -1328,10 +1332,10 @@ describe "TextEditorPresenter", -> describe ".blockDecorations", -> it "contains all block decorations that are present before a line, both initially and when decorations change", -> - blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) presenter = buildPresenter() - blockDecoration2 = addBlockDecorationForScreenRow(3) - blockDecoration3 = addBlockDecorationForScreenRow(7) + blockDecoration2 = addBlockDecorationBeforeScreenRow(3) + blockDecoration3 = addBlockDecorationBeforeScreenRow(7) waitsForStateToUpdate presenter runs -> @@ -1618,8 +1622,8 @@ describe "TextEditorPresenter", -> 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 = addBlockDecorationForScreenRow(0) - blockDecoration2 = addBlockDecorationForScreenRow(1) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) + blockDecoration2 = addBlockDecorationBeforeScreenRow(1) waitsForStateToUpdate presenter, -> presenter.setBlockDecorationDimensions(blockDecoration1, 0, 30) @@ -2183,7 +2187,7 @@ describe "TextEditorPresenter", -> getState(presenter).content.blockDecorations[decoration.id] it "contains state for measured block decorations that are not visible when they are on ::mouseWheelScreenRow", -> - blockDecoration1 = addBlockDecorationForScreenRow(0) + 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) @@ -2202,9 +2206,9 @@ describe "TextEditorPresenter", -> expect(stateForBlockDecoration(presenter, blockDecoration1)).toBeUndefined() it "invalidates block decorations that intersect a change in the buffer", -> - blockDecoration1 = addBlockDecorationForScreenRow(9) - blockDecoration2 = addBlockDecorationForScreenRow(10) - blockDecoration3 = addBlockDecorationForScreenRow(11) + blockDecoration1 = addBlockDecorationBeforeScreenRow(9) + blockDecoration2 = addBlockDecorationBeforeScreenRow(10) + blockDecoration3 = addBlockDecorationBeforeScreenRow(11) presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) expectValues stateForBlockDecoration(presenter, blockDecoration1), { @@ -2247,7 +2251,7 @@ describe "TextEditorPresenter", -> } it "invalidates all block decorations when content frame width, window size or bounding client rect change", -> - blockDecoration1 = addBlockDecorationForScreenRow(11) + blockDecoration1 = addBlockDecorationBeforeScreenRow(11) presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0) expectValues stateForBlockDecoration(presenter, blockDecoration1), { @@ -2291,10 +2295,10 @@ describe "TextEditorPresenter", -> it "contains state for on-screen and unmeasured block decorations, both initially and when they are updated or destroyed", -> item = {} - blockDecoration1 = addBlockDecorationForScreenRow(0, item) - blockDecoration2 = addBlockDecorationForScreenRow(4, item) - blockDecoration3 = addBlockDecorationForScreenRow(4, item) - blockDecoration4 = addBlockDecorationForScreenRow(10, 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), { @@ -2387,7 +2391,7 @@ describe "TextEditorPresenter", -> } it "doesn't throw an error when setting the dimensions for a destroyed decoration", -> - blockDecoration = addBlockDecorationForScreenRow(0) + blockDecoration = addBlockDecorationBeforeScreenRow(0) presenter = buildPresenter() blockDecoration.destroy() @@ -2896,16 +2900,18 @@ describe "TextEditorPresenter", -> 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 = addBlockDecorationForScreenRow(0) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) presenter = buildPresenter() - blockDecoration2 = addBlockDecorationForScreenRow(3) - blockDecoration3 = addBlockDecorationForScreenRow(3) - blockDecoration4 = addBlockDecorationForScreenRow(7) + blockDecoration2 = addBlockDecorationBeforeScreenRow(3) + blockDecoration3 = addBlockDecorationBeforeScreenRow(3) + blockDecoration4 = addBlockDecorationBeforeScreenRow(7) + blockDecoration5 = addBlockDecorationAfterScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 10) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 20) presenter.setBlockDecorationDimensions(blockDecoration3, 0, 30) presenter.setBlockDecorationDimensions(blockDecoration4, 0, 40) + presenter.setBlockDecorationDimensions(blockDecoration5, 0, 50) waitsForStateToUpdate presenter runs -> @@ -2917,7 +2923,7 @@ describe "TextEditorPresenter", -> 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) + expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(50) expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0) expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0) expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(0) @@ -2936,7 +2942,7 @@ describe "TextEditorPresenter", -> 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) + expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(50) expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(30) expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0) expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(0) @@ -2954,7 +2960,7 @@ describe "TextEditorPresenter", -> 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) + expect(lineNumberStateForScreenRow(presenter, 8).blockDecorationsHeight).toBe(50) expect(lineNumberStateForScreenRow(presenter, 9).blockDecorationsHeight).toBe(0) expect(lineNumberStateForScreenRow(presenter, 10).blockDecorationsHeight).toBe(0) expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(0) @@ -3190,14 +3196,16 @@ describe "TextEditorPresenter", -> it "updates when block decorations are added, changed or removed", -> # block decoration before decoration1 - blockDecoration1 = addBlockDecorationForScreenRow(0) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 3) # block decoration between decoration1 and decoration2 - blockDecoration2 = addBlockDecorationForScreenRow(3) + blockDecoration2 = addBlockDecorationBeforeScreenRow(3) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 5) # block decoration between decoration2 and decoration3 - blockDecoration3 = addBlockDecorationForScreenRow(10) + 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 @@ -3205,7 +3213,7 @@ describe "TextEditorPresenter", -> 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 + 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() @@ -3215,10 +3223,10 @@ describe "TextEditorPresenter", -> 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 + 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 + 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' @@ -3228,10 +3236,10 @@ describe "TextEditorPresenter", -> 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 + 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 + 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' @@ -3446,9 +3454,9 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expect(getState(presenter).verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 - blockDecoration1 = addBlockDecorationForScreenRow(0) - blockDecoration2 = addBlockDecorationForScreenRow(3) - blockDecoration3 = addBlockDecorationForScreenRow(7) + blockDecoration1 = addBlockDecorationBeforeScreenRow(0) + blockDecoration2 = addBlockDecorationBeforeScreenRow(3) + blockDecoration3 = addBlockDecorationBeforeScreenRow(7) presenter.setBlockDecorationDimensions(blockDecoration1, 0, 35.8) presenter.setBlockDecorationDimensions(blockDecoration2, 0, 50.3) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index d81261ace..470be8342 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -349,8 +349,8 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 - top = Math.round(@lineTopIndex.pixelPositionForRow(tileStartRow - 1) + @lineHeight) - bottom = Math.round(@lineTopIndex.pixelPositionForRow(tileEndRow - 1) + @lineHeight) + top = Math.round(@lineTopIndex.pixelPositionForFirstBlockAtRow(tileStartRow)) + bottom = Math.round(@lineTopIndex.pixelPositionForFirstBlockAtRow(tileEndRow)) height = bottom - top tile = @state.content.tiles[tileStartRow] ?= {} @@ -558,7 +558,7 @@ class TextEditorPresenter continue unless @gutterIsVisible(gutter) for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName] top = @lineTopIndex.pixelPositionForRow(screenRange.start.row) - bottom = @lineTopIndex.pixelPositionForRow(screenRange.end.row) + @lineHeight + bottom = @lineTopIndex.pixelPositionForFirstBlockAtRow(screenRange.end.row + 1) @customGutterDecorations[gutterName][decorationId] = top: top height: bottom - top @@ -609,8 +609,9 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - previousRowBottomPixelPosition = @lineTopIndex.pixelPositionForRow(screenRow - 1) + @lineHeight - blockDecorationsHeight = @lineTopIndex.pixelPositionForRow(screenRow) - previousRowBottomPixelPosition + blockDecorationsAfterPreviousScreenRowHeight = @lineTopIndex.pixelPositionForFirstBlockAtRow(screenRow) - @lineHeight - @lineTopIndex.pixelPositionForRow(screenRow - 1) + blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionForRow(screenRow) - @lineTopIndex.pixelPositionForFirstBlockAtRow(screenRow) + blockDecorationsHeight = blockDecorationsAfterPreviousScreenRowHeight + blockDecorationsBeforeCurrentScreenRowHeight tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight} visibleLineNumberIds[line.id] = true @@ -1199,7 +1200,7 @@ class TextEditorPresenter screenRange.end.column = 0 repositionRegionWithinTile: (region, tileStartRow) -> - region.top += @scrollTop - (@lineTopIndex.pixelPositionForRow(tileStartRow - 1) + @lineHeight) + region.top += @scrollTop - @lineTopIndex.pixelPositionForFirstBlockAtRow(tileStartRow) region.left += @scrollLeft buildHighlightRegions: (screenRange) -> @@ -1308,7 +1309,8 @@ class TextEditorPresenter didDestroyDisposable.dispose() @didDestroyBlockDecoration(decoration) - @lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0) + isAfter = decoration.getProperties().position is "after" + @lineTopIndex.insertBlock(decoration.getId(), decoration.getMarker().getHeadScreenPosition().row, 0, isAfter) @observedBlockDecorations.add(decoration) @invalidateBlockDecorationDimensions(decoration) From 5c5e108d9b4d869901ac4ce11532f827fc08ddb8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Jan 2016 11:38:48 -0700 Subject: [PATCH 154/173] Conform to the new LineTopIndex interface --- spec/fake-lines-yardstick.coffee | 2 +- src/lines-yardstick.coffee | 2 +- src/text-editor-presenter.coffee | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/fake-lines-yardstick.coffee b/spec/fake-lines-yardstick.coffee index f01e6161b..888272295 100644 --- a/spec/fake-lines-yardstick.coffee +++ b/spec/fake-lines-yardstick.coffee @@ -27,7 +27,7 @@ class FakeLinesYardstick targetColumn = screenPosition.column baseCharacterWidth = @model.getDefaultCharWidth() - top = @lineTopIndex.pixelPositionForRow(targetRow) + top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow) left = 0 column = 0 diff --git a/src/lines-yardstick.coffee b/src/lines-yardstick.coffee index 3be276412..88a836c60 100644 --- a/src/lines-yardstick.coffee +++ b/src/lines-yardstick.coffee @@ -84,7 +84,7 @@ class LinesYardstick targetRow = screenPosition.row targetColumn = screenPosition.column - top = @lineTopIndex.pixelPositionForRow(targetRow) + top = @lineTopIndex.pixelPositionAfterBlocksForRow(targetRow) left = @leftPixelPositionForScreenPosition(targetRow, targetColumn) {top, left} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 470be8342..4104e5b01 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -349,8 +349,8 @@ class TextEditorPresenter continue if rowsWithinTile.length is 0 - top = Math.round(@lineTopIndex.pixelPositionForFirstBlockAtRow(tileStartRow)) - bottom = Math.round(@lineTopIndex.pixelPositionForFirstBlockAtRow(tileEndRow)) + top = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow)) + bottom = Math.round(@lineTopIndex.pixelPositionBeforeBlocksForRow(tileEndRow)) height = bottom - top tile = @state.content.tiles[tileStartRow] ?= {} @@ -557,8 +557,8 @@ class TextEditorPresenter continue unless @gutterIsVisible(gutter) for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName] - top = @lineTopIndex.pixelPositionForRow(screenRange.start.row) - bottom = @lineTopIndex.pixelPositionForFirstBlockAtRow(screenRange.end.row + 1) + top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row) + bottom = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRange.end.row + 1) @customGutterDecorations[gutterName][decorationId] = top: top height: bottom - top @@ -609,8 +609,8 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - blockDecorationsAfterPreviousScreenRowHeight = @lineTopIndex.pixelPositionForFirstBlockAtRow(screenRow) - @lineHeight - @lineTopIndex.pixelPositionForRow(screenRow - 1) - blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionForRow(screenRow) - @lineTopIndex.pixelPositionForFirstBlockAtRow(screenRow) + blockDecorationsAfterPreviousScreenRowHeight = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) - @lineHeight - @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow - 1) + blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) blockDecorationsHeight = blockDecorationsAfterPreviousScreenRowHeight + blockDecorationsBeforeCurrentScreenRowHeight tileState.lineNumbers[line.id] = {screenRow, bufferRow, softWrapped, decorationClasses, foldable, blockDecorationsHeight} @@ -664,7 +664,7 @@ class TextEditorPresenter updateVerticalDimensions: -> if @lineHeight? oldContentHeight = @contentHeight - @contentHeight = Math.round(@lineTopIndex.pixelPositionForRow(@model.getScreenLineCount())) + @contentHeight = Math.round(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getScreenLineCount())) if @contentHeight isnt oldContentHeight @updateHeight() @@ -1200,7 +1200,7 @@ class TextEditorPresenter screenRange.end.column = 0 repositionRegionWithinTile: (region, tileStartRow) -> - region.top += @scrollTop - @lineTopIndex.pixelPositionForFirstBlockAtRow(tileStartRow) + region.top += @scrollTop - @lineTopIndex.pixelPositionBeforeBlocksForRow(tileStartRow) region.left += @scrollLeft buildHighlightRegions: (screenRange) -> @@ -1397,7 +1397,7 @@ class TextEditorPresenter @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> - @setScrollTop(@lineTopIndex.pixelPositionForRow(screenRow)) + @setScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(screenRow)) getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) @@ -1418,8 +1418,8 @@ class TextEditorPresenter verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - top = @lineTopIndex.pixelPositionForRow(screenRange.start.row) - bottom = @lineTopIndex.pixelPositionForRow(screenRange.end.row) + @lineHeight + top = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.start.row) + bottom = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRange.end.row) + @lineHeight if options?.center desiredScrollCenter = (top + bottom) / 2 @@ -1491,7 +1491,7 @@ class TextEditorPresenter restoreScrollTopIfNeeded: -> unless @scrollTop? - @updateScrollTop(@lineTopIndex.pixelPositionForRow(@model.getFirstVisibleScreenRow())) + @updateScrollTop(@lineTopIndex.pixelPositionAfterBlocksForRow(@model.getFirstVisibleScreenRow())) restoreScrollLeftIfNeeded: -> unless @scrollLeft? From 4499fa8d6f86f4cc63fdb54f5b2ec6af4f2b0f6e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Jan 2016 12:02:52 -0700 Subject: [PATCH 155/173] Split following and preceding block decorations In TextEditorPresenter's state for lines. --- spec/text-editor-presenter-spec.coffee | 161 +++++++++++++++++-------- src/text-editor-presenter.coffee | 22 ++-- 2 files changed, 122 insertions(+), 61 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 252e6031a..ca03757ec 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1331,85 +1331,140 @@ describe "TextEditorPresenter", -> 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 a line, both initially and when decorations change", -> + 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).blockDecorations).toEqual([blockDecoration1]) - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([blockDecoration2]) - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([blockDecoration3]) - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) + 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).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([blockDecoration1]) - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([blockDecoration2, blockDecoration3]) - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) + 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).blockDecorations).toEqual([blockDecoration1]) - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([blockDecoration2]) - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) + 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).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 1).blockDecorations).toEqual([blockDecoration1]) - expect(lineStateForScreenRow(presenter, 2).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 3).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 4).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 5).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 6).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 7).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 8).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 9).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 10).blockDecorations).toEqual([blockDecoration2]) - expect(lineStateForScreenRow(presenter, 11).blockDecorations).toEqual([]) - expect(lineStateForScreenRow(presenter, 12).blockDecorations).toEqual([]) + 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", -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 4104e5b01..2bf1f1992 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -396,13 +396,14 @@ class TextEditorPresenter throw new Error("No line exists for row #{screenRow}. Last screen row: #{@model.getLastScreenRow()}") visibleLineIds[line.id] = true - blockDecorations = @blockDecorationsByScreenRow[screenRow] ? [] + 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.blockDecorations = blockDecorations - lineState.hasBlockDecorations = blockDecorations.length > 0 + lineState.precedingBlockDecorations = precedingBlockDecorations + lineState.followingBlockDecorations = followingBlockDecorations else tileState.lines[line.id] = screenRow: screenRow @@ -419,8 +420,8 @@ class TextEditorPresenter tabLength: line.tabLength fold: line.fold decorationClasses: @lineDecorationClassesForRow(screenRow) - blockDecorations: blockDecorations - hasBlockDecorations: blockDecorations.length > 0 + precedingBlockDecorations: precedingBlockDecorations + followingBlockDecorations: followingBlockDecorations for id, line of tileState.lines delete tileState.lines[id] unless visibleLineIds.hasOwnProperty(id) @@ -1048,7 +1049,8 @@ class TextEditorPresenter updateBlockDecorations: -> @blockDecorationsToRenderById = {} - @blockDecorationsByScreenRow = {} + @precedingBlockDecorationsByScreenRow = {} + @followingBlockDecorationsByScreenRow = {} visibleDecorationsByMarkerId = @model.decorationsForScreenRowRange(@getStartTileRow(), @getEndTileRow() + @tileSize - 1) if @invalidateAllBlockDecorationsDimensions @@ -1073,8 +1075,12 @@ class TextEditorPresenter return if @blockDecorationsToRenderById[decoration.getId()] screenRow = decoration.getMarker().getHeadScreenPosition().row - @blockDecorationsByScreenRow[screenRow] ?= [] - @blockDecorationsByScreenRow[screenRow].push(decoration) + 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 From 6e5c3e0212e24bf1e6f9504a5bc2835741b0675c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Jan 2016 12:51:29 -0700 Subject: [PATCH 156/173] Insert following block decorations into the DOM --- spec/text-editor-component-spec.js | 43 ++++++++++----- src/lines-tile-component.coffee | 88 ++++++++++++++++++++++-------- src/text-editor-presenter.coffee | 4 ++ 3 files changed, 99 insertions(+), 36 deletions(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 7717ea55d..1477f9e6d 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1652,12 +1652,22 @@ describe('TextEditorComponent', function () { }) describe('block decorations rendering', function () { - function createBlockDecorationForScreenRowWith(screenRow, {className}) { + 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} + {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] } @@ -1673,16 +1683,18 @@ describe('TextEditorComponent', function () { }) 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] = createBlockDecorationForScreenRowWith(0, {className: "decoration-1"}) - let [item2, blockDecoration2] = createBlockDecorationForScreenRowWith(2, {className: "decoration-2"}) - let [item3, blockDecoration3] = createBlockDecorationForScreenRowWith(4, {className: "decoration-3"}) - let [item4, blockDecoration4] = createBlockDecorationForScreenRowWith(7, {className: "decoration-4"}) + 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-4 { width: 30px; height: 120px; } + atom-text-editor .decoration-5 { width: 30px; height: 42px; }`, {context: 'atom-text-editor'} ) await nextAnimationFramePromise() @@ -1693,13 +1705,14 @@ describe('TextEditorComponent', function () { 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 + "px") + 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) @@ -1717,13 +1730,14 @@ describe('TextEditorComponent', function () { 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 + "px") + 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) @@ -1742,13 +1756,14 @@ describe('TextEditorComponent', function () { 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 + "px") + 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) @@ -1764,21 +1779,23 @@ describe('TextEditorComponent', function () { 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 + "px") + 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 elements, both initially and when decorations move", async function () { - let [item, blockDecoration] = createBlockDecorationForScreenRowWith(0, {className: "decoration-1"}) + let [item, blockDecoration] = createBlockDecorationBeforeScreenRow(0, {className: "decoration-1"}) atom.styles.addStyleSheet( 'atom-text-editor .decoration-1 { width: 30px; height: 80px; }', {context: 'atom-text-editor'} @@ -1824,7 +1841,7 @@ describe('TextEditorComponent', function () { }) it('measures block decorations taking into account both top and bottom margins', async function () { - let [item, blockDecoration] = createBlockDecorationForScreenRowWith(0, {className: "decoration-1"}) + 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'} diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index 24c05dc2e..defcc0d8a 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -20,7 +20,8 @@ class LinesTileComponent @screenRowsByLineId = {} @lineIdsByScreenRow = {} @textNodesByLineId = {} - @insertionPointsByLineId = {} + @insertionPointsBeforeLineById = {} + @insertionPointsAfterLineById = {} @domNode = @domElementPool.buildElement("div") @domNode.style.position = "absolute" @domNode.style.display = "block" @@ -81,7 +82,8 @@ class LinesTileComponent removeLineNode: (id) -> @domElementPool.freeElementAndDescendants(@lineNodesByLineId[id]) - @removeBlockDecorationInsertionPoint(id) + @removeBlockDecorationInsertionPointBeforeLine(id) + @removeBlockDecorationInsertionPointAfterLine(id) delete @lineNodesByLineId[id] delete @textNodesByLineId[id] @@ -119,37 +121,70 @@ class LinesTileComponent else @domNode.appendChild(lineNode) - @insertBlockDecorationInsertionPoint(id) + @insertBlockDecorationInsertionPointBeforeLine(id) + @insertBlockDecorationInsertionPointAfterLine(id) - removeBlockDecorationInsertionPoint: (id) -> - if insertionPoint = @insertionPointsByLineId[id] + removeBlockDecorationInsertionPointBeforeLine: (id) -> + if insertionPoint = @insertionPointsBeforeLineById[id] @domElementPool.freeElementAndDescendants(insertionPoint) - delete @insertionPointsByLineId[id] + delete @insertionPointsBeforeLineById[id] - insertBlockDecorationInsertionPoint: (id) -> - {hasBlockDecorations, screenRow} = @newTileState.lines[id] + insertBlockDecorationInsertionPointBeforeLine: (id) -> + {hasPrecedingBlockDecorations, screenRow} = @newTileState.lines[id] - if hasBlockDecorations + if hasPrecedingBlockDecorations lineNode = @lineNodesByLineId[id] insertionPoint = @domElementPool.buildElement("content") @domNode.insertBefore(insertionPoint, lineNode) - @insertionPointsByLineId[id] = insertionPoint + @insertionPointsBeforeLineById[id] = insertionPoint insertionPoint.dataset.screenRow = screenRow - @updateBlockDecorationInsertionPoint(id) + @updateBlockDecorationInsertionPointBeforeLine(id) - updateBlockDecorationInsertionPoint: (id) -> + updateBlockDecorationInsertionPointBeforeLine: (id) -> oldLineState = @oldTileState.lines[id] newLineState = @newTileState.lines[id] - insertionPoint = @insertionPointsByLineId[id] + insertionPoint = @insertionPointsBeforeLineById[id] return unless insertionPoint? if newLineState.screenRow isnt oldLineState.screenRow insertionPoint.dataset.screenRow = newLineState.screenRow - blockDecorationsSelector = newLineState.blockDecorations.map((d) -> "#atom--block-decoration-#{d.id}").join(',') - if blockDecorationsSelector isnt oldLineState.blockDecorationsSelector - insertionPoint.setAttribute("select", blockDecorationsSelector) - oldLineState.blockDecorationsSelector = blockDecorationsSelector + 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 @@ -371,20 +406,27 @@ class LinesTileComponent oldLineState.decorationClasses = newLineState.decorationClasses - if not oldLineState.hasBlockDecorations and newLineState.hasBlockDecorations - @insertBlockDecorationInsertionPoint(id) - else if oldLineState.hasBlockDecorations and not newLineState.hasBlockDecorations - @removeBlockDecorationInsertionPoint(id) + 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 @lineIdsByScreenRow[newLineState.screenRow] = id @screenRowsByLineId[id] = newLineState.screenRow - @updateBlockDecorationInsertionPoint(id) + @updateBlockDecorationInsertionPointBeforeLine(id) + @updateBlockDecorationInsertionPointAfterLine(id) oldLineState.screenRow = newLineState.screenRow - oldLineState.hasBlockDecorations = newLineState.hasBlockDecorations + oldLineState.hasPrecedingBlockDecorations = newLineState.hasPrecedingBlockDecorations + oldLineState.hasFollowingBlockDecorations = newLineState.hasFollowingBlockDecorations lineNodeForScreenRow: (screenRow) -> @lineNodesByLineId[@lineIdsByScreenRow[screenRow]] diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2bf1f1992..5085ea15f 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -404,6 +404,8 @@ class TextEditorPresenter 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 @@ -422,6 +424,8 @@ class TextEditorPresenter 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) From bc69e1624b2551d805083fcb86de8c6b08714b50 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 12 Jan 2016 15:41:26 -0800 Subject: [PATCH 157/173] Don't rely on compile cache in babel compilation specs --- spec/babel-spec.coffee | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index caaaed9f2..e913a7939 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -1,4 +1,21 @@ +path = require('path') +temp = require('temp').track() +CompileCache = require('../src/compile-cache') + describe "Babel transpiler support", -> + originalCacheDir = null + + beforeEach -> + originalCacheDir = CompileCache.getCacheDirectory() + CompileCache.setCacheDirectory(temp.mkdirSync('compile-cache')) + for cacheKey in Object.keys(require.cache) + if cacheKey.startsWith(path.join(__dirname, 'fixtures', 'babel')) + console.log('deleting', cacheKey) + delete require.cache[cacheKey] + + afterEach -> + CompileCache.setCacheDirectory(originalCacheDir) + describe 'when a .js file starts with /** @babel */;', -> it "transpiles it using babel", -> transpiled = require('./fixtures/babel/babel-comment.js') From 680e123f880368bf91d8c0b3c85e79348de33b58 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 12 Jan 2016 15:42:44 -0800 Subject: [PATCH 158/173] Prevent babel from logging to stderr --- spec/babel-spec.coffee | 14 ++++++++++++++ src/babel.js | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/spec/babel-spec.coffee b/spec/babel-spec.coffee index e913a7939..3c4a4fe4b 100644 --- a/spec/babel-spec.coffee +++ b/spec/babel-spec.coffee @@ -1,3 +1,8 @@ +# Users may have this environment variable set. Currently, it causes babel to +# log to stderr, which causes errors on Windows. +# See https://github.com/atom/electron/issues/2033 +process.env.DEBUG='*' + path = require('path') temp = require('temp').track() CompileCache = require('../src/compile-cache') @@ -34,3 +39,12 @@ describe "Babel transpiler support", -> describe "when a .js file does not start with 'use babel';", -> it "does not transpile it using babel", -> expect(-> require('./fixtures/babel/invalid.js')).toThrow() + + it "does not try to log to stdout or stderr while parsing the file", -> + spyOn(process.stderr, 'write') + spyOn(process.stdout, 'write') + + transpiled = require('./fixtures/babel/babel-double-quotes.js') + + expect(process.stdout.write).not.toHaveBeenCalled() + expect(process.stderr.write).not.toHaveBeenCalled() diff --git a/src/babel.js b/src/babel.js index f53dbc758..1f450ff96 100644 --- a/src/babel.js +++ b/src/babel.js @@ -42,6 +42,10 @@ exports.getCachePath = function (sourceCode) { exports.compile = function (sourceCode, filePath) { if (!babel) { babel = require('babel-core') + var Logger = require('babel-core/lib/transformation/file/logger') + var noop = function () {} + Logger.prototype.debug = noop + Logger.prototype.verbose = noop } var options = {filename: filePath} From 1f0e6a09024adc8615c339bdfff9d77c25286535 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Jan 2016 09:21:31 -0700 Subject: [PATCH 159/173] Don't calculate height for following decorations at beginning of tiles --- spec/text-editor-presenter-spec.coffee | 17 ++++++++++------- src/text-editor-presenter.coffee | 6 ++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index ca03757ec..05b0e2708 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2956,17 +2956,20 @@ describe "TextEditorPresenter", -> 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() + 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 -> @@ -2978,10 +2981,10 @@ describe "TextEditorPresenter", -> 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(50) + 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(0) + expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60) waitsForStateToUpdate presenter, -> blockDecoration1.getMarker().setHeadBufferPosition([1, 0]) @@ -2997,10 +3000,10 @@ describe "TextEditorPresenter", -> 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(50) + 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(0) + expect(lineNumberStateForScreenRow(presenter, 11).blockDecorationsHeight).toBe(60) waitsForStateToUpdate presenter, -> blockDecoration1.destroy() @@ -3015,10 +3018,10 @@ describe "TextEditorPresenter", -> 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(50) + 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(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", -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5085ea15f..e3078790e 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -614,9 +614,11 @@ class TextEditorPresenter line = @model.tokenizedLineForScreenRow(screenRow) decorationClasses = @lineNumberDecorationClassesForRow(screenRow) foldable = @model.isFoldableAtScreenRow(screenRow) - blockDecorationsAfterPreviousScreenRowHeight = @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) - @lineHeight - @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow - 1) blockDecorationsBeforeCurrentScreenRowHeight = @lineTopIndex.pixelPositionAfterBlocksForRow(screenRow) - @lineTopIndex.pixelPositionBeforeBlocksForRow(screenRow) - blockDecorationsHeight = blockDecorationsAfterPreviousScreenRowHeight + blockDecorationsBeforeCurrentScreenRowHeight + 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, blockDecorationsHeight} visibleLineNumberIds[line.id] = true From d370164624fd72213953ea702e78466008c089ab Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Jan 2016 09:31:17 -0700 Subject: [PATCH 160/173] Slightly improve algorithm to calculate @endRow --- src/text-editor-presenter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index e3078790e..aad660d57 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -638,7 +638,7 @@ class TextEditorPresenter @endRow = Math.min( @model.getScreenLineCount(), - @lineTopIndex.rowForPixelPosition(@scrollTop + @height + (2 * @lineHeight - 1)) + @lineTopIndex.rowForPixelPosition(@scrollTop + @height + @lineHeight - 1) + 1 ) updateRowsPerPage: -> From 904a8fae8f9d0245e0dec1b44c878ff55817e126 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Jan 2016 09:33:14 -0700 Subject: [PATCH 161/173] :arrow_up: line-top-index --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3db04d4c2..35a0bcb2d 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "jquery": "^2.1.1", "key-path-helpers": "^0.4.0", "less-cache": "0.22", - "line-top-index": "0.1.1", + "line-top-index": "0.2.0", "marked": "^0.3.4", "normalize-package-data": "^2.0.0", "nslog": "^3", From 2af5d395dda2b29bead39c946ffdb1494cf942cb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Jan 2016 09:38:28 -0700 Subject: [PATCH 162/173] :memo: Include info for preceding/following block decorations --- src/text-editor.coffee | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 325e4a304..a3cb1c812 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1423,8 +1423,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__: A decoration that lies between the {TextEditorMarker} row and - # the previous one. + # * __block__: Positions the view associated with the given item before or + # after the row of the given `TextEditorMarker`. # # ## Arguments # @@ -1444,6 +1444,9 @@ 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 @@ -1458,9 +1461,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) -> From e6a1763f8c40d45dc82eec6e8f22970782e50551 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Jan 2016 09:45:14 -0700 Subject: [PATCH 163/173] :green_heart: Give specs a final touch :sparkles: --- spec/text-editor-component-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.js b/spec/text-editor-component-spec.js index 1477f9e6d..c85e8818e 100644 --- a/spec/text-editor-component-spec.js +++ b/spec/text-editor-component-spec.js @@ -1673,7 +1673,7 @@ describe('TextEditorComponent', function () { } beforeEach(async function () { - wrapperNode.style.height = 9 * lineHeightInPixels + 'px' + wrapperNode.style.height = 5 * lineHeightInPixels + 'px' component.measureDimensions() await nextViewUpdatePromise() }) From beb1456f69f2b52df84c012e2f8dacf1a35943ba Mon Sep 17 00:00:00 2001 From: joshaber Date: Wed, 13 Jan 2016 13:30:03 -0500 Subject: [PATCH 164/173] :arrow_up: about@1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af0940ead..98b98b8c4 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "one-light-syntax": "1.1.2", "solarized-dark-syntax": "0.39.0", "solarized-light-syntax": "0.23.0", - "about": "1.2.0", + "about": "1.3.0", "archive-view": "0.61.0", "autocomplete-atom-api": "0.9.2", "autocomplete-css": "0.11.0", From d229fed7e73c0f89631ed3b6c2cf16497d0ccbab Mon Sep 17 00:00:00 2001 From: joshaber Date: Wed, 13 Jan 2016 15:11:55 -0500 Subject: [PATCH 165/173] Merge pull request #10377 from atom/remove-release-notes Remove release-notes --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 93632da54..eee53b978 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "one-light-ui": "1.1.8", "solarized-dark-syntax": "0.39.0", "solarized-light-syntax": "0.23.0", - "about": "1.1.0", + "about": "1.3.0", "archive-view": "0.61.0", "autocomplete-atom-api": "0.9.2", "autocomplete-css": "0.11.0", @@ -102,7 +102,6 @@ "notifications": "0.62.1", "open-on-github": "0.40.0", "package-generator": "0.41.0", - "release-notes": "0.53.0", "settings-view": "0.232.1", "snippets": "1.0.1", "spell-check": "0.63.0", From 6b48c9d8ecf5bac3e47d38af43edd70b9b5a04ff Mon Sep 17 00:00:00 2001 From: joshaber Date: Wed, 13 Jan 2016 16:29:24 -0500 Subject: [PATCH 166/173] 1.6.0-dev --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb973f94d..ac5a90b78 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.5.0-dev", + "version": "1.6.0-dev", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From de44221087081d640e7096ffe8992cd90aa22016 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 13 Jan 2016 17:56:46 -0800 Subject: [PATCH 167/173] :arrow_up: language-gfm@0.83 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac5a90b78..0b2645d4a 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "language-coffee-script": "0.46.0", "language-csharp": "0.11.0", "language-css": "0.36.0", - "language-gfm": "0.82.0", + "language-gfm": "0.83.0", "language-git": "0.11.0", "language-go": "0.42.0", "language-html": "0.44.0", From 39e9075e018cadebc1f223362410893f639de4d0 Mon Sep 17 00:00:00 2001 From: Wliu Date: Thu, 14 Jan 2016 14:31:09 +0000 Subject: [PATCH 168/173] Don't cascade maximized windows Fixes #10335 --- src/browser/atom-application.coffee | 1 + src/browser/atom-window.coffee | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 3096fb7c0..45fae2387 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -369,6 +369,7 @@ class AtomApplication # Get the dimensions for opening a new window by cascading as appropriate to # the platform. getDimensionsForNewWindow: -> + return if (@focusedWindow() ? @lastFocusedWindow)?.isMaximized() and process.platform isnt 'darwin' dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions() offset = @getWindowOffsetForCurrentPlatform() if dimensions? and offset? diff --git a/src/browser/atom-window.coffee b/src/browser/atom-window.coffee index c507b634c..6e2d39266 100644 --- a/src/browser/atom-window.coffee +++ b/src/browser/atom-window.coffee @@ -212,6 +212,8 @@ class AtomWindow isFocused: -> @browserWindow.isFocused() + isMaximized: -> @browserWindow.isMaximized() + isMinimized: -> @browserWindow.isMinimized() isWebViewFocused: -> @browserWindow.isWebViewFocused() From 8c767a82d6d8cfd3f98949cb2ff62c0fd5d2863a Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 14 Jan 2016 11:01:59 -0500 Subject: [PATCH 169/173] Remove OS X conditional logic --- src/browser/atom-application.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 45fae2387..44848eb72 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -369,7 +369,7 @@ class AtomApplication # Get the dimensions for opening a new window by cascading as appropriate to # the platform. getDimensionsForNewWindow: -> - return if (@focusedWindow() ? @lastFocusedWindow)?.isMaximized() and process.platform isnt 'darwin' + return if (@focusedWindow() ? @lastFocusedWindow)?.isMaximized() dimensions = (@focusedWindow() ? @lastFocusedWindow)?.getDimensions() offset = @getWindowOffsetForCurrentPlatform() if dimensions? and offset? From 4f6ff440e46e3ee3ec13108c7183f8d74e16efc1 Mon Sep 17 00:00:00 2001 From: joshaber Date: Thu, 14 Jan 2016 11:31:54 -0500 Subject: [PATCH 170/173] :arrow_up: language-git@0.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b2645d4a..9ece819e5 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "language-csharp": "0.11.0", "language-css": "0.36.0", "language-gfm": "0.83.0", - "language-git": "0.11.0", + "language-git": "0.12.0", "language-go": "0.42.0", "language-html": "0.44.0", "language-hyperlink": "0.16.0", From b46c72a8c5c902c262773fb7871c7ecc99a36c37 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Thu, 14 Jan 2016 12:15:22 -0500 Subject: [PATCH 171/173] :arrow_up: language-git@0.12.1 Fixes specs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ece819e5..ebd7a1b5d 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "language-csharp": "0.11.0", "language-css": "0.36.0", "language-gfm": "0.83.0", - "language-git": "0.12.0", + "language-git": "0.12.1", "language-go": "0.42.0", "language-html": "0.44.0", "language-hyperlink": "0.16.0", From a812f282a280aac86a575aa7e3f6b6f4661f36a3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 Jan 2016 11:26:18 -0700 Subject: [PATCH 172/173] Measure decorations only for visible editors --- src/text-editor-component.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 1576de4bf..4a51badbd 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -187,7 +187,7 @@ class TextEditorComponent readAfterUpdateSync: => @overlayManager?.measureOverlays() - @blockDecorationsComponent?.measureBlockDecorations() + @blockDecorationsComponent?.measureBlockDecorations() if @isVisible() mountGutterContainerComponent: -> @gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views}) From da14788e7333f6e539ce6d3926490baa5ac35b7e Mon Sep 17 00:00:00 2001 From: Lee Dohm Date: Thu, 14 Jan 2016 14:26:02 -0800 Subject: [PATCH 173/173] :arrow_up: bookmarks@0.38.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d916434bc..bafa45f18 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "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",