From 48cc5e713ea6f5110f680401b3bcdf287861a601 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Nov 2015 10:13:09 -0800 Subject: [PATCH] Make presenter respond to external changes to model's first visible screen row --- spec/text-editor-spec.coffee | 65 ++++++++++++++++++++++++++++++++ src/text-editor-presenter.coffee | 6 ++- src/text-editor.coffee | 33 +++++++++++++--- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index ed299ab54..77a2ba30a 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -4784,6 +4784,71 @@ 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() + + 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.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-presenter.coffee b/src/text-editor-presenter.coffee index bda61ab87..3b679b319 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -213,6 +213,7 @@ class TextEditorPresenter @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this)) + @disposables.add @model.onDidChangeFirstVisibleScreenRow(@didChangeFirstVisibleScreenRow.bind(this)) @observeDecoration(decoration) for decoration in @model.getDecorations() @observeCursor(cursor) for cursor in @model.getCursors() @disposables.add @model.onDidAddGutter(@didAddGutter.bind(this)) @@ -776,7 +777,7 @@ class TextEditorPresenter if scrollTop isnt @realScrollTop and not Number.isNaN(scrollTop) @realScrollTop = scrollTop @scrollTop = Math.round(scrollTop) - @model.setFirstVisibleScreenRow(Math.round(@scrollTop / @lineHeight)) + @model.setFirstVisibleScreenRow(Math.round(@scrollTop / @lineHeight), true) @updateStartRow() @updateEndRow() @@ -1539,6 +1540,9 @@ class TextEditorPresenter @emitDidUpdateState() + didChangeFirstVisibleScreenRow: (screenRow) -> + @updateScrollTop(screenRow * @lineHeight) + getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 327647cb7..0e678b066 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -448,6 +448,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.") @@ -3088,13 +3091,27 @@ class TextEditor extends Model Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.") @displayBuffer.getWidth() - setFirstVisibleScreenRow: (@firstVisibleScreenRow) -> + # 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.min(screenRow, maxScreenRow) + + unless screenRow is @firstVisibleScreenRow + @emitter.emit 'did-change-first-visible-screen-row', screenRow unless fromView + @firstVisibleScreenRow = screenRow + getFirstVisibleScreenRow: -> @firstVisibleScreenRow setFirstVisibleScreenColumn: (@firstVisibleScreenColumn) -> getFirstVisibleScreenColumn: -> @firstVisibleScreenColumn - getScrollTop: -> Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.") @@ -3151,9 +3168,15 @@ 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() + height = @displayBuffer.getHeight() + lineHeightInPixels = @displayBuffer.getLineHeightInPixels() + if height? and lineHeightInPixels? + [ + @firstVisibleScreenRow, + Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getLineCount() - 1) + ] + else + null intersectsVisibleRowRange: (startRow, endRow) -> Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")