diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index cc7eba628..ce319ead0 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -33,50 +33,6 @@ 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 "::state", -> - describe ".scrollHeight", -> - it "is initialized based on the lineHeight, the number of lines, and the height", -> - presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) - expect(presenter.state.scrollHeight).toBe editor.getScreenLineCount() * 10 - - presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10, height: 500) - expect(presenter.state.scrollHeight).toBe 500 - - it "updates when the ::lineHeight changes", -> - presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) - expectStateUpdate presenter, -> presenter.setLineHeight(20) - expect(presenter.state.scrollHeight).toBe editor.getScreenLineCount() * 20 - - it "updates when the line count changes", -> - presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) - expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") - expect(presenter.state.scrollHeight).toBe editor.getScreenLineCount() * 10 - - it "updates when ::height changes", -> - presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) - expectStateUpdate presenter, -> presenter.setHeight(500) - expect(presenter.state.scrollHeight).toBe 500 - - describe ".scrollTop", -> - it "tracks the value of ::scrollTop", -> - presenter = new TextEditorPresenter(model: editor, scrollTop: 10, lineHeight: 10) - expect(presenter.state.scrollTop).toBe 10 - expectStateUpdate presenter, -> presenter.setScrollTop(50) - expect(presenter.state.scrollTop).toBe 50 - - describe ".scrollingVertically", -> - it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", -> - presenter = new TextEditorPresenter(model: editor, scrollTop: 10, stoppedScrollingDelay: 200) - expect(presenter.state.scrollingVertically).toBe false - expectStateUpdate presenter, -> presenter.setScrollTop(0) - expect(presenter.state.scrollingVertically).toBe true - advanceClock(100) - expect(presenter.state.scrollingVertically).toBe true - presenter.setScrollTop(10) - advanceClock(100) - expect(presenter.state.scrollingVertically).toBe true - expectStateUpdate presenter, -> advanceClock(100) - expect(presenter.state.scrollingVertically).toBe false - describe ".horizontalScrollbar", -> describe ".visible", -> it "is true if the scrollWidth exceeds the computed client width", -> @@ -127,6 +83,58 @@ describe "TextEditorPresenter", -> presenter.setHeight((editor.getLineCount() * 10) - 1) expect(state.horizontalScrollbar.right).toBe 10 + describe ".scrollWidth", -> + it "is initialized as the max of the ::contentFrameWidth and the width of the longest line", -> + maxLineLength = editor.getMaxScreenLineLength() + + presenter = new TextEditorPresenter(model: editor, contentFrameWidth: 50, baseCharacterWidth: 10) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + + presenter = new TextEditorPresenter(model: editor, contentFrameWidth: 10 * maxLineLength + 20, baseCharacterWidth: 10) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20 + + it "updates when the ::contentFrameWidth changes", -> + maxLineLength = editor.getMaxScreenLineLength() + presenter = new TextEditorPresenter(model: editor, contentFrameWidth: 50, baseCharacterWidth: 10) + + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + expectStateUpdate presenter, -> presenter.setContentFrameWidth(10 * maxLineLength + 20) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20 + + it "updates when the ::baseCharacterWidth changes", -> + maxLineLength = editor.getMaxScreenLineLength() + presenter = new TextEditorPresenter(model: editor, contentFrameWidth: 50, baseCharacterWidth: 10) + + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 15 * maxLineLength + 1 + + it "updates when the scoped character widths change", -> + waitsForPromise -> atom.packages.activatePackage('language-javascript') + + runs -> + maxLineLength = editor.getMaxScreenLineLength() + presenter = new TextEditorPresenter(model: editor, contentFrameWidth: 50, baseCharacterWidth: 10) + + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + expectStateUpdate presenter, -> presenter.setScopedCharWidth(['source.js', 'support.function.js'], 'p', 20) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide + + it "updates when ::softWrapped changes on the editor", -> + presenter = new TextEditorPresenter(model: editor, contentFrameWidth: 50, baseCharacterWidth: 10) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expectStateUpdate presenter, -> editor.setSoftWrapped(true) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + expectStateUpdate presenter, -> editor.setSoftWrapped(false) + expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + + describe ".scrollLeft", -> + it "tracks the value of ::scrollLeft", -> + presenter = new TextEditorPresenter(model: editor, scrollLeft: 10, lineHeight: 10, lineOverdrawMargin: 1) + expect(presenter.state.horizontalScrollbar.scrollLeft).toBe 10 + expectStateUpdate presenter, -> presenter.setScrollLeft(50) + expect(presenter.state.horizontalScrollbar.scrollLeft).toBe 50 + describe ".verticalScrollbar", -> describe ".visible", -> it "is true if the scrollHeight exceeds the computed client height", -> @@ -177,7 +185,74 @@ describe "TextEditorPresenter", -> presenter.setContentFrameWidth(editor.getMaxScreenLineLength() * 10) expect(state.verticalScrollbar.bottom).toBe 10 + describe ".scrollHeight", -> + it "is initialized based on the lineHeight, the number of lines, and the height", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expect(presenter.state.verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 + + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10, height: 500) + expect(presenter.state.verticalScrollbar.scrollHeight).toBe 500 + + it "updates when the ::lineHeight changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> presenter.setLineHeight(20) + expect(presenter.state.verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 20 + + it "updates when the line count changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") + expect(presenter.state.verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 + + it "updates when ::height changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> presenter.setHeight(500) + expect(presenter.state.verticalScrollbar.scrollHeight).toBe 500 + + describe ".scrollTop", -> + it "tracks the value of ::scrollTop", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 10, lineHeight: 10) + expect(presenter.state.verticalScrollbar.scrollTop).toBe 10 + expectStateUpdate presenter, -> presenter.setScrollTop(50) + expect(presenter.state.verticalScrollbar.scrollTop).toBe 50 + describe ".content", -> + describe ".scrollingVertically", -> + it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 10, stoppedScrollingDelay: 200) + expect(presenter.state.content.scrollingVertically).toBe false + expectStateUpdate presenter, -> presenter.setScrollTop(0) + expect(presenter.state.content.scrollingVertically).toBe true + advanceClock(100) + expect(presenter.state.content.scrollingVertically).toBe true + presenter.setScrollTop(10) + advanceClock(100) + expect(presenter.state.content.scrollingVertically).toBe true + expectStateUpdate presenter, -> advanceClock(100) + expect(presenter.state.content.scrollingVertically).toBe false + + describe ".scrollHeight", -> + it "is initialized based on the lineHeight, the number of lines, and the height", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expect(presenter.state.content.scrollHeight).toBe editor.getScreenLineCount() * 10 + + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10, height: 500) + expect(presenter.state.content.scrollHeight).toBe 500 + + it "updates when the ::lineHeight changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> presenter.setLineHeight(20) + expect(presenter.state.content.scrollHeight).toBe editor.getScreenLineCount() * 20 + + it "updates when the line count changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") + expect(presenter.state.content.scrollHeight).toBe editor.getScreenLineCount() * 10 + + it "updates when ::height changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> presenter.setHeight(500) + expect(presenter.state.content.scrollHeight).toBe 500 + describe ".scrollWidth", -> it "is initialized as the max of the ::contentFrameWidth and the width of the longest line", -> maxLineLength = editor.getMaxScreenLineLength() @@ -223,6 +298,13 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.setSoftWrapped(false) expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + describe ".scrollTop", -> + it "tracks the value of ::scrollTop", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 10, lineHeight: 10) + expect(presenter.state.content.scrollTop).toBe 10 + expectStateUpdate presenter, -> presenter.setScrollTop(50) + expect(presenter.state.content.scrollTop).toBe 50 + describe ".scrollLeft", -> it "tracks the value of ::scrollLeft", -> presenter = new TextEditorPresenter(model: editor, scrollLeft: 10, lineHeight: 10, lineOverdrawMargin: 1) @@ -1236,6 +1318,36 @@ describe "TextEditorPresenter", -> expect(presenter.state.content.overlays).not.toEqual({}) describe ".gutter", -> + describe ".scrollHeight", -> + it "is initialized based on the lineHeight, the number of lines, and the height", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expect(presenter.state.gutter.scrollHeight).toBe editor.getScreenLineCount() * 10 + + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10, height: 500) + expect(presenter.state.gutter.scrollHeight).toBe 500 + + it "updates when the ::lineHeight changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> presenter.setLineHeight(20) + expect(presenter.state.gutter.scrollHeight).toBe editor.getScreenLineCount() * 20 + + it "updates when the line count changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") + expect(presenter.state.gutter.scrollHeight).toBe editor.getScreenLineCount() * 10 + + it "updates when ::height changes", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 0, lineHeight: 10) + expectStateUpdate presenter, -> presenter.setHeight(500) + expect(presenter.state.gutter.scrollHeight).toBe 500 + + describe ".scrollTop", -> + it "tracks the value of ::scrollTop", -> + presenter = new TextEditorPresenter(model: editor, scrollTop: 10, lineHeight: 10) + expect(presenter.state.gutter.scrollTop).toBe 10 + expectStateUpdate presenter, -> presenter.setScrollTop(50) + expect(presenter.state.gutter.scrollTop).toBe 50 + describe ".backgroundColor", -> it "is assigned to ::gutterBackgroundColor if present, and to ::backgroundColor otherwise", -> presenter = new TextEditorPresenter(model: editor, backgroundColor: "rgba(255, 0, 0, 0)", gutterBackgroundColor: "rgba(0, 255, 0, 0)") diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 26469065b..5b6af4f6a 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -19,16 +19,20 @@ GutterComponent = React.createClass render: -> {presenter} = @props + @newState = presenter.state.gutter + @oldState ?= {lineNumbers: {}} + + {scrollHeight, backgroundColor} = @newState div className: 'gutter', div className: 'line-numbers', ref: 'lineNumbers', style: - height: presenter.state.scrollHeight + height: scrollHeight WebkitTransform: @getTransform() if presenter.hasRequiredMeasurements() - backgroundColor: presenter.state.gutter.backgroundColor + backgroundColor: backgroundColor getTransform: -> - {presenter, useHardwareAcceleration} = @props - {scrollTop} = presenter.state + {useHardwareAcceleration} = @props + {scrollTop} = @newState if useHardwareAcceleration "translate3d(0px, #{-scrollTop}px, 0px)" @@ -39,7 +43,7 @@ GutterComponent = React.createClass @lineNumberNodesById = {} componentDidMount: -> - {@maxLineNumberDigits} = @props.presenter.state.gutter + {@maxLineNumberDigits} = @newState @appendDummyLineNumber() @updateLineNumbers() @@ -48,7 +52,7 @@ GutterComponent = React.createClass node.addEventListener 'mousedown', @onMouseDown componentDidUpdate: (oldProps) -> - {maxLineNumberDigits} = @props.presenter.state.gutter + {maxLineNumberDigits} = @newState unless maxLineNumberDigits is @maxLineNumberDigits @maxLineNumberDigits = maxLineNumberDigits @updateDummyLineNumber() @@ -69,13 +73,10 @@ GutterComponent = React.createClass @dummyLineNumberNode.innerHTML = @buildLineNumberInnerHTML(0, false) updateLineNumbers: -> - {presenter} = @props - @oldState ?= {lineNumbers: {}} - newState = presenter.state.gutter newLineNumberIds = null newLineNumbersHTML = null - for id, lineNumberState of newState.lineNumbers + for id, lineNumberState of @newState.lineNumbers if @oldState.lineNumbers.hasOwnProperty(id) @updateLineNumberNode(id, lineNumberState) else @@ -96,7 +97,7 @@ GutterComponent = React.createClass node.appendChild(lineNumberNode) for id, lineNumberState of @oldState.lineNumbers - unless newState.lineNumbers.hasOwnProperty(id) + unless @newState.lineNumbers.hasOwnProperty(id) @lineNumberNodesById[id].remove() delete @lineNumberNodesById[id] delete @oldState.lineNumbers[id] @@ -113,7 +114,7 @@ GutterComponent = React.createClass "