From b5a59017d51b7500cd1acac0ba708f621cee2250 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 16:20:15 +0200 Subject: [PATCH 01/58] wip --- spec/text-editor-presenter-spec.coffee | 8 ++- src/display-buffer.coffee | 38 +++++++++++ src/text-editor-presenter.coffee | 91 +++++++++++++++++++++++--- src/text-editor.coffee | 3 + 4 files changed, 129 insertions(+), 11 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 76589416e..ca4ab4ba4 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' -describe "TextEditorPresenter", -> +fdescribe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. # Please maintain this structure when adding specs for new state fields. describe "::getState()", -> @@ -483,7 +483,7 @@ describe "TextEditorPresenter", -> describe ".hiddenInput", -> describe ".top/.left", -> - it "is positioned over the last cursor it is in view and the editor is focused", -> + fffit "is positioned over the last cursor it is in view and the editor is focused", -> editor.setCursorBufferPosition([3, 6]) presenter = buildPresenter(focused: false, explicitHeight: 50, contentFrameWidth: 300, horizontalScrollbarHeight: 0, verticalScrollbarWidth: 0) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} @@ -503,8 +503,10 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollLeft(70) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} + global.enableLogs = true expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43]) expectValues presenter.getState().hiddenInput, {top: 11 * 10 - editor.getScrollTop(), left: 43 * 10 - editor.getScrollLeft()} + global.enableLogs = false newCursor = null expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10]) @@ -1821,7 +1823,7 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.insertNewline() - editor.setScrollTop(scrollTop) # I'm fighting the editor + presenter.setScrollTop(scrollTop) # I'm fighting the editor expectValues stateForOverlay(presenter, decoration), { item: item pixelPosition: {top: 6 * 10 - scrollTop - itemHeight, left: gutterWidth} diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 5d5628689..94831eac1 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -132,6 +132,9 @@ class DisplayBuffer extends Model onDidChangeScrollLeft: (callback) -> @emitter.on 'did-change-scroll-left', callback + onDidChangeScrollPosition: (callback) -> + @emitter.on 'did-change-scroll-position', callback + observeScrollTop: (callback) -> callback(@scrollTop) @onDidChangeScrollTop(callback) @@ -348,6 +351,10 @@ class DisplayBuffer extends Model [startRow, endRow] + getLogicalHeight: -> + [startRow, endRow] = @getVisibleRowRange() + endRow - startRow + intersectsVisibleRowRange: (startRow, endRow) -> [visibleStart, visibleEnd] = @getVisibleRowRange() not (endRow <= visibleStart or visibleEnd <= startRow) @@ -356,7 +363,31 @@ class DisplayBuffer extends Model {start, end} = selection.getScreenRange() @intersectsVisibleRowRange(start.row, end.row + 1) + scrollToScreenRangeLogical: (screenRange, options) -> + top = screenRange.start.row + left = screenRange.start.column + bottom = screenRange.end.row + right = screenRange.end.column + + if options?.center + center = (top + bottom) / 2 + top = center - @getLogicalHeight() / 2 + bottom = center + @getLogicalHeight() / 2 + else + top -= @getVerticalScrollMargin() + bottom += @getVerticalScrollMargin() + + left -= @getHorizontalScrollMargin() + right += @getHorizontalScrollMargin() + + screenRange = new Range(new Point(top, left), new Point(bottom, right)) + + scrollEvent = {screenRange, options} + @emitter.emit "did-change-scroll-position", scrollEvent + scrollToScreenRange: (screenRange, options) -> + @scrollToScreenRangeLogical(screenRange, options) + verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() @@ -377,6 +408,13 @@ class DisplayBuffer extends Model desiredScrollLeft = left - horizontalScrollMarginInPixels desiredScrollRight = right + horizontalScrollMarginInPixels + if global.enableLogs + console.log "====== DB ======" + console.log "Client Height: #{@getClientHeight()}" + console.log "#{desiredScrollTop}/#{desiredScrollBottom}" + console.log "#{@getScrollTop()}/#{@getScrollBottom()}" + console.log "================" + if options?.reversed ? true if desiredScrollBottom > @getScrollBottom() @setScrollBottom(desiredScrollBottom) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index e6a9043c5..91450ca57 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -71,6 +71,7 @@ class TextEditorPresenter @updateContentDimensions() @updateScrollbarDimensions() + @updateScrollPosition() @updateStartRow() @updateEndRow() @updateCommonGutterState() @@ -115,8 +116,6 @@ class TextEditorPresenter observeModel: -> @disposables.add @model.onDidChange => - @updateContentDimensions() - @shouldUpdateHeightState = true @shouldUpdateVerticalScrollState = true @shouldUpdateHorizontalScrollState = true @@ -162,8 +161,9 @@ class TextEditorPresenter @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) - @disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this)) - @disposables.add @model.onDidChangeScrollLeft(@setScrollLeft.bind(this)) + @disposables.add @model.onDidChangeScrollPosition(@didChangeScrollPosition.bind(this)) + # @disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this)) + # @disposables.add @model.onDidChangeScrollLeft(@setScrollLeft.bind(this)) @observeDecoration(decoration) for decoration in @model.getDecorations() @observeCursor(cursor) for cursor in @model.getCursors() @disposables.add @model.onDidAddGutter(@didAddGutter.bind(this)) @@ -813,7 +813,7 @@ class TextEditorPresenter unless @scrollTop is scrollTop or Number.isNaN(scrollTop) @scrollTop = scrollTop - @model.setScrollTop(scrollTop) + @model.setScrollTop(@scrollTop) @didStartScrolling() @shouldUpdateVerticalScrollState = true @shouldUpdateHiddenInputState = true @@ -852,7 +852,7 @@ class TextEditorPresenter unless @scrollLeft is scrollLeft or Number.isNaN(scrollLeft) oldScrollLeft = @scrollLeft @scrollLeft = scrollLeft - @model.setScrollLeft(scrollLeft) + @model.setScrollLeft(@scrollLeft) @shouldUpdateHorizontalScrollState = true @shouldUpdateHiddenInputState = true @shouldUpdateCursorsState = true @@ -865,11 +865,29 @@ class TextEditorPresenter getScrollLeft: -> @scrollLeft + getClientHeight: -> + if @clientHeight + @clientHeight + else + @explicitHeight - @horizontalScrollbarHeight + + getClientWidth: -> + @clientWidth ? @contentFrameWidth + + getScrollBottom: -> @getScrollTop() + @getClientHeight() + setScrollBottom: (scrollBottom) -> + @setScrollTop(scrollBottom - @getClientHeight()) + @getScrollBottom() + + getScrollRight: -> @getScrollLeft() + @getClientWidth() + setScrollRight: (scrollRight) -> + @setScrollLeft(scrollRight - @getClientWidth()) + @getScrollRight() + setHorizontalScrollbarHeight: (horizontalScrollbarHeight) -> unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight - @model.setHorizontalScrollbarHeight(horizontalScrollbarHeight) @shouldUpdateScrollbarsState = true @shouldUpdateVerticalScrollState = true @shouldUpdateHorizontalScrollState = true @@ -881,7 +899,6 @@ class TextEditorPresenter unless @measuredVerticalScrollbarWidth is verticalScrollbarWidth oldVerticalScrollbarWidth = @measuredVerticalScrollbarWidth @measuredVerticalScrollbarWidth = verticalScrollbarWidth - @model.setVerticalScrollbarWidth(verticalScrollbarWidth) @shouldUpdateScrollbarsState = true @shouldUpdateVerticalScrollState = true @shouldUpdateHorizontalScrollState = true @@ -1438,3 +1455,61 @@ class TextEditorPresenter @startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay()) @startBlinkingCursorsAfterDelay() @emitDidUpdateState() + + didChangeScrollPosition: (position) -> + @pendingScrollLogicalPosition = position + + @shouldUpdateCursorsState = true + @shouldUpdateCustomGutterDecorationState = true + @shouldUpdateDecorations = true + @shouldUpdateHiddenInputState = true + @shouldUpdateHorizontalScrollState = true + @shouldUpdateLinesState = true + @shouldUpdateLineNumbersState = true + @shouldUpdateOverlaysState = true + @shouldUpdateScrollPosition = true + @shouldUpdateVerticalScrollState = true + + @emitDidUpdateState() + + updateScrollPosition: -> + return unless @pendingScrollLogicalPosition? + + {screenRange, options} = @pendingScrollLogicalPosition + + console.log screenRange.start.toString() + console.log screenRange.end.toString() + {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) + {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) + bottom = endTop + endHeight + right = endLeft + + if global.enableLogs + console.log "====== presenter ======" + console.log "Client Height: #{@getClientHeight()}" + console.log "#{desiredScrollTop}/#{desiredScrollBottom}" + console.log "#{@getScrollTop()}/#{@getScrollBottom()}" + console.log "=======================" + + if options?.reversed ? true + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) + + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) + else + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) + + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) + + @pendingScrollLogicalPosition = null diff --git a/src/text-editor.coffee b/src/text-editor.coffee index edd35ae93..9f3e230fb 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -464,6 +464,9 @@ class TextEditor extends Model onDidChangeScrollLeft: (callback) -> @emitter.on 'did-change-scroll-left', callback + onDidChangeScrollPosition: (callback) -> + @displayBuffer.onDidChangeScrollPosition(callback) + # TODO Remove once the tabs package no longer uses .on subscriptions onDidChangeIcon: (callback) -> @emitter.on 'did-change-icon', callback From 68e2d7e7e4012c01d0576ab401eaf63cd73f4513 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 17:30:51 +0200 Subject: [PATCH 02/58] wip --- spec/text-editor-presenter-spec.coffee | 2 +- src/display-buffer.coffee | 5 +++ src/text-editor-presenter.coffee | 42 ++++++++++++++------------ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index ca4ab4ba4..43dc6ef3c 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -483,7 +483,7 @@ fdescribe "TextEditorPresenter", -> describe ".hiddenInput", -> describe ".top/.left", -> - fffit "is positioned over the last cursor it is in view and the editor is focused", -> + it "is positioned over the last cursor it is in view and the editor is focused", -> editor.setCursorBufferPosition([3, 6]) presenter = buildPresenter(focused: false, explicitHeight: 50, contentFrameWidth: 300, horizontalScrollbarHeight: 0, verticalScrollbarWidth: 0) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 94831eac1..ea3bb76f8 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -380,6 +380,11 @@ class DisplayBuffer extends Model left -= @getHorizontalScrollMargin() right += @getHorizontalScrollMargin() + top = Math.max(0, Math.min(@getLineCount() - 1, top)) + bottom = Math.max(0, Math.min(@getLineCount() - 1, bottom)) + left = Math.max(0, left) + right = Math.max(0, right) + screenRange = new Range(new Point(top, left), new Point(bottom, right)) scrollEvent = {screenRange, options} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 91450ca57..ae9584204 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1477,39 +1477,43 @@ class TextEditorPresenter {screenRange, options} = @pendingScrollLogicalPosition - console.log screenRange.start.toString() - console.log screenRange.end.toString() + # console.log screenRange.start.toString() {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) bottom = endTop + endHeight right = endLeft + left += @scrollLeft + right += @scrollLeft + top += @scrollTop + bottom += @scrollTop + if global.enableLogs console.log "====== presenter ======" console.log "Client Height: #{@getClientHeight()}" - console.log "#{desiredScrollTop}/#{desiredScrollBottom}" + console.log "#{top}/#{bottom}" console.log "#{@getScrollTop()}/#{@getScrollBottom()}" console.log "=======================" if options?.reversed ? true - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) + if bottom > @getScrollBottom() + @setScrollBottom(bottom) + if top < @getScrollTop() + @setScrollTop(top) - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) + if right > @getScrollRight() + @setScrollRight(right) + if left < @getScrollLeft() + @setScrollLeft(left) else - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) + if top < @getScrollTop() + @setScrollTop(top) + if bottom > @getScrollBottom() + @setScrollBottom(bottom) - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) + if left < @getScrollLeft() + @setScrollLeft(left) + if right > @getScrollRight() + @setScrollRight(right) @pendingScrollLogicalPosition = null From 6b5ab3e4c9aed7ba69224388b12c7557258cdcce Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 17:31:43 +0200 Subject: [PATCH 03/58] Remove logs --- src/text-editor-presenter.coffee | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ae9584204..704980e05 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1477,7 +1477,6 @@ class TextEditorPresenter {screenRange, options} = @pendingScrollLogicalPosition - # console.log screenRange.start.toString() {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) bottom = endTop + endHeight @@ -1488,13 +1487,6 @@ class TextEditorPresenter top += @scrollTop bottom += @scrollTop - if global.enableLogs - console.log "====== presenter ======" - console.log "Client Height: #{@getClientHeight()}" - console.log "#{top}/#{bottom}" - console.log "#{@getScrollTop()}/#{@getScrollBottom()}" - console.log "=======================" - if options?.reversed ? true if bottom > @getScrollBottom() @setScrollBottom(bottom) From 2588e9779ebb7824f14bda81b26e94f706f92282 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 18:13:41 +0200 Subject: [PATCH 04/58] Avoid relying on ::scrollTop to calculate logical height --- src/display-buffer.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index ea3bb76f8..c25abf436 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -352,8 +352,7 @@ class DisplayBuffer extends Model [startRow, endRow] getLogicalHeight: -> - [startRow, endRow] = @getVisibleRowRange() - endRow - startRow + Math.floor(@getHeight() / @getLineHeightInPixels()) intersectsVisibleRowRange: (startRow, endRow) -> [visibleStart, visibleEnd] = @getVisibleRowRange() From e3dabd5200afea5074fb63003951ef677539413e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 18:33:31 +0200 Subject: [PATCH 05/58] Move scroll margin into TextEditorPresenter --- src/display-buffer.coffee | 34 +++-------------- src/text-editor-presenter.coffee | 65 +++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index c25abf436..509cb4f88 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -362,35 +362,12 @@ class DisplayBuffer extends Model {start, end} = selection.getScreenRange() @intersectsVisibleRowRange(start.row, end.row + 1) - scrollToScreenRangeLogical: (screenRange, options) -> - top = screenRange.start.row - left = screenRange.start.column - bottom = screenRange.end.row - right = screenRange.end.column - - if options?.center - center = (top + bottom) / 2 - top = center - @getLogicalHeight() / 2 - bottom = center + @getLogicalHeight() / 2 - else - top -= @getVerticalScrollMargin() - bottom += @getVerticalScrollMargin() - - left -= @getHorizontalScrollMargin() - right += @getHorizontalScrollMargin() - - top = Math.max(0, Math.min(@getLineCount() - 1, top)) - bottom = Math.max(0, Math.min(@getLineCount() - 1, bottom)) - left = Math.max(0, left) - right = Math.max(0, right) - - screenRange = new Range(new Point(top, left), new Point(bottom, right)) - + logicalScrollToScreenRange: (screenRange, options) -> scrollEvent = {screenRange, options} @emitter.emit "did-change-scroll-position", scrollEvent scrollToScreenRange: (screenRange, options) -> - @scrollToScreenRangeLogical(screenRange, options) + @logicalScrollToScreenRange(screenRange, options) verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() @@ -414,9 +391,10 @@ class DisplayBuffer extends Model if global.enableLogs console.log "====== DB ======" - console.log "Client Height: #{@getClientHeight()}" - console.log "#{desiredScrollTop}/#{desiredScrollBottom}" - console.log "#{@getScrollTop()}/#{@getScrollBottom()}" + console.log "Screen Range: #{screenRange.toString()}" + console.log "Client Width: #{@getClientWidth()}" + console.log "#{desiredScrollLeft}/#{desiredScrollRight}" + console.log "#{@getScrollLeft()}/#{@getScrollRight()}" console.log "================" if options?.reversed ? true diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 704980e05..47c854e2a 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1472,40 +1472,69 @@ class TextEditorPresenter @emitDidUpdateState() + getVerticalScrollMarginInPixels: -> + @model.getVerticalScrollMargin() * @lineHeight + + getHorizontalScrollMarginInPixels: -> + @model.getHorizontalScrollMargin() * @baseCharacterWidth + updateScrollPosition: -> return unless @pendingScrollLogicalPosition? {screenRange, options} = @pendingScrollLogicalPosition + verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() + horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() + {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) bottom = endTop + endHeight right = endLeft - left += @scrollLeft - right += @scrollLeft top += @scrollTop bottom += @scrollTop + left += @scrollLeft + right += @scrollLeft + + if options?.center + desiredScrollCenter = (top + bottom) / 2 + unless @getScrollTop() < desiredScrollCenter < @getScrollBottom() + desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2 + desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2 + else + desiredScrollTop = top - verticalScrollMarginInPixels + desiredScrollBottom = bottom + verticalScrollMarginInPixels + + desiredScrollLeft = left - horizontalScrollMarginInPixels + desiredScrollRight = right + horizontalScrollMarginInPixels + + if global.enableLogs + console.log "====== DB ======" + console.log "Screen Range: #{screenRange.toString()}" + console.log "Client Width: #{@getClientWidth()}" + console.log "#{desiredScrollLeft}/#{desiredScrollRight}" + console.log "#{@getScrollLeft()}/#{@getScrollRight()}" + console.log "================" if options?.reversed ? true - if bottom > @getScrollBottom() - @setScrollBottom(bottom) - if top < @getScrollTop() - @setScrollTop(top) + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) - if right > @getScrollRight() - @setScrollRight(right) - if left < @getScrollLeft() - @setScrollLeft(left) + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) else - if top < @getScrollTop() - @setScrollTop(top) - if bottom > @getScrollBottom() - @setScrollBottom(bottom) + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) - if left < @getScrollLeft() - @setScrollLeft(left) - if right > @getScrollRight() - @setScrollRight(right) + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) @pendingScrollLogicalPosition = null From ddd7aacd9d6dd4fbd3ffe357c90dae172126e8fa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 18:45:38 +0200 Subject: [PATCH 06/58] :green_heart: Fix TextEditorComponent specs --- spec/text-editor-component-spec.coffee | 86 +++++++++++++------------- spec/text-editor-presenter-spec.coffee | 2 +- src/text-editor-component.coffee | 24 +++++++ src/text-editor-element.coffee | 24 +++++++ 4 files changed, 92 insertions(+), 44 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 921e77a23..2d38dca96 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -1674,8 +1674,8 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getCursorScreenPosition()).toEqual [0, 0] - editor.setScrollTop(3 * lineHeightInPixels) - editor.setScrollLeft(3 * charWidth) + wrapperNode.setScrollTop(3 * lineHeightInPixels) + wrapperNode.setScrollLeft(3 * charWidth) nextAnimationFrame() expect(inputNode.offsetTop).toBe 0 @@ -1690,8 +1690,8 @@ describe "TextEditorComponent", -> # In bounds and focused wrapperNode.focus() # updates via state change nextAnimationFrame() - expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - editor.getScrollTop() - expect(inputNode.offsetLeft).toBe (4 * charWidth) - editor.getScrollLeft() + expect(inputNode.offsetTop).toBe (5 * lineHeightInPixels) - wrapperNode.getScrollTop() + expect(inputNode.offsetLeft).toBe (4 * charWidth) - wrapperNode.getScrollLeft() # In bounds, not focused inputNode.blur() # updates via state change @@ -1755,8 +1755,8 @@ describe "TextEditorComponent", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' wrapperNode.style.width = 10 * charWidth + 'px' component.measureDimensions() - editor.setScrollTop(3.5 * lineHeightInPixels) - editor.setScrollLeft(2 * charWidth) + wrapperNode.setScrollTop(3.5 * lineHeightInPixels) + wrapperNode.setScrollLeft(2 * charWidth) nextAnimationFrame() linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([4, 8]))) @@ -1873,33 +1873,33 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - expect(editor.getScrollTop()).toBe(0) - expect(editor.getScrollLeft()).toBe(0) + expect(wrapperNode.getScrollTop()).toBe(0) + expect(wrapperNode.getScrollLeft()).toBe(0) linesNode.dispatchEvent(buildMouseEvent('mousedown', {clientX: 0, clientY: 0}, which: 1)) linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 50}, which: 1)) nextAnimationFrame() - expect(editor.getScrollTop()).toBe(0) - expect(editor.getScrollLeft()).toBeGreaterThan(0) + expect(wrapperNode.getScrollTop()).toBe(0) + expect(wrapperNode.getScrollLeft()).toBeGreaterThan(0) linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 100, clientY: 100}, which: 1)) nextAnimationFrame() - expect(editor.getScrollTop()).toBeGreaterThan(0) + expect(wrapperNode.getScrollTop()).toBeGreaterThan(0) - previousScrollTop = editor.getScrollTop() - previousScrollLeft = editor.getScrollLeft() + previousScrollTop = wrapperNode.getScrollTop() + previousScrollLeft = wrapperNode.getScrollLeft() linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 50}, which: 1)) nextAnimationFrame() - expect(editor.getScrollTop()).toBe(previousScrollTop) - expect(editor.getScrollLeft()).toBeLessThan(previousScrollLeft) + expect(wrapperNode.getScrollTop()).toBe(previousScrollTop) + expect(wrapperNode.getScrollLeft()).toBeLessThan(previousScrollLeft) linesNode.dispatchEvent(buildMouseEvent('mousemove', {clientX: 10, clientY: 10}, which: 1)) nextAnimationFrame() - expect(editor.getScrollTop()).toBeLessThan(previousScrollTop) + expect(wrapperNode.getScrollTop()).toBeLessThan(previousScrollTop) it "stops selecting if the mouse is dragged into the dev tools", -> linesNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenPosition([2, 4]), which: 1)) @@ -1992,12 +1992,12 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[5, 6], [12, 2]] - maximalScrollTop = editor.getScrollTop() + maximalScrollTop = wrapperNode.getScrollTop() linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([9, 3]), which: 1)) nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[5, 6], [9, 4]] - expect(editor.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression) + expect(wrapperNode.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression) linesNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenPosition([9, 3]), which: 1)) @@ -2019,12 +2019,12 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [12, 2]] - maximalScrollTop = editor.getScrollTop() + maximalScrollTop = wrapperNode.getScrollTop() linesNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenPosition([8, 4]), which: 1)) nextAnimationFrame() expect(editor.getSelectedScreenRange()).toEqual [[5, 0], [8, 0]] - expect(editor.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression) + expect(wrapperNode.getScrollTop()).toBe maximalScrollTop # does not autoscroll upward (regression) linesNode.dispatchEvent(buildMouseEvent('mouseup', clientCoordinatesForScreenPosition([9, 3]), which: 1)) @@ -2119,23 +2119,23 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - expect(editor.getScrollTop()).toBe 0 + expect(wrapperNode.getScrollTop()).toBe 0 gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2))) gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(8))) nextAnimationFrame() - expect(editor.getScrollTop()).toBeGreaterThan 0 - maxScrollTop = editor.getScrollTop() + expect(wrapperNode.getScrollTop()).toBeGreaterThan 0 + maxScrollTop = wrapperNode.getScrollTop() gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(10))) nextAnimationFrame() - expect(editor.getScrollTop()).toBe maxScrollTop + expect(wrapperNode.getScrollTop()).toBe maxScrollTop gutterNode.dispatchEvent(buildMouseEvent('mousemove', clientCoordinatesForScreenRowInGutter(7))) nextAnimationFrame() - expect(editor.getScrollTop()).toBeLessThan maxScrollTop + expect(wrapperNode.getScrollTop()).toBeLessThan maxScrollTop it "stops selecting if a textInput event occurs during the drag", -> gutterNode.dispatchEvent(buildMouseEvent('mousedown', clientCoordinatesForScreenRowInGutter(2))) @@ -2416,7 +2416,7 @@ describe "TextEditorComponent", -> expect(verticalScrollbarNode.scrollTop).toBe 0 - editor.setScrollTop(10) + wrapperNode.setScrollTop(10) nextAnimationFrame() expect(verticalScrollbarNode.scrollTop).toBe 10 @@ -2434,7 +2434,7 @@ describe "TextEditorComponent", -> expect(horizontalScrollbarNode.scrollLeft).toBe 0 - editor.setScrollLeft(100) + wrapperNode.setScrollLeft(100) nextAnimationFrame() top = 0 @@ -2449,18 +2449,18 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - expect(editor.getScrollLeft()).toBe 0 + expect(wrapperNode.getScrollLeft()).toBe 0 horizontalScrollbarNode.scrollLeft = 100 horizontalScrollbarNode.dispatchEvent(new UIEvent('scroll')) nextAnimationFrame() - expect(editor.getScrollLeft()).toBe 100 + expect(wrapperNode.getScrollLeft()).toBe 100 it "does not obscure the last line with the horizontal scrollbar", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' wrapperNode.style.width = 10 * charWidth + 'px' component.measureDimensions() - editor.setScrollBottom(editor.getScrollHeight()) + wrapperNode.setScrollBottom(editor.getScrollHeight()) nextAnimationFrame() lastLineNode = component.lineNodeForScreenRow(editor.getLastScreenRow()) bottomOfLastLine = lastLineNode.getBoundingClientRect().bottom @@ -2479,7 +2479,7 @@ describe "TextEditorComponent", -> wrapperNode.style.height = 7 * lineHeightInPixels + 'px' wrapperNode.style.width = 10 * charWidth + 'px' component.measureDimensions() - editor.setScrollLeft(Infinity) + wrapperNode.setScrollLeft(Infinity) nextAnimationFrame() rightOfLongestLine = component.lineNodeForScreenRow(6).querySelector('.line > span:last-child').getBoundingClientRect().right @@ -2649,14 +2649,14 @@ describe "TextEditorComponent", -> expect(component.presenter.mouseWheelScreenRow).toBe null it "clears the mouseWheelScreenRow after a delay even if the event does not cause scrolling", -> - expect(editor.getScrollTop()).toBe 0 + expect(wrapperNode.getScrollTop()).toBe 0 lineNode = componentNode.querySelector('.line') wheelEvent = new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 10) Object.defineProperty(wheelEvent, 'target', get: -> lineNode) componentNode.dispatchEvent(wheelEvent) - expect(editor.getScrollTop()).toBe 0 + expect(wrapperNode.getScrollTop()).toBe 0 expect(component.presenter.mouseWheelScreenRow).toBe 0 advanceClock(component.presenter.stoppedScrollingDelay) @@ -2701,36 +2701,36 @@ describe "TextEditorComponent", -> # try to scroll past the top, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 50)) - expect(editor.getScrollTop()).toBe 0 + expect(wrapperNode.getScrollTop()).toBe 0 expect(WheelEvent::preventDefault).not.toHaveBeenCalled() # scroll to the bottom in one huge event componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -3000)) nextAnimationFrame() - maxScrollTop = editor.getScrollTop() + maxScrollTop = wrapperNode.getScrollTop() expect(WheelEvent::preventDefault).toHaveBeenCalled() WheelEvent::preventDefault.reset() # try to scroll past the bottom, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -30)) - expect(editor.getScrollTop()).toBe maxScrollTop + expect(wrapperNode.getScrollTop()).toBe maxScrollTop expect(WheelEvent::preventDefault).not.toHaveBeenCalled() # try to scroll past the left side, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 50, wheelDeltaY: 0)) - expect(editor.getScrollLeft()).toBe 0 + expect(wrapperNode.getScrollLeft()).toBe 0 expect(WheelEvent::preventDefault).not.toHaveBeenCalled() # scroll all the way right componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -3000, wheelDeltaY: 0)) nextAnimationFrame() - maxScrollLeft = editor.getScrollLeft() + maxScrollLeft = wrapperNode.getScrollLeft() expect(WheelEvent::preventDefault).toHaveBeenCalled() WheelEvent::preventDefault.reset() # try to scroll past the right side, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -30, wheelDeltaY: 0)) - expect(editor.getScrollLeft()).toBe maxScrollLeft + expect(wrapperNode.getScrollLeft()).toBe maxScrollLeft expect(WheelEvent::preventDefault).not.toHaveBeenCalled() describe "input events", -> @@ -3370,15 +3370,15 @@ describe "TextEditorComponent", -> clientCoordinatesForScreenPosition = (screenPosition) -> positionOffset = wrapperNode.pixelPositionForScreenPosition(screenPosition) scrollViewClientRect = componentNode.querySelector('.scroll-view').getBoundingClientRect() - clientX = scrollViewClientRect.left + positionOffset.left - editor.getScrollLeft() - clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop() + clientX = scrollViewClientRect.left + positionOffset.left - wrapperNode.getScrollLeft() + clientY = scrollViewClientRect.top + positionOffset.top - wrapperNode.getScrollTop() {clientX, clientY} clientCoordinatesForScreenRowInGutter = (screenRow) -> positionOffset = wrapperNode.pixelPositionForScreenPosition([screenRow, Infinity]) gutterClientRect = componentNode.querySelector('.gutter').getBoundingClientRect() - clientX = gutterClientRect.left + positionOffset.left - editor.getScrollLeft() - clientY = gutterClientRect.top + positionOffset.top - editor.getScrollTop() + clientX = gutterClientRect.left + positionOffset.left - wrapperNode.getScrollLeft() + clientY = gutterClientRect.top + positionOffset.top - wrapperNode.getScrollTop() {clientX, clientY} lineAndLineNumberHaveClass = (screenRow, klass) -> diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 43dc6ef3c..ee7d16918 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' -fdescribe "TextEditorPresenter", -> +describe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. # Please maintain this structure when adding specs for new state fields. describe "::getState()", -> diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index dbcc8fef4..48aeb16e9 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -358,6 +358,30 @@ class TextEditorComponent @scrollViewNode.scrollTop = 0 @scrollViewNode.scrollLeft = 0 + setScrollLeft: (scrollLeft) -> + @presenter.setScrollLeft(scrollLeft) + + setScrollRight: (scrollRight) -> + @presenter.setScrollRight(scrollRight) + + setScrollTop: (scrollTop) -> + @presenter.setScrollTop(scrollTop) + + setScrollBottom: (scrollBottom) -> + @presenter.setScrollBottom(scrollBottom) + + getScrollTop: -> + @presenter.getScrollTop() + + getScrollLeft: -> + @presenter.getScrollLeft() + + getScrollRight: -> + @presenter.getScrollRight() + + getScrollBottom: -> + @presenter.getScrollBottom() + 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 e5023e821..c2adc83dc 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -219,6 +219,30 @@ class TextEditorElement extends HTMLElement onDidDetach: (callback) -> @emitter.on("did-detach", callback) + setScrollLeft: (scrollLeft) -> + @component.setScrollLeft(scrollLeft) + + setScrollRight: (scrollRight) -> + @component.setScrollRight(scrollRight) + + setScrollTop: (scrollTop) -> + @component.setScrollTop(scrollTop) + + setScrollBottom: (scrollBottom) -> + @component.setScrollBottom(scrollBottom) + + getScrollTop: -> + @component.getScrollTop() + + getScrollLeft: -> + @component.getScrollLeft() + + getScrollRight: -> + @component.getScrollRight() + + getScrollBottom: -> + @component.getScrollBottom() + stopEventPropagation = (commandListeners) -> newCommandListeners = {} for commandName, commandListener of commandListeners From e526ceae6d34040274b33ed1bed78300fab4a0f9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 20:03:50 +0200 Subject: [PATCH 07/58] Update scroll top on ::getState --- src/text-editor-presenter.coffee | 81 +++++++++++++++++--------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 47c854e2a..c56731064 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -809,22 +809,21 @@ class TextEditorPresenter @emitDidUpdateState() setScrollTop: (scrollTop) -> - scrollTop = @constrainScrollTop(scrollTop) + return unless scrollTop? - unless @scrollTop is scrollTop or Number.isNaN(scrollTop) - @scrollTop = scrollTop - @model.setScrollTop(@scrollTop) - @didStartScrolling() - @shouldUpdateVerticalScrollState = true - @shouldUpdateHiddenInputState = true - @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateCursorsState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateCustomGutterDecorationState = true - @shouldUpdateOverlaysState = true + @pendingScrollTop = scrollTop - @emitDidUpdateState() + @shouldUpdateVerticalScrollState = true + @shouldUpdateHiddenInputState = true + @shouldUpdateDecorations = true + @shouldUpdateLinesState = true + @shouldUpdateCursorsState = true + @shouldUpdateLineNumbersState = true + @shouldUpdateCustomGutterDecorationState = true + @shouldUpdateOverlaysState = true + + @didStartScrolling() + @emitDidUpdateState() getScrollTop: -> @scrollTop @@ -848,19 +847,18 @@ class TextEditorPresenter @emitDidUpdateState() setScrollLeft: (scrollLeft) -> - scrollLeft = @constrainScrollLeft(scrollLeft) - unless @scrollLeft is scrollLeft or Number.isNaN(scrollLeft) - oldScrollLeft = @scrollLeft - @scrollLeft = scrollLeft - @model.setScrollLeft(@scrollLeft) - @shouldUpdateHorizontalScrollState = true - @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true - @shouldUpdateOverlaysState = true - @shouldUpdateDecorations = true - @shouldUpdateLinesState = true + return unless scrollLeft? - @emitDidUpdateState() + @pendingScrollLeft = scrollLeft + + @shouldUpdateHorizontalScrollState = true + @shouldUpdateHiddenInputState = true + @shouldUpdateCursorsState = true + @shouldUpdateOverlaysState = true + @shouldUpdateDecorations = true + @shouldUpdateLinesState = true + + @emitDidUpdateState() getScrollLeft: -> @scrollLeft @@ -1478,9 +1476,7 @@ class TextEditorPresenter getHorizontalScrollMarginInPixels: -> @model.getHorizontalScrollMargin() * @baseCharacterWidth - updateScrollPosition: -> - return unless @pendingScrollLogicalPosition? - + commitPendingLogicalScrollPosition: -> {screenRange, options} = @pendingScrollLogicalPosition verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() @@ -1508,14 +1504,6 @@ class TextEditorPresenter desiredScrollLeft = left - horizontalScrollMarginInPixels desiredScrollRight = right + horizontalScrollMarginInPixels - if global.enableLogs - console.log "====== DB ======" - console.log "Screen Range: #{screenRange.toString()}" - console.log "Client Width: #{@getClientWidth()}" - console.log "#{desiredScrollLeft}/#{desiredScrollRight}" - console.log "#{@getScrollLeft()}/#{@getScrollRight()}" - console.log "================" - if options?.reversed ? true if desiredScrollBottom > @getScrollBottom() @setScrollBottom(desiredScrollBottom) @@ -1537,4 +1525,23 @@ class TextEditorPresenter if desiredScrollRight > @getScrollRight() @setScrollRight(desiredScrollRight) + commitPendingScrollLeftPosition: -> + scrollLeft = @constrainScrollLeft(@pendingScrollLeft) + if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) + @scrollLeft = scrollLeft + @model.setScrollLeft(scrollLeft) + + commitPendingScrollTopPosition: -> + scrollTop = @constrainScrollTop(@pendingScrollTop) + if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) + @scrollTop = scrollTop + @model.setScrollTop(scrollTop) + + updateScrollPosition: -> + @commitPendingLogicalScrollPosition() if @pendingScrollLogicalPosition? + @commitPendingScrollLeftPosition() if @pendingScrollLeft? + @commitPendingScrollTopPosition() if @pendingScrollTop? + + @pendingScrollTop = null + @pendingScrollLeft = null @pendingScrollLogicalPosition = null From 37fb253bfd59d5a9d53f33c5077d271a1a9299d8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 22 Sep 2015 20:26:39 +0200 Subject: [PATCH 08/58] Prevent default for mousewheel event on mini editors (ref. 0346e58) --- spec/text-editor-component-spec.coffee | 55 +++++++++++++++++++++++--- src/text-editor-component.coffee | 4 +- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 2d38dca96..374fab15f 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2691,7 +2691,54 @@ describe "TextEditorComponent", -> expect(componentNode.contains(lineNumberNode)).toBe true - it "only prevents the default action of the mousewheel event if it actually lead to scrolling", -> + it "prevents the default action of mousewheel events for normal editors", -> + spyOn(WheelEvent::, 'preventDefault').andCallThrough() + + wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' + wrapperNode.style.width = 20 * charWidth + 'px' + component.measureDimensions() + nextAnimationFrame() + + # try to scroll past the top, which is impossible + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 50)) + expect(wrapperNode.getScrollTop()).toBe 0 + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() + + # scroll to the bottom in one huge event + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -3000)) + nextAnimationFrame() + maxScrollTop = wrapperNode.getScrollTop() + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() + + # try to scroll past the bottom, which is impossible + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -30)) + expect(wrapperNode.getScrollTop()).toBe maxScrollTop + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() + + # try to scroll past the left side, which is impossible + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 50, wheelDeltaY: 0)) + expect(wrapperNode.getScrollLeft()).toBe 0 + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() + + # scroll all the way right + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -3000, wheelDeltaY: 0)) + nextAnimationFrame() + maxScrollLeft = wrapperNode.getScrollLeft() + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() + + # try to scroll past the right side, which is impossible + componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -30, wheelDeltaY: 0)) + expect(wrapperNode.getScrollLeft()).toBe maxScrollLeft + expect(WheelEvent::preventDefault).toHaveBeenCalled() + + it "doesn't prevent the default action of mousewheel events for mini editors", -> + editor.setMini(true) + spyOn(WheelEvent::, 'preventDefault').andCallThrough() wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' @@ -2708,8 +2755,7 @@ describe "TextEditorComponent", -> componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -3000)) nextAnimationFrame() maxScrollTop = wrapperNode.getScrollTop() - expect(WheelEvent::preventDefault).toHaveBeenCalled() - WheelEvent::preventDefault.reset() + expect(WheelEvent::preventDefault).not.toHaveBeenCalled() # try to scroll past the bottom, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -30)) @@ -2725,8 +2771,7 @@ describe "TextEditorComponent", -> componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -3000, wheelDeltaY: 0)) nextAnimationFrame() maxScrollLeft = wrapperNode.getScrollLeft() - expect(WheelEvent::preventDefault).toHaveBeenCalled() - WheelEvent::preventDefault.reset() + expect(WheelEvent::preventDefault).not.toHaveBeenCalled() # try to scroll past the right side, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -30, wheelDeltaY: 0)) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 48aeb16e9..f06e0c339 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -328,6 +328,8 @@ class TextEditorComponent @pendingScrollLeft = null onMouseWheel: (event) => + event.preventDefault() unless @editor.isMini() + # Only scroll in one direction at a time {wheelDeltaX, wheelDeltaY} = event @@ -344,13 +346,11 @@ class TextEditorComponent # Scrolling horizontally previousScrollLeft = @presenter.getScrollLeft() @presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)) - event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft() else # Scrolling vertically @presenter.setMouseWheelScreenRow(@screenRowForNode(event.target)) previousScrollTop = @presenter.getScrollTop() @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) - event.preventDefault() unless previousScrollTop is @presenter.getScrollTop() onScrollViewScroll: => if @mounted From d3b1d309ba0b6ddac47ff840090760fcd35c92e5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 08:21:24 +0200 Subject: [PATCH 09/58] Make sure scroll positions are integers --- spec/text-editor-presenter-spec.coffee | 16 ++++++++++++++++ src/text-editor-presenter.coffee | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index ee7d16918..8db6fa90b 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -333,6 +333,14 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollLeft(50) expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 50 + it "is always rounded to the nearest integer", -> + presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) + expect(presenter.getState().content.scrollLeft).toBe 10 + expectStateUpdate presenter, -> presenter.setScrollLeft(11.4) + expect(presenter.getState().content.scrollLeft).toBe 11 + expectStateUpdate presenter, -> presenter.setScrollLeft(12.6) + expect(presenter.getState().content.scrollLeft).toBe 13 + it "never exceeds the computed scrollWidth minus the computed clientWidth", -> presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, explicitHeight: 100, contentFrameWidth: 500) expectStateUpdate presenter, -> presenter.setScrollLeft(300) @@ -674,6 +682,14 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollTop(50) expect(presenter.getState().content.scrollTop).toBe 50 + it "is always rounded to the nearest integer", -> + presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20) + expect(presenter.getState().content.scrollTop).toBe 10 + expectStateUpdate presenter, -> presenter.setScrollTop(11.4) + expect(presenter.getState().content.scrollTop).toBe 11 + expectStateUpdate presenter, -> presenter.setScrollTop(12.6) + expect(presenter.getState().content.scrollTop).toBe 13 + it "scrolls down automatically when the model is changed", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c56731064..58caaa6e5 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1526,13 +1526,13 @@ class TextEditorPresenter @setScrollRight(desiredScrollRight) commitPendingScrollLeftPosition: -> - scrollLeft = @constrainScrollLeft(@pendingScrollLeft) + scrollLeft = Math.round(@constrainScrollLeft(@pendingScrollLeft)) if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @scrollLeft = scrollLeft @model.setScrollLeft(scrollLeft) commitPendingScrollTopPosition: -> - scrollTop = @constrainScrollTop(@pendingScrollTop) + scrollTop = Math.round(@constrainScrollTop(@pendingScrollTop)) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @scrollTop = scrollTop @model.setScrollTop(scrollTop) From 57a006d19b0a58a50e8222f8ad631f25e48fce0c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 09:39:33 +0200 Subject: [PATCH 10/58] Start porting scroll-related specs --- spec/text-editor-component-spec.coffee | 166 +++++++++++++++++++++++++ spec/text-editor-spec.coffee | 112 ----------------- src/text-editor-component.coffee | 6 + src/text-editor-element.coffee | 6 + src/text-editor-presenter.coffee | 11 +- 5 files changed, 188 insertions(+), 113 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 374fab15f..455d2cca1 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3371,6 +3371,172 @@ describe "TextEditorComponent", -> expect(line1LeafNodes[0].classList.contains('indent-guide')).toBe false expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false + fffdescribe "autoscroll", -> + beforeEach -> + editor.setVerticalScrollMargin(2) + editor.setHorizontalScrollMargin(2) + component.setLineHeight("10px") + component.setFontSize(17) + component.measureDimensions() + nextAnimationFrame() + + # Why does this set an incorrect width? :confused: + wrapperNode.style.width = "108px" + wrapperNode.style.height = 5.5 * 10 + "px" + component.measureDimensions() + nextAnimationFrame() + + component.presenter.setHorizontalScrollbarHeight(0) + component.presenter.setVerticalScrollbarWidth(0) + nextAnimationFrame() # perform requested update + + afterEach -> + atom.themes.removeStylesheet("test") + + describe "moving cursors", -> + it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", -> + expect(wrapperNode.getScrollTop()).toBe 0 + expect(wrapperNode.getScrollBottom()).toBe 5.5 * 10 + + editor.setCursorScreenPosition([2, 0]) + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe 5.5 * 10 + + editor.moveDown() + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe 6 * 10 + + editor.moveDown() + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe 7 * 10 + + it "scrolls up when the last cursor gets closer than ::verticalScrollMargin to the top of the editor", -> + editor.setCursorScreenPosition([11, 0]) + nextAnimationFrame() + wrapperNode.setScrollBottom(wrapperNode.getScrollHeight()) + nextAnimationFrame() + + editor.moveUp() + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe wrapperNode.getScrollHeight() + + editor.moveUp() + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 7 * 10 + + editor.moveUp() + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 6 * 10 + + it "scrolls right when the last cursor gets closer than ::horizontalScrollMargin to the right of the editor", -> + expect(wrapperNode.getScrollLeft()).toBe 0 + expect(wrapperNode.getScrollRight()).toBe 5.5 * 10 + + editor.setCursorScreenPosition([0, 2]) + nextAnimationFrame() + expect(wrapperNode.getScrollRight()).toBe 5.5 * 10 + + editor.moveRight() + nextAnimationFrame() + expect(wrapperNode.getScrollRight()).toBe 6 * 10 + + editor.moveRight() + nextAnimationFrame() + expect(wrapperNode.getScrollRight()).toBe 7 * 10 + + it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", -> + wrapperNode.setScrollRight(wrapperNode.getScrollWidth()) + nextAnimationFrame() + expect(wrapperNode.getScrollRight()).toBe wrapperNode.getScrollWidth() + editor.setCursorScreenPosition([6, 62], autoscroll: false) + nextAnimationFrame() + + editor.moveLeft() + nextAnimationFrame() + expect(wrapperNode.getScrollLeft()).toBe 59 * 10 + + editor.moveLeft() + nextAnimationFrame() + expect(wrapperNode.getScrollLeft()).toBe 58 * 10 + + it "scrolls down when inserting lines makes the document longer than the editor's height", -> + editor.setCursorScreenPosition([13, Infinity]) + editor.insertNewline() + nextAnimationFrame() + + expect(wrapperNode.getScrollBottom()).toBe 14 * 10 + editor.insertNewline() + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe 15 * 10 + + it "autoscrolls to the cursor when it moves due to undo", -> + editor.insertText('abc') + wrapperNode.setScrollTop(Infinity) + nextAnimationFrame() + + editor.undo() + nextAnimationFrame() + + expect(wrapperNode.getScrollTop()).toBe 0 + + it "doesn't scroll when the cursor moves into the visible area", -> + editor.setCursorBufferPosition([0, 0]) + nextAnimationFrame() + + wrapperNode.setScrollTop(40) + nextAnimationFrame() + + editor.setCursorBufferPosition([6, 0]) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 40 + + it "honors the autoscroll option on cursor and selection manipulation methods", -> + expect(wrapperNode.getScrollTop()).toBe 0 + editor.addCursorAtScreenPosition([11, 11], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.addCursorAtBufferPosition([11, 11], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.setCursorScreenPosition([11, 11], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.setCursorBufferPosition([11, 11], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.addSelectionForBufferRange([[11, 11], [11, 11]], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.addSelectionForScreenRange([[11, 11], [11, 12]], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.setSelectedBufferRange([[11, 0], [11, 1]], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.setSelectedScreenRange([[11, 0], [11, 6]], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.clearSelections(autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + + editor.addSelectionForScreenRange([[0, 0], [0, 4]]) + nextAnimationFrame() + + editor.getCursors()[0].setScreenPosition([11, 11], autoscroll: true) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBeGreaterThan 0 + editor.getCursors()[0].setBufferPosition([0, 0], autoscroll: true) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + editor.getSelections()[0].setScreenRange([[11, 0], [11, 4]], autoscroll: true) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBeGreaterThan 0 + editor.getSelections()[0].setBufferRange([[0, 0], [0, 4]], autoscroll: true) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + + describe "middle mouse paste on Linux", -> originalPlatform = null diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 203b138cd..c73926b41 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -909,118 +909,6 @@ describe "TextEditor", -> cursor2 = editor.addCursorAtBufferPosition([1, 4]) expect(cursor2.marker).toBe cursor1.marker - describe "autoscroll", -> - beforeEach -> - editor.setVerticalScrollMargin(2) - editor.setHorizontalScrollMargin(2) - editor.setLineHeightInPixels(10) - editor.setDefaultCharWidth(10) - editor.setHorizontalScrollbarHeight(0) - editor.setHeight(5.5 * 10) - editor.setWidth(5.5 * 10) - - it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", -> - expect(editor.getScrollTop()).toBe 0 - expect(editor.getScrollBottom()).toBe 5.5 * 10 - - editor.setCursorScreenPosition([2, 0]) - expect(editor.getScrollBottom()).toBe 5.5 * 10 - - editor.moveDown() - expect(editor.getScrollBottom()).toBe 6 * 10 - - editor.moveDown() - expect(editor.getScrollBottom()).toBe 7 * 10 - - it "scrolls up when the last cursor gets closer than ::verticalScrollMargin to the top of the editor", -> - editor.setCursorScreenPosition([11, 0]) - editor.setScrollBottom(editor.getScrollHeight()) - - editor.moveUp() - expect(editor.getScrollBottom()).toBe editor.getScrollHeight() - - editor.moveUp() - expect(editor.getScrollTop()).toBe 7 * 10 - - editor.moveUp() - expect(editor.getScrollTop()).toBe 6 * 10 - - it "scrolls right when the last cursor gets closer than ::horizontalScrollMargin to the right of the editor", -> - expect(editor.getScrollLeft()).toBe 0 - expect(editor.getScrollRight()).toBe 5.5 * 10 - - editor.setCursorScreenPosition([0, 2]) - expect(editor.getScrollRight()).toBe 5.5 * 10 - - editor.moveRight() - expect(editor.getScrollRight()).toBe 6 * 10 - - editor.moveRight() - expect(editor.getScrollRight()).toBe 7 * 10 - - it "scrolls left when the last cursor gets closer than ::horizontalScrollMargin to the left of the editor", -> - editor.setScrollRight(editor.getScrollWidth()) - expect(editor.getScrollRight()).toBe editor.getScrollWidth() - editor.setCursorScreenPosition([6, 62], autoscroll: false) - - editor.moveLeft() - expect(editor.getScrollLeft()).toBe 59 * 10 - - editor.moveLeft() - expect(editor.getScrollLeft()).toBe 58 * 10 - - it "scrolls down when inserting lines makes the document longer than the editor's height", -> - editor.setCursorScreenPosition([13, Infinity]) - editor.insertNewline() - expect(editor.getScrollBottom()).toBe 14 * 10 - editor.insertNewline() - expect(editor.getScrollBottom()).toBe 15 * 10 - - it "autoscrolls to the cursor when it moves due to undo", -> - editor.insertText('abc') - editor.setScrollTop(Infinity) - editor.undo() - expect(editor.getScrollTop()).toBe 0 - - it "doesn't scroll when the cursor moves into the visible area", -> - editor.setCursorBufferPosition([0, 0]) - editor.setScrollTop(40) - expect(editor.getVisibleRowRange()).toEqual([4, 9]) - editor.setCursorBufferPosition([6, 0]) - expect(editor.getScrollTop()).toBe 40 - - it "honors the autoscroll option on cursor and selection manipulation methods", -> - expect(editor.getScrollTop()).toBe 0 - editor.addCursorAtScreenPosition([11, 11], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.addCursorAtBufferPosition([11, 11], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.setCursorScreenPosition([11, 11], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.setCursorBufferPosition([11, 11], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.addSelectionForBufferRange([[11, 11], [11, 11]], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.addSelectionForScreenRange([[11, 11], [11, 12]], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.setSelectedBufferRange([[11, 0], [11, 1]], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.setSelectedScreenRange([[11, 0], [11, 6]], autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - editor.clearSelections(autoscroll: false) - expect(editor.getScrollTop()).toBe 0 - - editor.addSelectionForScreenRange([[0, 0], [0, 4]]) - - editor.getCursors()[0].setScreenPosition([11, 11], autoscroll: true) - expect(editor.getScrollTop()).toBeGreaterThan 0 - editor.getCursors()[0].setBufferPosition([0, 0], autoscroll: true) - expect(editor.getScrollTop()).toBe 0 - editor.getSelections()[0].setScreenRange([[11, 0], [11, 4]], autoscroll: true) - expect(editor.getScrollTop()).toBeGreaterThan 0 - editor.getSelections()[0].setBufferRange([[0, 0], [0, 4]], autoscroll: true) - expect(editor.getScrollTop()).toBe 0 - describe '.logCursorScope()', -> beforeEach -> spyOn(atom.notifications, 'addInfo') diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index f06e0c339..cb6329f05 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -382,6 +382,12 @@ class TextEditorComponent getScrollBottom: -> @presenter.getScrollBottom() + getScrollHeight: -> + @presenter.getScrollHeight() + + getScrollWidth: -> + @presenter.getScrollWidth() + 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 c2adc83dc..96e1d3969 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -243,6 +243,12 @@ class TextEditorElement extends HTMLElement getScrollBottom: -> @component.getScrollBottom() + getScrollHeight: -> + @component.getScrollHeight() + + getScrollWidth: -> + @component.getScrollWidth() + stopEventPropagation = (commandListeners) -> newCommandListeners = {} for commandName, commandListener of commandListeners diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 58caaa6e5..6c6f56881 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -870,7 +870,10 @@ class TextEditorPresenter @explicitHeight - @horizontalScrollbarHeight getClientWidth: -> - @clientWidth ? @contentFrameWidth + if @clientWidth + @clientWidth + else + @contentFrameWidth - @verticalScrollbarWidth getScrollBottom: -> @getScrollTop() + @getClientHeight() setScrollBottom: (scrollBottom) -> @@ -882,6 +885,12 @@ class TextEditorPresenter @setScrollLeft(scrollRight - @getClientWidth()) @getScrollRight() + getScrollHeight: -> + @scrollHeight + + getScrollWidth: -> + @scrollWidth + setHorizontalScrollbarHeight: (horizontalScrollbarHeight) -> unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight From 7f3160eba694128ab098d09ca0efefc3db0f2cdf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 09:57:06 +0200 Subject: [PATCH 11/58] Port .selectLinesContainingCursors() specs --- spec/text-editor-component-spec.coffee | 13 +++++++++++++ spec/text-editor-spec.coffee | 14 -------------- src/text-editor-element.coffee | 6 ++++++ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 455d2cca1..41778003c 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3393,6 +3393,19 @@ describe "TextEditorComponent", -> afterEach -> atom.themes.removeStylesheet("test") + describe "when selecting lines containing cursors", -> + it "autoscrolls to the selection", -> + editor.setCursorScreenPosition([5, 6]) + nextAnimationFrame() + + wrapperNode.scrollToTop() + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + + editor.selectLinesContainingCursors() + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 + describe "moving cursors", -> it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", -> expect(wrapperNode.getScrollTop()).toBe 0 diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index c73926b41..63d4c403d 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1194,20 +1194,6 @@ describe "TextEditor", -> editor.selectLinesContainingCursors() expect(editor.getSelectedBufferRange()).toEqual [[1, 0], [4, 0]] - it "autoscrolls to the selection", -> - editor.setLineHeightInPixels(10) - editor.setDefaultCharWidth(10) - editor.setHeight(50) - editor.setWidth(50) - editor.setHorizontalScrollbarHeight(0) - editor.setCursorScreenPosition([5, 6]) - - editor.scrollToTop() - expect(editor.getScrollTop()).toBe 0 - - editor.selectLinesContainingCursors() - expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 - describe ".selectToBeginningOfWord()", -> it "selects text from cusor position to beginning of word", -> editor.setCursorScreenPosition [0, 13] diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 96e1d3969..d1fb14007 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -231,6 +231,12 @@ class TextEditorElement extends HTMLElement setScrollBottom: (scrollBottom) -> @component.setScrollBottom(scrollBottom) + scrollToTop: -> + @setScrollTop(0) + + scrollToBottom: -> + @setScrollBottom(Infinity) + getScrollTop: -> @component.getScrollTop() From 25735b2ee193df13eacb49e37211d4ef118b5b00 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:01:39 +0200 Subject: [PATCH 12/58] Port autoscroll when selecting buffer ranges --- spec/text-editor-component-spec.coffee | 19 +++++++++++++++++++ spec/text-editor-spec.coffee | 24 ------------------------ 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 41778003c..686350e07 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3393,6 +3393,25 @@ describe "TextEditorComponent", -> afterEach -> atom.themes.removeStylesheet("test") + describe "when selecting buffer ranges", -> + it "autoscrolls the selection if it is last unless the 'autoscroll' option is false", -> + expect(wrapperNode.getScrollTop()).toBe 0 + + editor.setSelectedBufferRange([[5, 6], [6, 8]]) + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 + expect(wrapperNode.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10 + + editor.setSelectedBufferRange([[0, 0], [0, 0]]) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + expect(wrapperNode.getScrollLeft()).toBe 0 + + editor.setSelectedBufferRange([[6, 6], [6, 8]]) + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 + expect(wrapperNode.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10 + describe "when selecting lines containing cursors", -> it "autoscrolls to the selection", -> editor.setCursorScreenPosition([5, 6]) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 63d4c403d..99b6b0544 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1446,30 +1446,6 @@ describe "TextEditor", -> expect(selection1).toBe selection expect(selection1.getScreenRange()).toEqual [[2, 2], [3, 4]] - describe ".setSelectedBufferRange(range)", -> - it "autoscrolls the selection if it is last unless the 'autoscroll' option is false", -> - editor.setVerticalScrollMargin(2) - editor.setHorizontalScrollMargin(2) - editor.setLineHeightInPixels(10) - editor.setDefaultCharWidth(10) - editor.setHeight(70) - editor.setWidth(100) - editor.setHorizontalScrollbarHeight(0) - - expect(editor.getScrollTop()).toBe 0 - - editor.setSelectedBufferRange([[5, 6], [6, 8]]) - expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 - expect(editor.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10 - - editor.setSelectedBufferRange([[0, 0], [0, 0]]) - expect(editor.getScrollTop()).toBe 0 - expect(editor.getScrollLeft()).toBe 0 - - editor.setSelectedBufferRange([[6, 6], [6, 8]]) - expect(editor.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 - expect(editor.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10 - describe ".selectMarker(marker)", -> describe "if the marker is valid", -> it "selects the marker's range and returns the selected range", -> From 0ccb34de4223b525016fa67035e620e49ae79c60 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:15:38 +0200 Subject: [PATCH 13/58] wip --- spec/text-editor-component-spec.coffee | 21 +++++++++++++++++++++ spec/text-editor-spec.coffee | 21 --------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 686350e07..7dddb59a4 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3412,6 +3412,13 @@ describe "TextEditorComponent", -> expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 expect(wrapperNode.getScrollRight()).toBe (8 + editor.getHorizontalScrollMargin()) * 10 + describe "when adding selections for buffer ranges", -> + it "autoscrolls to the added selection if needed", -> + editor.addSelectionForBufferRange([[8, 10], [8, 15]]) + nextAnimationFrame() + expect(wrapperNode.getScrollBottom()).toBe (9 * 10) + (2 * 10) + expect(wrapperNode.getScrollRight()).toBe (15 * 10) + (2 * 10) + describe "when selecting lines containing cursors", -> it "autoscrolls to the selection", -> editor.setCursorScreenPosition([5, 6]) @@ -3425,6 +3432,20 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(wrapperNode.getScrollBottom()).toBe (7 + editor.getVerticalScrollMargin()) * 10 + describe "when inserting text", -> + describe "when there are multiple empty selections on different lines", -> + it "autoscrolls to the last cursor", -> + editor.setCursorScreenPosition([1, 2], autoscroll: false) + nextAnimationFrame() + + editor.addCursorAtScreenPosition([10, 4], autoscroll: false) + nextAnimationFrame() + + expect(wrapperNode.getScrollTop()).toBe 0 + editor.insertText('a') + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 75 + describe "moving cursors", -> it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", -> expect(wrapperNode.getScrollTop()).toBe 0 diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 99b6b0544..9881546fa 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -1465,17 +1465,6 @@ describe "TextEditor", -> editor.addSelectionForBufferRange([[3, 4], [5, 6]]) expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [0, 0]], [[3, 4], [5, 6]]] - it "autoscrolls to the added selection if needed", -> - editor.setVerticalScrollMargin(2) - editor.setHorizontalScrollMargin(2) - editor.setLineHeightInPixels(10) - editor.setDefaultCharWidth(10) - editor.setHeight(80) - editor.setWidth(100) - editor.addSelectionForBufferRange([[8, 10], [8, 15]]) - expect(editor.getScrollBottom()).toBe (9 * 10) + (2 * 10) - expect(editor.getScrollRight()).toBe (15 * 10) + (2 * 10) - describe ".addSelectionBelow()", -> describe "when the selection is non-empty", -> it "selects the same region of the line below current selections if possible", -> @@ -1826,16 +1815,6 @@ describe "TextEditor", -> expect(cursor1.getBufferPosition()).toEqual [1, 5] expect(cursor2.getBufferPosition()).toEqual [2, 7] - it "autoscrolls to the last cursor", -> - editor.setCursorScreenPosition([1, 2]) - editor.addCursorAtScreenPosition([10, 4]) - editor.setLineHeightInPixels(10) - editor.setHeight(50) - - expect(editor.getScrollTop()).toBe 0 - editor.insertText('a') - expect(editor.getScrollTop()).toBe 80 - describe "when there are multiple non-empty selections", -> describe "when the selections are on the same line", -> it "replaces each selection range with the inserted characters", -> From 4136c195c9df72e07a73a857c457cc702c5a96e5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:25:53 +0200 Subject: [PATCH 14/58] Port .scrollToCursorPosition() specs --- spec/text-editor-component-spec.coffee | 19 ++++++++++++++++++- spec/text-editor-spec.coffee | 20 -------------------- src/text-editor-element.coffee | 3 +++ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 7dddb59a4..9562a7984 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3381,7 +3381,7 @@ describe "TextEditorComponent", -> nextAnimationFrame() # Why does this set an incorrect width? :confused: - wrapperNode.style.width = "108px" + wrapperNode.style.width = "108px" # this should be 55px wrapperNode.style.height = 5.5 * 10 + "px" component.measureDimensions() nextAnimationFrame() @@ -3446,6 +3446,23 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(wrapperNode.getScrollTop()).toBe 75 + describe "when scrolled to cursor position", -> + it "scrolls the last cursor into view, centering around the cursor if possible and the 'center' option isn't false", -> + editor.setCursorScreenPosition([8, 8], autoscroll: false) + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe 0 + expect(wrapperNode.getScrollLeft()).toBe 0 + + wrapperNode.scrollToCursorPosition() + nextAnimationFrame() + expect(wrapperNode.getScrollTop()).toBe (8.8 * 10) - 30 + expect(wrapperNode.getScrollBottom()).toBe (8.3 * 10) + 30 + expect(wrapperNode.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10 + + wrapperNode.setScrollTop(0) + wrapperNode.scrollToCursorPosition(center: false) + expect(editor.getScrollBottom()).toBe (8 + editor.getVerticalScrollMargin()) * 10 + describe "moving cursors", -> it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", -> expect(wrapperNode.getScrollTop()).toBe 0 diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 9881546fa..b0cd021cb 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -4376,26 +4376,6 @@ describe "TextEditor", -> editor.normalizeTabsInBufferRange([[0, 0], [Infinity, Infinity]]) expect(editor.getText()).toBe ' ' - describe ".scrollToCursorPosition()", -> - it "scrolls the last cursor into view, centering around the cursor if possible and the 'center' option isn't false", -> - editor.setCursorScreenPosition([8, 8]) - editor.setLineHeightInPixels(10) - editor.setDefaultCharWidth(10) - editor.setHeight(60) - editor.setWidth(130) - editor.setHorizontalScrollbarHeight(0) - expect(editor.getScrollTop()).toBe 0 - expect(editor.getScrollLeft()).toBe 0 - - editor.scrollToCursorPosition() - expect(editor.getScrollTop()).toBe (8.5 * 10) - 30 - expect(editor.getScrollBottom()).toBe (8.5 * 10) + 30 - expect(editor.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10 - - editor.setScrollTop(0) - editor.scrollToCursorPosition(center: false) - expect(editor.getScrollBottom()).toBe (9 + editor.getVerticalScrollMargin()) * 10 - describe ".pageUp/Down()", -> it "moves the cursor down one page length", -> editor.setLineHeightInPixels(10) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index d1fb14007..eac2e7d5c 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -237,6 +237,9 @@ class TextEditorElement extends HTMLElement scrollToBottom: -> @setScrollBottom(Infinity) + scrollToCursorPosition: (options) -> + @getModel().getLastCursor().autoscroll(center: options?.center ? true) + getScrollTop: -> @component.getScrollTop() From d0eabb25fde72f6345924968e5df2ea985b69bdb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:26:51 +0200 Subject: [PATCH 15/58] :fire: Remove assertion about scrolling --- spec/text-editor-spec.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index b0cd021cb..63282b1cb 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -4398,7 +4398,6 @@ describe "TextEditor", -> it "selects one screen height of text up or down", -> editor.setLineHeightInPixels(10) editor.setHeight(50) - expect(editor.getScrollHeight()).toBe 130 expect(editor.getCursorBufferPosition().row).toBe 0 editor.selectPageDown() From 5d42dd557a941f752f1f73c90756865783c3e5f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:28:18 +0200 Subject: [PATCH 16/58] Finish porting TextEditor specs --- spec/text-editor-component-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 9562a7984..0177826f9 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3371,7 +3371,7 @@ describe "TextEditorComponent", -> expect(line1LeafNodes[0].classList.contains('indent-guide')).toBe false expect(line1LeafNodes[1].classList.contains('indent-guide')).toBe false - fffdescribe "autoscroll", -> + describe "autoscroll", -> beforeEach -> editor.setVerticalScrollMargin(2) editor.setHorizontalScrollMargin(2) From 8463d5c59deedd5f606160d883ad2c8d891e8d16 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:38:07 +0200 Subject: [PATCH 17/58] Start porting DisplayBuffer specs --- spec/display-buffer-spec.coffee | 20 -------------------- spec/text-editor-presenter-spec.coffee | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index c7acbfa26..6c3b17f56 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -47,14 +47,6 @@ describe "DisplayBuffer", -> buffer.insert([0, 0], oneHundredLines) expect(displayBuffer.getLineCount()).toBe 100 + originalLineCount - it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", -> - displayBuffer.setHeight(50) - displayBuffer.setLineHeightInPixels(10) - displayBuffer.setScrollTop(80) - - buffer.delete([[8, 0], [10, 0]]) - expect(displayBuffer.getScrollTop()).toBe 60 - it "updates the display buffer prior to invoking change handlers registered on the buffer", -> buffer.onDidChange -> expect(displayBuffer2.tokenizedLineForScreenRow(0).text).toBe "testing" displayBuffer2 = new DisplayBuffer({buffer, tabLength}) @@ -296,18 +288,6 @@ describe "DisplayBuffer", -> displayBuffer.setEditorWidthInChars(-1) expect(displayBuffer.editorWidthInChars).not.toBe -1 - it "sets ::scrollLeft to 0 and keeps it there when soft wrapping is enabled", -> - displayBuffer.setDefaultCharWidth(10) - displayBuffer.setWidth(85) - - displayBuffer.setSoftWrapped(false) - displayBuffer.setScrollLeft(Infinity) - expect(displayBuffer.getScrollLeft()).toBeGreaterThan 0 - displayBuffer.setSoftWrapped(true) - expect(displayBuffer.getScrollLeft()).toBe 0 - displayBuffer.setScrollLeft(10) - expect(displayBuffer.getScrollLeft()).toBe 0 - describe "primitive folding", -> beforeEach -> displayBuffer.destroy() diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 8db6fa90b..6d5f4a13d 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -365,6 +365,18 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollLeft(-300) expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 0 + it "is always 0 when soft wrapping is enabled", -> + presenter = buildPresenter(scrollLeft: 0, verticalScrollbarWidth: 0, contentFrameWidth: 85, baseCharacterWidth: 10) + + editor.setSoftWrapped(false) + presenter.setScrollLeft(Infinity) + expect(presenter.getState().content.scrollLeft).toBeGreaterThan 0 + + editor.setSoftWrapped(true) + expect(presenter.getState().content.scrollLeft).toBe 0 + presenter.setScrollLeft(10) + expect(presenter.getState().content.scrollLeft).toBe 0 + describe ".verticalScrollbar", -> describe ".visible", -> it "is true if the scrollHeight exceeds the computed client height", -> @@ -682,6 +694,12 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollTop(50) expect(presenter.getState().content.scrollTop).toBe 50 + it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", -> + presenter = buildPresenter(scrollTop: 80, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 0) + expect(presenter.getState().content.scrollTop).toBe(80) + buffer.deleteRows(10, 9, 8) + expect(presenter.getState().content.scrollTop).toBe(60) + it "is always rounded to the nearest integer", -> presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20) expect(presenter.getState().content.scrollTop).toBe 10 From e75812cfbfc4b372965b30ed91eb1e155e72d494 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:43:51 +0200 Subject: [PATCH 18/58] :fire: Remove specs ...as we are already testing a similar behavior in the presenter --- spec/display-buffer-spec.coffee | 64 --------------------------------- 1 file changed, 64 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 6c3b17f56..bd26e37d4 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1233,70 +1233,6 @@ describe "DisplayBuffer", -> expect(displayBuffer.getDecorations(class: 'two').length).toEqual 0 expect(displayBuffer.getDecorations(class: 'one').length).toEqual 1 - describe "::setScrollTop", -> - beforeEach -> - displayBuffer.setLineHeightInPixels(10) - - it "disallows negative values", -> - displayBuffer.setHeight(displayBuffer.getScrollHeight() + 100) - expect(displayBuffer.setScrollTop(-10)).toBe 0 - expect(displayBuffer.getScrollTop()).toBe 0 - - it "disallows values that would make ::getScrollBottom() exceed ::getScrollHeight()", -> - displayBuffer.setHeight(50) - maxScrollTop = displayBuffer.getScrollHeight() - displayBuffer.getHeight() - - expect(displayBuffer.setScrollTop(maxScrollTop)).toBe maxScrollTop - expect(displayBuffer.getScrollTop()).toBe maxScrollTop - - expect(displayBuffer.setScrollTop(maxScrollTop + 50)).toBe maxScrollTop - expect(displayBuffer.getScrollTop()).toBe maxScrollTop - - describe "editor.scrollPastEnd", -> - describe "when editor.scrollPastEnd is false", -> - beforeEach -> - atom.config.set("editor.scrollPastEnd", false) - displayBuffer.setLineHeightInPixels(10) - - it "does not add the height of the view to the scroll height", -> - lineHeight = displayBuffer.getLineHeightInPixels() - originalScrollHeight = displayBuffer.getScrollHeight() - displayBuffer.setHeight(50) - - expect(displayBuffer.getScrollHeight()).toBe originalScrollHeight - - describe "when editor.scrollPastEnd is true", -> - beforeEach -> - atom.config.set("editor.scrollPastEnd", true) - displayBuffer.setLineHeightInPixels(10) - - it "adds the height of the view to the scroll height", -> - lineHeight = displayBuffer.getLineHeightInPixels() - originalScrollHeight = displayBuffer.getScrollHeight() - displayBuffer.setHeight(50) - - expect(displayBuffer.getScrollHeight()).toEqual(originalScrollHeight + displayBuffer.height - (lineHeight * 3)) - - describe "::setScrollLeft", -> - beforeEach -> - displayBuffer.setLineHeightInPixels(10) - displayBuffer.setDefaultCharWidth(10) - - it "disallows negative values", -> - displayBuffer.setWidth(displayBuffer.getScrollWidth() + 100) - expect(displayBuffer.setScrollLeft(-10)).toBe 0 - expect(displayBuffer.getScrollLeft()).toBe 0 - - it "disallows values that would make ::getScrollRight() exceed ::getScrollWidth()", -> - displayBuffer.setWidth(50) - maxScrollLeft = displayBuffer.getScrollWidth() - displayBuffer.getWidth() - - expect(displayBuffer.setScrollLeft(maxScrollLeft)).toBe maxScrollLeft - expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft - - expect(displayBuffer.setScrollLeft(maxScrollLeft + 50)).toBe maxScrollLeft - expect(displayBuffer.getScrollLeft()).toBe maxScrollLeft - describe "::scrollToScreenPosition(position, [options])", -> beforeEach -> displayBuffer.setLineHeightInPixels(10) From 49fae9b0293a6f2a15c50f3577380851e920bbe9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:53:20 +0200 Subject: [PATCH 19/58] Test that the logical scroll event is triggered --- spec/display-buffer-spec.coffee | 30 ++++++++---------------------- src/display-buffer.coffee | 10 +--------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index bd26e37d4..899764476 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1234,31 +1234,17 @@ describe "DisplayBuffer", -> expect(displayBuffer.getDecorations(class: 'one').length).toEqual 1 describe "::scrollToScreenPosition(position, [options])", -> - beforeEach -> - displayBuffer.setLineHeightInPixels(10) - displayBuffer.setDefaultCharWidth(10) - displayBuffer.setHorizontalScrollbarHeight(0) - displayBuffer.setHeight(50) - displayBuffer.setWidth(150) - - it "sets the scroll top and scroll left so the given screen position is in view", -> - displayBuffer.scrollToScreenPosition([8, 20]) - expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10 - expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10 + it "triggers ::onDidChangeScrollPosition with the logical coordinates along with the options", -> + scrollSpy = jasmine.createSpy("::onDidChangeScrollPosition") + displayBuffer.onDidChangeScrollPosition(scrollSpy) displayBuffer.scrollToScreenPosition([8, 20]) - expect(displayBuffer.getScrollBottom()).toBe (9 + displayBuffer.getVerticalScrollMargin()) * 10 - expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10 + displayBuffer.scrollToScreenPosition([8, 20], center: true) + displayBuffer.scrollToScreenPosition([8, 20], center: false, reversed: true) - describe "when the 'center' option is true", -> - it "vertically scrolls to center the given position vertically", -> - displayBuffer.scrollToScreenPosition([8, 20], center: true) - expect(displayBuffer.getScrollTop()).toBe (8 * 10) + 5 - (50 / 2) - expect(displayBuffer.getScrollRight()).toBe (20 + displayBuffer.getHorizontalScrollMargin()) * 10 - - it "does not scroll vertically if the position is already in view", -> - displayBuffer.scrollToScreenPosition([4, 20], center: true) - expect(displayBuffer.getScrollTop()).toBe 0 + expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {}) + expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true}) + expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true}) describe "scroll width", -> cursorWidth = 1 diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 509cb4f88..014d0b4f6 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -362,7 +362,7 @@ class DisplayBuffer extends Model {start, end} = selection.getScreenRange() @intersectsVisibleRowRange(start.row, end.row + 1) - logicalScrollToScreenRange: (screenRange, options) -> + logicalScrollToScreenRange: (screenRange, options = {}) -> scrollEvent = {screenRange, options} @emitter.emit "did-change-scroll-position", scrollEvent @@ -389,14 +389,6 @@ class DisplayBuffer extends Model desiredScrollLeft = left - horizontalScrollMarginInPixels desiredScrollRight = right + horizontalScrollMarginInPixels - if global.enableLogs - console.log "====== DB ======" - console.log "Screen Range: #{screenRange.toString()}" - console.log "Client Width: #{@getClientWidth()}" - console.log "#{desiredScrollLeft}/#{desiredScrollRight}" - console.log "#{@getScrollLeft()}/#{@getScrollRight()}" - console.log "================" - if options?.reversed ? true if desiredScrollBottom > @getScrollBottom() @setScrollBottom(desiredScrollBottom) From f1c0658470aed19c2fd94ec6723dc2439d2fb087 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:53:55 +0200 Subject: [PATCH 20/58] :fire: Remove double tested behavior --- spec/display-buffer-spec.coffee | 35 --------------------------------- 1 file changed, 35 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 899764476..1a72ce806 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1246,41 +1246,6 @@ describe "DisplayBuffer", -> expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true}) expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true}) - describe "scroll width", -> - cursorWidth = 1 - beforeEach -> - displayBuffer.setDefaultCharWidth(10) - - it "recomputes the scroll width when the default character width changes", -> - expect(displayBuffer.getScrollWidth()).toBe 10 * 65 + cursorWidth - - displayBuffer.setDefaultCharWidth(12) - expect(displayBuffer.getScrollWidth()).toBe 12 * 65 + cursorWidth - - it "recomputes the scroll width when the max line length changes", -> - buffer.insert([6, 12], ' ') - expect(displayBuffer.getScrollWidth()).toBe 10 * 66 + cursorWidth - - buffer.delete([[6, 10], [6, 12]], ' ') - expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + cursorWidth - - it "recomputes the scroll width when the scoped character widths change", -> - operatorWidth = 20 - displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth) - expect(displayBuffer.getScrollWidth()).toBe 10 * 64 + operatorWidth + cursorWidth - - it "recomputes the scroll width when the scoped character widths change in a batch", -> - operatorWidth = 20 - - displayBuffer.onDidChangeCharacterWidths changedSpy = jasmine.createSpy() - - displayBuffer.batchCharacterMeasurement -> - displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '<', operatorWidth) - displayBuffer.setScopedCharWidth(['source.js', 'keyword.operator.js'], '?', operatorWidth) - - expect(displayBuffer.getScrollWidth()).toBe 10 * 63 + operatorWidth * 2 + cursorWidth - expect(changedSpy.callCount).toBe 1 - describe "::getVisibleRowRange()", -> beforeEach -> displayBuffer.setLineHeightInPixels(10) From fda981ed1daf68f5c4d1fcf7266955a6887cc920 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 10:58:15 +0200 Subject: [PATCH 21/58] :fire: Remove absolute scrolling from DisplayBuffer --- src/display-buffer.coffee | 67 +-------------------------------------- 1 file changed, 1 insertion(+), 66 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 014d0b4f6..ec2dc63b4 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -341,75 +341,10 @@ class DisplayBuffer extends Model getScrollWidth: -> @scrollWidth - # Returns an {Array} of two numbers representing the first and the last visible rows. - getVisibleRowRange: -> - return [0, 0] unless @getLineHeightInPixels() > 0 - - startRow = Math.floor(@getScrollTop() / @getLineHeightInPixels()) - endRow = Math.ceil((@getScrollTop() + @getHeight()) / @getLineHeightInPixels()) - 1 - endRow = Math.min(@getLineCount(), endRow) - - [startRow, endRow] - - getLogicalHeight: -> - Math.floor(@getHeight() / @getLineHeightInPixels()) - - intersectsVisibleRowRange: (startRow, endRow) -> - [visibleStart, visibleEnd] = @getVisibleRowRange() - not (endRow <= visibleStart or visibleEnd <= startRow) - - selectionIntersectsVisibleRowRange: (selection) -> - {start, end} = selection.getScreenRange() - @intersectsVisibleRowRange(start.row, end.row + 1) - - logicalScrollToScreenRange: (screenRange, options = {}) -> + scrollToScreenRange: (screenRange, options = {}) -> scrollEvent = {screenRange, options} @emitter.emit "did-change-scroll-position", scrollEvent - scrollToScreenRange: (screenRange, options) -> - @logicalScrollToScreenRange(screenRange, options) - - verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() - - {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) - {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) - bottom = endTop + endHeight - right = endLeft - - if options?.center - desiredScrollCenter = (top + bottom) / 2 - unless @getScrollTop() < desiredScrollCenter < @getScrollBottom() - desiredScrollTop = desiredScrollCenter - @getHeight() / 2 - desiredScrollBottom = desiredScrollCenter + @getHeight() / 2 - else - desiredScrollTop = top - verticalScrollMarginInPixels - desiredScrollBottom = bottom + verticalScrollMarginInPixels - - desiredScrollLeft = left - horizontalScrollMarginInPixels - desiredScrollRight = right + horizontalScrollMarginInPixels - - if options?.reversed ? true - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) - - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) - else - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) - - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) - scrollToScreenPosition: (screenPosition, options) -> @scrollToScreenRange(new Range(screenPosition, screenPosition), options) From 60fdd3793fdf8b26671bfaa03c4f81bf1e505fcf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 11:23:18 +0200 Subject: [PATCH 22/58] Stop reporting scroll positions to the model --- spec/text-editor-component-spec.coffee | 3 ++- spec/text-editor-presenter-spec.coffee | 8 +++----- src/text-editor-presenter.coffee | 6 ------ 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 0177826f9..46e635088 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3461,7 +3461,8 @@ describe "TextEditorComponent", -> wrapperNode.setScrollTop(0) wrapperNode.scrollToCursorPosition(center: false) - expect(editor.getScrollBottom()).toBe (8 + editor.getVerticalScrollMargin()) * 10 + expect(wrapperNode.getScrollTop()).toBe (7.8 - editor.getVerticalScrollMargin()) * 10 + expect(wrapperNode.getScrollBottom()).toBe (9.3 + editor.getVerticalScrollMargin()) * 10 describe "moving cursors", -> it "scrolls down when the last cursor gets closer than ::verticalScrollMargin to the bottom of the editor", -> diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 6d5f4a13d..0aacef524 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' -describe "TextEditorPresenter", -> +fdescribe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. # Please maintain this structure when adding specs for new state fields. describe "::getState()", -> @@ -523,14 +523,12 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollLeft(70) expectValues presenter.getState().hiddenInput, {top: 0, left: 0} - global.enableLogs = true expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43]) - expectValues presenter.getState().hiddenInput, {top: 11 * 10 - editor.getScrollTop(), left: 43 * 10 - editor.getScrollLeft()} - global.enableLogs = false + expectValues presenter.getState().hiddenInput, {top: 11 * 10 - presenter.getScrollTop(), left: 43 * 10 - presenter.getScrollLeft()} newCursor = null expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10]) - expectValues presenter.getState().hiddenInput, {top: (6 * 10) - editor.getScrollTop(), left: (10 * 10) - editor.getScrollLeft()} + expectValues presenter.getState().hiddenInput, {top: (6 * 10) - presenter.getScrollTop(), left: (10 * 10) - presenter.getScrollLeft()} expectStateUpdate presenter, -> newCursor.destroy() expectValues presenter.getState().hiddenInput, {top: 50 - 10, left: 300 - 10} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 6c6f56881..3bba67275 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -54,10 +54,6 @@ class TextEditorPresenter @model.setWidth(@contentFrameWidth) if @contentFrameWidth? @model.setLineHeightInPixels(@lineHeight) if @lineHeight? @model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth? - @model.setScrollTop(@scrollTop) if @scrollTop? - @model.setScrollLeft(@scrollLeft) if @scrollLeft? - @model.setVerticalScrollbarWidth(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth? - @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? # Private: Determines whether {TextEditorPresenter} is currently batching changes. # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them. @@ -1538,13 +1534,11 @@ class TextEditorPresenter scrollLeft = Math.round(@constrainScrollLeft(@pendingScrollLeft)) if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @scrollLeft = scrollLeft - @model.setScrollLeft(scrollLeft) commitPendingScrollTopPosition: -> scrollTop = Math.round(@constrainScrollTop(@pendingScrollTop)) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @scrollTop = scrollTop - @model.setScrollTop(scrollTop) updateScrollPosition: -> @commitPendingLogicalScrollPosition() if @pendingScrollLogicalPosition? From abd1b05c65df1b058f2715221be5ae7701ac98b2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 11:44:37 +0200 Subject: [PATCH 23/58] Report the already calculated height to the model --- src/display-buffer.coffee | 16 ++++------------ src/text-editor-presenter.coffee | 4 ++-- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index ec2dc63b4..2e94307b6 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -200,21 +200,13 @@ class DisplayBuffer extends Model setVerticalScrollbarWidth: (@verticalScrollbarWidth) -> @verticalScrollbarWidth getHeight: -> - if @height? - @height - else - if @horizontallyScrollable() - @getScrollHeight() + @getHorizontalScrollbarHeight() - else - @getScrollHeight() + @height or 0 - setHeight: (@height) -> @height + setHeight: (@height) -> + @height getClientHeight: (reentrant) -> - if @horizontallyScrollable(reentrant) - @getHeight() - @getHorizontalScrollbarHeight() - else - @getHeight() + @getHeight() getClientWidth: (reentrant) -> if @verticallyScrollable(reentrant) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 3bba67275..c999b51de 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -50,7 +50,7 @@ class TextEditorPresenter @emitter.emit "did-update-state" if @isBatching() transferMeasurementsToModel: -> - @model.setHeight(@explicitHeight) if @explicitHeight? + # @model.setHeight(@explicitHeight) if @explicitHeight? @model.setWidth(@contentFrameWidth) if @contentFrameWidth? @model.setLineHeightInPixels(@lineHeight) if @lineHeight? @model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth? @@ -688,6 +688,7 @@ class TextEditorPresenter clientHeight = @height - @horizontalScrollbarHeight unless @clientHeight is clientHeight @clientHeight = clientHeight + @model.setHeight(@clientHeight) @updateScrollHeight() @updateScrollTop() @@ -919,7 +920,6 @@ class TextEditorPresenter setExplicitHeight: (explicitHeight) -> unless @explicitHeight is explicitHeight @explicitHeight = explicitHeight - @model.setHeight(explicitHeight) @updateHeight() @shouldUpdateVerticalScrollState = true @shouldUpdateScrollbarsState = true From 140624326a78ade9868f032bc2641202a00704d7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 14:29:08 +0200 Subject: [PATCH 24/58] Report the already calculated width to the model --- src/display-buffer.coffee | 78 +------------------------------- src/text-editor-presenter.coffee | 5 +- 2 files changed, 4 insertions(+), 79 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 2e94307b6..d374b909f 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -205,78 +205,15 @@ class DisplayBuffer extends Model setHeight: (@height) -> @height - getClientHeight: (reentrant) -> - @getHeight() - - getClientWidth: (reentrant) -> - if @verticallyScrollable(reentrant) - @getWidth() - @getVerticalScrollbarWidth() - else - @getWidth() - - horizontallyScrollable: (reentrant) -> - return false unless @width? - return false if @isSoftWrapped() - if reentrant - @getScrollWidth() > @getWidth() - else - @getScrollWidth() > @getClientWidth(true) - - verticallyScrollable: (reentrant) -> - return false unless @height? - if reentrant - @getScrollHeight() > @getHeight() - else - @getScrollHeight() > @getClientHeight(true) - getWidth: -> - if @width? - @width - else - if @verticallyScrollable() - @getScrollWidth() + @getVerticalScrollbarWidth() - else - @getScrollWidth() + @width or 0 setWidth: (newWidth) -> oldWidth = @width @width = newWidth @updateWrappedScreenLines() if newWidth isnt oldWidth and @isSoftWrapped() - @setScrollTop(@getScrollTop()) # Ensure scrollTop is still valid in case horizontal scrollbar disappeared @width - getScrollTop: -> @scrollTop - setScrollTop: (scrollTop) -> - scrollTop = Math.round(Math.max(0, Math.min(@getMaxScrollTop(), scrollTop))) - unless scrollTop is @scrollTop - @scrollTop = scrollTop - @emitter.emit 'did-change-scroll-top', @scrollTop - @scrollTop - - getMaxScrollTop: -> - @getScrollHeight() - @getClientHeight() - - getScrollBottom: -> @scrollTop + @getClientHeight() - setScrollBottom: (scrollBottom) -> - @setScrollTop(scrollBottom - @getClientHeight()) - @getScrollBottom() - - getScrollLeft: -> @scrollLeft - setScrollLeft: (scrollLeft) -> - scrollLeft = Math.round(Math.max(0, Math.min(@getScrollWidth() - @getClientWidth(), scrollLeft))) - unless scrollLeft is @scrollLeft - @scrollLeft = scrollLeft - @emitter.emit 'did-change-scroll-left', @scrollLeft - @scrollLeft - - getMaxScrollLeft: -> - @getScrollWidth() - @getClientWidth() - - getScrollRight: -> @scrollLeft + @width - setScrollRight: (scrollRight) -> - @setScrollLeft(scrollRight - @width) - @getScrollRight() - getLineHeightInPixels: -> @lineHeightInPixels setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels @@ -284,7 +221,6 @@ class DisplayBuffer extends Model setDefaultCharWidth: (defaultCharWidth) -> if defaultCharWidth isnt @defaultCharWidth @defaultCharWidth = defaultCharWidth - @computeScrollWidth() defaultCharWidth getCursorWidth: -> 1 @@ -313,7 +249,6 @@ class DisplayBuffer extends Model @characterWidthsChanged() unless @batchingCharacterMeasurement characterWidthsChanged: -> - @computeScrollWidth() @emit 'character-widths-changed', @scopedCharacterWidthsChangeCount if Grim.includeDeprecatedAPIs @emitter.emit 'did-change-character-widths', @scopedCharacterWidthsChangeCount @@ -400,8 +335,7 @@ class DisplayBuffer extends Model # Returns the editor width in characters for soft wrap. getEditorWidthInChars: -> - width = @width ? @getScrollWidth() - width -= @getVerticalScrollbarWidth() + width = @getWidth() if width? and @defaultCharWidth > 0 Math.max(0, Math.floor(width / @defaultCharWidth)) else @@ -1128,7 +1062,6 @@ class DisplayBuffer extends Model @changeCount = @tokenizedBuffer.changeCount {start, end, delta, bufferChange} = tokenizedBufferChange @updateScreenLines(start, end + 1, delta, refreshMarkers: false) - @setScrollTop(Math.min(@getScrollTop(), @getMaxScrollTop())) if delta < 0 updateScreenLines: (startBufferRow, endBufferRow, bufferDelta=0, options={}) -> return if @largeFileMode @@ -1233,13 +1166,6 @@ class DisplayBuffer extends Model @longestScreenRow = screenRow @maxLineLength = length - @computeScrollWidth() if oldMaxLineLength isnt @maxLineLength - - computeScrollWidth: -> - @scrollWidth = @pixelPositionForScreenPosition([@longestScreenRow, @maxLineLength]).left - @scrollWidth += 1 unless @isSoftWrapped() - @setScrollLeft(Math.min(@getScrollLeft(), @getMaxScrollLeft())) - handleBufferMarkerCreated: (textBufferMarker) => if textBufferMarker.matchesParams(@getFoldMarkerAttributes()) fold = new Fold(this, textBufferMarker) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c999b51de..c6ef81c3b 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -50,8 +50,6 @@ class TextEditorPresenter @emitter.emit "did-update-state" if @isBatching() transferMeasurementsToModel: -> - # @model.setHeight(@explicitHeight) if @explicitHeight? - @model.setWidth(@contentFrameWidth) if @contentFrameWidth? @model.setLineHeightInPixels(@lineHeight) if @lineHeight? @model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth? @@ -679,6 +677,7 @@ class TextEditorPresenter @updateScrollHeight() if @contentWidth isnt oldContentWidth + @model.setWidth(Math.min(@clientWidth, @contentWidth)) @updateScrollbarDimensions() @updateScrollWidth() @@ -698,6 +697,7 @@ class TextEditorPresenter clientWidth = @contentFrameWidth - @verticalScrollbarWidth unless @clientWidth is clientWidth @clientWidth = clientWidth + @model.setWidth(Math.min(@clientWidth, @contentWidth)) @updateScrollWidth() @updateScrollLeft() @@ -944,7 +944,6 @@ class TextEditorPresenter unless @contentFrameWidth is contentFrameWidth oldContentFrameWidth = @contentFrameWidth @contentFrameWidth = contentFrameWidth - @model.setWidth(contentFrameWidth) @updateScrollbarDimensions() @updateClientWidth() @shouldUpdateVerticalScrollState = true From fffcfb3405edd0ec045f2d298fa8f98af547363b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 14:49:11 +0200 Subject: [PATCH 25/58] Save scroll positions in the model to serialize 'em --- spec/text-editor-presenter-spec.coffee | 2 +- src/display-buffer.coffee | 13 +++++++++++-- src/text-editor-presenter.coffee | 2 ++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 0aacef524..bb2e492eb 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' -fdescribe "TextEditorPresenter", -> +describe "TextEditorPresenter", -> # These `describe` and `it` blocks mirror the structure of the ::state object. # Please maintain this structure when adding specs for new state fields. describe "::getState()", -> diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index d374b909f..42ca817ab 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -98,8 +98,6 @@ class DisplayBuffer extends Model copy: -> newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @largeFileMode}) - newDisplayBuffer.setScrollTop(@getScrollTop()) - newDisplayBuffer.setScrollLeft(@getScrollLeft()) for marker in @findMarkers(displayBufferId: @id) marker.copy(displayBufferId: newDisplayBuffer.id) @@ -1178,6 +1176,17 @@ class DisplayBuffer extends Model @emit 'marker-created', marker if Grim.includeDeprecatedAPIs @emitter.emit 'did-create-marker', marker + # TODO: serialize state in TextEditorElement, rather than saving scroll + # positions here. + + getScrollTop: -> @scrollTop + + setScrollTop: (@scrollTop) -> + + getScrollLeft: -> @scrollLeft + + setScrollLeft: (@scrollLeft) -> + decorateFold: (fold) -> @decorateMarker(fold.marker, type: 'line-number', class: 'folded') diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c6ef81c3b..0c3294b04 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1533,11 +1533,13 @@ class TextEditorPresenter scrollLeft = Math.round(@constrainScrollLeft(@pendingScrollLeft)) if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @scrollLeft = scrollLeft + @model.setScrollLeft(@scrollLeft) commitPendingScrollTopPosition: -> scrollTop = Math.round(@constrainScrollTop(@pendingScrollTop)) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @scrollTop = scrollTop + @model.setScrollTop(@scrollTop) updateScrollPosition: -> @commitPendingLogicalScrollPosition() if @pendingScrollLogicalPosition? From b198acc995fdeeb348ac7948bc66c6dbd1394570 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 15:24:35 +0200 Subject: [PATCH 26/58] Fix soft wrapping when editorWidthInChars is used --- spec/text-editor-presenter-spec.coffee | 6 ++++++ src/display-buffer.coffee | 4 ++-- src/text-editor-presenter.coffee | 14 ++++++++++---- src/text-editor.coffee | 4 ++++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index bb2e492eb..d726685c5 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -2132,6 +2132,12 @@ describe "TextEditorPresenter", -> expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 3, softWrapped: true} expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 4, softWrapped: false} + presenter.setContentFrameWidth(500) + + expectValues lineNumberStateForScreenRow(presenter, 5), {screenRow: 5, bufferRow: 4, softWrapped: false} + expectValues lineNumberStateForScreenRow(presenter, 6), {screenRow: 6, bufferRow: 5, softWrapped: false} + expectValues lineNumberStateForScreenRow(presenter, 7), {screenRow: 7, bufferRow: 6, softWrapped: false} + describe ".decorationClasses", -> it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", -> marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 42ca817ab..8679ad868 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -198,13 +198,13 @@ class DisplayBuffer extends Model setVerticalScrollbarWidth: (@verticalScrollbarWidth) -> @verticalScrollbarWidth getHeight: -> - @height or 0 + @height setHeight: (@height) -> @height getWidth: -> - @width or 0 + @width setWidth: (newWidth) -> oldWidth = @width diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 0c3294b04..77bb87f53 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -32,6 +32,7 @@ class TextEditorPresenter @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterNameAndScreenRow = {} @transferMeasurementsToModel() + @transferMeasurementsFromModel() @observeModel() @observeConfig() @buildState() @@ -53,6 +54,9 @@ class TextEditorPresenter @model.setLineHeightInPixels(@lineHeight) if @lineHeight? @model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth? + transferMeasurementsFromModel: -> + @editorWidthInChars = @model.getEditorWidthInChars() + # Private: Determines whether {TextEditorPresenter} is currently batching changes. # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them. isBatching: -> @@ -677,7 +681,6 @@ class TextEditorPresenter @updateScrollHeight() if @contentWidth isnt oldContentWidth - @model.setWidth(Math.min(@clientWidth, @contentWidth)) @updateScrollbarDimensions() @updateScrollWidth() @@ -685,9 +688,10 @@ class TextEditorPresenter return unless @height? and @horizontalScrollbarHeight? clientHeight = @height - @horizontalScrollbarHeight + @model.setHeight(clientHeight) + unless @clientHeight is clientHeight @clientHeight = clientHeight - @model.setHeight(@clientHeight) @updateScrollHeight() @updateScrollTop() @@ -695,9 +699,10 @@ class TextEditorPresenter return unless @contentFrameWidth? and @verticalScrollbarWidth? clientWidth = @contentFrameWidth - @verticalScrollbarWidth + @model.setWidth(clientWidth) unless @editorWidthInChars + unless @clientWidth is clientWidth @clientWidth = clientWidth - @model.setWidth(Math.min(@clientWidth, @contentWidth)) @updateScrollWidth() @updateScrollLeft() @@ -941,9 +946,10 @@ class TextEditorPresenter @updateEndRow() setContentFrameWidth: (contentFrameWidth) -> - unless @contentFrameWidth is contentFrameWidth + if @contentFrameWidth isnt contentFrameWidth or @editorWidthInChars? oldContentFrameWidth = @contentFrameWidth @contentFrameWidth = contentFrameWidth + @editorWidthInChars = null @updateScrollbarDimensions() @updateClientWidth() @shouldUpdateVerticalScrollState = true diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 9f3e230fb..c77b2e3fb 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -554,6 +554,10 @@ class TextEditor extends Model setEditorWidthInChars: (editorWidthInChars) -> @displayBuffer.setEditorWidthInChars(editorWidthInChars) + # Returns the editor width in characters. + getEditorWidthInChars: -> + @displayBuffer.getEditorWidthInChars() + ### Section: File Details ### From 870a5303e116e7fe4e01b52752c01fcdaa581572 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 15:33:34 +0200 Subject: [PATCH 27/58] :fire: Remove unused code --- src/display-buffer.coffee | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 8679ad868..91310a0d6 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -253,16 +253,6 @@ class DisplayBuffer extends Model clearScopedCharWidths: -> @charWidthsByScope = {} - getScrollHeight: -> - lineHeight = @getLineHeightInPixels() - return 0 unless lineHeight > 0 - - scrollHeight = @getLineCount() * lineHeight - if @height? and @configSettings.scrollPastEnd - scrollHeight = scrollHeight + @height - (lineHeight * 3) - - scrollHeight - getScrollWidth: -> @scrollWidth @@ -276,19 +266,6 @@ class DisplayBuffer extends Model scrollToBufferPosition: (bufferPosition, options) -> @scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition), options) - pixelRectForScreenRange: (screenRange) -> - if screenRange.end.row > screenRange.start.row - top = @pixelPositionForScreenPosition(screenRange.start).top - left = 0 - height = (screenRange.end.row - screenRange.start.row + 1) * @getLineHeightInPixels() - width = @getScrollWidth() - else - {top, left} = @pixelPositionForScreenPosition(screenRange.start, false) - height = @getLineHeightInPixels() - width = @pixelPositionForScreenPosition(screenRange.end, false).left - left - - {top, left, width, height} - # Retrieves the current tab length. # # Returns a {Number}. From 0f7d88c77b28879c0a8ddb2ceb42cc286dc136a6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 16:14:15 +0200 Subject: [PATCH 28/58] :green_heart: Fix remaining failures in TextEditorPresenter --- spec/text-editor-presenter-spec.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index d726685c5..7e5c43ee1 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1853,9 +1853,10 @@ describe "TextEditorPresenter", -> pixelPosition: {top: 6 * 10 - scrollTop, left: gutterWidth} } - expectStateUpdate presenter, -> - editor.insertNewline() - presenter.setScrollTop(scrollTop) # I'm fighting the editor + editor.insertNewline() + presenter.getState() # forces scroll top to be changed + presenter.setScrollTop(scrollTop) # I'm fighting the editor + expectValues stateForOverlay(presenter, decoration), { item: item pixelPosition: {top: 6 * 10 - scrollTop - itemHeight, left: gutterWidth} From 87c7a0ae30c8f2530dcb9d8e5bb7d5de6fa7be8d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 16:25:50 +0200 Subject: [PATCH 29/58] :green_heart: Fix some TextEditorComponent specs --- spec/text-editor-component-spec.coffee | 18 +++++++++--------- src/text-editor-component.coffee | 6 ++++++ src/text-editor-element.coffee | 6 ++++++ src/text-editor-presenter.coffee | 6 ++++++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 46e635088..74abcadb9 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -285,18 +285,18 @@ describe "TextEditorComponent", -> componentNode.style.width = gutterWidth + (30 * charWidth) + 'px' component.measureDimensions() nextAnimationFrame() - expect(editor.getScrollWidth()).toBeGreaterThan scrollViewNode.offsetWidth + expect(wrapperNode.getScrollWidth()).toBeGreaterThan scrollViewNode.offsetWidth # At the time of writing, using width: 100% to achieve the full-width # lines caused full-screen repaints after switching away from an editor # and back again Please ensure you don't cause a performance regression if # you change this behavior. - editorFullWidth = editor.getScrollWidth() + editor.getVerticalScrollbarWidth() + editorFullWidth = wrapperNode.getScrollWidth() + wrapperNode.getVerticalScrollbarWidth() for lineNode in lineNodes expect(lineNode.getBoundingClientRect().width).toBe(editorFullWidth) - componentNode.style.width = gutterWidth + editor.getScrollWidth() + 100 + 'px' + componentNode.style.width = gutterWidth + wrapperNode.getScrollWidth() + 100 + 'px' component.measureDimensions() nextAnimationFrame() scrollViewWidth = scrollViewNode.offsetWidth @@ -473,7 +473,7 @@ describe "TextEditorComponent", -> editor.setText "a line that wraps \n" editor.setSoftWrapped(true) nextAnimationFrame() - componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px' + componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px' component.measureDimensions() nextAnimationFrame() @@ -893,7 +893,7 @@ describe "TextEditorComponent", -> beforeEach -> editor.setSoftWrapped(true) nextAnimationFrame() - componentNode.style.width = 16 * charWidth + editor.getVerticalScrollbarWidth() + 'px' + componentNode.style.width = 16 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px' component.measureDimensions() nextAnimationFrame() @@ -2244,7 +2244,7 @@ describe "TextEditorComponent", -> editor.setSoftWrapped(true) nextAnimationFrame() - componentNode.style.width = 21 * charWidth + editor.getVerticalScrollbarWidth() + 'px' + componentNode.style.width = 21 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px' component.measureDimensions() nextAnimationFrame() @@ -2460,7 +2460,7 @@ describe "TextEditorComponent", -> wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' wrapperNode.style.width = 10 * charWidth + 'px' component.measureDimensions() - wrapperNode.setScrollBottom(editor.getScrollHeight()) + wrapperNode.setScrollBottom(wrapperNode.getScrollHeight()) nextAnimationFrame() lastLineNode = component.lineNodeForScreenRow(editor.getLastScreenRow()) bottomOfLastLine = lastLineNode.getBoundingClientRect().bottom @@ -2570,7 +2570,7 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - expect(horizontalScrollbarNode.scrollWidth).toBe editor.getScrollWidth() + expect(horizontalScrollbarNode.scrollWidth).toBe wrapperNode.getScrollWidth() expect(horizontalScrollbarNode.style.left).toBe '0px' describe "mousewheel events", -> @@ -3101,7 +3101,7 @@ describe "TextEditorComponent", -> expect(componentNode.querySelectorAll('.line')).toHaveLength(6) gutterWidth = componentNode.querySelector('.gutter').offsetWidth - componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px' + componentNode.style.width = gutterWidth + 14 * charWidth + wrapperNode.getVerticalScrollbarWidth() + 'px' atom.views.performDocumentPoll() nextAnimationFrame() expect(componentNode.querySelector('.line').textContent).toBe "var quicksort " diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index cb6329f05..1d265415c 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -388,6 +388,12 @@ class TextEditorComponent getScrollWidth: -> @presenter.getScrollWidth() + getVerticalScrollbarWidth: -> + @presenter.getVerticalScrollbarWidth() + + getHorizontalScrollbarHeight: -> + @presenter.getHorizontalScrollbarHeight() + 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 eac2e7d5c..9d139eb11 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -258,6 +258,12 @@ class TextEditorElement extends HTMLElement getScrollWidth: -> @component.getScrollWidth() + getVerticalScrollbarWidth: -> + @component.getVerticalScrollbarWidth() + + getHorizontalScrollbarHeight: -> + @component.getHorizontalScrollbarHeight() + stopEventPropagation = (commandListeners) -> newCommandListeners = {} for commandName, commandListener of commandListeners diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 77bb87f53..8dc9a90a9 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1486,6 +1486,12 @@ class TextEditorPresenter getHorizontalScrollMarginInPixels: -> @model.getHorizontalScrollMargin() * @baseCharacterWidth + getVerticalScrollbarWidth: -> + @verticalScrollbarWidth + + getHorizontalScrollbarHeight: -> + @horizontalScrollbarHeight + commitPendingLogicalScrollPosition: -> {screenRange, options} = @pendingScrollLogicalPosition From 7666e4b82ee792828e1d07af1a818a773ea3032e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 16:48:00 +0200 Subject: [PATCH 30/58] Commit pending positions when a frame is being served --- src/text-editor-component.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 1d265415c..4db79e716 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -316,6 +316,7 @@ class TextEditorComponent pendingScrollTop = @pendingScrollTop @pendingScrollTop = null @presenter.setScrollTop(pendingScrollTop) + @presenter.commitPendingScrollTopPosition() onHorizontalScroll: (scrollLeft) => return if @updateRequested or scrollLeft is @editor.getScrollLeft() @@ -325,6 +326,7 @@ class TextEditorComponent unless animationFramePending @requestAnimationFrame => @presenter.setScrollLeft(@pendingScrollLeft) + @presenter.commitPendingScrollLeftPosition() @pendingScrollLeft = null onMouseWheel: (event) => @@ -575,9 +577,11 @@ class TextEditorComponent if mouseYDelta? @presenter.setScrollTop(@presenter.getScrollTop() + yDirection * scaleScrollDelta(mouseYDelta)) + @presenter.commitPendingScrollTopPosition() if mouseXDelta? @presenter.setScrollLeft(@presenter.getScrollLeft() + xDirection * scaleScrollDelta(mouseXDelta)) + @presenter.commitPendingScrollLeftPosition() scaleScrollDelta = (scrollDelta) -> Math.pow(scrollDelta / 2, 3) / 280 From baf27bf99a34db13e7d2750eef09d31e9147ef65 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 17:40:48 +0200 Subject: [PATCH 31/58] Preserve un-rounded scroll positions --- src/text-editor-component.coffee | 8 ++++---- src/text-editor-presenter.coffee | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 4db79e716..ef2af790a 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -809,10 +809,10 @@ class TextEditorComponent {clientX, clientY} = event linesClientRect ?= @linesComponent.getDomNode().getBoundingClientRect() - top = clientY - linesClientRect.top + @presenter.scrollTop - left = clientX - linesClientRect.left + @presenter.scrollLeft - bottom = linesClientRect.top + @presenter.scrollTop + linesClientRect.height - clientY - right = linesClientRect.left + @presenter.scrollLeft + linesClientRect.width - clientX + top = clientY - linesClientRect.top + @presenter.getRealScrollTop() + left = clientX - linesClientRect.left + @presenter.getRealScrollLeft() + bottom = linesClientRect.top + @presenter.getRealScrollTop() + linesClientRect.height - clientY + right = linesClientRect.left + @presenter.getRealScrollLeft() + linesClientRect.width - clientX {top, left, bottom, right} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 8dc9a90a9..9474e9c5f 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -830,6 +830,9 @@ class TextEditorPresenter getScrollTop: -> @scrollTop + getRealScrollTop: -> + @realScrollTop ? @scrollTop + didStartScrolling: -> if @stoppedScrollingTimeoutId? clearTimeout(@stoppedScrollingTimeoutId) @@ -865,6 +868,9 @@ class TextEditorPresenter getScrollLeft: -> @scrollLeft + getRealScrollLeft: -> + @realScrollLeft ? @scrollLeft + getClientHeight: -> if @clientHeight @clientHeight @@ -1542,15 +1548,17 @@ class TextEditorPresenter @setScrollRight(desiredScrollRight) commitPendingScrollLeftPosition: -> - scrollLeft = Math.round(@constrainScrollLeft(@pendingScrollLeft)) + scrollLeft = @constrainScrollLeft(@pendingScrollLeft) if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) - @scrollLeft = scrollLeft + @realScrollLeft = scrollLeft + @scrollLeft = Math.round(scrollLeft) @model.setScrollLeft(@scrollLeft) commitPendingScrollTopPosition: -> - scrollTop = Math.round(@constrainScrollTop(@pendingScrollTop)) + scrollTop = @constrainScrollTop(@pendingScrollTop) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) - @scrollTop = scrollTop + @realScrollTop = scrollTop + @scrollTop = Math.round(scrollTop) @model.setScrollTop(@scrollTop) updateScrollPosition: -> From e85f61cced5d68df52a4102f9ba76fe6eb54515e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 17:43:53 +0200 Subject: [PATCH 32/58] Continue emitting events when setting scroll top in the model --- src/display-buffer.coffee | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 91310a0d6..a733b297d 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -1158,11 +1158,19 @@ class DisplayBuffer extends Model getScrollTop: -> @scrollTop - setScrollTop: (@scrollTop) -> + setScrollTop: (scrollTop) -> + unless scrollTop is @scrollTop + @scrollTop = scrollTop + @emitter.emit 'did-change-scroll-top', @scrollTop + @scrollTop getScrollLeft: -> @scrollLeft - setScrollLeft: (@scrollLeft) -> + setScrollLeft: (scrollLeft) -> + unless scrollLeft is @scrollLeft + @scrollLeft = scrollLeft + @emitter.emit 'did-change-scroll-left', @scrollLeft + @scrollLeft decorateFold: (fold) -> @decorateMarker(fold.marker, type: 'line-number', class: 'folded') From 2112b0a302df695771d27b155cf53d30e2901fa4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 17:49:41 +0200 Subject: [PATCH 33/58] :fire: Remove unused method --- src/text-editor.coffee | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index c77b2e3fb..d2f92fcd2 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -3036,12 +3036,6 @@ class TextEditor extends Model getScrollHeight: -> @displayBuffer.getScrollHeight() getScrollWidth: -> @displayBuffer.getScrollWidth() - getVisibleRowRange: -> @displayBuffer.getVisibleRowRange() - - intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow) - - selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection) - screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition) pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange) From 36b0aa01265d9f4b5fd80cffb8085b351c368dd0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 18:45:32 +0200 Subject: [PATCH 34/58] :fire: --- spec/text-editor-component-spec.coffee | 4 ++-- src/text-editor-element.coffee | 3 --- src/text-editor-presenter.coffee | 2 -- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 74abcadb9..05936f1d8 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3453,14 +3453,14 @@ describe "TextEditorComponent", -> expect(wrapperNode.getScrollTop()).toBe 0 expect(wrapperNode.getScrollLeft()).toBe 0 - wrapperNode.scrollToCursorPosition() + editor.scrollToCursorPosition() nextAnimationFrame() expect(wrapperNode.getScrollTop()).toBe (8.8 * 10) - 30 expect(wrapperNode.getScrollBottom()).toBe (8.3 * 10) + 30 expect(wrapperNode.getScrollRight()).toBe (9 + editor.getHorizontalScrollMargin()) * 10 wrapperNode.setScrollTop(0) - wrapperNode.scrollToCursorPosition(center: false) + editor.scrollToCursorPosition(center: false) expect(wrapperNode.getScrollTop()).toBe (7.8 - editor.getVerticalScrollMargin()) * 10 expect(wrapperNode.getScrollBottom()).toBe (9.3 + editor.getVerticalScrollMargin()) * 10 diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 9d139eb11..03ed3c422 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -237,9 +237,6 @@ class TextEditorElement extends HTMLElement scrollToBottom: -> @setScrollBottom(Infinity) - scrollToCursorPosition: (options) -> - @getModel().getLastCursor().autoscroll(center: options?.center ? true) - getScrollTop: -> @component.getScrollTop() diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 9474e9c5f..922f06805 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1552,14 +1552,12 @@ class TextEditorPresenter if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @realScrollLeft = scrollLeft @scrollLeft = Math.round(scrollLeft) - @model.setScrollLeft(@scrollLeft) commitPendingScrollTopPosition: -> scrollTop = @constrainScrollTop(@pendingScrollTop) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @realScrollTop = scrollTop @scrollTop = Math.round(scrollTop) - @model.setScrollTop(@scrollTop) updateScrollPosition: -> @commitPendingLogicalScrollPosition() if @pendingScrollLogicalPosition? From 6f120de1a6b330b32a2e198d4efb3cf225df4b3a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 23 Sep 2015 18:52:04 +0200 Subject: [PATCH 35/58] Bring back the visible row range API --- src/text-editor.coffee | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 999a70e3a..2edffe30b 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -3007,6 +3007,12 @@ class TextEditor extends Model getScrollHeight: -> @displayBuffer.getScrollHeight() getScrollWidth: -> @displayBuffer.getScrollWidth() + getVisibleRowRange: -> @displayBuffer.getVisibleRowRange() + + intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow) + + selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection) + screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition) pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange) From 1f81c633e077a8beaeb05404ce20a0a1821a2b38 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 09:01:51 +0200 Subject: [PATCH 36/58] Serialize state using logical coordinates --- spec/text-editor-presenter-spec.coffee | 47 +++++++++++++++++++++----- src/display-buffer.coffee | 7 ---- src/text-editor-component.coffee | 4 +-- src/text-editor-presenter.coffee | 12 +++++-- src/text-editor.coffee | 10 ++++-- 5 files changed, 59 insertions(+), 21 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 7e5c43ee1..496f05dde 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -333,14 +333,6 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollLeft(50) expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 50 - it "is always rounded to the nearest integer", -> - presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) - expect(presenter.getState().content.scrollLeft).toBe 10 - expectStateUpdate presenter, -> presenter.setScrollLeft(11.4) - expect(presenter.getState().content.scrollLeft).toBe 11 - expectStateUpdate presenter, -> presenter.setScrollLeft(12.6) - expect(presenter.getState().content.scrollLeft).toBe 13 - it "never exceeds the computed scrollWidth minus the computed clientWidth", -> presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, explicitHeight: 100, contentFrameWidth: 500) expectStateUpdate presenter, -> presenter.setScrollLeft(300) @@ -574,6 +566,7 @@ describe "TextEditorPresenter", -> advanceClock(100) expect(presenter.getState().content.scrollingVertically).toBe true presenter.setScrollTop(10) + presenter.getState() # commits scroll position advanceClock(100) expect(presenter.getState().content.scrollingVertically).toBe true expectStateUpdate presenter, -> advanceClock(100) @@ -686,12 +679,27 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 describe ".scrollTop", -> + it "corresponds to the passed logical coordinates when building the presenter", -> + presenter = buildPresenter(scrollRow: 4, lineHeight: 10, explicitHeight: 20) + expect(presenter.getState().content.scrollTop).toBe(40) + it "tracks the value of ::scrollTop", -> presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20) expect(presenter.getState().content.scrollTop).toBe 10 expectStateUpdate presenter, -> presenter.setScrollTop(50) expect(presenter.getState().content.scrollTop).toBe 50 + it "keeps the model up to date with the corresponding logical coordinates", -> + presenter = buildPresenter(scrollTop: 0, explicitHeight: 20, horizontalScrollbarHeight: 10, lineHeight: 10) + + expectStateUpdate presenter, -> presenter.setScrollTop(50) + presenter.getState() # commits scroll position + expect(editor.getScrollRow()).toBe(5) + + expectStateUpdate presenter, -> presenter.setScrollTop(57) + presenter.getState() # commits scroll position + expect(editor.getScrollRow()).toBe(6) + it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", -> presenter = buildPresenter(scrollTop: 80, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 0) expect(presenter.getState().content.scrollTop).toBe(80) @@ -753,12 +761,35 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight describe ".scrollLeft", -> + it "corresponds to the passed logical coordinates when building the presenter", -> + presenter = buildPresenter(scrollColumn: 3, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) + expect(presenter.getState().content.scrollLeft).toBe(30) + it "tracks the value of ::scrollLeft", -> presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expect(presenter.getState().content.scrollLeft).toBe 10 expectStateUpdate presenter, -> presenter.setScrollLeft(50) expect(presenter.getState().content.scrollLeft).toBe 50 + it "keeps the model up to date with the corresponding logical coordinates", -> + presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) + + expectStateUpdate presenter, -> presenter.setScrollLeft(50) + presenter.getState() # commits scroll position + expect(editor.getScrollColumn()).toBe(5) + + expectStateUpdate presenter, -> presenter.setScrollLeft(57) + presenter.getState() # commits scroll position + expect(editor.getScrollColumn()).toBe(6) + + it "is always rounded to the nearest integer", -> + presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) + expect(presenter.getState().content.scrollLeft).toBe 10 + expectStateUpdate presenter, -> presenter.setScrollLeft(11.4) + expect(presenter.getState().content.scrollLeft).toBe 11 + expectStateUpdate presenter, -> presenter.setScrollLeft(12.6) + expect(presenter.getState().content.scrollLeft).toBe 13 + it "never exceeds the computed scrollWidth minus the computed clientWidth", -> presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expectStateUpdate presenter, -> presenter.setScrollLeft(300) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 838e41257..7b544a16e 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -26,11 +26,6 @@ class DisplayBuffer extends Model defaultCharWidth: null height: null width: null - scrollTop: 0 - scrollLeft: 0 - scrollWidth: 0 - verticalScrollbarWidth: 15 - horizontalScrollbarHeight: 15 @deserialize: (state) -> state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer) @@ -99,8 +94,6 @@ class DisplayBuffer extends Model id: @id softWrapped: @isSoftWrapped() editorWidthInChars: @editorWidthInChars - scrollTop: @scrollTop - scrollLeft: @scrollLeft tokenizedBuffer: @tokenizedBuffer.serialize() largeFileMode: @largeFileMode diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index ae90234f9..8e442afbb 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -43,8 +43,8 @@ class TextEditorComponent @presenter = new TextEditorPresenter model: @editor - scrollTop: @editor.getScrollTop() - scrollLeft: @editor.getScrollLeft() + scrollRow: @editor.getScrollRow() + scrollColumn: @editor.getScrollColumn() tileSize: tileSize cursorBlinkPeriod: @cursorBlinkPeriod cursorBlinkResumeDelay: @cursorBlinkResumeDelay diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 922f06805..89dc72501 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -14,7 +14,7 @@ class TextEditorPresenter minimumReflowInterval: 200 constructor: (params) -> - {@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params + {@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params {horizontalScrollbarHeight, verticalScrollbarWidth} = params {@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params @@ -56,6 +56,8 @@ class TextEditorPresenter transferMeasurementsFromModel: -> @editorWidthInChars = @model.getEditorWidthInChars() + @setScrollTop(@scrollRow * @lineHeight) if @scrollRow? + @setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn? # Private: Determines whether {TextEditorPresenter} is currently batching changes. # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them. @@ -233,6 +235,7 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = true @updateContentDimensions() + @updateScrollPosition() @updateScrollbarDimensions() @updateStartRow() @updateEndRow() @@ -824,7 +827,6 @@ class TextEditorPresenter @shouldUpdateCustomGutterDecorationState = true @shouldUpdateOverlaysState = true - @didStartScrolling() @emitDidUpdateState() getScrollTop: -> @@ -1552,12 +1554,18 @@ class TextEditorPresenter if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @realScrollLeft = scrollLeft @scrollLeft = Math.round(scrollLeft) + @scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth) + @model.setScrollColumn(@scrollColumn) commitPendingScrollTopPosition: -> scrollTop = @constrainScrollTop(@pendingScrollTop) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @realScrollTop = scrollTop @scrollTop = Math.round(scrollTop) + @scrollRow = Math.round(@scrollTop / @lineHeight) + @model.setScrollRow(@scrollRow) + + @didStartScrolling() updateScrollPosition: -> @commitPendingLogicalScrollPosition() if @pendingScrollLogicalPosition? diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 2edffe30b..e8b183229 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -127,8 +127,8 @@ class TextEditor extends Model deserializer: 'TextEditor' id: @id softTabs: @softTabs - scrollTop: @scrollTop - scrollLeft: @scrollLeft + scrollRow: @scrollRow + scrollColumn: @scrollColumn displayBuffer: @displayBuffer.serialize() subscribeToBuffer: -> @@ -2992,6 +2992,12 @@ class TextEditor extends Model setWidth: (width) -> @displayBuffer.setWidth(width) getWidth: -> @displayBuffer.getWidth() + getScrollRow: -> @scrollRow + setScrollRow: (@scrollRow) -> + + getScrollColumn: -> @scrollColumn + setScrollColumn: (@scrollColumn) -> + getScrollTop: -> @displayBuffer.getScrollTop() setScrollTop: (scrollTop) -> @displayBuffer.setScrollTop(scrollTop) From 6673c892d58ecd039892080557317ef17b41a0b3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 09:15:10 +0200 Subject: [PATCH 37/58] Remove references to TextEditor::scroll... --- src/text-editor-component.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 8e442afbb..84d756526 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -306,7 +306,7 @@ class TextEditorComponent inputNode.value = event.data if insertedRange onVerticalScroll: (scrollTop) => - return if @updateRequested or scrollTop is @editor.getScrollTop() + return if @updateRequested or scrollTop is @presenter.getScrollTop() animationFramePending = @pendingScrollTop? @pendingScrollTop = scrollTop @@ -318,7 +318,7 @@ class TextEditorComponent @presenter.commitPendingScrollTopPosition() onHorizontalScroll: (scrollLeft) => - return if @updateRequested or scrollLeft is @editor.getScrollLeft() + return if @updateRequested or scrollLeft is @presenter.getScrollLeft() animationFramePending = @pendingScrollLeft? @pendingScrollLeft = scrollLeft From c76525fc63d459c3685c2e05a19e4c583235e1ca Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 10:01:11 +0200 Subject: [PATCH 38/58] Restore scroll position after we have pixel position requirements --- src/text-editor-component.coffee | 2 ++ src/text-editor-presenter.coffee | 33 +++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 84d756526..651a333e3 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -43,6 +43,8 @@ class TextEditorComponent @presenter = new TextEditorPresenter model: @editor + scrollTop: 0 + scrollLeft: 0 scrollRow: @editor.getScrollRow() scrollColumn: @editor.getScrollColumn() tileSize: tileSize diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 89dc72501..c6dd62606 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -56,8 +56,6 @@ class TextEditorPresenter transferMeasurementsFromModel: -> @editorWidthInChars = @model.getEditorWidthInChars() - @setScrollTop(@scrollRow * @lineHeight) if @scrollRow? - @setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn? # Private: Determines whether {TextEditorPresenter} is currently batching changes. # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them. @@ -1501,6 +1499,8 @@ class TextEditorPresenter @horizontalScrollbarHeight commitPendingLogicalScrollPosition: -> + return unless @pendingScrollLogicalPosition? + {screenRange, options} = @pendingScrollLogicalPosition verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() @@ -1549,7 +1549,11 @@ class TextEditorPresenter if desiredScrollRight > @getScrollRight() @setScrollRight(desiredScrollRight) + @pendingScrollLogicalPosition = null + commitPendingScrollLeftPosition: -> + return unless @pendingScrollLeft? + scrollLeft = @constrainScrollLeft(@pendingScrollLeft) if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @realScrollLeft = scrollLeft @@ -1557,7 +1561,11 @@ class TextEditorPresenter @scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth) @model.setScrollColumn(@scrollColumn) + @pendingScrollLeft = null + commitPendingScrollTopPosition: -> + return unless @pendingScrollTop? + scrollTop = @constrainScrollTop(@pendingScrollTop) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @realScrollTop = scrollTop @@ -1567,11 +1575,18 @@ class TextEditorPresenter @didStartScrolling() - updateScrollPosition: -> - @commitPendingLogicalScrollPosition() if @pendingScrollLogicalPosition? - @commitPendingScrollLeftPosition() if @pendingScrollLeft? - @commitPendingScrollTopPosition() if @pendingScrollTop? - @pendingScrollTop = null - @pendingScrollLeft = null - @pendingScrollLogicalPosition = null + + restoreScrollPosition: -> + return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements() + + @setScrollTop(@scrollRow * @lineHeight) if @scrollRow? + @setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn? + + @hasRestoredScrollPosition = true + + updateScrollPosition: -> + @restoreScrollPosition() + @commitPendingLogicalScrollPosition() + @commitPendingScrollLeftPosition() + @commitPendingScrollTopPosition() From 3236c8f0f8b5613a33bfe81865e3fccf143fb6e1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 10:07:38 +0200 Subject: [PATCH 39/58] Let's start with deprecations! --- src/text-editor-component.coffee | 6 ++++++ src/text-editor-element.coffee | 6 ++++++ src/text-editor-presenter.coffee | 9 +++++++++ src/text-editor.coffee | 8 ++++++-- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 651a333e3..a64d46668 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -361,6 +361,12 @@ class TextEditorComponent @scrollViewNode.scrollTop = 0 @scrollViewNode.scrollLeft = 0 + onDidChangeScrollTop: (callback) -> + @presenter.onDidChangeScrollTop(callback) + + onDidChangeScrollLeft: (callback) -> + @presenter.onDidChangeScrollLeft(callback) + setScrollLeft: (scrollLeft) -> @presenter.setScrollLeft(scrollLeft) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 7d29b72f9..fe66b6f44 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -218,6 +218,12 @@ class TextEditorElement extends HTMLElement onDidDetach: (callback) -> @emitter.on("did-detach", callback) + onDidChangeScrollTop: (callback) -> + @component.onDidChangeScrollTop(callback) + + onDidChangeScrollLeft: (callback) -> + @component.onDidChangeScrollLeft(callback) + setScrollLeft: (scrollLeft) -> @component.setScrollLeft(scrollLeft) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c6dd62606..e01a8a20d 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1561,6 +1561,8 @@ class TextEditorPresenter @scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth) @model.setScrollColumn(@scrollColumn) + @emitter.emit 'did-change-scroll-top', @scrollLeft + @pendingScrollLeft = null commitPendingScrollTopPosition: -> @@ -1574,6 +1576,7 @@ class TextEditorPresenter @model.setScrollRow(@scrollRow) @didStartScrolling() + @emitter.emit 'did-change-scroll-top', @scrollTop @pendingScrollTop = null @@ -1590,3 +1593,9 @@ class TextEditorPresenter @commitPendingLogicalScrollPosition() @commitPendingScrollLeftPosition() @commitPendingScrollTopPosition() + + onDidChangeScrollTop: (callback) -> + @emitter.on 'did-change-scroll-top', callback + + onDidChangeScrollLeft: (callback) -> + @emitter.on 'did-change-scroll-left', callback diff --git a/src/text-editor.coffee b/src/text-editor.coffee index e8b183229..e15ee13dd 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -433,10 +433,14 @@ class TextEditor extends Model @displayBuffer.onDidChangeCharacterWidths(callback) onDidChangeScrollTop: (callback) -> - @emitter.on 'did-change-scroll-top', callback + Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.") + + atom.views.getView(this).onDidChangeScrollTop(callback) onDidChangeScrollLeft: (callback) -> - @emitter.on 'did-change-scroll-left', callback + Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.") + + atom.views.getView(this).onDidChangeScrollLeft(callback) onDidChangeScrollPosition: (callback) -> @displayBuffer.onDidChangeScrollPosition(callback) From 535a9da94657f92498f8bc7f3f27c07c779555d6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 10:13:00 +0200 Subject: [PATCH 40/58] wip --- src/display-buffer.coffee | 33 -------- src/text-editor-component.coffee | 20 ++++- src/text-editor-element.coffee | 28 ++++++- src/text-editor-presenter.coffee | 49 ++++++++++- src/text-editor.coffee | 134 ++++++++++++++++++++----------- 5 files changed, 180 insertions(+), 84 deletions(-) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 7b544a16e..a039ecc4a 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -125,23 +125,9 @@ class DisplayBuffer extends Model onDidChangeCharacterWidths: (callback) -> @emitter.on 'did-change-character-widths', callback - onDidChangeScrollTop: (callback) -> - @emitter.on 'did-change-scroll-top', callback - - onDidChangeScrollLeft: (callback) -> - @emitter.on 'did-change-scroll-left', callback - onDidChangeScrollPosition: (callback) -> @emitter.on 'did-change-scroll-position', callback - observeScrollTop: (callback) -> - callback(@scrollTop) - @onDidChangeScrollTop(callback) - - observeScrollLeft: (callback) -> - callback(@scrollLeft) - @onDidChangeScrollLeft(callback) - observeDecorations: (callback) -> callback(decoration) for decoration in @getDecorations() @onDidAddDecoration(callback) @@ -1147,25 +1133,6 @@ class DisplayBuffer extends Model # this one. Only emit when the marker still exists. @emitter.emit 'did-create-marker', marker - # TODO: serialize state in TextEditorElement, rather than saving scroll - # positions here. - - getScrollTop: -> @scrollTop - - setScrollTop: (scrollTop) -> - unless scrollTop is @scrollTop - @scrollTop = scrollTop - @emitter.emit 'did-change-scroll-top', @scrollTop - @scrollTop - - getScrollLeft: -> @scrollLeft - - setScrollLeft: (scrollLeft) -> - unless scrollLeft is @scrollLeft - @scrollLeft = scrollLeft - @emitter.emit 'did-change-scroll-left', @scrollLeft - @scrollLeft - decorateFold: (fold) -> @decorateMarker(fold.marker, type: 'line-number', class: 'folded') diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index a64d46668..505be6a07 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -403,6 +403,24 @@ class TextEditorComponent getHorizontalScrollbarHeight: -> @presenter.getHorizontalScrollbarHeight() + getVisibleRowRange: -> + @presenter.getVisibleRowRange() + + pixelPositionForBufferPosition: (bufferPosition) -> + @presenter.pixelPositionForBufferPosition(bufferPosition) + + pixelPositionForScreenPosition: (screenPosition) -> + @presenter.pixelPositionForScreenPosition(screenPosition) + + screenPositionForPixelPosition: (pixelPosition) -> + @presenter.screenPositionForPixelPosition(pixelPosition) + + pixelRectForScreenRange: (screenRange) -> + @presenter.pixelRectForScreenRange(screenRange) + + pixelRangeForScreenRange: (screenRange, clip) -> + @presenter.pixelRangeForScreenRange(screenRange, clip) + 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 @@ -810,7 +828,7 @@ class TextEditorComponent screenPositionForMouseEvent: (event, linesClientRect) -> pixelPosition = @pixelPositionForMouseEvent(event, linesClientRect) - @editor.screenPositionForPixelPosition(pixelPosition) + @presenter.screenPositionForPixelPosition(pixelPosition) pixelPositionForMouseEvent: (event, linesClientRect) -> {clientX, clientY} = event diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index fe66b6f44..5b4322203 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -181,7 +181,7 @@ class TextEditorElement extends HTMLElement # # Returns an {Object} with two values: `top` and `left`, representing the pixel position. pixelPositionForBufferPosition: (bufferPosition) -> - @getModel().pixelPositionForBufferPosition(bufferPosition, true) + @component.pixelPositionForBufferPosition(bufferPosition, true) # Extended: Converts a screen position to a pixel position. # @@ -190,21 +190,21 @@ class TextEditorElement extends HTMLElement # # Returns an {Object} with two values: `top` and `left`, representing the pixel positions. pixelPositionForScreenPosition: (screenPosition) -> - @getModel().pixelPositionForScreenPosition(screenPosition, true) + @component.pixelPositionForScreenPosition(screenPosition, true) # Extended: Retrieves the number of the row that is visible and currently at the # top of the editor. # # Returns a {Number}. getFirstVisibleScreenRow: -> - @getModel().getFirstVisibleScreenRow(true) + @getVisibleRowRange()[0] # Extended: Retrieves the number of the row that is visible and currently at the # bottom of the editor. # # Returns a {Number}. getLastVisibleScreenRow: -> - @getModel().getLastVisibleScreenRow(true) + @getVisibleRowRange()[1] # Extended: call the given `callback` when the editor is attached to the DOM. # @@ -266,6 +266,26 @@ class TextEditorElement extends HTMLElement getHorizontalScrollbarHeight: -> @component.getHorizontalScrollbarHeight() + getVisibleRowRange: -> + @component.getVisibleRowRange() + + intersectsVisibleRowRange: (startRow, endRow) -> + [visibleStart, visibleEnd] = @getVisibleRowRange() + not (endRow <= visibleStart or visibleEnd <= startRow) + + selectionIntersectsVisibleRowRange: (selection) -> + {start, end} = selection.getScreenRange() + @intersectsVisibleRowRange(start.row, end.row + 1) + + screenPositionForPixelPosition: (pixelPosition) -> + @component.screenPositionForPixelPosition(pixelPosition) + + pixelRectForScreenRange: (screenRange) -> + @component.pixelRectForScreenRange(screenRange) + + pixelRangeForScreenRange: (screenRange, clip) -> + @component.pixelRangeForScreenRange(screenRange, clip) + stopEventPropagation = (commandListeners) -> newCommandListeners = {} for commandName, commandListener of commandListeners diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index e01a8a20d..15d19d1a6 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1561,7 +1561,7 @@ class TextEditorPresenter @scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth) @model.setScrollColumn(@scrollColumn) - @emitter.emit 'did-change-scroll-top', @scrollLeft + @emitter.emit 'did-change-scroll-left', @scrollLeft @pendingScrollLeft = null @@ -1599,3 +1599,50 @@ class TextEditorPresenter onDidChangeScrollLeft: (callback) -> @emitter.on 'did-change-scroll-left', callback + + getVisibleRowRange: -> + [@startRow, @endRow] + + pixelPositionForBufferPosition: (bufferPosition) -> + @pixelPositionForScreenPosition( + @model.screenPositionForBufferPosition(bufferPosition) + ) + + screenPositionForPixelPosition: (pixelPosition) -> + targetTop = pixelPosition.top + targetLeft = pixelPosition.left + defaultCharWidth = @baseCharacterWidth + row = Math.floor(targetTop / @lineHeight) + targetLeft = 0 if row < 0 + targetLeft = Infinity if row > @model.getLastScreenRow() + row = Math.min(row, @model.getLastScreenRow()) + row = Math.max(0, row) + + left = 0 + column = 0 + + iterator = @model.tokenizedLineForScreenRow(row).getTokenIterator() + while iterator.next() + charWidths = @getScopedCharacterWidths(iterator.getScopes()) + value = iterator.getText() + valueIndex = 0 + while valueIndex < value.length + if iterator.isPairedCharacter() + char = value + charLength = 2 + valueIndex += 2 + else + char = value[valueIndex] + charLength = 1 + valueIndex++ + + charWidth = charWidths[char] ? defaultCharWidth + break if targetLeft <= left + (charWidth / 2) + left += charWidth + column += charLength + + new Point(row, column) + + pixelRangeForScreenRange: (screenRange, clip=true) -> + {start, end} = Range.fromObject(screenRange) + {start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)} diff --git a/src/text-editor.coffee b/src/text-editor.coffee index e15ee13dd..76d09d381 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -109,12 +109,6 @@ class TextEditor extends Model @setEncoding(atom.config.get('core.fileEncoding', scope: @getRootScopeDescriptor())) - @disposables.add @displayBuffer.onDidChangeScrollTop (scrollTop) => - @emitter.emit 'did-change-scroll-top', scrollTop - - @disposables.add @displayBuffer.onDidChangeScrollLeft (scrollLeft) => - @emitter.emit 'did-change-scroll-left', scrollLeft - @gutterContainer = new GutterContainer(this) @lineNumberGutter = @gutterContainer.addGutter name: 'line-number' @@ -2865,23 +2859,27 @@ class TextEditor extends Model # Essential: Scrolls the editor to the top scrollToTop: -> - @setScrollTop(0) + Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.") + + atom.views.getView(this).scrollToTop() # Essential: Scrolls the editor to the bottom scrollToBottom: -> - @setScrollBottom(Infinity) + Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.") + + atom.views.getView(this).scrollToBottom() scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options) - horizontallyScrollable: -> @displayBuffer.horizontallyScrollable() + getHorizontalScrollbarHeight: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.") - verticallyScrollable: -> @displayBuffer.verticallyScrollable() + atom.views.getView(this).getHorizontalScrollbarHeight() - getHorizontalScrollbarHeight: -> @displayBuffer.getHorizontalScrollbarHeight() - setHorizontalScrollbarHeight: (height) -> @displayBuffer.setHorizontalScrollbarHeight(height) + getVerticalScrollbarWidth: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.") - getVerticalScrollbarWidth: -> @displayBuffer.getVerticalScrollbarWidth() - setVerticalScrollbarWidth: (width) -> @displayBuffer.setVerticalScrollbarWidth(width) + atom.views.getView(this).getVerticalScrollbarWidth() pageUp: -> @moveUp(@getRowsPerPage()) @@ -2944,25 +2942,21 @@ class TextEditor extends Model @placeholderText = placeholderText @emitter.emit 'did-change-placeholder-text', @placeholderText - getFirstVisibleScreenRow: (suppressDeprecation) -> - unless suppressDeprecation - deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.") - @getVisibleRowRange()[0] + getFirstVisibleScreenRow: -> + deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.") + atom.views.getView(this).getVisibleRowRange()[0] - getLastVisibleScreenRow: (suppressDeprecation) -> - unless suppressDeprecation - Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.") - @getVisibleRowRange()[1] + getLastVisibleScreenRow: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.") + atom.views.getView(this).getVisibleRowRange()[1] - pixelPositionForBufferPosition: (bufferPosition, suppressDeprecation) -> - unless suppressDeprecation - Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead") - @displayBuffer.pixelPositionForBufferPosition(bufferPosition) + pixelPositionForBufferPosition: (bufferPosition) -> + Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead") + atom.views.getView(this).pixelPositionForBufferPosition(bufferPosition) - pixelPositionForScreenPosition: (screenPosition, suppressDeprecation) -> - unless suppressDeprecation - Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead") - @displayBuffer.pixelPositionForScreenPosition(screenPosition) + pixelPositionForScreenPosition: (screenPosition) -> + Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead") + atom.views.getView(this).pixelPositionForScreenPosition(screenPosition) getSelectionMarkerAttributes: -> {type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true} @@ -3002,30 +2996,80 @@ class TextEditor extends Model getScrollColumn: -> @scrollColumn setScrollColumn: (@scrollColumn) -> - getScrollTop: -> @displayBuffer.getScrollTop() - setScrollTop: (scrollTop) -> @displayBuffer.setScrollTop(scrollTop) + getScrollTop: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.") - getScrollBottom: -> @displayBuffer.getScrollBottom() - setScrollBottom: (scrollBottom) -> @displayBuffer.setScrollBottom(scrollBottom) + atom.views.getView(this).getScrollTop() - getScrollLeft: -> @displayBuffer.getScrollLeft() - setScrollLeft: (scrollLeft) -> @displayBuffer.setScrollLeft(scrollLeft) + setScrollTop: (scrollTop) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollTop instead.") - getScrollRight: -> @displayBuffer.getScrollRight() - setScrollRight: (scrollRight) -> @displayBuffer.setScrollRight(scrollRight) + atom.views.getView(this).setScrollTop(scrollTop) - getScrollHeight: -> @displayBuffer.getScrollHeight() - getScrollWidth: -> @displayBuffer.getScrollWidth() + getScrollBottom: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollBottom instead.") - getVisibleRowRange: -> @displayBuffer.getVisibleRowRange() + atom.views.getView(this).getScrollBottom() - intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow) + setScrollBottom: (scrollBottom) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollBottom instead.") - selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection) + atom.views.getView(this).setScrollBottom(scrollBottom) - screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition) + getScrollLeft: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollLeft instead.") - pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange) + atom.views.getView(this).getScrollLeft() + + setScrollLeft: (scrollLeft) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollLeft instead.") + + atom.views.getView(this).setScrollLeft(scrollLeft) + + getScrollRight: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollRight instead.") + + atom.views.getView(this).getScrollRight() + + setScrollRight: (scrollRight) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollRight instead.") + + atom.views.getView(this).setScrollRight(scrollRight) + + getScrollHeight: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollHeight instead.") + + atom.views.getView(this).getScrollHeight() + + getScrollWidth: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollWidth instead.") + + atom.views.getView(this).getScrollWidth() + + getVisibleRowRange: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.") + + atom.views.getView(this).getVisibleRowRange() + + intersectsVisibleRowRange: (startRow, endRow) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.") + + atom.views.getView(this).intersectsVisibleRowRange(startRow, endRow) + + selectionIntersectsVisibleRowRange: (selection) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.") + + atom.views.getView(this).selectionIntersectsVisibleRowRange(selection) + + screenPositionForPixelPosition: (pixelPosition) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.") + + atom.views.getView(this).screenPositionForPixelPosition(pixelPosition) + + pixelRectForScreenRange: (screenRange) -> + Grim.deprecate("This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.") + + atom.views.getView(this).pixelRectForScreenRange(screenRange) ### Section: Utility From 19ca87f79fc3dc4b75798f0d2cd1b6e1d3745f22 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 11:27:54 +0200 Subject: [PATCH 41/58] :green_heart: Fix specs --- src/text-editor-component.coffee | 30 +++++++++++++++++++++++------- src/text-editor-element.coffee | 8 ++++---- src/text-editor-presenter.coffee | 9 --------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 505be6a07..a49ece358 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -406,20 +406,36 @@ class TextEditorComponent getVisibleRowRange: -> @presenter.getVisibleRowRange() - pixelPositionForBufferPosition: (bufferPosition) -> - @presenter.pixelPositionForBufferPosition(bufferPosition) - pixelPositionForScreenPosition: (screenPosition) -> - @presenter.pixelPositionForScreenPosition(screenPosition) + position = @presenter.pixelPositionForScreenPosition(screenPosition) + position.top += @presenter.getScrollTop() + position.left += @presenter.getScrollLeft() + position screenPositionForPixelPosition: (pixelPosition) -> @presenter.screenPositionForPixelPosition(pixelPosition) pixelRectForScreenRange: (screenRange) -> - @presenter.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 + width = @scrollWidth + else + {top, left} = @pixelPositionForScreenPosition(screenRange.start, false) + height = @lineHeight + width = @pixelPositionForScreenPosition(screenRange.end, false).left - left - pixelRangeForScreenRange: (screenRange, clip) -> - @presenter.pixelRangeForScreenRange(screenRange, clip) + {top, left, width, height} + + pixelRangeForScreenRange: (screenRange, clip=true) -> + {start, end} = Range.fromObject(screenRange) + {start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)} + + pixelPositionForBufferPosition: (bufferPosition) -> + @pixelPositionForScreenPosition( + @editor.screenPositionForBufferPosition(bufferPosition) + ) onMouseDown: (event) => unless event.button is 0 or (event.button is 1 and process.platform is 'linux') diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 5b4322203..cfa7d226c 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -181,7 +181,7 @@ class TextEditorElement extends HTMLElement # # Returns an {Object} with two values: `top` and `left`, representing the pixel position. pixelPositionForBufferPosition: (bufferPosition) -> - @component.pixelPositionForBufferPosition(bufferPosition, true) + @component.pixelPositionForBufferPosition(bufferPosition) # Extended: Converts a screen position to a pixel position. # @@ -190,7 +190,7 @@ class TextEditorElement extends HTMLElement # # Returns an {Object} with two values: `top` and `left`, representing the pixel positions. pixelPositionForScreenPosition: (screenPosition) -> - @component.pixelPositionForScreenPosition(screenPosition, true) + @component.pixelPositionForScreenPosition(screenPosition) # Extended: Retrieves the number of the row that is visible and currently at the # top of the editor. @@ -283,8 +283,8 @@ class TextEditorElement extends HTMLElement pixelRectForScreenRange: (screenRange) -> @component.pixelRectForScreenRange(screenRange) - pixelRangeForScreenRange: (screenRange, clip) -> - @component.pixelRangeForScreenRange(screenRange, clip) + pixelRangeForScreenRange: (screenRange) -> + @component.pixelRangeForScreenRange(screenRange) stopEventPropagation = (commandListeners) -> newCommandListeners = {} diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 15d19d1a6..8a07ead85 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1603,11 +1603,6 @@ class TextEditorPresenter getVisibleRowRange: -> [@startRow, @endRow] - pixelPositionForBufferPosition: (bufferPosition) -> - @pixelPositionForScreenPosition( - @model.screenPositionForBufferPosition(bufferPosition) - ) - screenPositionForPixelPosition: (pixelPosition) -> targetTop = pixelPosition.top targetLeft = pixelPosition.left @@ -1642,7 +1637,3 @@ class TextEditorPresenter column += charLength new Point(row, column) - - pixelRangeForScreenRange: (screenRange, clip=true) -> - {start, end} = Range.fromObject(screenRange) - {start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)} From 270334b71354575742664469f95e2f940358cf86 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 11:30:46 +0200 Subject: [PATCH 42/58] Adjust pixelRectForScreenRange to return absolute values --- src/text-editor-component.coffee | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index a49ece358..543317787 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -416,17 +416,12 @@ class TextEditorComponent @presenter.screenPositionForPixelPosition(pixelPosition) 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 - width = @scrollWidth - else - {top, left} = @pixelPositionForScreenPosition(screenRange.start, false) - height = @lineHeight - width = @pixelPositionForScreenPosition(screenRange.end, false).left - left - - {top, left, width, height} + rect = @presenter.pixelRectForScreenRange(screenRange) + rect.top += @presenter.getScrollTop() + rect.bottom += @presenter.getScrollTop() + rect.left += @presenter.getScrollLeft() + rect.right += @presenter.getScrollLeft() + rect pixelRangeForScreenRange: (screenRange, clip=true) -> {start, end} = Range.fromObject(screenRange) From 99e77dc09d9470edd5d8382b7c5e9195e1f0a7fc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 11:41:02 +0200 Subject: [PATCH 43/58] Port leftover specs from DisplayBuffer --- spec/display-buffer-spec.coffee | 41 -------------------------- spec/text-editor-component-spec.coffee | 34 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 4e71fa3e6..af51986bf 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -718,21 +718,6 @@ describe "DisplayBuffer", -> expect(displayBuffer.clipScreenPosition([0, 1], clip: 'forward')).toEqual [0, tabLength] expect(displayBuffer.clipScreenPosition([0, tabLength], clip: 'forward')).toEqual [0, tabLength] - describe "::screenPositionForPixelPosition(pixelPosition)", -> - it "clips pixel positions above buffer start", -> - displayBuffer.setLineHeightInPixels(20) - expect(displayBuffer.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0] - expect(displayBuffer.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0] - expect(displayBuffer.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0] - expect(displayBuffer.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29] - - it "clips pixel positions below buffer end", -> - displayBuffer.setLineHeightInPixels(20) - expect(displayBuffer.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2] - expect(displayBuffer.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2] - expect(displayBuffer.screenPositionForPixelPosition(top: displayBuffer.getHeight() + 1, left: 0)).toEqual [12, 2] - expect(displayBuffer.screenPositionForPixelPosition(top: displayBuffer.getHeight() - 1, left: 0)).toEqual [12, 0] - describe "::screenPositionForBufferPosition(bufferPosition, options)", -> it "clips the specified buffer position", -> expect(displayBuffer.screenPositionForBufferPosition([0, 2])).toEqual [0, 2] @@ -1247,32 +1232,6 @@ describe "DisplayBuffer", -> expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: true}) expect(scrollSpy).toHaveBeenCalledWith(screenRange: [[8, 20], [8, 20]], options: {center: false, reversed: true}) - describe "::getVisibleRowRange()", -> - beforeEach -> - displayBuffer.setLineHeightInPixels(10) - displayBuffer.setHeight(100) - - it "returns the first and the last visible rows", -> - displayBuffer.setScrollTop(0) - - expect(displayBuffer.getVisibleRowRange()).toEqual [0, 9] - - it "includes partially visible rows in the range", -> - displayBuffer.setScrollTop(5) - - expect(displayBuffer.getVisibleRowRange()).toEqual [0, 10] - - it "returns an empty range when lineHeight is 0", -> - displayBuffer.setLineHeightInPixels(0) - - expect(displayBuffer.getVisibleRowRange()).toEqual [0, 0] - - it "ends at last buffer row even if there's more space available", -> - displayBuffer.setHeight(150) - displayBuffer.setScrollTop(60) - - expect(displayBuffer.getVisibleRowRange()).toEqual [0, 13] - describe "::decorateMarker", -> describe "when decorating gutters", -> [marker] = [] diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 2c5d4d2f0..181d92583 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3605,6 +3605,40 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(wrapperNode.getScrollTop()).toBe 0 + describe "::screenPositionForPixelPosition(pixelPosition)", -> + it "clips pixel positions above buffer start", -> + expect(component.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0] + expect(component.screenPositionForPixelPosition(top: -Infinity, left: Infinity)).toEqual [0, 0] + expect(component.screenPositionForPixelPosition(top: -1, left: Infinity)).toEqual [0, 0] + expect(component.screenPositionForPixelPosition(top: 0, left: Infinity)).toEqual [0, 29] + + it "clips pixel positions below buffer end", -> + expect(component.screenPositionForPixelPosition(top: Infinity, left: -Infinity)).toEqual [12, 2] + expect(component.screenPositionForPixelPosition(top: Infinity, left: Infinity)).toEqual [12, 2] + expect(component.screenPositionForPixelPosition(top: component.getScrollHeight() + 1, left: 0)).toEqual [12, 2] + expect(component.screenPositionForPixelPosition(top: component.getScrollHeight() - 1, left: 0)).toEqual [12, 0] + + describe "::getVisibleRowRange()", -> + beforeEach -> + wrapperNode.style.height = lineHeightInPixels * 8 + "px" + component.measureDimensions() + nextAnimationFrame() + + it "returns the first and the last visible rows", -> + component.setScrollTop(0) + nextAnimationFrame() + + expect(component.getVisibleRowRange()).toEqual [0, 9] + + it "ends at last buffer row even if there's more space available", -> + wrapperNode.style.height = lineHeightInPixels * 13 + "px" + component.measureDimensions() + nextAnimationFrame() + + component.setScrollTop(60) + nextAnimationFrame() + + expect(component.getVisibleRowRange()).toEqual [0, 13] describe "middle mouse paste on Linux", -> originalPlatform = null From 365a58646631de9f4795fcb4ed9a07ee589ee761 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 15:16:40 +0200 Subject: [PATCH 44/58] Ensure realScroll... is up to date with scroll... --- 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 496f05dde..27ab2a290 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -731,17 +731,21 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setExplicitHeight(60) expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> presenter.setHorizontalScrollbarHeight(5) expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> editor.getBuffer().delete([[8, 0], [12, 0]]) expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getRealScrollTop()).toBe presenter.scrollHeight - presenter.clientHeight # Scroll top only gets smaller when needed as dimensions change, never bigger scrollTopBefore = presenter.getState().verticalScrollbar.scrollTop expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n') expect(presenter.getState().content.scrollTop).toBe scrollTopBefore + expect(presenter.getRealScrollTop()).toBe scrollTopBefore it "never goes negative", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) @@ -794,20 +798,25 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expectStateUpdate presenter, -> presenter.setScrollLeft(300) expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> presenter.setContentFrameWidth(600) expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> presenter.setVerticalScrollbarWidth(5) expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> editor.getBuffer().delete([[6, 0], [6, Infinity]]) expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getRealScrollLeft()).toBe presenter.scrollWidth - presenter.clientWidth # Scroll top only gets smaller when needed as dimensions change, never bigger scrollLeftBefore = presenter.getState().content.scrollLeft expectStateUpdate presenter, -> editor.getBuffer().insert([6, 0], new Array(100).join('x')) expect(presenter.getState().content.scrollLeft).toBe scrollLeftBefore + expect(presenter.getRealScrollLeft()).toBe scrollLeftBefore it "never goes negative", -> presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 8a07ead85..48213ef8c 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -711,6 +711,7 @@ class TextEditorPresenter scrollTop = @constrainScrollTop(@scrollTop) unless @scrollTop is scrollTop @scrollTop = scrollTop + @realScrollTop = @scrollTop @updateStartRow() @updateEndRow() @@ -720,6 +721,7 @@ class TextEditorPresenter updateScrollLeft: -> @scrollLeft = @constrainScrollLeft(@scrollLeft) + @realScrollLeft = @scrollLeft constrainScrollLeft: (scrollLeft) -> return scrollLeft unless scrollLeft? and @scrollWidth? and @clientWidth? From 2e6bb53303175823b55f6ddf347b66a754170cf0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 24 Sep 2015 16:25:58 +0200 Subject: [PATCH 45/58] Maintain scroll history --- spec/text-editor-component-spec.coffee | 12 ++ spec/text-editor-presenter-spec.coffee | 6 +- src/text-editor-component.coffee | 8 +- src/text-editor-presenter.coffee | 149 +++++++++++++------------ 4 files changed, 99 insertions(+), 76 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 181d92583..ee02c9297 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3605,6 +3605,18 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(wrapperNode.getScrollTop()).toBe 0 + describe "when many scroll events are triggered before an animation frame", -> + it "applies them sequentially, as if they were not batched", -> + wrapperNode.style.height = lineHeightInPixels * 7 + "px" + component.measureDimensions() + nextAnimationFrame() + + editor.scrollToScreenPosition([8, 0]) + editor.scrollToScreenPosition([8, 0], center: true) + nextAnimationFrame() + + expect(wrapperNode.getScrollTop()).toBe(4 * lineHeightInPixels) + describe "::screenPositionForPixelPosition(pixelPosition)", -> it "clips pixel positions above buffer start", -> expect(component.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0] diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 27ab2a290..5e7557beb 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1893,9 +1893,9 @@ describe "TextEditorPresenter", -> pixelPosition: {top: 6 * 10 - scrollTop, left: gutterWidth} } - editor.insertNewline() - presenter.getState() # forces scroll top to be changed - presenter.setScrollTop(scrollTop) # I'm fighting the editor + expectStateUpdate presenter, -> + editor.insertNewline() + presenter.setScrollTop(scrollTop) # I'm fighting the editor expectValues stateForOverlay(presenter, decoration), { item: item diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 543317787..c7a6cb56d 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -317,7 +317,7 @@ class TextEditorComponent pendingScrollTop = @pendingScrollTop @pendingScrollTop = null @presenter.setScrollTop(pendingScrollTop) - @presenter.commitPendingScrollTopPosition() + @presenter.commitScrollQueue() onHorizontalScroll: (scrollLeft) => return if @updateRequested or scrollLeft is @presenter.getScrollLeft() @@ -327,7 +327,7 @@ class TextEditorComponent unless animationFramePending @requestAnimationFrame => @presenter.setScrollLeft(@pendingScrollLeft) - @presenter.commitPendingScrollLeftPosition() + @presenter.commitScrollQueue() @pendingScrollLeft = null onMouseWheel: (event) => @@ -613,11 +613,11 @@ class TextEditorComponent if mouseYDelta? @presenter.setScrollTop(@presenter.getScrollTop() + yDirection * scaleScrollDelta(mouseYDelta)) - @presenter.commitPendingScrollTopPosition() + @presenter.commitScrollQueue() if mouseXDelta? @presenter.setScrollLeft(@presenter.getScrollLeft() + xDirection * scaleScrollDelta(mouseXDelta)) - @presenter.commitPendingScrollLeftPosition() + @presenter.commitScrollQueue() scaleScrollDelta = (scrollDelta) -> Math.pow(scrollDelta / 2, 3) / 280 diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 48213ef8c..e3a46fded 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -3,6 +3,23 @@ _ = require 'underscore-plus' Decoration = require './decoration' +class ScrollQueue + constructor: -> + @queue = [] + @committing = false + + enqueue: (scrollAction) -> + if @committing + @queue.unshift(scrollAction) + else + @queue.push(scrollAction) + + commit: -> + @committing = true + while scrollAction = @queue.shift() + scrollAction() + @committing = false + module.exports = class TextEditorPresenter toggleCursorBlinkHandle: null @@ -22,6 +39,7 @@ class TextEditorPresenter @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 @tileSize ?= 6 + @scrollQueue = new ScrollQueue @disposables = new CompositeDisposable @emitter = new Emitter @@ -816,7 +834,7 @@ class TextEditorPresenter setScrollTop: (scrollTop) -> return unless scrollTop? - @pendingScrollTop = scrollTop + @scrollQueue.enqueue => @commitScrollTop(scrollTop) @shouldUpdateVerticalScrollState = true @shouldUpdateHiddenInputState = true @@ -856,7 +874,7 @@ class TextEditorPresenter setScrollLeft: (scrollLeft) -> return unless scrollLeft? - @pendingScrollLeft = scrollLeft + @scrollQueue.enqueue => @commitScrollLeft(scrollLeft) @shouldUpdateHorizontalScrollState = true @shouldUpdateHiddenInputState = true @@ -1473,7 +1491,54 @@ class TextEditorPresenter @emitDidUpdateState() didChangeScrollPosition: (position) -> - @pendingScrollLogicalPosition = position + @scrollQueue.enqueue => + {screenRange, options} = position + + verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() + horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() + + {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) + {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) + bottom = endTop + endHeight + right = endLeft + + top += @getScrollTop() + bottom += @getScrollTop() + left += @getScrollLeft() + right += @getScrollLeft() + + if options?.center + desiredScrollCenter = (top + bottom) / 2 + unless @getScrollTop() < desiredScrollCenter < @getScrollBottom() + desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2 + desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2 + else + desiredScrollTop = top - verticalScrollMarginInPixels + desiredScrollBottom = bottom + verticalScrollMarginInPixels + + desiredScrollLeft = left - horizontalScrollMarginInPixels + desiredScrollRight = right + horizontalScrollMarginInPixels + + if options?.reversed ? true + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) + + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) + else + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) + + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) @shouldUpdateCursorsState = true @shouldUpdateCustomGutterDecorationState = true @@ -1500,63 +1565,10 @@ class TextEditorPresenter getHorizontalScrollbarHeight: -> @horizontalScrollbarHeight - commitPendingLogicalScrollPosition: -> - return unless @pendingScrollLogicalPosition? + commitScrollLeft: (scrollLeft) -> + return unless scrollLeft? - {screenRange, options} = @pendingScrollLogicalPosition - - verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() - - {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) - {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) - bottom = endTop + endHeight - right = endLeft - - top += @scrollTop - bottom += @scrollTop - left += @scrollLeft - right += @scrollLeft - - if options?.center - desiredScrollCenter = (top + bottom) / 2 - unless @getScrollTop() < desiredScrollCenter < @getScrollBottom() - desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2 - desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2 - else - desiredScrollTop = top - verticalScrollMarginInPixels - desiredScrollBottom = bottom + verticalScrollMarginInPixels - - desiredScrollLeft = left - horizontalScrollMarginInPixels - desiredScrollRight = right + horizontalScrollMarginInPixels - - if options?.reversed ? true - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) - - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) - else - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) - - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) - - @pendingScrollLogicalPosition = null - - commitPendingScrollLeftPosition: -> - return unless @pendingScrollLeft? - - scrollLeft = @constrainScrollLeft(@pendingScrollLeft) + scrollLeft = @constrainScrollLeft(scrollLeft) if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @realScrollLeft = scrollLeft @scrollLeft = Math.round(scrollLeft) @@ -1567,10 +1579,10 @@ class TextEditorPresenter @pendingScrollLeft = null - commitPendingScrollTopPosition: -> - return unless @pendingScrollTop? + commitScrollTop: (scrollTop) -> + return unless scrollTop? - scrollTop = @constrainScrollTop(@pendingScrollTop) + scrollTop = @constrainScrollTop(scrollTop) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @realScrollTop = scrollTop @scrollTop = Math.round(scrollTop) @@ -1580,10 +1592,8 @@ class TextEditorPresenter @didStartScrolling() @emitter.emit 'did-change-scroll-top', @scrollTop - @pendingScrollTop = null - restoreScrollPosition: -> - return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements() + return unless @hasPixelPositionRequirements() @setScrollTop(@scrollRow * @lineHeight) if @scrollRow? @setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn? @@ -1591,10 +1601,11 @@ class TextEditorPresenter @hasRestoredScrollPosition = true updateScrollPosition: -> - @restoreScrollPosition() - @commitPendingLogicalScrollPosition() - @commitPendingScrollLeftPosition() - @commitPendingScrollTopPosition() + @restoreScrollPosition() unless @hasRestoredScrollPosition + @commitScrollQueue() + + commitScrollQueue: -> + @scrollQueue.commit() onDidChangeScrollTop: (callback) -> @emitter.on 'did-change-scroll-top', callback From d65b5d179301311186b4335ffdb81994cd1f2341 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 10:31:48 +0200 Subject: [PATCH 46/58] Revert "Maintain scroll history" This reverts commit 2e6bb53303175823b55f6ddf347b66a754170cf0. --- spec/text-editor-component-spec.coffee | 12 -- spec/text-editor-presenter-spec.coffee | 6 +- src/text-editor-component.coffee | 8 +- src/text-editor-presenter.coffee | 149 ++++++++++++------------- 4 files changed, 76 insertions(+), 99 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index ee02c9297..181d92583 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3605,18 +3605,6 @@ describe "TextEditorComponent", -> nextAnimationFrame() expect(wrapperNode.getScrollTop()).toBe 0 - describe "when many scroll events are triggered before an animation frame", -> - it "applies them sequentially, as if they were not batched", -> - wrapperNode.style.height = lineHeightInPixels * 7 + "px" - component.measureDimensions() - nextAnimationFrame() - - editor.scrollToScreenPosition([8, 0]) - editor.scrollToScreenPosition([8, 0], center: true) - nextAnimationFrame() - - expect(wrapperNode.getScrollTop()).toBe(4 * lineHeightInPixels) - describe "::screenPositionForPixelPosition(pixelPosition)", -> it "clips pixel positions above buffer start", -> expect(component.screenPositionForPixelPosition(top: -Infinity, left: -Infinity)).toEqual [0, 0] diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 5e7557beb..27ab2a290 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1893,9 +1893,9 @@ describe "TextEditorPresenter", -> pixelPosition: {top: 6 * 10 - scrollTop, left: gutterWidth} } - expectStateUpdate presenter, -> - editor.insertNewline() - presenter.setScrollTop(scrollTop) # I'm fighting the editor + editor.insertNewline() + presenter.getState() # forces scroll top to be changed + presenter.setScrollTop(scrollTop) # I'm fighting the editor expectValues stateForOverlay(presenter, decoration), { item: item diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index c7a6cb56d..543317787 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -317,7 +317,7 @@ class TextEditorComponent pendingScrollTop = @pendingScrollTop @pendingScrollTop = null @presenter.setScrollTop(pendingScrollTop) - @presenter.commitScrollQueue() + @presenter.commitPendingScrollTopPosition() onHorizontalScroll: (scrollLeft) => return if @updateRequested or scrollLeft is @presenter.getScrollLeft() @@ -327,7 +327,7 @@ class TextEditorComponent unless animationFramePending @requestAnimationFrame => @presenter.setScrollLeft(@pendingScrollLeft) - @presenter.commitScrollQueue() + @presenter.commitPendingScrollLeftPosition() @pendingScrollLeft = null onMouseWheel: (event) => @@ -613,11 +613,11 @@ class TextEditorComponent if mouseYDelta? @presenter.setScrollTop(@presenter.getScrollTop() + yDirection * scaleScrollDelta(mouseYDelta)) - @presenter.commitScrollQueue() + @presenter.commitPendingScrollTopPosition() if mouseXDelta? @presenter.setScrollLeft(@presenter.getScrollLeft() + xDirection * scaleScrollDelta(mouseXDelta)) - @presenter.commitScrollQueue() + @presenter.commitPendingScrollLeftPosition() scaleScrollDelta = (scrollDelta) -> Math.pow(scrollDelta / 2, 3) / 280 diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index e3a46fded..48213ef8c 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -3,23 +3,6 @@ _ = require 'underscore-plus' Decoration = require './decoration' -class ScrollQueue - constructor: -> - @queue = [] - @committing = false - - enqueue: (scrollAction) -> - if @committing - @queue.unshift(scrollAction) - else - @queue.push(scrollAction) - - commit: -> - @committing = true - while scrollAction = @queue.shift() - scrollAction() - @committing = false - module.exports = class TextEditorPresenter toggleCursorBlinkHandle: null @@ -39,7 +22,6 @@ class TextEditorPresenter @measuredVerticalScrollbarWidth = verticalScrollbarWidth @gutterWidth ?= 0 @tileSize ?= 6 - @scrollQueue = new ScrollQueue @disposables = new CompositeDisposable @emitter = new Emitter @@ -834,7 +816,7 @@ class TextEditorPresenter setScrollTop: (scrollTop) -> return unless scrollTop? - @scrollQueue.enqueue => @commitScrollTop(scrollTop) + @pendingScrollTop = scrollTop @shouldUpdateVerticalScrollState = true @shouldUpdateHiddenInputState = true @@ -874,7 +856,7 @@ class TextEditorPresenter setScrollLeft: (scrollLeft) -> return unless scrollLeft? - @scrollQueue.enqueue => @commitScrollLeft(scrollLeft) + @pendingScrollLeft = scrollLeft @shouldUpdateHorizontalScrollState = true @shouldUpdateHiddenInputState = true @@ -1491,54 +1473,7 @@ class TextEditorPresenter @emitDidUpdateState() didChangeScrollPosition: (position) -> - @scrollQueue.enqueue => - {screenRange, options} = position - - verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() - horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() - - {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) - {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) - bottom = endTop + endHeight - right = endLeft - - top += @getScrollTop() - bottom += @getScrollTop() - left += @getScrollLeft() - right += @getScrollLeft() - - if options?.center - desiredScrollCenter = (top + bottom) / 2 - unless @getScrollTop() < desiredScrollCenter < @getScrollBottom() - desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2 - desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2 - else - desiredScrollTop = top - verticalScrollMarginInPixels - desiredScrollBottom = bottom + verticalScrollMarginInPixels - - desiredScrollLeft = left - horizontalScrollMarginInPixels - desiredScrollRight = right + horizontalScrollMarginInPixels - - if options?.reversed ? true - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) - - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) - else - if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop) - if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom) - - if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft) - if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight) + @pendingScrollLogicalPosition = position @shouldUpdateCursorsState = true @shouldUpdateCustomGutterDecorationState = true @@ -1565,10 +1500,63 @@ class TextEditorPresenter getHorizontalScrollbarHeight: -> @horizontalScrollbarHeight - commitScrollLeft: (scrollLeft) -> - return unless scrollLeft? + commitPendingLogicalScrollPosition: -> + return unless @pendingScrollLogicalPosition? - scrollLeft = @constrainScrollLeft(scrollLeft) + {screenRange, options} = @pendingScrollLogicalPosition + + verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels() + horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels() + + {top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start)) + {top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end)) + bottom = endTop + endHeight + right = endLeft + + top += @scrollTop + bottom += @scrollTop + left += @scrollLeft + right += @scrollLeft + + if options?.center + desiredScrollCenter = (top + bottom) / 2 + unless @getScrollTop() < desiredScrollCenter < @getScrollBottom() + desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2 + desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2 + else + desiredScrollTop = top - verticalScrollMarginInPixels + desiredScrollBottom = bottom + verticalScrollMarginInPixels + + desiredScrollLeft = left - horizontalScrollMarginInPixels + desiredScrollRight = right + horizontalScrollMarginInPixels + + if options?.reversed ? true + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) + + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) + else + if desiredScrollTop < @getScrollTop() + @setScrollTop(desiredScrollTop) + if desiredScrollBottom > @getScrollBottom() + @setScrollBottom(desiredScrollBottom) + + if desiredScrollLeft < @getScrollLeft() + @setScrollLeft(desiredScrollLeft) + if desiredScrollRight > @getScrollRight() + @setScrollRight(desiredScrollRight) + + @pendingScrollLogicalPosition = null + + commitPendingScrollLeftPosition: -> + return unless @pendingScrollLeft? + + scrollLeft = @constrainScrollLeft(@pendingScrollLeft) if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft) @realScrollLeft = scrollLeft @scrollLeft = Math.round(scrollLeft) @@ -1579,10 +1567,10 @@ class TextEditorPresenter @pendingScrollLeft = null - commitScrollTop: (scrollTop) -> - return unless scrollTop? + commitPendingScrollTopPosition: -> + return unless @pendingScrollTop? - scrollTop = @constrainScrollTop(scrollTop) + scrollTop = @constrainScrollTop(@pendingScrollTop) if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop) @realScrollTop = scrollTop @scrollTop = Math.round(scrollTop) @@ -1592,8 +1580,10 @@ class TextEditorPresenter @didStartScrolling() @emitter.emit 'did-change-scroll-top', @scrollTop + @pendingScrollTop = null + restoreScrollPosition: -> - return unless @hasPixelPositionRequirements() + return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements() @setScrollTop(@scrollRow * @lineHeight) if @scrollRow? @setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn? @@ -1601,11 +1591,10 @@ class TextEditorPresenter @hasRestoredScrollPosition = true updateScrollPosition: -> - @restoreScrollPosition() unless @hasRestoredScrollPosition - @commitScrollQueue() - - commitScrollQueue: -> - @scrollQueue.commit() + @restoreScrollPosition() + @commitPendingLogicalScrollPosition() + @commitPendingScrollLeftPosition() + @commitPendingScrollTopPosition() onDidChangeScrollTop: (callback) -> @emitter.on 'did-change-scroll-top', callback From 0e72593a0cbb6abc8798702c98f57801d1d85652 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 10:43:20 +0200 Subject: [PATCH 47/58] Enforce a 'last scroll wins' model for batched scrolls --- spec/text-editor-presenter-spec.coffee | 34 +++++++++++++++++++++++--- src/text-editor-presenter.coffee | 4 +++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 27ab2a290..1a2f8badc 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -679,6 +679,20 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 describe ".scrollTop", -> + it "changes based on the scroll operation that was performed last", -> + presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 20) + expect(presenter.getState().content.scrollTop).toBe(0) + + presenter.setScrollTop(20) + editor.setCursorBufferPosition([5, 0]) + + expect(presenter.getState().content.scrollTop).toBe(50) + + editor.setCursorBufferPosition([8, 0]) + presenter.setScrollTop(10) + + expect(presenter.getState().content.scrollTop).toBe(10) + it "corresponds to the passed logical coordinates when building the presenter", -> presenter = buildPresenter(scrollRow: 4, lineHeight: 10, explicitHeight: 20) expect(presenter.getState().content.scrollTop).toBe(40) @@ -765,6 +779,20 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight describe ".scrollLeft", -> + it "changes based on the scroll operation that was performed last", -> + presenter = buildPresenter(scrollLeft: 0, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 10) + expect(presenter.getState().content.scrollLeft).toBe(0) + + presenter.setScrollLeft(20) + editor.setCursorBufferPosition([0, 9]) + + expect(presenter.getState().content.scrollLeft).toBe(90) + + editor.setCursorBufferPosition([0, 18]) + presenter.setScrollLeft(50) + + expect(presenter.getState().content.scrollLeft).toBe(50) + it "corresponds to the passed logical coordinates when building the presenter", -> presenter = buildPresenter(scrollColumn: 3, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expect(presenter.getState().content.scrollLeft).toBe(30) @@ -1893,9 +1921,9 @@ describe "TextEditorPresenter", -> pixelPosition: {top: 6 * 10 - scrollTop, left: gutterWidth} } - editor.insertNewline() - presenter.getState() # forces scroll top to be changed - presenter.setScrollTop(scrollTop) # I'm fighting the editor + expectStateUpdate presenter, -> + editor.insertNewline() + presenter.setScrollTop(scrollTop) # I'm fighting the editor expectValues stateForOverlay(presenter, decoration), { item: item diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 48213ef8c..9d7309d75 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -816,6 +816,7 @@ class TextEditorPresenter setScrollTop: (scrollTop) -> return unless scrollTop? + @pendingScrollLogicalPosition = null @pendingScrollTop = scrollTop @shouldUpdateVerticalScrollState = true @@ -856,6 +857,7 @@ class TextEditorPresenter setScrollLeft: (scrollLeft) -> return unless scrollLeft? + @pendingScrollLogicalPosition = null @pendingScrollLeft = scrollLeft @shouldUpdateHorizontalScrollState = true @@ -1474,6 +1476,8 @@ class TextEditorPresenter didChangeScrollPosition: (position) -> @pendingScrollLogicalPosition = position + @pendingScrollTop = null + @pendingScrollLeft = null @shouldUpdateCursorsState = true @shouldUpdateCustomGutterDecorationState = true From b0d70a63c63328eb28688b3a9e1208b72455b56e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 10:55:03 +0200 Subject: [PATCH 48/58] Prevent default event for actions that won't lead to scrolling --- spec/text-editor-component-spec.coffee | 55 +++----------------------- src/text-editor-component.coffee | 12 ++++-- src/text-editor-presenter.coffee | 6 +++ 3 files changed, 19 insertions(+), 54 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 181d92583..ba87f023d 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -2689,54 +2689,7 @@ describe "TextEditorComponent", -> expect(componentNode.contains(lineNumberNode)).toBe true - it "prevents the default action of mousewheel events for normal editors", -> - spyOn(WheelEvent::, 'preventDefault').andCallThrough() - - wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' - wrapperNode.style.width = 20 * charWidth + 'px' - component.measureDimensions() - nextAnimationFrame() - - # try to scroll past the top, which is impossible - componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: 50)) - expect(wrapperNode.getScrollTop()).toBe 0 - expect(WheelEvent::preventDefault).toHaveBeenCalled() - WheelEvent::preventDefault.reset() - - # scroll to the bottom in one huge event - componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -3000)) - nextAnimationFrame() - maxScrollTop = wrapperNode.getScrollTop() - expect(WheelEvent::preventDefault).toHaveBeenCalled() - WheelEvent::preventDefault.reset() - - # try to scroll past the bottom, which is impossible - componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -30)) - expect(wrapperNode.getScrollTop()).toBe maxScrollTop - expect(WheelEvent::preventDefault).toHaveBeenCalled() - WheelEvent::preventDefault.reset() - - # try to scroll past the left side, which is impossible - componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 50, wheelDeltaY: 0)) - expect(wrapperNode.getScrollLeft()).toBe 0 - expect(WheelEvent::preventDefault).toHaveBeenCalled() - WheelEvent::preventDefault.reset() - - # scroll all the way right - componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -3000, wheelDeltaY: 0)) - nextAnimationFrame() - maxScrollLeft = wrapperNode.getScrollLeft() - expect(WheelEvent::preventDefault).toHaveBeenCalled() - WheelEvent::preventDefault.reset() - - # try to scroll past the right side, which is impossible - componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -30, wheelDeltaY: 0)) - expect(wrapperNode.getScrollLeft()).toBe maxScrollLeft - expect(WheelEvent::preventDefault).toHaveBeenCalled() - - it "doesn't prevent the default action of mousewheel events for mini editors", -> - editor.setMini(true) - + it "only prevents the default action of the mousewheel event if it actually lead to scrolling", -> spyOn(WheelEvent::, 'preventDefault').andCallThrough() wrapperNode.style.height = 4.5 * lineHeightInPixels + 'px' @@ -2753,7 +2706,8 @@ describe "TextEditorComponent", -> componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -3000)) nextAnimationFrame() maxScrollTop = wrapperNode.getScrollTop() - expect(WheelEvent::preventDefault).not.toHaveBeenCalled() + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() # try to scroll past the bottom, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: 0, wheelDeltaY: -30)) @@ -2769,7 +2723,8 @@ describe "TextEditorComponent", -> componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -3000, wheelDeltaY: 0)) nextAnimationFrame() maxScrollLeft = wrapperNode.getScrollLeft() - expect(WheelEvent::preventDefault).not.toHaveBeenCalled() + expect(WheelEvent::preventDefault).toHaveBeenCalled() + WheelEvent::preventDefault.reset() # try to scroll past the right side, which is impossible componentNode.dispatchEvent(new WheelEvent('mousewheel', wheelDeltaX: -30, wheelDeltaY: 0)) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 543317787..747396c0f 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -331,8 +331,6 @@ class TextEditorComponent @pendingScrollLeft = null onMouseWheel: (event) => - event.preventDefault() unless @editor.isMini() - # Only scroll in one direction at a time {wheelDeltaX, wheelDeltaY} = event @@ -348,12 +346,18 @@ class TextEditorComponent if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY) # Scrolling horizontally previousScrollLeft = @presenter.getScrollLeft() - @presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)) + updatedScrollLeft = previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity) + + event.preventDefault() if @presenter.canScrollLeftTo(updatedScrollLeft) + @presenter.setScrollLeft(updatedScrollLeft) else # Scrolling vertically @presenter.setMouseWheelScreenRow(@screenRowForNode(event.target)) previousScrollTop = @presenter.getScrollTop() - @presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)) + updatedScrollTop = previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity) + + event.preventDefault() if @presenter.canScrollTopTo(updatedScrollTop) + @presenter.setScrollTop(updatedScrollTop) onScrollViewScroll: => if @mounted diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 9d7309d75..ad6883c4e 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1600,6 +1600,12 @@ class TextEditorPresenter @commitPendingScrollLeftPosition() @commitPendingScrollTopPosition() + canScrollLeftTo: (scrollLeft) -> + @scrollLeft isnt @constrainScrollLeft(scrollLeft) + + canScrollTopTo: (scrollTop) -> + @scrollTop isnt @constrainScrollTop(scrollTop) + onDidChangeScrollTop: (callback) -> @emitter.on 'did-change-scroll-top', callback From ce714b98520dcb9ce167763012eab2db746f9814 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 10:57:09 +0200 Subject: [PATCH 49/58] :art: Rename to ::onDidRequestAutoscroll --- spec/display-buffer-spec.coffee | 6 +++--- src/display-buffer.coffee | 2 +- src/text-editor-presenter.coffee | 6 ++---- src/text-editor.coffee | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index af51986bf..494c31a08 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1220,9 +1220,9 @@ describe "DisplayBuffer", -> expect(displayBuffer.getDecorations(class: 'one').length).toEqual 1 describe "::scrollToScreenPosition(position, [options])", -> - it "triggers ::onDidChangeScrollPosition with the logical coordinates along with the options", -> - scrollSpy = jasmine.createSpy("::onDidChangeScrollPosition") - displayBuffer.onDidChangeScrollPosition(scrollSpy) + it "triggers ::onDidRequestAutoscroll with the logical coordinates along with the options", -> + scrollSpy = jasmine.createSpy("::onDidRequestAutoscroll") + displayBuffer.onDidRequestAutoscroll(scrollSpy) displayBuffer.scrollToScreenPosition([8, 20]) displayBuffer.scrollToScreenPosition([8, 20], center: true) diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index a039ecc4a..ac4fd86e7 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -125,7 +125,7 @@ class DisplayBuffer extends Model onDidChangeCharacterWidths: (callback) -> @emitter.on 'did-change-character-widths', callback - onDidChangeScrollPosition: (callback) -> + onDidRequestAutoscroll: (callback) -> @emitter.on 'did-change-scroll-position', callback observeDecorations: (callback) -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ad6883c4e..ac9ef8242 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -159,9 +159,7 @@ class TextEditorPresenter @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) - @disposables.add @model.onDidChangeScrollPosition(@didChangeScrollPosition.bind(this)) - # @disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this)) - # @disposables.add @model.onDidChangeScrollLeft(@setScrollLeft.bind(this)) + @disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this)) @observeDecoration(decoration) for decoration in @model.getDecorations() @observeCursor(cursor) for cursor in @model.getCursors() @disposables.add @model.onDidAddGutter(@didAddGutter.bind(this)) @@ -1474,7 +1472,7 @@ class TextEditorPresenter @startBlinkingCursorsAfterDelay() @emitDidUpdateState() - didChangeScrollPosition: (position) -> + requestAutoscroll: (position) -> @pendingScrollLogicalPosition = position @pendingScrollTop = null @pendingScrollLeft = null diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 76d09d381..2c39431a5 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -436,8 +436,8 @@ class TextEditor extends Model atom.views.getView(this).onDidChangeScrollLeft(callback) - onDidChangeScrollPosition: (callback) -> - @displayBuffer.onDidChangeScrollPosition(callback) + onDidRequestAutoscroll: (callback) -> + @displayBuffer.onDidRequestAutoscroll(callback) # TODO Remove once the tabs package no longer uses .on subscriptions onDidChangeIcon: (callback) -> From 33a67ad3c9bea2643edecd1d0fec81a43e4c7725 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 11:05:41 +0200 Subject: [PATCH 50/58] Shim ::setWidth and ::getWidth to TextEditorElement --- src/text-editor-component.coffee | 7 +++++++ src/text-editor-element.coffee | 6 ++++++ src/text-editor-presenter.coffee | 3 +++ src/text-editor.coffee | 12 ++++++++++-- 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 747396c0f..715e053d1 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -856,6 +856,13 @@ class TextEditorComponent {top, left, bottom, right} + setWidth: (width) -> + @domNode.style.width = (width + @presenter.getGutterWidth()) + "px" + @measureDimensions() + + getWidth: -> + @domNode.offsetWidth - @presenter.getGutterWidth() + getModel: -> @editor diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index cfa7d226c..b0b9a2f88 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -286,6 +286,12 @@ class TextEditorElement extends HTMLElement pixelRangeForScreenRange: (screenRange) -> @component.pixelRangeForScreenRange(screenRange) + setWidth: (width) -> + @component.setWidth(width) + + getWidth: -> + @component.getWidth() + stopEventPropagation = (commandListeners) -> newCommandListeners = {} for commandName, commandListener of commandListeners diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ac9ef8242..85bd32886 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1015,6 +1015,9 @@ class TextEditorPresenter @gutterWidth = gutterWidth @updateOverlaysState() + getGutterWidth: -> + @gutterWidth + setLineHeight: (lineHeight) -> unless @lineHeight is lineHeight @lineHeight = lineHeight diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 2c39431a5..44446b010 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2987,8 +2987,16 @@ class TextEditor extends Model getClientHeight: -> @displayBuffer.getClientHeight() - setWidth: (width) -> @displayBuffer.setWidth(width) - getWidth: -> @displayBuffer.getWidth() + setWidth: (width, reentrant=false) -> + if reentrant + @displayBuffer.setWidth(width) + else + Grim.deprecate("This is now a view method. Call TextEditorElement::setWidth instead.") + atom.views.getView(this).setWidth(width) + + getWidth: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.") + atom.views.getView(this).getWidth() getScrollRow: -> @scrollRow setScrollRow: (@scrollRow) -> From 051baebd9ccfbbde3206e8e872169835af9de0a3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 11:15:41 +0200 Subject: [PATCH 51/58] Shim ::setHeight and ::getHeight to TextEditorElement --- src/text-editor-component.coffee | 8 ++------ src/text-editor-element.coffee | 12 ++++++++++-- src/text-editor.coffee | 12 ++++++++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 715e053d1..90aa73ffb 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -856,12 +856,8 @@ class TextEditorComponent {top, left, bottom, right} - setWidth: (width) -> - @domNode.style.width = (width + @presenter.getGutterWidth()) + "px" - @measureDimensions() - - getWidth: -> - @domNode.offsetWidth - @presenter.getGutterWidth() + getGutterWidth: -> + @presenter.getGutterWidth() getModel: -> @editor diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index b0b9a2f88..27d7f4767 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -287,10 +287,18 @@ class TextEditorElement extends HTMLElement @component.pixelRangeForScreenRange(screenRange) setWidth: (width) -> - @component.setWidth(width) + @style.width = (@component.getGutterWidth() + width) + "px" + @component.measureDimensions() getWidth: -> - @component.getWidth() + @style.offsetWidth - @component.getGutterWidth() + + setHeight: (height) -> + @style.height = height + "px" + @component.measureDimensions() + + getHeight: -> + @style.offsetHeight stopEventPropagation = (commandListeners) -> newCommandListeners = {} diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 44446b010..192ff2f39 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2982,8 +2982,16 @@ class TextEditor extends Model getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth() setDefaultCharWidth: (defaultCharWidth) -> @displayBuffer.setDefaultCharWidth(defaultCharWidth) - setHeight: (height) -> @displayBuffer.setHeight(height) - getHeight: -> @displayBuffer.getHeight() + setHeight: (height, reentrant=false) -> + if reentrant + @displayBuffer.setHeight(height) + else + Grim.deprecate("This is now a view method. Call TextEditorElement::setHeight instead.") + atom.views.getView(this).setHeight(height) + + getHeight: -> + Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.") + atom.views.getView(this).getHeight() getClientHeight: -> @displayBuffer.getClientHeight() From bded7e4fb0c69f4612e5a7fc4ff03bcb66b8771e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 12:04:25 +0200 Subject: [PATCH 52/58] :green_heart: --- spec/text-editor-spec.coffee | 4 ++-- src/text-editor-presenter.coffee | 4 ++-- src/text-editor.coffee | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index f7f9e5466..1141fc78e 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -4379,7 +4379,7 @@ describe "TextEditor", -> describe ".pageUp/Down()", -> it "moves the cursor down one page length", -> editor.setLineHeightInPixels(10) - editor.setHeight(50) + editor.setHeight(50, true) expect(editor.getCursorBufferPosition().row).toBe 0 editor.pageDown() @@ -4397,7 +4397,7 @@ describe "TextEditor", -> describe ".selectPageUp/Down()", -> it "selects one screen height of text up or down", -> editor.setLineHeightInPixels(10) - editor.setHeight(50) + editor.setHeight(50, true) expect(editor.getCursorBufferPosition().row).toBe 0 editor.selectPageDown() diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 85bd32886..658d1672c 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -687,7 +687,7 @@ class TextEditorPresenter return unless @height? and @horizontalScrollbarHeight? clientHeight = @height - @horizontalScrollbarHeight - @model.setHeight(clientHeight) + @model.setHeight(clientHeight, true) unless @clientHeight is clientHeight @clientHeight = clientHeight @@ -698,7 +698,7 @@ class TextEditorPresenter return unless @contentFrameWidth? and @verticalScrollbarWidth? clientWidth = @contentFrameWidth - @verticalScrollbarWidth - @model.setWidth(clientWidth) unless @editorWidthInChars + @model.setWidth(clientWidth, true) unless @editorWidthInChars unless @clientWidth is clientWidth @clientWidth = clientWidth diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 192ff2f39..3d18481a5 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2991,7 +2991,7 @@ class TextEditor extends Model getHeight: -> Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.") - atom.views.getView(this).getHeight() + @displayBuffer.getHeight() getClientHeight: -> @displayBuffer.getClientHeight() @@ -3004,7 +3004,7 @@ class TextEditor extends Model getWidth: -> Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.") - atom.views.getView(this).getWidth() + @displayBuffer.getWidth() getScrollRow: -> @scrollRow setScrollRow: (@scrollRow) -> From a0277728d631cc4156d8e25c0d87340bd7b06827 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 12:58:23 +0200 Subject: [PATCH 53/58] :fire: Remove deprecated methods --- spec/display-buffer-spec.coffee | 14 ----- src/display-buffer.coffee | 91 +-------------------------------- 2 files changed, 2 insertions(+), 103 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 494c31a08..7ed97f0d0 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1151,20 +1151,6 @@ describe "DisplayBuffer", -> expect(marker1.getProperties()).toEqual a: 1, b: 2 expect(marker2.getProperties()).toEqual a: 1, b: 3 - describe "Marker::getPixelRange()", -> - it "returns the start and end positions of the marker based on the line height and character widths assigned to the DisplayBuffer", -> - marker = displayBuffer.markScreenRange([[5, 10], [6, 4]]) - - displayBuffer.setLineHeightInPixels(20) - displayBuffer.setDefaultCharWidth(10) - - for char in ['r', 'e', 't', 'u', 'r', 'n'] - displayBuffer.setScopedCharWidth(["source.js", "keyword.control.js"], char, 11) - - {start, end} = marker.getPixelRange() - expect(start.top).toBe 5 * 20 - expect(start.left).toBe (4 * 10) + (6 * 11) - describe 'when there are multiple DisplayBuffers for a buffer', -> describe 'when a marker is created', -> it 'the second display buffer will not emit a marker-created event when the marker has been deleted in the first marker-created event', -> diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index ac4fd86e7..27d1b44cf 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -126,7 +126,7 @@ class DisplayBuffer extends Model @emitter.on 'did-change-character-widths', callback onDidRequestAutoscroll: (callback) -> - @emitter.on 'did-change-scroll-position', callback + @emitter.on 'did-request-autoscroll', callback observeDecorations: (callback) -> callback(decoration) for decoration in @getDecorations() @@ -169,19 +169,9 @@ class DisplayBuffer extends Model setVerticalScrollMargin: (@verticalScrollMargin) -> @verticalScrollMargin - getVerticalScrollMarginInPixels: -> @getVerticalScrollMargin() * @getLineHeightInPixels() - getHorizontalScrollMargin: -> Math.min(@horizontalScrollMargin, Math.floor(((@getWidth() / @getDefaultCharWidth()) - 1) / 2)) setHorizontalScrollMargin: (@horizontalScrollMargin) -> @horizontalScrollMargin - getHorizontalScrollMarginInPixels: -> scrollMarginInPixels = @getHorizontalScrollMargin() * @getDefaultCharWidth() - - getHorizontalScrollbarHeight: -> @horizontalScrollbarHeight - setHorizontalScrollbarHeight: (@horizontalScrollbarHeight) -> @horizontalScrollbarHeight - - getVerticalScrollbarWidth: -> @verticalScrollbarWidth - setVerticalScrollbarWidth: (@verticalScrollbarWidth) -> @verticalScrollbarWidth - getHeight: -> @height @@ -237,12 +227,9 @@ class DisplayBuffer extends Model clearScopedCharWidths: -> @charWidthsByScope = {} - getScrollWidth: -> - @scrollWidth - scrollToScreenRange: (screenRange, options = {}) -> scrollEvent = {screenRange, options} - @emitter.emit "did-change-scroll-position", scrollEvent + @emitter.emit "did-request-autoscroll", scrollEvent scrollToScreenPosition: (screenPosition, options) -> @scrollToScreenRange(new Range(screenPosition, screenPosition), options) @@ -505,80 +492,6 @@ class DisplayBuffer extends Model end = @bufferPositionForScreenPosition(screenRange.end) new Range(start, end) - pixelRangeForScreenRange: (screenRange, clip=true) -> - {start, end} = Range.fromObject(screenRange) - {start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)} - - pixelPositionForScreenPosition: (screenPosition, clip=true) -> - screenPosition = Point.fromObject(screenPosition) - screenPosition = @clipScreenPosition(screenPosition) if clip - - targetRow = screenPosition.row - targetColumn = screenPosition.column - defaultCharWidth = @defaultCharWidth - - top = targetRow * @lineHeightInPixels - left = 0 - column = 0 - - iterator = @tokenizedLineForScreenRow(targetRow).getTokenIterator() - while iterator.next() - charWidths = @getScopedCharWidths(iterator.getScopes()) - valueIndex = 0 - value = iterator.getText() - while valueIndex < value.length - if iterator.isPairedCharacter() - char = value - charLength = 2 - valueIndex += 2 - else - char = value[valueIndex] - charLength = 1 - valueIndex++ - - return {top, left} if column is targetColumn - left += charWidths[char] ? defaultCharWidth unless char is '\0' - column += charLength - {top, left} - - screenPositionForPixelPosition: (pixelPosition) -> - targetTop = pixelPosition.top - targetLeft = pixelPosition.left - defaultCharWidth = @defaultCharWidth - row = Math.floor(targetTop / @getLineHeightInPixels()) - targetLeft = 0 if row < 0 - targetLeft = Infinity if row > @getLastRow() - row = Math.min(row, @getLastRow()) - row = Math.max(0, row) - - left = 0 - column = 0 - - iterator = @tokenizedLineForScreenRow(row).getTokenIterator() - while iterator.next() - charWidths = @getScopedCharWidths(iterator.getScopes()) - value = iterator.getText() - valueIndex = 0 - while valueIndex < value.length - if iterator.isPairedCharacter() - char = value - charLength = 2 - valueIndex += 2 - else - char = value[valueIndex] - charLength = 1 - valueIndex++ - - charWidth = charWidths[char] ? defaultCharWidth - break if targetLeft <= left + (charWidth / 2) - left += charWidth - column += charLength - - new Point(row, column) - - pixelPositionForBufferPosition: (bufferPosition) -> - @pixelPositionForScreenPosition(@screenPositionForBufferPosition(bufferPosition)) - # Gets the number of screen lines. # # Returns a {Number}. From a96af2465f51564953670f259d6e9eea939677c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 13:26:30 +0200 Subject: [PATCH 54/58] :art: Use ::setWidth and ::setHeight --- spec/text-editor-component-spec.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index ba87f023d..323f6efb3 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3333,9 +3333,8 @@ describe "TextEditorComponent", -> component.measureDimensions() nextAnimationFrame() - # Why does this set an incorrect width? :confused: - wrapperNode.style.width = "108px" # this should be 55px - wrapperNode.style.height = 5.5 * 10 + "px" + wrapperNode.setWidth(55) + wrapperNode.setHeight(55) component.measureDimensions() nextAnimationFrame() From 7cd318f55aa4d0cdbe01c50295ff86a895510863 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 13:28:18 +0200 Subject: [PATCH 55/58] :fire: --- src/marker.coffee | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/marker.coffee b/src/marker.coffee index 29274068d..16f644027 100644 --- a/src/marker.coffee +++ b/src/marker.coffee @@ -368,6 +368,3 @@ class Marker @wasValid = isValid @emitter.emit 'did-change', changeEvent - - getPixelRange: -> - @displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false) From 7316eb4d5281a69137774c4180ecb2b684e5965c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 17:27:14 +0200 Subject: [PATCH 56/58] :fire: Drop deprecated methods from the public API --- src/text-editor-element.coffee | 2 ++ src/text-editor.coffee | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-editor-element.coffee b/src/text-editor-element.coffee index 27d7f4767..685877a2e 100644 --- a/src/text-editor-element.coffee +++ b/src/text-editor-element.coffee @@ -236,9 +236,11 @@ class TextEditorElement extends HTMLElement setScrollBottom: (scrollBottom) -> @component.setScrollBottom(scrollBottom) + # Essential: Scrolls the editor to the top scrollToTop: -> @setScrollTop(0) + # Essential: Scrolls the editor to the bottom scrollToBottom: -> @setScrollBottom(Infinity) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 3d18481a5..1a925e4c1 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -2857,13 +2857,11 @@ class TextEditor extends Model scrollToScreenPosition: (screenPosition, options) -> @displayBuffer.scrollToScreenPosition(screenPosition, options) - # Essential: Scrolls the editor to the top scrollToTop: -> Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.") atom.views.getView(this).scrollToTop() - # Essential: Scrolls the editor to the bottom scrollToBottom: -> Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.") From 74deceb03750a9652276c70b542e17386c79cd32 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Sep 2015 18:03:07 +0200 Subject: [PATCH 57/58] :art: --- src/text-editor.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 1a925e4c1..04f386439 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -80,7 +80,7 @@ class TextEditor extends Model state.registerEditor = true new this(state) - constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) -> + constructor: ({@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) -> super @emitter = new Emitter @@ -121,8 +121,8 @@ class TextEditor extends Model deserializer: 'TextEditor' id: @id softTabs: @softTabs - scrollRow: @scrollRow - scrollColumn: @scrollColumn + scrollRow: @getScrollRow() + scrollColumn: @getScrollColumn() displayBuffer: @displayBuffer.serialize() subscribeToBuffer: -> From a8702527028efbf0cf6f4b6f32731f7ecdb4b009 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 26 Sep 2015 18:04:15 +0200 Subject: [PATCH 58/58] :fire: Remove unused code --- spec/text-editor-component-spec.coffee | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index 323f6efb3..27e9864aa 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -3340,10 +3340,7 @@ describe "TextEditorComponent", -> component.presenter.setHorizontalScrollbarHeight(0) component.presenter.setVerticalScrollbarWidth(0) - nextAnimationFrame() # perform requested update - - afterEach -> - atom.themes.removeStylesheet("test") + nextAnimationFrame() describe "when selecting buffer ranges", -> it "autoscrolls the selection if it is last unless the 'autoscroll' option is false", ->