diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 7376b5823..54899a1d6 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -25,27 +25,34 @@ describe "TextEditorPresenter", -> editor.destroy() buffer.destroy() - buildPresenter = (params={}) -> + buildPresenterWithoutMeasurements = (params={}) -> _.defaults params, model: editor - explicitHeight: 130 - contentFrameWidth: 500 - windowWidth: 500 - windowHeight: 130 - boundingClientRect: {left: 0, top: 0, width: 500, height: 130} - gutterWidth: 0 - lineHeight: 10 - baseCharacterWidth: 10 - horizontalScrollbarHeight: 10 - verticalScrollbarWidth: 10 - scrollTop: 0 - scrollLeft: 0 config: atom.config - + contentFrameWidth: 500 presenter = new TextEditorPresenter(params) presenter.setLinesYardstick(new FakeLinesYardstick(editor, presenter)) presenter + buildPresenter = (params={}) -> + presenter = buildPresenterWithoutMeasurements(params) + presenter.setScrollTop(params.scrollTop) if params.scrollTop? + presenter.setScrollLeft(params.scrollLeft) if params.scrollLeft? + presenter.setExplicitHeight(params.explicitHeight ? 130) + presenter.setWindowSize(params.windowWidth ? 500, params.windowHeight ? 130) + presenter.setBoundingClientRect(params.boundingClientRect ? { + left: 0 + top: 0 + width: 500 + height: 130 + }) + presenter.setGutterWidth(params.gutterWidth ? 0) + presenter.setLineHeight(params.lineHeight ? 10) + presenter.setBaseCharacterWidth(params.baseCharacterWidth ? 10) + presenter.setHorizontalScrollbarHeight(params.horizontalScrollbarHeight ? 10) + presenter.setVerticalScrollbarWidth(params.verticalScrollbarWidth ? 10) + presenter + expectValues = (actual, expected) -> for key, value of expected expect(actual[key]).toEqual value @@ -167,16 +174,14 @@ describe "TextEditorPresenter", -> expect(stateFn(presenter).tiles[12]).toBeDefined() it "is empty until all of the required measurements are assigned", -> - presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null) + presenter = buildPresenterWithoutMeasurements() expect(stateFn(presenter).tiles).toEqual({}) presenter.setExplicitHeight(25) expect(stateFn(presenter).tiles).toEqual({}) + # Sets scroll row from model's logical position presenter.setLineHeight(10) - expect(stateFn(presenter).tiles).toEqual({}) - - presenter.setScrollTop(0) expect(stateFn(presenter).tiles).not.toEqual({}) it "updates when ::scrollTop changes", -> @@ -619,6 +624,8 @@ describe "TextEditorPresenter", -> describe ".scrollingVertically", -> it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", -> presenter = buildPresenter(scrollTop: 10, stoppedScrollingDelay: 200, explicitHeight: 100) + expect(presenter.getState().content.scrollingVertically).toBe true + advanceClock(300) expect(presenter.getState().content.scrollingVertically).toBe false expectStateUpdate presenter, -> presenter.setScrollTop(0) expect(presenter.getState().content.scrollingVertically).toBe true @@ -761,7 +768,8 @@ describe "TextEditorPresenter", -> 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) + editor.setFirstVisibleScreenRow(4) + presenter = buildPresenter(lineHeight: 10, explicitHeight: 20) expect(presenter.getState().content.scrollTop).toBe(40) it "tracks the value of ::scrollTop", -> @@ -775,11 +783,11 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollTop(50) presenter.getState() # commits scroll position - expect(editor.getScrollRow()).toBe(5) + expect(editor.getFirstVisibleScreenRow()).toBe 5 expectStateUpdate presenter, -> presenter.setScrollTop(57) presenter.getState() # commits scroll position - expect(editor.getScrollRow()).toBe(6) + expect(editor.getFirstVisibleScreenRow()).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) @@ -888,7 +896,8 @@ describe "TextEditorPresenter", -> 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) + editor.setFirstVisibleScreenColumn(3) + presenter = buildPresenter(lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expect(presenter.getState().content.scrollLeft).toBe(30) it "tracks the value of ::scrollLeft", -> @@ -902,11 +911,11 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> presenter.setScrollLeft(50) presenter.getState() # commits scroll position - expect(editor.getScrollColumn()).toBe(5) + expect(editor.getFirstVisibleScreenColumn()).toBe 5 expectStateUpdate presenter, -> presenter.setScrollLeft(57) presenter.getState() # commits scroll position - expect(editor.getScrollColumn()).toBe(6) + expect(editor.getFirstVisibleScreenColumn()).toBe 6 it "is always rounded to the nearest integer", -> presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) @@ -1006,20 +1015,25 @@ describe "TextEditorPresenter", -> describe ".backgroundColor", -> it "is assigned to ::backgroundColor unless the editor is mini", -> - presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)') + presenter = buildPresenter() + presenter.setBackgroundColor('rgba(255, 0, 0, 0)') expect(presenter.getState().content.backgroundColor).toBe 'rgba(255, 0, 0, 0)' + editor.setMini(true) - presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)') + presenter = buildPresenter() + presenter.setBackgroundColor('rgba(255, 0, 0, 0)') expect(presenter.getState().content.backgroundColor).toBeNull() it "updates when ::backgroundColor changes", -> - presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)') + presenter = buildPresenter() + presenter.setBackgroundColor('rgba(255, 0, 0, 0)') expect(presenter.getState().content.backgroundColor).toBe 'rgba(255, 0, 0, 0)' expectStateUpdate presenter, -> presenter.setBackgroundColor('rgba(0, 0, 255, 0)') expect(presenter.getState().content.backgroundColor).toBe 'rgba(0, 0, 255, 0)' it "updates when ::mini changes", -> - presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)') + presenter = buildPresenter() + presenter.setBackgroundColor('rgba(255, 0, 0, 0)') expect(presenter.getState().content.backgroundColor).toBe 'rgba(255, 0, 0, 0)' expectStateUpdate presenter, -> editor.setMini(true) expect(presenter.getState().content.backgroundColor).toBeNull() @@ -1047,6 +1061,7 @@ describe "TextEditorPresenter", -> describe "[tileId].lines[lineId]", -> # line state objects it "includes the state for visible lines in a tile", -> presenter = buildPresenter(explicitHeight: 3, scrollTop: 4, lineHeight: 1, tileSize: 3, stoppedScrollingDelay: 200) + presenter.setExplicitHeight(3) expect(lineStateForScreenRow(presenter, 2)).toBeUndefined() @@ -1320,7 +1335,7 @@ describe "TextEditorPresenter", -> expect(stateForCursor(presenter, 4)).toBeUndefined() it "is empty until all of the required measurements are assigned", -> - presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null, horizontalScrollbarHeight: null) + presenter = buildPresenterWithoutMeasurements() expect(presenter.getState().content.cursors).toEqual({}) presenter.setExplicitHeight(25) @@ -1335,6 +1350,15 @@ describe "TextEditorPresenter", -> presenter.setBaseCharacterWidth(8) expect(presenter.getState().content.cursors).toEqual({}) + presenter.setBoundingClientRect(top: 0, left: 0, width: 500, height: 130) + expect(presenter.getState().content.cursors).toEqual({}) + + presenter.setWindowSize(500, 130) + expect(presenter.getState().content.cursors).toEqual({}) + + presenter.setVerticalScrollbarWidth(10) + expect(presenter.getState().content.cursors).toEqual({}) + presenter.setHorizontalScrollbarHeight(10) expect(presenter.getState().content.cursors).not.toEqual({}) @@ -1466,7 +1490,8 @@ describe "TextEditorPresenter", -> it "alternates between true and false twice per ::cursorBlinkPeriod when the editor is focused", -> cursorBlinkPeriod = 100 cursorBlinkResumeDelay = 200 - presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true}) + presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay}) + presenter.setFocused(true) expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) @@ -1493,7 +1518,8 @@ describe "TextEditorPresenter", -> it "stops alternating for ::cursorBlinkResumeDelay when a cursor moves or a cursor is added", -> cursorBlinkPeriod = 100 cursorBlinkResumeDelay = 200 - presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true}) + presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay}) + presenter.setFocused(true) expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) @@ -1643,7 +1669,7 @@ describe "TextEditorPresenter", -> [[0, 2], [2, 4]], ]) - presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null, tileSize: 2) + presenter = buildPresenterWithoutMeasurements(tileSize: 2) for tileId, tileState of presenter.getState().content.tiles expect(tileState.highlights).toEqual({}) @@ -1970,7 +1996,7 @@ describe "TextEditorPresenter", -> marker = editor.markBufferRange([[2, 13], [4, 14]], invalidate: 'touch') decoration = editor.decorateMarker(marker, {type: 'overlay', position: 'tail', item}) - presenter = buildPresenter(baseCharacterWidth: null, lineHeight: null, windowWidth: null, windowHeight: null, boundingClientRect: null) + presenter = buildPresenterWithoutMeasurements() expect(presenter.getState().content.overlays).toEqual({}) presenter.setBaseCharacterWidth(10) @@ -1982,6 +2008,12 @@ describe "TextEditorPresenter", -> presenter.setWindowSize(500, 100) expect(presenter.getState().content.overlays).toEqual({}) + presenter.setVerticalScrollbarWidth(10) + expect(presenter.getState().content.overlays).toEqual({}) + + presenter.setHorizontalScrollbarHeight(10) + expect(presenter.getState().content.overlays).toEqual({}) + presenter.setBoundingClientRect({top: 0, left: 0, height: 100, width: 500}) expect(presenter.getState().content.overlays).not.toEqual({}) @@ -2168,7 +2200,8 @@ describe "TextEditorPresenter", -> expect(editor.getRowsPerPage()).toBe(20) it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", -> - presenter = buildPresenter(explicitHeight: null, autoHeight: true) + presenter = buildPresenter(explicitHeight: null) + presenter.setAutoHeight(true) expect(presenter.getState().height).toBe editor.getScreenLineCount() * 10 expectStateUpdate presenter, -> presenter.setAutoHeight(false) @@ -2185,7 +2218,9 @@ describe "TextEditorPresenter", -> describe ".focused", -> it "tracks the value of ::focused", -> - presenter = buildPresenter(focused: false) + presenter = buildPresenter() + presenter.setFocused(false) + expect(presenter.getState().focused).toBe false expectStateUpdate presenter, -> presenter.setFocused(true) expect(presenter.getState().focused).toBe true @@ -2882,7 +2917,9 @@ describe "TextEditorPresenter", -> describe ".backgroundColor", -> it "is assigned to ::gutterBackgroundColor if present, and to ::backgroundColor otherwise", -> - presenter = buildPresenter(backgroundColor: "rgba(255, 0, 0, 0)", gutterBackgroundColor: "rgba(0, 255, 0, 0)") + presenter = buildPresenter() + presenter.setBackgroundColor("rgba(255, 0, 0, 0)") + presenter.setGutterBackgroundColor("rgba(0, 255, 0, 0)") expect(getStylesForGutterWithName(presenter, 'line-number').backgroundColor).toBe "rgba(0, 255, 0, 0)" expect(getStylesForGutterWithName(presenter, 'test-gutter').backgroundColor).toBe "rgba(0, 255, 0, 0)" diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index 0ad43046b..05e454da3 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -5444,6 +5444,73 @@ describe "TextEditor", -> editor.selectPageUp() expect(editor.getSelectedBufferRanges()).toEqual [[[0, 0], [12, 2]]] + describe "::setFirstVisibleScreenRow() and ::getFirstVisibleScreenRow()", -> + beforeEach -> + line = Array(9).join('0123456789') + editor.setText([1..100].map(-> line).join('\n')) + expect(editor.getLineCount()).toBe 100 + expect(editor.lineTextForBufferRow(0).length).toBe 80 + + describe "when the editor doesn't have a height and lineHeightInPixels", -> + it "does not affect the editor's visible row range", -> + expect(editor.getVisibleRowRange()).toBeNull() + + editor.setFirstVisibleScreenRow(1) + expect(editor.getFirstVisibleScreenRow()).toEqual 1 + + editor.setFirstVisibleScreenRow(3) + expect(editor.getFirstVisibleScreenRow()).toEqual 3 + + expect(editor.getVisibleRowRange()).toBeNull() + expect(editor.getLastVisibleScreenRow()).toBeNull() + + describe "when the editor has a height and lineHeightInPixels", -> + beforeEach -> + atom.config.set('editor.scrollPastEnd', true) + editor.setHeight(100, true) + editor.setLineHeightInPixels(10) + + it "updates the editor's visible row range", -> + editor.setFirstVisibleScreenRow(2) + expect(editor.getFirstVisibleScreenRow()).toEqual 2 + expect(editor.getLastVisibleScreenRow()).toBe 12 + expect(editor.getVisibleRowRange()).toEqual [2, 12] + + it "notifies ::onDidChangeFirstVisibleScreenRow observers", -> + changeCount = 0 + editor.onDidChangeFirstVisibleScreenRow -> changeCount++ + + editor.setFirstVisibleScreenRow(2) + expect(changeCount).toBe 1 + + editor.setFirstVisibleScreenRow(2) + expect(changeCount).toBe 1 + + editor.setFirstVisibleScreenRow(3) + expect(changeCount).toBe 2 + + it "ensures that the top row is less than the buffer's line count", -> + editor.setFirstVisibleScreenRow(102) + expect(editor.getFirstVisibleScreenRow()).toEqual 99 + expect(editor.getVisibleRowRange()).toEqual [99, 99] + + it "ensures that the left column is less than the length of the longest screen line", -> + editor.setFirstVisibleScreenRow(10) + expect(editor.getFirstVisibleScreenRow()).toEqual 10 + + editor.setText("\n\n\n") + + editor.setFirstVisibleScreenRow(10) + expect(editor.getFirstVisibleScreenRow()).toEqual 3 + + describe "when the 'editor.scrollPastEnd' option is set to false", -> + it "ensures that the bottom row is less than the buffer's line count", -> + atom.config.set('editor.scrollPastEnd', false) + + editor.setFirstVisibleScreenRow(95) + expect(editor.getFirstVisibleScreenRow()).toEqual 89 + expect(editor.getVisibleRowRange()).toEqual [89, 99] + describe '.get/setPlaceholderText()', -> it 'can be created with placeholderText', -> newEditor = atom.workspace.buildTextEditor( diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 99938ef5f..430b0c0fd 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -50,10 +50,6 @@ class TextEditorComponent @presenter = new TextEditorPresenter model: @editor - scrollTop: 0 - scrollLeft: 0 - 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 018ef72e2..7bd66b87f 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -13,15 +13,12 @@ class TextEditorPresenter minimumReflowInterval: 200 constructor: (params) -> - {@model, @config, @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 - @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight - @measuredVerticalScrollbarWidth = verticalScrollbarWidth - @gutterWidth ?= 0 - @tileSize ?= 6 + {@model, @config} = params + {@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params + {@contentFrameWidth} = params + @gutterWidth = 0 + @tileSize ?= 6 @realScrollTop = @scrollTop @realScrollLeft = @scrollLeft @disposables = new CompositeDisposable @@ -77,7 +74,6 @@ class TextEditorPresenter @updateVerticalDimensions() @updateScrollbarDimensions() - @restoreScrollPosition() @commitPendingLogicalScrollTopPosition() @commitPendingScrollTopPosition() @@ -218,6 +214,7 @@ class TextEditorPresenter @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this)) + @disposables.add @model.onDidChangeFirstVisibleScreenRow(@didChangeFirstVisibleScreenRow.bind(this)) @observeCursor(cursor) for cursor in @model.getCursors() @disposables.add @model.onDidAddGutter(@didAddGutter.bind(this)) return @@ -778,8 +775,7 @@ class TextEditorPresenter if scrollTop isnt @realScrollTop and not Number.isNaN(scrollTop) @realScrollTop = scrollTop @scrollTop = Math.round(scrollTop) - @scrollRow = Math.round(@scrollTop / @lineHeight) - @model.setScrollRow(@scrollRow) + @model.setFirstVisibleScreenRow(Math.round(@scrollTop / @lineHeight), true) @updateStartRow() @updateEndRow() @@ -795,8 +791,7 @@ class TextEditorPresenter if scrollLeft isnt @realScrollLeft and not Number.isNaN(scrollLeft) @realScrollLeft = scrollLeft @scrollLeft = Math.round(scrollLeft) - @scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth) - @model.setScrollColumn(@scrollColumn) + @model.setFirstVisibleScreenColumn(Math.round(@scrollLeft / @baseCharacterWidth)) @emitter.emit 'did-change-scroll-left', @scrollLeft @@ -1095,6 +1090,7 @@ class TextEditorPresenter setLineHeight: (lineHeight) -> unless @lineHeight is lineHeight @lineHeight = lineHeight + @restoreScrollTopIfNeeded() @model.setLineHeightInPixels(lineHeight) @shouldUpdateHeightState = true @shouldUpdateHorizontalScrollState = true @@ -1122,6 +1118,7 @@ class TextEditorPresenter @halfWidthCharWidth = halfWidthCharWidth @koreanCharWidth = koreanCharWidth @model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) + @restoreScrollLeftIfNeeded() @characterWidthsChanged() characterWidthsChanged: -> @@ -1433,6 +1430,9 @@ class TextEditorPresenter @emitDidUpdateState() + didChangeFirstVisibleScreenRow: (screenRow) -> + @updateScrollTop(screenRow * @lineHeight) + getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) @@ -1512,14 +1512,6 @@ class TextEditorPresenter @updateScrollTop(@pendingScrollTop) @pendingScrollTop = null - restoreScrollPosition: -> - return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements() - - @setScrollTop(@scrollRow * @lineHeight) if @scrollRow? - @setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn? - - @hasRestoredScrollPosition = true - clearPendingScrollPosition: -> @pendingScrollLogicalPosition = null @pendingScrollTop = null @@ -1531,6 +1523,14 @@ class TextEditorPresenter canScrollTopTo: (scrollTop) -> @scrollTop isnt @constrainScrollTop(scrollTop) + restoreScrollTopIfNeeded: -> + unless @scrollTop? + @updateScrollTop(@model.getFirstVisibleScreenRow() * @lineHeight) + + restoreScrollLeftIfNeeded: -> + unless @scrollLeft? + @updateScrollLeft(@model.getFirstVisibleScreenColumn() * @baseCharacterWidth) + onDidChangeScrollTop: (callback) -> @emitter.on 'did-change-scroll-top', callback diff --git a/src/text-editor.coffee b/src/text-editor.coffee index d44791013..e5b3bd481 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -54,13 +54,11 @@ GutterContainer = require './gutter-container' # soft wraps and folds to ensure your code interacts with them correctly. module.exports = class TextEditor extends Model - callDisplayBufferCreatedHook: false buffer: null languageMode: null cursors: null selections: null suppressSelectionMerging: false - updateBatchDepth: 0 selectionFlashDuration: 500 gutterContainer: null @@ -90,7 +88,7 @@ class TextEditor extends Model super { - @softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength, + @softTabs, @firstVisibleScreenRow, @firstVisibleScreenColumn, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry, @@ -106,6 +104,8 @@ class TextEditor extends Model throw new Error("Must pass a project parameter when constructing TextEditors") unless @project? throw new Error("Must pass an assert parameter when constructing TextEditors") unless @assert? + @firstVisibleScreenRow ?= 0 + @firstVisibleScreenColumn ?= 0 @emitter = new Emitter @disposables = new CompositeDisposable @cursors = [] @@ -146,8 +146,8 @@ class TextEditor extends Model deserializer: 'TextEditor' id: @id softTabs: @softTabs - scrollRow: @getScrollRow() - scrollColumn: @getScrollColumn() + firstVisibleScreenRow: @getFirstVisibleScreenRow() + firstVisibleScreenColumn: @getFirstVisibleScreenColumn() displayBuffer: @displayBuffer.serialize() selectionsMarkerLayerId: @selectionsMarkerLayer.id @@ -453,6 +453,9 @@ class TextEditor extends Model onDidChangeCharacterWidths: (callback) -> @displayBuffer.onDidChangeCharacterWidths(callback) + onDidChangeFirstVisibleScreenRow: (callback, fromView) -> + @emitter.on 'did-change-first-visible-screen-row', callback + onDidChangeScrollTop: (callback) -> Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.") @@ -3130,14 +3133,6 @@ class TextEditor extends Model @placeholderText = placeholderText @emitter.emit 'did-change-placeholder-text', @placeholderText - getFirstVisibleScreenRow: -> - deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.") - @viewRegistry.getView(this).getVisibleRowRange()[0] - - getLastVisibleScreenRow: -> - Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.") - @viewRegistry.getView(this).getVisibleRowRange()[1] - pixelPositionForBufferPosition: (bufferPosition) -> Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead") @viewRegistry.getView(this).pixelPositionForBufferPosition(bufferPosition) @@ -3192,11 +3187,40 @@ class TextEditor extends Model Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.") @displayBuffer.getWidth() - getScrollRow: -> @scrollRow - setScrollRow: (@scrollRow) -> + # Experimental: Scroll the editor such that the given screen row is at the + # top of the visible area. + setFirstVisibleScreenRow: (screenRow, fromView) -> + unless fromView + maxScreenRow = @getLineCount() - 1 + unless @config.get('editor.scrollPastEnd') + height = @displayBuffer.getHeight() + lineHeightInPixels = @displayBuffer.getLineHeightInPixels() + if height? and lineHeightInPixels? + maxScreenRow -= Math.floor(height / lineHeightInPixels) + screenRow = Math.max(Math.min(screenRow, maxScreenRow), 0) - getScrollColumn: -> @scrollColumn - setScrollColumn: (@scrollColumn) -> + unless screenRow is @firstVisibleScreenRow + @firstVisibleScreenRow = screenRow + @emitter.emit 'did-change-first-visible-screen-row', screenRow unless fromView + + getFirstVisibleScreenRow: -> @firstVisibleScreenRow + + getLastVisibleScreenRow: -> + height = @displayBuffer.getHeight() + lineHeightInPixels = @displayBuffer.getLineHeightInPixels() + if height? and lineHeightInPixels? + Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getLineCount() - 1) + else + null + + getVisibleRowRange: -> + if lastVisibleScreenRow = @getLastVisibleScreenRow() + [@firstVisibleScreenRow, lastVisibleScreenRow] + else + null + + setFirstVisibleScreenColumn: (@firstVisibleScreenColumn) -> + getFirstVisibleScreenColumn: -> @firstVisibleScreenColumn getScrollTop: -> Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.") @@ -3253,11 +3277,6 @@ class TextEditor extends Model @viewRegistry.getView(this).getMaxScrollTop() - getVisibleRowRange: -> - Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.") - - @viewRegistry.getView(this).getVisibleRowRange() - intersectsVisibleRowRange: (startRow, endRow) -> Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")