From b2bed7cea85747ea4ecb925d05b2326d322c2efb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 24 Feb 2015 21:06:26 +0100 Subject: [PATCH 01/21] Detect batch selections --- src/text-editor-presenter.coffee | 26 ++++++++++++++++++++ src/text-editor.coffee | 42 ++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 955b63cc4..08e73d175 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -44,7 +44,28 @@ class TextEditorPresenter @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? observeModel: -> + @disposables.add @model.onWillSelectMultiple => + @multipleSelectionBatch = true + + @disposables.add @model.onDidSelectMultiple => + @multipleSelectionBatch = false + + @updateStartRow() + @updateEndRow() + @updateHeightState() + @didStartScrolling() + @updateVerticalScrollState() + @updateHorizontalScrollState() + @updateScrollbarsState() + @updateContentState() + @updateDecorations() + @updateLinesState() + @updateGutterState() + @updateLineNumbersState() + @disposables.add @model.onDidChange => + return if @multipleSelectionBatch + @updateContentDimensions() @updateEndRow() @updateHeightState() @@ -516,6 +537,8 @@ class TextEditorPresenter unless @scrollTop is scrollTop or Number.isNaN(scrollTop) @scrollTop = scrollTop @model.setScrollTop(scrollTop) + return if @multipleSelectionBatch + @updateStartRow() @updateEndRow() @didStartScrolling() @@ -790,6 +813,8 @@ class TextEditorPresenter @updateOverlaysState() didDestroyDecoration: (decoration) -> + return if @multipleSelectionBatch + if decoration.isType('line') or decoration.isType('line-number') @removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @updateLinesState() if decoration.isType('line') @@ -810,6 +835,7 @@ class TextEditorPresenter didAddDecoration: (decoration) -> @observeDecoration(decoration) + return if @multipleSelectionBatch if decoration.isType('line') or decoration.isType('line-number') @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @updateLinesState() if decoration.isType('line') diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 8badd9287..822e38ec7 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -92,9 +92,10 @@ class TextEditor extends Model @updateInvisibles() - for marker in @findMarkers(@getSelectionMarkerAttributes()) - marker.setProperties(preserveFolds: true) - @addSelection(marker) + @addMultipleSelections => + for marker in @findMarkers(@getSelectionMarkerAttributes()) + marker.setProperties(preserveFolds: true) + @addSelection(marker) @subscribeToBuffer() @subscribeToDisplayBuffer() @@ -987,23 +988,38 @@ class TextEditor extends Model selection.insertText(fn(text)) selection.setBufferRange(range) + addMultipleSelections: (fn) -> + @emitter.emit "will-select-multiple" + @suppressSelectionMerging = true + fn() + @suppressSelectionMerging = false + @mergeIntersectingSelections() + @emitter.emit "did-select-multiple" + + onDidSelectMultiple: (callback) -> + @emitter.on "did-select-multiple", callback + + onWillSelectMultiple: (callback) -> + @emitter.on "will-select-multiple", callback + # Split multi-line selections into one selection per line. # # Operates on all selections. This method breaks apart all multi-line # selections to create multiple single-line selections that cumulatively cover # the same original area. splitSelectionsIntoLines: -> - for selection in @getSelections() - range = selection.getBufferRange() - continue if range.isSingleLine() + @addMultipleSelections => + for selection in @getSelections() + range = selection.getBufferRange() + continue if range.isSingleLine() - selection.destroy() - {start, end} = range - @addSelectionForBufferRange([start, [start.row, Infinity]]) - {row} = start - while ++row < end.row - @addSelectionForBufferRange([[row, 0], [row, Infinity]]) - @addSelectionForBufferRange([[end.row, 0], [end.row, end.column]]) unless end.column is 0 + selection.destroy() + {start, end} = range + @addSelectionForBufferRange([start, [start.row, Infinity]]) + {row} = start + while ++row < end.row + @addSelectionForBufferRange([[row, 0], [row, Infinity]]) + @addSelectionForBufferRange([[end.row, 0], [end.row, end.column]]) unless end.column is 0 # Extended: For each selection, transpose the selected text. # From ce8a5a210c5fcde29181b0d99ae76f101b20890f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 24 Feb 2015 21:17:23 +0100 Subject: [PATCH 02/21] Detect batch cursor move --- src/text-editor-presenter.coffee | 33 ++++++++++++++++++++++++++------ src/text-editor.coffee | 8 ++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 08e73d175..ff1b7db62 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -44,11 +44,30 @@ class TextEditorPresenter @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? observeModel: -> + @disposables.add @model.onWillMoveCursors => + @batchMode = true + + @disposables.add @model.onDidMoveCursors => + @batchMode = false + + @updateStartRow() + @updateEndRow() + @updateHeightState() + @didStartScrolling() + @updateVerticalScrollState() + @updateHorizontalScrollState() + @updateScrollbarsState() + @updateContentState() + @updateDecorations() + @updateLinesState() + @updateGutterState() + @updateLineNumbersState() + @disposables.add @model.onWillSelectMultiple => - @multipleSelectionBatch = true + @batchMode = true @disposables.add @model.onDidSelectMultiple => - @multipleSelectionBatch = false + @batchMode = false @updateStartRow() @updateEndRow() @@ -64,7 +83,7 @@ class TextEditorPresenter @updateLineNumbersState() @disposables.add @model.onDidChange => - return if @multipleSelectionBatch + return if @batchMode @updateContentDimensions() @updateEndRow() @@ -537,7 +556,7 @@ class TextEditorPresenter unless @scrollTop is scrollTop or Number.isNaN(scrollTop) @scrollTop = scrollTop @model.setScrollTop(scrollTop) - return if @multipleSelectionBatch + return if @batchMode @updateStartRow() @updateEndRow() @@ -785,6 +804,8 @@ class TextEditorPresenter @disposables.add(decorationDisposables) decorationMarkerDidChange: (decoration, change) -> + return if @batchMode + if decoration.isType('line') or decoration.isType('line-number') return if change.textChanged @@ -813,7 +834,7 @@ class TextEditorPresenter @updateOverlaysState() didDestroyDecoration: (decoration) -> - return if @multipleSelectionBatch + return if @batchMode if decoration.isType('line') or decoration.isType('line-number') @removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @@ -835,7 +856,7 @@ class TextEditorPresenter didAddDecoration: (decoration) -> @observeDecoration(decoration) - return if @multipleSelectionBatch + return if @batchMode if decoration.isType('line') or decoration.isType('line-number') @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @updateLinesState() if decoration.isType('line') diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 822e38ec7..f4315d209 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1804,8 +1804,16 @@ class TextEditor extends Model @emitter.emit 'did-remove-cursor', cursor moveCursors: (fn) -> + @emitter.emit "will-move-cursors" fn(cursor) for cursor in @getCursors() @mergeCursors() + @emitter.emit "did-move-cursors" + + onWillMoveCursors: (callback) -> + @emitter.on "will-move-cursors", callback + + onDidMoveCursors: (callback) -> + @emitter.on "did-move-cursors", callback cursorMoved: (event) -> @emit 'cursor-moved', event From 8f2df60eab552c484d310861fbba4c9d851ce51a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 24 Feb 2015 21:44:09 +0100 Subject: [PATCH 03/21] :art: Refactor batchMode --- src/text-editor-presenter.coffee | 73 ++++++++++++++------------------ 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index ff1b7db62..d85093a1d 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -43,47 +43,36 @@ class TextEditorPresenter @model.setVerticalScrollbarWidth(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth? @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? + enterBatchMode: -> + @batchMode = true + + isInBatchMode: -> + @batchMode == true + + exitBatchMode: -> + @batchMode = false + + @updateStartRow() + @updateEndRow() + @updateHeightState() + @didStartScrolling() + @updateVerticalScrollState() + @updateHorizontalScrollState() + @updateScrollbarsState() + @updateContentState() + @updateDecorations() + @updateLinesState() + @updateGutterState() + @updateLineNumbersState() + observeModel: -> - @disposables.add @model.onWillMoveCursors => - @batchMode = true - - @disposables.add @model.onDidMoveCursors => - @batchMode = false - - @updateStartRow() - @updateEndRow() - @updateHeightState() - @didStartScrolling() - @updateVerticalScrollState() - @updateHorizontalScrollState() - @updateScrollbarsState() - @updateContentState() - @updateDecorations() - @updateLinesState() - @updateGutterState() - @updateLineNumbersState() - - @disposables.add @model.onWillSelectMultiple => - @batchMode = true - - @disposables.add @model.onDidSelectMultiple => - @batchMode = false - - @updateStartRow() - @updateEndRow() - @updateHeightState() - @didStartScrolling() - @updateVerticalScrollState() - @updateHorizontalScrollState() - @updateScrollbarsState() - @updateContentState() - @updateDecorations() - @updateLinesState() - @updateGutterState() - @updateLineNumbersState() + @disposables.add @model.onWillMoveCursors => @enterBatchMode() + @disposables.add @model.onDidMoveCursors => @exitBatchMode() + @disposables.add @model.onWillSelectMultiple => @enterBatchMode() + @disposables.add @model.onDidSelectMultiple => @exitBatchMode() @disposables.add @model.onDidChange => - return if @batchMode + return if @isInBatchMode() @updateContentDimensions() @updateEndRow() @@ -556,7 +545,7 @@ class TextEditorPresenter unless @scrollTop is scrollTop or Number.isNaN(scrollTop) @scrollTop = scrollTop @model.setScrollTop(scrollTop) - return if @batchMode + return if @isInBatchMode() @updateStartRow() @updateEndRow() @@ -804,7 +793,7 @@ class TextEditorPresenter @disposables.add(decorationDisposables) decorationMarkerDidChange: (decoration, change) -> - return if @batchMode + return if @isInBatchMode() if decoration.isType('line') or decoration.isType('line-number') return if change.textChanged @@ -834,7 +823,7 @@ class TextEditorPresenter @updateOverlaysState() didDestroyDecoration: (decoration) -> - return if @batchMode + return if @isInBatchMode() if decoration.isType('line') or decoration.isType('line-number') @removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @@ -856,7 +845,7 @@ class TextEditorPresenter didAddDecoration: (decoration) -> @observeDecoration(decoration) - return if @batchMode + return if @isInBatchMode() if decoration.isType('line') or decoration.isType('line-number') @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @updateLinesState() if decoration.isType('line') From cbfbf84990423bd3c8cfa9783243cf99f62310ce Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 25 Feb 2015 17:40:39 +0100 Subject: [PATCH 04/21] :racehorse: Introduce batching, and update UI accordingly --- src/text-editor-presenter.coffee | 19 ++---------- src/text-editor.coffee | 52 ++++++++++++++------------------ 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index d85093a1d..a554f8c03 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -52,24 +52,11 @@ class TextEditorPresenter exitBatchMode: -> @batchMode = false - @updateStartRow() - @updateEndRow() - @updateHeightState() - @didStartScrolling() - @updateVerticalScrollState() - @updateHorizontalScrollState() - @updateScrollbarsState() - @updateContentState() - @updateDecorations() - @updateLinesState() - @updateGutterState() - @updateLineNumbersState() + @updateState() observeModel: -> - @disposables.add @model.onWillMoveCursors => @enterBatchMode() - @disposables.add @model.onDidMoveCursors => @exitBatchMode() - @disposables.add @model.onWillSelectMultiple => @enterBatchMode() - @disposables.add @model.onDidSelectMultiple => @exitBatchMode() + @disposables.add @model.onWillStartBatchOperation => @enterBatchMode() + @disposables.add @model.onDidFinishBatchOperation => @exitBatchMode() @disposables.add @model.onDidChange => return if @isInBatchMode() diff --git a/src/text-editor.coffee b/src/text-editor.coffee index f4315d209..35519c431 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -92,7 +92,7 @@ class TextEditor extends Model @updateInvisibles() - @addMultipleSelections => + @batch => @mergeIntersectingSelections => for marker in @findMarkers(@getSelectionMarkerAttributes()) marker.setProperties(preserveFolds: true) @addSelection(marker) @@ -835,7 +835,8 @@ class TextEditor extends Model # argument will be a {Selection} and the second argument will be the # {Number} index of that selection. mutateSelectedText: (fn) -> - @transact => fn(selection, index) for selection, index in @getSelections() + @mergeIntersectingSelections => @transact => + fn(selection, index) for selection, index in @getSelections() # Move lines intersection the most recent selection up by one row in screen # coordinates. @@ -988,27 +989,13 @@ class TextEditor extends Model selection.insertText(fn(text)) selection.setBufferRange(range) - addMultipleSelections: (fn) -> - @emitter.emit "will-select-multiple" - @suppressSelectionMerging = true - fn() - @suppressSelectionMerging = false - @mergeIntersectingSelections() - @emitter.emit "did-select-multiple" - - onDidSelectMultiple: (callback) -> - @emitter.on "did-select-multiple", callback - - onWillSelectMultiple: (callback) -> - @emitter.on "will-select-multiple", callback - # Split multi-line selections into one selection per line. # # Operates on all selections. This method breaks apart all multi-line # selections to create multiple single-line selections that cumulatively cover # the same original area. splitSelectionsIntoLines: -> - @addMultipleSelections => + @batch => @mergeIntersectingSelections => for selection in @getSelections() range = selection.getBufferRange() continue if range.isSingleLine() @@ -1156,7 +1143,19 @@ class TextEditor extends Model # with a positive `groupingInterval` is committed while the previous transaction is # still 'groupable', the two transactions are merged with respect to undo and redo. # * `fn` A {Function} to call inside the transaction. - transact: (groupingInterval, fn) -> @buffer.transact(groupingInterval, fn) + transact: (groupingInterval, fn) -> + @batch => @buffer.transact(groupingInterval, fn) + + batch: (fn) -> + @emitter.emit "will-start-batch-operation" + fn() + @emitter.emit "did-finish-batch-operation" + + onWillStartBatchOperation: (callback) -> + @emitter.on "will-start-batch-operation", callback + + onDidFinishBatchOperation: (callback) -> + @emitter.on "did-finish-batch-operation", callback # Deprecated: Start an open-ended transaction. beginTransaction: (groupingInterval) -> @buffer.beginTransaction(groupingInterval) @@ -1804,16 +1803,9 @@ class TextEditor extends Model @emitter.emit 'did-remove-cursor', cursor moveCursors: (fn) -> - @emitter.emit "will-move-cursors" - fn(cursor) for cursor in @getCursors() - @mergeCursors() - @emitter.emit "did-move-cursors" - - onWillMoveCursors: (callback) -> - @emitter.on "will-move-cursors", callback - - onDidMoveCursors: (callback) -> - @emitter.on "did-move-cursors", callback + @batch => @mergeIntersectingSelections => + fn(cursor) for cursor in @getCursors() + @mergeCursors() cursorMoved: (event) -> @emit 'cursor-moved', event @@ -2210,13 +2202,13 @@ class TextEditor extends Model # Calls the given function with each selection, then merges selections expandSelectionsForward: (fn) -> - @mergeIntersectingSelections => + @batch => @mergeIntersectingSelections => fn(selection) for selection in @getSelections() # Calls the given function with each selection, then merges selections in the # reversed orientation expandSelectionsBackward: (fn) -> - @mergeIntersectingSelections reversed: true, => + @batch => @mergeIntersectingSelections reversed: true, => fn(selection) for selection in @getSelections() finalizeSelections: -> From fdcbb9c8ffb641fb9100a653e57d741800c88cf6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 25 Feb 2015 18:44:08 +0100 Subject: [PATCH 05/21] Don't change mergeIntersectingSelections behavior for now --- src/text-editor.coffee | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 35519c431..a25f98496 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -92,7 +92,7 @@ class TextEditor extends Model @updateInvisibles() - @batch => @mergeIntersectingSelections => + @batch => for marker in @findMarkers(@getSelectionMarkerAttributes()) marker.setProperties(preserveFolds: true) @addSelection(marker) @@ -835,8 +835,7 @@ class TextEditor extends Model # argument will be a {Selection} and the second argument will be the # {Number} index of that selection. mutateSelectedText: (fn) -> - @mergeIntersectingSelections => @transact => - fn(selection, index) for selection, index in @getSelections() + @transact => fn(selection, index) for selection, index in @getSelections() # Move lines intersection the most recent selection up by one row in screen # coordinates. @@ -995,7 +994,7 @@ class TextEditor extends Model # selections to create multiple single-line selections that cumulatively cover # the same original area. splitSelectionsIntoLines: -> - @batch => @mergeIntersectingSelections => + @batch => for selection in @getSelections() range = selection.getBufferRange() continue if range.isSingleLine() @@ -1803,7 +1802,7 @@ class TextEditor extends Model @emitter.emit 'did-remove-cursor', cursor moveCursors: (fn) -> - @batch => @mergeIntersectingSelections => + @batch => fn(cursor) for cursor in @getCursors() @mergeCursors() From 0ce3c496889a2aa9a4938671edd34a064ceb4897 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Feb 2015 10:50:43 +0100 Subject: [PATCH 06/21] Batch returns the function value * Emit only one event for nested batches --- src/text-editor.coffee | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index a25f98496..f828a6b06 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1146,9 +1146,13 @@ class TextEditor extends Model @batch => @buffer.transact(groupingInterval, fn) batch: (fn) -> - @emitter.emit "will-start-batch-operation" - fn() - @emitter.emit "did-finish-batch-operation" + @batchCount++ + @emitter.emit "will-start-batch-operation" if @batchCount == 1 + value = fn() + @emitter.emit "did-finish-batch-operation" if @batchCount == 1 + @batchCount-- + + value onWillStartBatchOperation: (callback) -> @emitter.on "will-start-batch-operation", callback From 50f2aded3ac6c9addf470047707da1299b4bfd27 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Feb 2015 11:26:48 +0100 Subject: [PATCH 07/21] Better batching --- src/text-editor-presenter.coffee | 119 ++++++++++++++++++++++++++++--- src/text-editor.coffee | 3 +- 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index a554f8c03..2748ef9db 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -52,15 +52,48 @@ class TextEditorPresenter exitBatchMode: -> @batchMode = false - @updateState() + @updateContentDimensions() if @shouldUpdateContentDimensions + @updateScrollbarDimensions() if @shouldUpdateScrollbarDimensions + @updateStartRow() if @shouldUpdateStartRow + @updateEndRow() if @shouldUpdateEndRow + + @updateFocusedState() if @shouldUpdateFocusedState + @updateHeightState() if @shouldUpdateHeightState + @updateVerticalScrollState() if @shouldUpdateVerticalScrollState + @updateHorizontalScrollState() if @shouldUpdateHorizontalScrollState + @updateScrollbarsState() if @shouldUpdateScrollbarsState + @updateHiddenInputState() if @shouldUpdateHiddenInputState + @updateContentState() if @shouldUpdateContentState + @updateDecorations() if @shouldUpdateDecorations + @updateLinesState() if @shouldUpdateLinesState + @updateCursorsState() if @shouldUpdateCursorsState + @updateOverlaysState() if @shouldUpdateOverlaysState + @updateGutterState() if @shouldUpdateGutterState + @updateLineNumbersState() if @shouldUpdateLineNumbersState + + @shouldUpdateContentDimensions = false + @shouldUpdateScrollbarDimensions = false + @shouldUpdateStartRow = false + @shouldUpdateEndRow = false + @shouldUpdateFocusedState = false + @shouldUpdateHeightState = false + @shouldUpdateVerticalScrollState = false + @shouldUpdateHorizontalScrollState = false + @shouldUpdateScrollbarsState = false + @shouldUpdateHiddenInputState = false + @shouldUpdateContentState = false + @shouldUpdateDecorations = false + @shouldUpdateLinesState = false + @shouldUpdateCursorsState = false + @shouldUpdateOverlaysState = false + @shouldUpdateGutterState = false + @shouldUpdateLineNumbersState = false observeModel: -> @disposables.add @model.onWillStartBatchOperation => @enterBatchMode() @disposables.add @model.onDidFinishBatchOperation => @exitBatchMode() @disposables.add @model.onDidChange => - return if @isInBatchMode() - @updateContentDimensions() @updateEndRow() @updateHeightState() @@ -158,9 +191,17 @@ class TextEditorPresenter @updateLineNumbersState() updateFocusedState: -> + if @isInBatchMode() + @shouldUpdateFocusedState = true + return + @state.focused = @focused updateHeightState: -> + if @isInBatchMode() + @shouldUpdateHeightState = true + return + if @autoHeight @state.height = @contentHeight else @@ -169,6 +210,10 @@ class TextEditorPresenter @emitter.emit 'did-update-state' updateVerticalScrollState: -> + if @isInBatchMode() + @shouldUpdateVerticalScrollState = true + return + @state.content.scrollHeight = @scrollHeight @state.gutter.scrollHeight = @scrollHeight @state.verticalScrollbar.scrollHeight = @scrollHeight @@ -180,6 +225,10 @@ class TextEditorPresenter @emitter.emit 'did-update-state' updateHorizontalScrollState: -> + if @isInBatchMode() + @shouldUpdateHorizontalScrollState = true + return + @state.content.scrollWidth = @scrollWidth @state.horizontalScrollbar.scrollWidth = @scrollWidth @@ -189,6 +238,10 @@ class TextEditorPresenter @emitter.emit 'did-update-state' updateScrollbarsState: -> + if @isInBatchMode() + @shouldUpdateScrollbarsState = true + return + @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight @state.horizontalScrollbar.right = @verticalScrollbarWidth @@ -200,6 +253,10 @@ class TextEditorPresenter @emitter.emit 'did-update-state' updateHiddenInputState: -> + if @isInBatchMode() + @shouldUpdateHiddenInputState = true + return + return unless lastCursor = @model.getLastCursor() {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange()) @@ -219,6 +276,10 @@ class TextEditorPresenter @emitter.emit 'did-update-state' updateContentState: -> + if @isInBatchMode() + @shouldUpdateContentState = true + return + @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide @@ -227,6 +288,10 @@ class TextEditorPresenter @emitter.emit 'did-update-state' updateLinesState: -> + if @isInBatchMode() + @shouldUpdateLinesState = true + return + return unless @startRow? and @endRow? and @lineHeight? visibleLineIds = {} @@ -273,6 +338,10 @@ class TextEditorPresenter decorationClasses: @lineDecorationClassesForRow(row) updateCursorsState: -> + if @isInBatchMode() + @shouldUpdateCursorsState = true + return + @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation @@ -281,6 +350,10 @@ class TextEditorPresenter updateCursorState: (cursor, destroyOnly = false) -> + if @isInBatchMode() + @shouldUpdateCursorsState = true + return + delete @state.content.cursors[cursor.id] return if destroyOnly @@ -292,6 +365,10 @@ class TextEditorPresenter @state.content.cursors[cursor.id] = pixelRect updateOverlaysState: -> + if @isInBatchMode() + @shouldUpdateOverlaysState = true + return + return unless @hasPixelRectRequirements() visibleDecorationIds = {} @@ -315,6 +392,10 @@ class TextEditorPresenter @emitter.emit "did-update-state" updateGutterState: -> + if @isInBatchMode() + @shouldUpdateGutterState = true + return + @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length @state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)" @@ -324,6 +405,10 @@ class TextEditorPresenter @emitter.emit "did-update-state" updateLineNumbersState: -> + if @isInBatchMode() + @shouldUpdateLineNumbersState = true + return + return unless @startRow? and @endRow? and @lineHeight? visibleLineNumberIds = {} @@ -369,12 +454,20 @@ class TextEditorPresenter @emitter.emit 'did-update-state' updateStartRow: -> + if @isInBatchMode() + @shouldUpdateStartRow = true + return + return unless @scrollTop? and @lineHeight? startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin @startRow = Math.max(0, startRow) updateEndRow: -> + if @isInBatchMode() + @shouldUpdateEndRow = true + return + return unless @scrollTop? and @lineHeight? and @height? startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight)) @@ -404,6 +497,11 @@ class TextEditorPresenter @updateScrollTop() updateContentDimensions: -> + if @isInBatchMode() + @shouldUpdateContentDimensions = true + return + + if @lineHeight? oldContentHeight = @contentHeight @contentHeight = @lineHeight * @model.getScreenLineCount() @@ -459,6 +557,10 @@ class TextEditorPresenter Math.max(0, Math.min(scrollLeft, @scrollWidth - @clientWidth)) updateScrollbarDimensions: -> + if @isInBatchMode() + @shouldUpdateScrollbarDimensions = true + return + return unless @contentFrameWidth? and @height? return unless @measuredVerticalScrollbarWidth? and @measuredHorizontalScrollbarHeight? return unless @contentWidth? and @contentHeight? @@ -532,8 +634,6 @@ class TextEditorPresenter unless @scrollTop is scrollTop or Number.isNaN(scrollTop) @scrollTop = scrollTop @model.setScrollTop(scrollTop) - return if @isInBatchMode() - @updateStartRow() @updateEndRow() @didStartScrolling() @@ -780,8 +880,6 @@ class TextEditorPresenter @disposables.add(decorationDisposables) decorationMarkerDidChange: (decoration, change) -> - return if @isInBatchMode() - if decoration.isType('line') or decoration.isType('line-number') return if change.textChanged @@ -810,8 +908,6 @@ class TextEditorPresenter @updateOverlaysState() didDestroyDecoration: (decoration) -> - return if @isInBatchMode() - if decoration.isType('line') or decoration.isType('line-number') @removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @updateLinesState() if decoration.isType('line') @@ -832,7 +928,6 @@ class TextEditorPresenter didAddDecoration: (decoration) -> @observeDecoration(decoration) - return if @isInBatchMode() if decoration.isType('line') or decoration.isType('line-number') @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @updateLinesState() if decoration.isType('line') @@ -843,6 +938,10 @@ class TextEditorPresenter @updateOverlaysState() updateDecorations: -> + if @isInBatchMode() + @shouldUpdateDecorations = true + return + @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @highlightDecorationsById = {} diff --git a/src/text-editor.coffee b/src/text-editor.coffee index f828a6b06..0502f4987 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -85,6 +85,7 @@ class TextEditor extends Model @cursors = [] @selections = [] + @batchCount = 0 buffer ?= new TextBuffer @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped}) @buffer = @displayBuffer.buffer @@ -994,7 +995,7 @@ class TextEditor extends Model # selections to create multiple single-line selections that cumulatively cover # the same original area. splitSelectionsIntoLines: -> - @batch => + @batch => @mergeIntersectingSelections => for selection in @getSelections() range = selection.getBufferRange() continue if range.isSingleLine() From c638c23e3ff8c38a09110e04082a2cd5e36a45bc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 26 Feb 2015 18:27:08 +0100 Subject: [PATCH 08/21] :racehorse: One `mergeIntersectingSelections` per text mutation * Return fn value from `mergeIntersectingSelections` if supplied --- src/text-editor.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 0de8ecdae..03ae19d5b 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -836,7 +836,8 @@ class TextEditor extends Model # argument will be a {Selection} and the second argument will be the # {Number} index of that selection. mutateSelectedText: (fn) -> - @transact => fn(selection, index) for selection, index in @getSelections() + @mergeIntersectingSelections => + @transact => fn(selection, index) for selection, index in @getSelections() # Move lines intersection the most recent selection up by one row in screen # coordinates. @@ -2248,6 +2249,7 @@ class TextEditor extends Model [head, tail...] = @getSelectionsOrderedByBufferPosition() _.reduce(tail, reducer, [head]) + return result if fn? # Add a {Selection} based on the given {Marker}. # From af658498808dc4010940817768ac83b6262090bd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Feb 2015 12:17:34 +0100 Subject: [PATCH 09/21] Move batching management down into presentation layer --- src/text-editor-component.coffee | 4 +++ src/text-editor-presenter.coffee | 45 ++++++++++++++------------------ src/text-editor.coffee | 36 +++++++------------------ 3 files changed, 32 insertions(+), 53 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index cea4cae3e..a696798e5 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -103,6 +103,8 @@ class TextEditorComponent window.removeEventListener 'resize', @requestHeightAndWidthMeasurement updateSync: -> + @presenter.exitBatchMode() + @oldState ?= {} @newState = @presenter.state @@ -148,6 +150,8 @@ class TextEditorComponent @hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged @hostElement.__spacePenView.trigger 'editor:display-updated' + @presenter.enterBatchMode() + readAfterUpdateSync: => @linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 2748ef9db..5d3cff62e 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -26,6 +26,7 @@ class TextEditorPresenter @observeConfig() @buildState() @startBlinkingCursors() + @enterBatchMode() destroy: -> @disposables.dispose() @@ -90,9 +91,6 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = false observeModel: -> - @disposables.add @model.onWillStartBatchOperation => @enterBatchMode() - @disposables.add @model.onDidFinishBatchOperation => @exitBatchMode() - @disposables.add @model.onDidChange => @updateContentDimensions() @updateEndRow() @@ -193,6 +191,7 @@ class TextEditorPresenter updateFocusedState: -> if @isInBatchMode() @shouldUpdateFocusedState = true + @emitter.emit "did-update-state" return @state.focused = @focused @@ -200,6 +199,7 @@ class TextEditorPresenter updateHeightState: -> if @isInBatchMode() @shouldUpdateHeightState = true + @emitter.emit "did-update-state" return if @autoHeight @@ -207,11 +207,10 @@ class TextEditorPresenter else @state.height = null - @emitter.emit 'did-update-state' - updateVerticalScrollState: -> if @isInBatchMode() @shouldUpdateVerticalScrollState = true + @emitter.emit "did-update-state" return @state.content.scrollHeight = @scrollHeight @@ -222,11 +221,10 @@ class TextEditorPresenter @state.gutter.scrollTop = @scrollTop @state.verticalScrollbar.scrollTop = @scrollTop - @emitter.emit 'did-update-state' - updateHorizontalScrollState: -> if @isInBatchMode() @shouldUpdateHorizontalScrollState = true + @emitter.emit "did-update-state" return @state.content.scrollWidth = @scrollWidth @@ -235,11 +233,10 @@ class TextEditorPresenter @state.content.scrollLeft = @scrollLeft @state.horizontalScrollbar.scrollLeft = @scrollLeft - @emitter.emit 'did-update-state' - updateScrollbarsState: -> if @isInBatchMode() @shouldUpdateScrollbarsState = true + @emitter.emit "did-update-state" return @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 @@ -250,11 +247,10 @@ class TextEditorPresenter @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth @state.verticalScrollbar.bottom = @horizontalScrollbarHeight - @emitter.emit 'did-update-state' - updateHiddenInputState: -> if @isInBatchMode() @shouldUpdateHiddenInputState = true + @emitter.emit "did-update-state" return return unless lastCursor = @model.getLastCursor() @@ -273,11 +269,10 @@ class TextEditorPresenter @state.hiddenInput.height = height @state.hiddenInput.width = Math.max(width, 2) - @emitter.emit 'did-update-state' - updateContentState: -> if @isInBatchMode() @shouldUpdateContentState = true + @emitter.emit "did-update-state" return @state.content.scrollWidth = @scrollWidth @@ -285,11 +280,11 @@ class TextEditorPresenter @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null - @emitter.emit 'did-update-state' updateLinesState: -> if @isInBatchMode() @shouldUpdateLinesState = true + @emitter.emit "did-update-state" return return unless @startRow? and @endRow? and @lineHeight? @@ -316,8 +311,6 @@ class TextEditorPresenter unless visibleLineIds.hasOwnProperty(id) delete @state.content.lines[id] - @emitter.emit 'did-update-state' - updateLineState: (row, line) -> lineState = @state.content.lines[line.id] lineState.screenRow = row @@ -340,18 +333,18 @@ class TextEditorPresenter updateCursorsState: -> if @isInBatchMode() @shouldUpdateCursorsState = true + @emitter.emit "did-update-state" return @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation - @emitter.emit 'did-update-state' - updateCursorState: (cursor, destroyOnly = false) -> if @isInBatchMode() @shouldUpdateCursorsState = true + @emitter.emit "did-update-state" return delete @state.content.cursors[cursor.id] @@ -367,6 +360,7 @@ class TextEditorPresenter updateOverlaysState: -> if @isInBatchMode() @shouldUpdateOverlaysState = true + @emitter.emit "did-update-state" return return unless @hasPixelRectRequirements() @@ -389,11 +383,10 @@ class TextEditorPresenter for id of @state.content.overlays delete @state.content.overlays[id] unless visibleDecorationIds[id] - @emitter.emit "did-update-state" - updateGutterState: -> if @isInBatchMode() @shouldUpdateGutterState = true + @emitter.emit "did-update-state" return @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers @@ -402,11 +395,11 @@ class TextEditorPresenter @gutterBackgroundColor else @backgroundColor - @emitter.emit "did-update-state" updateLineNumbersState: -> if @isInBatchMode() @shouldUpdateLineNumbersState = true + @emitter.emit "did-update-state" return return unless @startRow? and @endRow? and @lineHeight? @@ -451,11 +444,10 @@ class TextEditorPresenter for id of @state.gutter.lineNumbers delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] - @emitter.emit 'did-update-state' - updateStartRow: -> if @isInBatchMode() @shouldUpdateStartRow = true + @emitter.emit "did-update-state" return return unless @scrollTop? and @lineHeight? @@ -466,6 +458,7 @@ class TextEditorPresenter updateEndRow: -> if @isInBatchMode() @shouldUpdateEndRow = true + @emitter.emit "did-update-state" return return unless @scrollTop? and @lineHeight? and @height? @@ -499,9 +492,9 @@ class TextEditorPresenter updateContentDimensions: -> if @isInBatchMode() @shouldUpdateContentDimensions = true + @emitter.emit "did-update-state" return - if @lineHeight? oldContentHeight = @contentHeight @contentHeight = @lineHeight * @model.getScreenLineCount() @@ -559,6 +552,7 @@ class TextEditorPresenter updateScrollbarDimensions: -> if @isInBatchMode() @shouldUpdateScrollbarDimensions = true + @emitter.emit "did-update-state" return return unless @contentFrameWidth? and @height? @@ -940,6 +934,7 @@ class TextEditorPresenter updateDecorations: -> if @isInBatchMode() @shouldUpdateDecorations = true + @emitter.emit "did-update-state" return @lineDecorationsByScreenRow = {} @@ -961,8 +956,6 @@ class TextEditorPresenter unless visibleHighlights[id] delete @state.content.highlights[id] - @emitter.emit 'did-update-state' - removeFromLineDecorationCaches: (decoration, range) -> for row in [range.start.row..range.end.row] by 1 delete @lineDecorationsByScreenRow[row]?[decoration.id] diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 03ae19d5b..b44b6e276 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -85,7 +85,6 @@ class TextEditor extends Model @cursors = [] @selections = [] - @batchCount = 0 buffer ?= new TextBuffer @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped}) @buffer = @displayBuffer.buffer @@ -93,10 +92,9 @@ class TextEditor extends Model @updateInvisibles() - @batch => - for marker in @findMarkers(@getSelectionMarkerAttributes()) - marker.setProperties(preserveFolds: true) - @addSelection(marker) + for marker in @findMarkers(@getSelectionMarkerAttributes()) + marker.setProperties(preserveFolds: true) + @addSelection(marker) @subscribeToBuffer() @subscribeToDisplayBuffer() @@ -996,7 +994,7 @@ class TextEditor extends Model # selections to create multiple single-line selections that cumulatively cover # the same original area. splitSelectionsIntoLines: -> - @batch => @mergeIntersectingSelections => + @mergeIntersectingSelections => for selection in @getSelections() range = selection.getBufferRange() continue if range.isSingleLine() @@ -1145,22 +1143,7 @@ class TextEditor extends Model # still 'groupable', the two transactions are merged with respect to undo and redo. # * `fn` A {Function} to call inside the transaction. transact: (groupingInterval, fn) -> - @batch => @buffer.transact(groupingInterval, fn) - - batch: (fn) -> - @batchCount++ - @emitter.emit "will-start-batch-operation" if @batchCount == 1 - value = fn() - @emitter.emit "did-finish-batch-operation" if @batchCount == 1 - @batchCount-- - - value - - onWillStartBatchOperation: (callback) -> - @emitter.on "will-start-batch-operation", callback - - onDidFinishBatchOperation: (callback) -> - @emitter.on "did-finish-batch-operation", callback + @buffer.transact(groupingInterval, fn) # Deprecated: Start an open-ended transaction. beginTransaction: (groupingInterval) -> @buffer.beginTransaction(groupingInterval) @@ -1808,9 +1791,8 @@ class TextEditor extends Model @emitter.emit 'did-remove-cursor', cursor moveCursors: (fn) -> - @batch => - fn(cursor) for cursor in @getCursors() - @mergeCursors() + fn(cursor) for cursor in @getCursors() + @mergeCursors() cursorMoved: (event) -> @emit 'cursor-moved', event @@ -2207,13 +2189,13 @@ class TextEditor extends Model # Calls the given function with each selection, then merges selections expandSelectionsForward: (fn) -> - @batch => @mergeIntersectingSelections => + @mergeIntersectingSelections => fn(selection) for selection in @getSelections() # Calls the given function with each selection, then merges selections in the # reversed orientation expandSelectionsBackward: (fn) -> - @batch => @mergeIntersectingSelections reversed: true, => + @mergeIntersectingSelections reversed: true, => fn(selection) for selection in @getSelections() finalizeSelections: -> From 2f00d3e361feb5bda9a68f9aa238d63028f8fd32 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Feb 2015 15:10:17 +0100 Subject: [PATCH 10/21] Batch on @requestUpdate --- src/text-editor-component.coffee | 6 +- src/text-editor-presenter.coffee | 506 ++++++++++++++++--------------- 2 files changed, 256 insertions(+), 256 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index a696798e5..cd99d5d90 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -103,8 +103,6 @@ class TextEditorComponent window.removeEventListener 'resize', @requestHeightAndWidthMeasurement updateSync: -> - @presenter.exitBatchMode() - @oldState ?= {} @newState = @presenter.state @@ -150,8 +148,6 @@ class TextEditorComponent @hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged @hostElement.__spacePenView.trigger 'editor:display-updated' - @presenter.enterBatchMode() - readAfterUpdateSync: => @linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically @@ -183,7 +179,9 @@ class TextEditorComponent @updateSync() else unless @updateRequested @updateRequested = true + @presenter.enterBatchMode() atom.views.updateDocument => + @presenter.exitBatchMode() @updateRequested = false @updateSync() if @editor.isAlive() atom.views.readDocument(@readAfterUpdateSync) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index bf1eba3da..8c0a5f314 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -26,7 +26,6 @@ class TextEditorPresenter @observeConfig() @buildState() @startBlinkingCursors() if @focused - @enterBatchMode() destroy: -> @disposables.dispose() @@ -191,125 +190,125 @@ class TextEditorPresenter updateFocusedState: -> if @isInBatchMode() @shouldUpdateFocusedState = true - @emitter.emit "did-update-state" - return + else + @state.focused = @focused - @state.focused = @focused + @emitter.emit "did-update-state" updateHeightState: -> if @isInBatchMode() @shouldUpdateHeightState = true - @emitter.emit "did-update-state" - return - - if @autoHeight - @state.height = @contentHeight else - @state.height = null + if @autoHeight + @state.height = @contentHeight + else + @state.height = null + + @emitter.emit "did-update-state" updateVerticalScrollState: -> if @isInBatchMode() @shouldUpdateVerticalScrollState = true - @emitter.emit "did-update-state" - return + else + @state.content.scrollHeight = @scrollHeight + @state.gutter.scrollHeight = @scrollHeight + @state.verticalScrollbar.scrollHeight = @scrollHeight - @state.content.scrollHeight = @scrollHeight - @state.gutter.scrollHeight = @scrollHeight - @state.verticalScrollbar.scrollHeight = @scrollHeight + @state.content.scrollTop = @scrollTop + @state.gutter.scrollTop = @scrollTop + @state.verticalScrollbar.scrollTop = @scrollTop - @state.content.scrollTop = @scrollTop - @state.gutter.scrollTop = @scrollTop - @state.verticalScrollbar.scrollTop = @scrollTop + @emitter.emit "did-update-state" updateHorizontalScrollState: -> if @isInBatchMode() @shouldUpdateHorizontalScrollState = true - @emitter.emit "did-update-state" - return + else + @state.content.scrollWidth = @scrollWidth + @state.horizontalScrollbar.scrollWidth = @scrollWidth - @state.content.scrollWidth = @scrollWidth - @state.horizontalScrollbar.scrollWidth = @scrollWidth + @state.content.scrollLeft = @scrollLeft + @state.horizontalScrollbar.scrollLeft = @scrollLeft - @state.content.scrollLeft = @scrollLeft - @state.horizontalScrollbar.scrollLeft = @scrollLeft + @emitter.emit "did-update-state" updateScrollbarsState: -> if @isInBatchMode() @shouldUpdateScrollbarsState = true - @emitter.emit "did-update-state" - return + else + @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 + @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight + @state.horizontalScrollbar.right = @verticalScrollbarWidth - @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 - @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight - @state.horizontalScrollbar.right = @verticalScrollbarWidth + @state.verticalScrollbar.visible = @verticalScrollbarWidth > 0 + @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth + @state.verticalScrollbar.bottom = @horizontalScrollbarHeight - @state.verticalScrollbar.visible = @verticalScrollbarWidth > 0 - @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth - @state.verticalScrollbar.bottom = @horizontalScrollbarHeight + @emitter.emit "did-update-state" updateHiddenInputState: -> if @isInBatchMode() @shouldUpdateHiddenInputState = true - @emitter.emit "did-update-state" - return - - return unless lastCursor = @model.getLastCursor() - - {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange()) - - if @focused - top -= @scrollTop - left -= @scrollLeft - @state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0) - @state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0) else - @state.hiddenInput.top = 0 - @state.hiddenInput.left = 0 + return unless lastCursor = @model.getLastCursor() - @state.hiddenInput.height = height - @state.hiddenInput.width = Math.max(width, 2) + {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange()) + + if @focused + top -= @scrollTop + left -= @scrollLeft + @state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0) + @state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0) + else + @state.hiddenInput.top = 0 + @state.hiddenInput.left = 0 + + @state.hiddenInput.height = height + @state.hiddenInput.width = Math.max(width, 2) + + @emitter.emit "did-update-state" updateContentState: -> if @isInBatchMode() @shouldUpdateContentState = true - @emitter.emit "did-update-state" - return + else + @state.content.scrollWidth = @scrollWidth + @state.content.scrollLeft = @scrollLeft + @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide + @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor + @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null - @state.content.scrollWidth = @scrollWidth - @state.content.scrollLeft = @scrollLeft - @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide - @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor - @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null + @emitter.emit "did-update-state" updateLinesState: -> if @isInBatchMode() @shouldUpdateLinesState = true - @emitter.emit "did-update-state" - return + else + return unless @startRow? and @endRow? and @lineHeight? - return unless @startRow? and @endRow? and @lineHeight? + visibleLineIds = {} + row = @startRow + while row < @endRow + line = @model.tokenizedLineForScreenRow(row) + unless line? + throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") - visibleLineIds = {} - row = @startRow - while row < @endRow - line = @model.tokenizedLineForScreenRow(row) - unless line? - throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") + visibleLineIds[line.id] = true + if @state.content.lines.hasOwnProperty(line.id) + @updateLineState(row, line) + else + @buildLineState(row, line) + row++ - visibleLineIds[line.id] = true - if @state.content.lines.hasOwnProperty(line.id) - @updateLineState(row, line) - else - @buildLineState(row, line) - row++ + if @mouseWheelScreenRow? + if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow) + visibleLineIds[preservedLine.id] = true - if @mouseWheelScreenRow? - if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow) - visibleLineIds[preservedLine.id] = true + for id, line of @state.content.lines + unless visibleLineIds.hasOwnProperty(id) + delete @state.content.lines[id] - for id, line of @state.content.lines - unless visibleLineIds.hasOwnProperty(id) - delete @state.content.lines[id] + @emitter.emit "did-update-state" updateLineState: (row, line) -> lineState = @state.content.lines[line.id] @@ -333,141 +332,141 @@ class TextEditorPresenter updateCursorsState: -> if @isInBatchMode() @shouldUpdateCursorsState = true - @emitter.emit "did-update-state" - return + else + @state.content.cursors = {} - @state.content.cursors = {} - - @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation + @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation + @emitter.emit "did-update-state" updateCursorState: (cursor, destroyOnly = false) -> if @isInBatchMode() @shouldUpdateCursorsState = true - @emitter.emit "did-update-state" - return + else + delete @state.content.cursors[cursor.id] - delete @state.content.cursors[cursor.id] + return if destroyOnly + return unless @startRow? and @endRow? and @hasPixelRectRequirements() and @baseCharacterWidth? + return unless cursor.isVisible() and @startRow <= cursor.getScreenRow() < @endRow - return if destroyOnly - return unless @startRow? and @endRow? and @hasPixelRectRequirements() and @baseCharacterWidth? - return unless cursor.isVisible() and @startRow <= cursor.getScreenRow() < @endRow + pixelRect = @pixelRectForScreenRange(cursor.getScreenRange()) + pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 + @state.content.cursors[cursor.id] = pixelRect - pixelRect = @pixelRectForScreenRange(cursor.getScreenRange()) - pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 - @state.content.cursors[cursor.id] = pixelRect + @emitter.emit "did-update-state" updateOverlaysState: -> if @isInBatchMode() @shouldUpdateOverlaysState = true - @emitter.emit "did-update-state" - return - return unless @hasPixelRectRequirements() + else + return unless @hasPixelRectRequirements() - visibleDecorationIds = {} + visibleDecorationIds = {} - for decoration in @model.getOverlayDecorations() - continue unless decoration.getMarker().isValid() + for decoration in @model.getOverlayDecorations() + continue unless decoration.getMarker().isValid() - {item, position} = decoration.getProperties() - if position is 'tail' - screenPosition = decoration.getMarker().getTailScreenPosition() - else - screenPosition = decoration.getMarker().getHeadScreenPosition() + {item, position} = decoration.getProperties() + if position is 'tail' + screenPosition = decoration.getMarker().getTailScreenPosition() + else + screenPosition = decoration.getMarker().getHeadScreenPosition() - @state.content.overlays[decoration.id] ?= {item} - @state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition) - visibleDecorationIds[decoration.id] = true + @state.content.overlays[decoration.id] ?= {item} + @state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition) + visibleDecorationIds[decoration.id] = true - for id of @state.content.overlays - delete @state.content.overlays[id] unless visibleDecorationIds[id] + for id of @state.content.overlays + delete @state.content.overlays[id] unless visibleDecorationIds[id] + + @emitter.emit "did-update-state" updateGutterState: -> if @isInBatchMode() @shouldUpdateGutterState = true - @emitter.emit "did-update-state" - return - - @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers - @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length - @state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)" - @gutterBackgroundColor else - @backgroundColor + @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers + @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length + @state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)" + @gutterBackgroundColor + else + @backgroundColor + + @emitter.emit "did-update-state" updateLineNumbersState: -> if @isInBatchMode() @shouldUpdateLineNumbersState = true - @emitter.emit "did-update-state" - return - - return unless @startRow? and @endRow? and @lineHeight? - - visibleLineNumberIds = {} - - if @startRow > 0 - rowBeforeStartRow = @startRow - 1 - lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow) - wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow) else - lastBufferRow = null - wrapCount = 0 + return unless @startRow? and @endRow? and @lineHeight? - if @endRow > @startRow - for bufferRow, i in @model.bufferRowsForScreenRows(@startRow, @endRow - 1) - if bufferRow is lastBufferRow - wrapCount++ - id = bufferRow + '-' + wrapCount - softWrapped = true - else - id = bufferRow - wrapCount = 0 - lastBufferRow = bufferRow - softWrapped = false + visibleLineNumberIds = {} - screenRow = @startRow + i - top = screenRow * @lineHeight - decorationClasses = @lineNumberDecorationClassesForRow(screenRow) - foldable = @model.isFoldableAtScreenRow(screenRow) + if @startRow > 0 + rowBeforeStartRow = @startRow - 1 + lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow) + wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow) + else + lastBufferRow = null + wrapCount = 0 - @state.gutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} + if @endRow > @startRow + for bufferRow, i in @model.bufferRowsForScreenRows(@startRow, @endRow - 1) + if bufferRow is lastBufferRow + wrapCount++ + id = bufferRow + '-' + wrapCount + softWrapped = true + else + id = bufferRow + wrapCount = 0 + lastBufferRow = bufferRow + softWrapped = false + + screenRow = @startRow + i + top = screenRow * @lineHeight + decorationClasses = @lineNumberDecorationClassesForRow(screenRow) + foldable = @model.isFoldableAtScreenRow(screenRow) + + @state.gutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} + visibleLineNumberIds[id] = true + + if @mouseWheelScreenRow? + bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow) + wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow) + id = bufferRow + id += '-' + wrapCount if wrapCount > 0 visibleLineNumberIds[id] = true - if @mouseWheelScreenRow? - bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow) - wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow) - id = bufferRow - id += '-' + wrapCount if wrapCount > 0 - visibleLineNumberIds[id] = true + for id of @state.gutter.lineNumbers + delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] - for id of @state.gutter.lineNumbers - delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] + @emitter.emit "did-update-state" updateStartRow: -> if @isInBatchMode() @shouldUpdateStartRow = true - @emitter.emit "did-update-state" - return + else + return unless @scrollTop? and @lineHeight? - return unless @scrollTop? and @lineHeight? + startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin + @startRow = Math.max(0, startRow) - startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin - @startRow = Math.max(0, startRow) + @emitter.emit "did-update-state" updateEndRow: -> if @isInBatchMode() @shouldUpdateEndRow = true - @emitter.emit "did-update-state" - return - - return unless @scrollTop? and @lineHeight? and @height? + else + return unless @scrollTop? and @lineHeight? and @height? startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight)) visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 endRow = startRow + visibleLinesCount + @lineOverdrawMargin @endRow = Math.min(@model.getScreenLineCount(), endRow) + @emitter.emit "did-update-state" + updateScrollWidth: -> return unless @contentWidth? and @clientWidth? @@ -492,26 +491,26 @@ class TextEditorPresenter updateContentDimensions: -> if @isInBatchMode() @shouldUpdateContentDimensions = true - @emitter.emit "did-update-state" - return + else + if @lineHeight? + oldContentHeight = @contentHeight + @contentHeight = @lineHeight * @model.getScreenLineCount() - if @lineHeight? - oldContentHeight = @contentHeight - @contentHeight = @lineHeight * @model.getScreenLineCount() + if @baseCharacterWidth? + oldContentWidth = @contentWidth + @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left + @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width - if @baseCharacterWidth? - oldContentWidth = @contentWidth - @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left - @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width + if @contentHeight isnt oldContentHeight + @updateHeight() + @updateScrollbarDimensions() + @updateScrollHeight() - if @contentHeight isnt oldContentHeight - @updateHeight() - @updateScrollbarDimensions() - @updateScrollHeight() + if @contentWidth isnt oldContentWidth + @updateScrollbarDimensions() + @updateScrollWidth() - if @contentWidth isnt oldContentWidth - @updateScrollbarDimensions() - @updateScrollWidth() + @emitter.emit "did-update-state" updateClientHeight: -> return unless @height? and @horizontalScrollbarHeight? @@ -552,47 +551,47 @@ class TextEditorPresenter updateScrollbarDimensions: -> if @isInBatchMode() @shouldUpdateScrollbarDimensions = true - @emitter.emit "did-update-state" - return + else + return unless @contentFrameWidth? and @height? + return unless @measuredVerticalScrollbarWidth? and @measuredHorizontalScrollbarHeight? + return unless @contentWidth? and @contentHeight? - return unless @contentFrameWidth? and @height? - return unless @measuredVerticalScrollbarWidth? and @measuredHorizontalScrollbarHeight? - return unless @contentWidth? and @contentHeight? + clientWidthWithoutVerticalScrollbar = @contentFrameWidth + clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @measuredVerticalScrollbarWidth + clientHeightWithoutHorizontalScrollbar = @height + clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @measuredHorizontalScrollbarHeight - clientWidthWithoutVerticalScrollbar = @contentFrameWidth - clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @measuredVerticalScrollbarWidth - clientHeightWithoutHorizontalScrollbar = @height - clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @measuredHorizontalScrollbarHeight + horizontalScrollbarVisible = + not @model.isMini() and + (@contentWidth > clientWidthWithoutVerticalScrollbar or + @contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar) - horizontalScrollbarVisible = - not @model.isMini() and - (@contentWidth > clientWidthWithoutVerticalScrollbar or - @contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar) + verticalScrollbarVisible = + not @model.isMini() and + (@contentHeight > clientHeightWithoutHorizontalScrollbar or + @contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar) - verticalScrollbarVisible = - not @model.isMini() and - (@contentHeight > clientHeightWithoutHorizontalScrollbar or - @contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar) + horizontalScrollbarHeight = + if horizontalScrollbarVisible + @measuredHorizontalScrollbarHeight + else + 0 - horizontalScrollbarHeight = - if horizontalScrollbarVisible - @measuredHorizontalScrollbarHeight - else - 0 + verticalScrollbarWidth = + if verticalScrollbarVisible + @measuredVerticalScrollbarWidth + else + 0 - verticalScrollbarWidth = - if verticalScrollbarVisible - @measuredVerticalScrollbarWidth - else - 0 + unless @horizontalScrollbarHeight is horizontalScrollbarHeight + @horizontalScrollbarHeight = horizontalScrollbarHeight + @updateClientHeight() - unless @horizontalScrollbarHeight is horizontalScrollbarHeight - @horizontalScrollbarHeight = horizontalScrollbarHeight - @updateClientHeight() + unless @verticalScrollbarWidth is verticalScrollbarWidth + @verticalScrollbarWidth = verticalScrollbarWidth + @updateClientWidth() - unless @verticalScrollbarWidth is verticalScrollbarWidth - @verticalScrollbarWidth = verticalScrollbarWidth - @updateClientWidth() + @emitter.emit "did-update-state" lineDecorationClassesForRow: (row) -> return null if @model.isMini() @@ -938,27 +937,27 @@ class TextEditorPresenter updateDecorations: -> if @isInBatchMode() @shouldUpdateDecorations = true - @emitter.emit "did-update-state" - return + else + @lineDecorationsByScreenRow = {} + @lineNumberDecorationsByScreenRow = {} + @highlightDecorationsById = {} - @lineDecorationsByScreenRow = {} - @lineNumberDecorationsByScreenRow = {} - @highlightDecorationsById = {} + visibleHighlights = {} + return unless 0 <= @startRow <= @endRow <= Infinity - visibleHighlights = {} - return unless 0 <= @startRow <= @endRow <= Infinity + for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1) + range = @model.getMarker(markerId).getScreenRange() + for decoration in decorations + if decoration.isType('line') or decoration.isType('line-number') + @addToLineDecorationCaches(decoration, range) + else if decoration.isType('highlight') + visibleHighlights[decoration.id] = @updateHighlightState(decoration) - for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1) - range = @model.getMarker(markerId).getScreenRange() - for decoration in decorations - if decoration.isType('line') or decoration.isType('line-number') - @addToLineDecorationCaches(decoration, range) - else if decoration.isType('highlight') - visibleHighlights[decoration.id] = @updateHighlightState(decoration) + for id of @state.content.highlights + unless visibleHighlights[id] + delete @state.content.highlights[id] - for id of @state.content.highlights - unless visibleHighlights[id] - delete @state.content.highlights[id] + @emitter.emit "did-update-state" removeFromLineDecorationCaches: (decoration, range) -> for row in [range.start.row..range.end.row] by 1 @@ -990,37 +989,40 @@ class TextEditorPresenter @lineNumberDecorationsByScreenRow[row][decoration.id] = decoration updateHighlightState: (decoration) -> - return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements() + if @isInBatchMode() + @shouldUpdateDecorations = true + else + return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements() - properties = decoration.getProperties() - marker = decoration.getMarker() - range = marker.getScreenRange() + properties = decoration.getProperties() + marker = decoration.getMarker() + range = marker.getScreenRange() - if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) - delete @state.content.highlights[decoration.id] - @emitter.emit 'did-update-state' - return + if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) + delete @state.content.highlights[decoration.id] + @emitter.emit 'did-update-state' + return - if range.start.row < @startRow - range.start.row = @startRow - range.start.column = 0 - if range.end.row >= @endRow - range.end.row = @endRow - range.end.column = 0 + if range.start.row < @startRow + range.start.row = @startRow + range.start.column = 0 + if range.end.row >= @endRow + range.end.row = @endRow + range.end.column = 0 - if range.isEmpty() - delete @state.content.highlights[decoration.id] - @emitter.emit 'did-update-state' - return + if range.isEmpty() + delete @state.content.highlights[decoration.id] + @emitter.emit 'did-update-state' + return - highlightState = @state.content.highlights[decoration.id] ?= { - flashCount: 0 - flashDuration: null - flashClass: null - } - highlightState.class = properties.class - highlightState.deprecatedRegionClass = properties.deprecatedRegionClass - highlightState.regions = @buildHighlightRegions(range) + highlightState = @state.content.highlights[decoration.id] ?= { + flashCount: 0 + flashDuration: null + flashClass: null + } + highlightState.class = properties.class + highlightState.deprecatedRegionClass = properties.deprecatedRegionClass + highlightState.regions = @buildHighlightRegions(range) @emitter.emit 'did-update-state' true From bf16287b16540b756e886a1dd8e786709cf86a61 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Feb 2015 16:59:29 +0100 Subject: [PATCH 11/21] :art: Avoid entering/exiting from the outside --- src/text-editor-component.coffee | 4 +- src/text-editor-presenter.coffee | 113 +++++++++++++++---------------- 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index cd99d5d90..e0078342b 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -103,6 +103,8 @@ class TextEditorComponent window.removeEventListener 'resize', @requestHeightAndWidthMeasurement updateSync: -> + @presenter.applyChanges() + @oldState ?= {} @newState = @presenter.state @@ -179,9 +181,7 @@ class TextEditorComponent @updateSync() else unless @updateRequested @updateRequested = true - @presenter.enterBatchMode() atom.views.updateDocument => - @presenter.exitBatchMode() @updateRequested = false @updateSync() if @editor.isAlive() atom.views.readDocument(@readAfterUpdateSync) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 8c0a5f314..3ab62102c 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -26,6 +26,7 @@ class TextEditorPresenter @observeConfig() @buildState() @startBlinkingCursors() if @focused + @enterBatchMode() destroy: -> @disposables.dispose() @@ -46,11 +47,14 @@ class TextEditorPresenter enterBatchMode: -> @batchMode = true + exitBatchMode: -> + @batchMode = false + isInBatchMode: -> @batchMode == true - exitBatchMode: -> - @batchMode = false + applyChanges: -> + @exitBatchMode() @updateContentDimensions() if @shouldUpdateContentDimensions @updateScrollbarDimensions() if @shouldUpdateScrollbarDimensions @@ -89,6 +93,8 @@ class TextEditorPresenter @shouldUpdateGutterState = false @shouldUpdateLineNumbersState = false + @enterBatchMode() + observeModel: -> @disposables.add @model.onDidChange => @updateContentDimensions() @@ -190,25 +196,26 @@ class TextEditorPresenter updateFocusedState: -> if @isInBatchMode() @shouldUpdateFocusedState = true + @emitter.emit "did-update-state" else @state.focused = @focused - @emitter.emit "did-update-state" updateHeightState: -> if @isInBatchMode() @shouldUpdateHeightState = true + @emitter.emit "did-update-state" else if @autoHeight @state.height = @contentHeight else @state.height = null - @emitter.emit "did-update-state" updateVerticalScrollState: -> if @isInBatchMode() @shouldUpdateVerticalScrollState = true + @emitter.emit "did-update-state" else @state.content.scrollHeight = @scrollHeight @state.gutter.scrollHeight = @scrollHeight @@ -218,11 +225,11 @@ class TextEditorPresenter @state.gutter.scrollTop = @scrollTop @state.verticalScrollbar.scrollTop = @scrollTop - @emitter.emit "did-update-state" updateHorizontalScrollState: -> if @isInBatchMode() @shouldUpdateHorizontalScrollState = true + @emitter.emit "did-update-state" else @state.content.scrollWidth = @scrollWidth @state.horizontalScrollbar.scrollWidth = @scrollWidth @@ -230,11 +237,11 @@ class TextEditorPresenter @state.content.scrollLeft = @scrollLeft @state.horizontalScrollbar.scrollLeft = @scrollLeft - @emitter.emit "did-update-state" updateScrollbarsState: -> if @isInBatchMode() @shouldUpdateScrollbarsState = true + @emitter.emit "did-update-state" else @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight @@ -244,11 +251,11 @@ class TextEditorPresenter @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth @state.verticalScrollbar.bottom = @horizontalScrollbarHeight - @emitter.emit "did-update-state" updateHiddenInputState: -> if @isInBatchMode() @shouldUpdateHiddenInputState = true + @emitter.emit "did-update-state" else return unless lastCursor = @model.getLastCursor() @@ -266,11 +273,10 @@ class TextEditorPresenter @state.hiddenInput.height = height @state.hiddenInput.width = Math.max(width, 2) - @emitter.emit "did-update-state" - updateContentState: -> if @isInBatchMode() @shouldUpdateContentState = true + @emitter.emit "did-update-state" else @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft @@ -278,11 +284,11 @@ class TextEditorPresenter @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null - @emitter.emit "did-update-state" updateLinesState: -> if @isInBatchMode() @shouldUpdateLinesState = true + @emitter.emit "did-update-state" else return unless @startRow? and @endRow? and @lineHeight? @@ -308,7 +314,6 @@ class TextEditorPresenter unless visibleLineIds.hasOwnProperty(id) delete @state.content.lines[id] - @emitter.emit "did-update-state" updateLineState: (row, line) -> lineState = @state.content.lines[line.id] @@ -332,16 +337,16 @@ class TextEditorPresenter updateCursorsState: -> if @isInBatchMode() @shouldUpdateCursorsState = true + @emitter.emit "did-update-state" else @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation - @emitter.emit "did-update-state" - updateCursorState: (cursor, destroyOnly = false) -> if @isInBatchMode() @shouldUpdateCursorsState = true + @emitter.emit "did-update-state" else delete @state.content.cursors[cursor.id] @@ -353,12 +358,11 @@ class TextEditorPresenter pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect - @emitter.emit "did-update-state" updateOverlaysState: -> if @isInBatchMode() @shouldUpdateOverlaysState = true - + @emitter.emit "did-update-state" else return unless @hasPixelRectRequirements() @@ -380,11 +384,11 @@ class TextEditorPresenter for id of @state.content.overlays delete @state.content.overlays[id] unless visibleDecorationIds[id] - @emitter.emit "did-update-state" updateGutterState: -> if @isInBatchMode() @shouldUpdateGutterState = true + @emitter.emit "did-update-state" else @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length @@ -393,11 +397,11 @@ class TextEditorPresenter else @backgroundColor - @emitter.emit "did-update-state" updateLineNumbersState: -> if @isInBatchMode() @shouldUpdateLineNumbersState = true + @emitter.emit "did-update-state" else return unless @startRow? and @endRow? and @lineHeight? @@ -441,31 +445,30 @@ class TextEditorPresenter for id of @state.gutter.lineNumbers delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] - @emitter.emit "did-update-state" updateStartRow: -> if @isInBatchMode() @shouldUpdateStartRow = true + @emitter.emit "did-update-state" else return unless @scrollTop? and @lineHeight? startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin @startRow = Math.max(0, startRow) - @emitter.emit "did-update-state" updateEndRow: -> if @isInBatchMode() @shouldUpdateEndRow = true + @emitter.emit "did-update-state" else return unless @scrollTop? and @lineHeight? and @height? - startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight)) - visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 - endRow = startRow + visibleLinesCount + @lineOverdrawMargin - @endRow = Math.min(@model.getScreenLineCount(), endRow) + startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight)) + visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 + endRow = startRow + visibleLinesCount + @lineOverdrawMargin + @endRow = Math.min(@model.getScreenLineCount(), endRow) - @emitter.emit "did-update-state" updateScrollWidth: -> return unless @contentWidth? and @clientWidth? @@ -491,6 +494,7 @@ class TextEditorPresenter updateContentDimensions: -> if @isInBatchMode() @shouldUpdateContentDimensions = true + @emitter.emit "did-update-state" else if @lineHeight? oldContentHeight = @contentHeight @@ -510,7 +514,6 @@ class TextEditorPresenter @updateScrollbarDimensions() @updateScrollWidth() - @emitter.emit "did-update-state" updateClientHeight: -> return unless @height? and @horizontalScrollbarHeight? @@ -551,6 +554,7 @@ class TextEditorPresenter updateScrollbarDimensions: -> if @isInBatchMode() @shouldUpdateScrollbarDimensions = true + @emitter.emit "did-update-state" else return unless @contentFrameWidth? and @height? return unless @measuredVerticalScrollbarWidth? and @measuredHorizontalScrollbarHeight? @@ -591,7 +595,6 @@ class TextEditorPresenter @verticalScrollbarWidth = verticalScrollbarWidth @updateClientWidth() - @emitter.emit "did-update-state" lineDecorationClassesForRow: (row) -> return null if @model.isMini() @@ -937,6 +940,7 @@ class TextEditorPresenter updateDecorations: -> if @isInBatchMode() @shouldUpdateDecorations = true + @emitter.emit "did-update-state" else @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @@ -957,7 +961,6 @@ class TextEditorPresenter unless visibleHighlights[id] delete @state.content.highlights[id] - @emitter.emit "did-update-state" removeFromLineDecorationCaches: (decoration, range) -> for row in [range.start.row..range.end.row] by 1 @@ -989,42 +992,38 @@ class TextEditorPresenter @lineNumberDecorationsByScreenRow[row][decoration.id] = decoration updateHighlightState: (decoration) -> - if @isInBatchMode() - @shouldUpdateDecorations = true - else - return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements() + @emitter.emit "did-update-state" if @isInBatchMode() - properties = decoration.getProperties() - marker = decoration.getMarker() - range = marker.getScreenRange() + return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements() - if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) - delete @state.content.highlights[decoration.id] - @emitter.emit 'did-update-state' - return + properties = decoration.getProperties() + marker = decoration.getMarker() + range = marker.getScreenRange() - if range.start.row < @startRow - range.start.row = @startRow - range.start.column = 0 - if range.end.row >= @endRow - range.end.row = @endRow - range.end.column = 0 + if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) + delete @state.content.highlights[decoration.id] + return - if range.isEmpty() - delete @state.content.highlights[decoration.id] - @emitter.emit 'did-update-state' - return + if range.start.row < @startRow + range.start.row = @startRow + range.start.column = 0 + if range.end.row >= @endRow + range.end.row = @endRow + range.end.column = 0 - highlightState = @state.content.highlights[decoration.id] ?= { - flashCount: 0 - flashDuration: null - flashClass: null - } - highlightState.class = properties.class - highlightState.deprecatedRegionClass = properties.deprecatedRegionClass - highlightState.regions = @buildHighlightRegions(range) + if range.isEmpty() + delete @state.content.highlights[decoration.id] + return + + highlightState = @state.content.highlights[decoration.id] ?= { + flashCount: 0 + flashDuration: null + flashClass: null + } + highlightState.class = properties.class + highlightState.deprecatedRegionClass = properties.deprecatedRegionClass + highlightState.regions = @buildHighlightRegions(range) - @emitter.emit 'did-update-state' true buildHighlightRegions: (screenRange) -> From a4fe18abd59da6e60dd9f73b80373834513be39b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Feb 2015 19:52:34 +0100 Subject: [PATCH 12/21] :art: Extract helpers and make code simpler --- src/text-editor-presenter.coffee | 219 ++++++++++++++----------------- 1 file changed, 96 insertions(+), 123 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 3ab62102c..1ffd9d70d 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -26,7 +26,7 @@ class TextEditorPresenter @observeConfig() @buildState() @startBlinkingCursors() if @focused - @enterBatchMode() + @applyingChanges = false destroy: -> @disposables.dispose() @@ -44,22 +44,14 @@ class TextEditorPresenter @model.setVerticalScrollbarWidth(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth? @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? - enterBatchMode: -> - @batchMode = true + needsRefresh: -> + @emitter.emit "did-update-state" unless @applyingChanges - exitBatchMode: -> - @batchMode = false - - isInBatchMode: -> - @batchMode == true + isBatching: -> + @applyingChanges == false applyChanges: -> - @exitBatchMode() - - @updateContentDimensions() if @shouldUpdateContentDimensions - @updateScrollbarDimensions() if @shouldUpdateScrollbarDimensions - @updateStartRow() if @shouldUpdateStartRow - @updateEndRow() if @shouldUpdateEndRow + @applyingChanges = true @updateFocusedState() if @shouldUpdateFocusedState @updateHeightState() if @shouldUpdateHeightState @@ -75,10 +67,6 @@ class TextEditorPresenter @updateGutterState() if @shouldUpdateGutterState @updateLineNumbersState() if @shouldUpdateLineNumbersState - @shouldUpdateContentDimensions = false - @shouldUpdateScrollbarDimensions = false - @shouldUpdateStartRow = false - @shouldUpdateEndRow = false @shouldUpdateFocusedState = false @shouldUpdateHeightState = false @shouldUpdateVerticalScrollState = false @@ -93,7 +81,7 @@ class TextEditorPresenter @shouldUpdateGutterState = false @shouldUpdateLineNumbersState = false - @enterBatchMode() + @applyingChanges = false observeModel: -> @disposables.add @model.onDidChange => @@ -194,17 +182,17 @@ class TextEditorPresenter @updateLineNumbersState() updateFocusedState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateFocusedState = true - @emitter.emit "did-update-state" + @needsRefresh() else @state.focused = @focused updateHeightState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateHeightState = true - @emitter.emit "did-update-state" + @needsRefresh() else if @autoHeight @state.height = @contentHeight @@ -213,9 +201,9 @@ class TextEditorPresenter updateVerticalScrollState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateVerticalScrollState = true - @emitter.emit "did-update-state" + @needsRefresh() else @state.content.scrollHeight = @scrollHeight @state.gutter.scrollHeight = @scrollHeight @@ -227,9 +215,9 @@ class TextEditorPresenter updateHorizontalScrollState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateHorizontalScrollState = true - @emitter.emit "did-update-state" + @needsRefresh() else @state.content.scrollWidth = @scrollWidth @state.horizontalScrollbar.scrollWidth = @scrollWidth @@ -239,9 +227,9 @@ class TextEditorPresenter updateScrollbarsState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateScrollbarsState = true - @emitter.emit "did-update-state" + @needsRefresh() else @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight @@ -253,9 +241,9 @@ class TextEditorPresenter updateHiddenInputState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateHiddenInputState = true - @emitter.emit "did-update-state" + @needsRefresh() else return unless lastCursor = @model.getLastCursor() @@ -274,9 +262,9 @@ class TextEditorPresenter @state.hiddenInput.width = Math.max(width, 2) updateContentState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateContentState = true - @emitter.emit "did-update-state" + @needsRefresh() else @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft @@ -286,9 +274,9 @@ class TextEditorPresenter updateLinesState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateLinesState = true - @emitter.emit "did-update-state" + @needsRefresh() else return unless @startRow? and @endRow? and @lineHeight? @@ -335,18 +323,18 @@ class TextEditorPresenter decorationClasses: @lineDecorationClassesForRow(row) updateCursorsState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateCursorsState = true - @emitter.emit "did-update-state" + @needsRefresh() else @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation updateCursorState: (cursor, destroyOnly = false) -> - if @isInBatchMode() + if @isBatching() @shouldUpdateCursorsState = true - @emitter.emit "did-update-state" + @needsRefresh() else delete @state.content.cursors[cursor.id] @@ -360,9 +348,9 @@ class TextEditorPresenter updateOverlaysState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateOverlaysState = true - @emitter.emit "did-update-state" + @needsRefresh() else return unless @hasPixelRectRequirements() @@ -386,9 +374,9 @@ class TextEditorPresenter updateGutterState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateGutterState = true - @emitter.emit "did-update-state" + @needsRefresh() else @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length @@ -399,9 +387,9 @@ class TextEditorPresenter updateLineNumbersState: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateLineNumbersState = true - @emitter.emit "did-update-state" + @needsRefresh() else return unless @startRow? and @endRow? and @lineHeight? @@ -447,27 +435,19 @@ class TextEditorPresenter updateStartRow: -> - if @isInBatchMode() - @shouldUpdateStartRow = true - @emitter.emit "did-update-state" - else - return unless @scrollTop? and @lineHeight? + return unless @scrollTop? and @lineHeight? - startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin - @startRow = Math.max(0, startRow) + startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin + @startRow = Math.max(0, startRow) updateEndRow: -> - if @isInBatchMode() - @shouldUpdateEndRow = true - @emitter.emit "did-update-state" - else - return unless @scrollTop? and @lineHeight? and @height? + return unless @scrollTop? and @lineHeight? and @height? - startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight)) - visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 - endRow = startRow + visibleLinesCount + @lineOverdrawMargin - @endRow = Math.min(@model.getScreenLineCount(), endRow) + startRow = Math.max(0, Math.floor(@scrollTop / @lineHeight)) + visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 + endRow = startRow + visibleLinesCount + @lineOverdrawMargin + @endRow = Math.min(@model.getScreenLineCount(), endRow) updateScrollWidth: -> @@ -492,27 +472,23 @@ class TextEditorPresenter @updateScrollTop() updateContentDimensions: -> - if @isInBatchMode() - @shouldUpdateContentDimensions = true - @emitter.emit "did-update-state" - else - if @lineHeight? - oldContentHeight = @contentHeight - @contentHeight = @lineHeight * @model.getScreenLineCount() + if @lineHeight? + oldContentHeight = @contentHeight + @contentHeight = @lineHeight * @model.getScreenLineCount() - if @baseCharacterWidth? - oldContentWidth = @contentWidth - @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left - @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width + if @baseCharacterWidth? + oldContentWidth = @contentWidth + @contentWidth = @pixelPositionForScreenPosition([@model.getLongestScreenRow(), Infinity]).left + @contentWidth += 1 unless @model.isSoftWrapped() # account for cursor width - if @contentHeight isnt oldContentHeight - @updateHeight() - @updateScrollbarDimensions() - @updateScrollHeight() + if @contentHeight isnt oldContentHeight + @updateHeight() + @updateScrollbarDimensions() + @updateScrollHeight() - if @contentWidth isnt oldContentWidth - @updateScrollbarDimensions() - @updateScrollWidth() + if @contentWidth isnt oldContentWidth + @updateScrollbarDimensions() + @updateScrollWidth() updateClientHeight: -> @@ -552,48 +528,44 @@ class TextEditorPresenter Math.max(0, Math.min(scrollLeft, @scrollWidth - @clientWidth)) updateScrollbarDimensions: -> - if @isInBatchMode() - @shouldUpdateScrollbarDimensions = true - @emitter.emit "did-update-state" - else - return unless @contentFrameWidth? and @height? - return unless @measuredVerticalScrollbarWidth? and @measuredHorizontalScrollbarHeight? - return unless @contentWidth? and @contentHeight? + return unless @contentFrameWidth? and @height? + return unless @measuredVerticalScrollbarWidth? and @measuredHorizontalScrollbarHeight? + return unless @contentWidth? and @contentHeight? - clientWidthWithoutVerticalScrollbar = @contentFrameWidth - clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @measuredVerticalScrollbarWidth - clientHeightWithoutHorizontalScrollbar = @height - clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @measuredHorizontalScrollbarHeight + clientWidthWithoutVerticalScrollbar = @contentFrameWidth + clientWidthWithVerticalScrollbar = clientWidthWithoutVerticalScrollbar - @measuredVerticalScrollbarWidth + clientHeightWithoutHorizontalScrollbar = @height + clientHeightWithHorizontalScrollbar = clientHeightWithoutHorizontalScrollbar - @measuredHorizontalScrollbarHeight - horizontalScrollbarVisible = - not @model.isMini() and - (@contentWidth > clientWidthWithoutVerticalScrollbar or - @contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar) + horizontalScrollbarVisible = + not @model.isMini() and + (@contentWidth > clientWidthWithoutVerticalScrollbar or + @contentWidth > clientWidthWithVerticalScrollbar and @contentHeight > clientHeightWithoutHorizontalScrollbar) - verticalScrollbarVisible = - not @model.isMini() and - (@contentHeight > clientHeightWithoutHorizontalScrollbar or - @contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar) + verticalScrollbarVisible = + not @model.isMini() and + (@contentHeight > clientHeightWithoutHorizontalScrollbar or + @contentHeight > clientHeightWithHorizontalScrollbar and @contentWidth > clientWidthWithoutVerticalScrollbar) - horizontalScrollbarHeight = - if horizontalScrollbarVisible - @measuredHorizontalScrollbarHeight - else - 0 + horizontalScrollbarHeight = + if horizontalScrollbarVisible + @measuredHorizontalScrollbarHeight + else + 0 - verticalScrollbarWidth = - if verticalScrollbarVisible - @measuredVerticalScrollbarWidth - else - 0 + verticalScrollbarWidth = + if verticalScrollbarVisible + @measuredVerticalScrollbarWidth + else + 0 - unless @horizontalScrollbarHeight is horizontalScrollbarHeight - @horizontalScrollbarHeight = horizontalScrollbarHeight - @updateClientHeight() + unless @horizontalScrollbarHeight is horizontalScrollbarHeight + @horizontalScrollbarHeight = horizontalScrollbarHeight + @updateClientHeight() - unless @verticalScrollbarWidth is verticalScrollbarWidth - @verticalScrollbarWidth = verticalScrollbarWidth - @updateClientWidth() + unless @verticalScrollbarWidth is verticalScrollbarWidth + @verticalScrollbarWidth = verticalScrollbarWidth + @updateClientWidth() lineDecorationClassesForRow: (row) -> @@ -650,7 +622,7 @@ class TextEditorPresenter @stoppedScrollingTimeoutId = null @stoppedScrollingTimeoutId = setTimeout(@didStopScrolling.bind(this), @stoppedScrollingDelay) @state.content.scrollingVertically = true - @emitter.emit 'did-update-state' + @needsRefresh() didStopScrolling: -> @state.content.scrollingVertically = false @@ -659,7 +631,7 @@ class TextEditorPresenter @updateLinesState() @updateLineNumbersState() else - @emitter.emit 'did-update-state' + @needsRefresh() setScrollLeft: (scrollLeft) -> scrollLeft = @constrainScrollLeft(scrollLeft) @@ -923,7 +895,7 @@ class TextEditorPresenter decorationState.flashCount++ decorationState.flashClass = flash.class decorationState.flashDuration = flash.duration - @emitter.emit "did-update-state" + @needsRefresh() didAddDecoration: (decoration) -> @observeDecoration(decoration) @@ -938,9 +910,9 @@ class TextEditorPresenter @updateOverlaysState() updateDecorations: -> - if @isInBatchMode() + if @isBatching() @shouldUpdateDecorations = true - @emitter.emit "did-update-state" + @needsRefresh() else @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @@ -992,8 +964,6 @@ class TextEditorPresenter @lineNumberDecorationsByScreenRow[row][decoration.id] = decoration updateHighlightState: (decoration) -> - @emitter.emit "did-update-state" if @isInBatchMode() - return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements() properties = decoration.getProperties() @@ -1002,6 +972,7 @@ class TextEditorPresenter if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) delete @state.content.highlights[decoration.id] + @needsRefresh() return if range.start.row < @startRow @@ -1013,6 +984,7 @@ class TextEditorPresenter if range.isEmpty() delete @state.content.highlights[decoration.id] + @needsRefresh() return highlightState = @state.content.highlights[decoration.id] ?= { @@ -1023,6 +995,7 @@ class TextEditorPresenter highlightState.class = properties.class highlightState.deprecatedRegionClass = properties.deprecatedRegionClass highlightState.regions = @buildHighlightRegions(range) + @needsRefresh() true @@ -1109,10 +1082,10 @@ class TextEditorPresenter toggleCursorBlink: -> @state.content.cursorsVisible = not @state.content.cursorsVisible - @emitter.emit 'did-update-state' + @needsRefresh() pauseCursorBlinking: -> @stopBlinkingCursors(true) @startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay()) @startBlinkingCursorsAfterDelay() - @emitter.emit 'did-update-state' + @needsRefresh() From bcc99c163f6d1a0fdeb3ed5fc64a01edc3228e6b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Feb 2015 20:42:04 +0100 Subject: [PATCH 13/21] Rename applyChanges to update --- src/text-editor-component.coffee | 2 +- src/text-editor-presenter.coffee | 61 ++++++++++++++++---------------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index e0078342b..50729cc95 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -103,7 +103,7 @@ class TextEditorComponent window.removeEventListener 'resize', @requestHeightAndWidthMeasurement updateSync: -> - @presenter.applyChanges() + @presenter.update() @oldState ?= {} @newState = @presenter.state diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 1ffd9d70d..a1ffcacf8 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -26,7 +26,7 @@ class TextEditorPresenter @observeConfig() @buildState() @startBlinkingCursors() if @focused - @applyingChanges = false + @updating = false destroy: -> @disposables.dispose() @@ -45,13 +45,13 @@ class TextEditorPresenter @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? needsRefresh: -> - @emitter.emit "did-update-state" unless @applyingChanges + @emitter.emit "did-update-state" unless @updating isBatching: -> - @applyingChanges == false + @updating == false - applyChanges: -> - @applyingChanges = true + update: -> + @updating = true @updateFocusedState() if @shouldUpdateFocusedState @updateHeightState() if @shouldUpdateHeightState @@ -81,7 +81,7 @@ class TextEditorPresenter @shouldUpdateGutterState = false @shouldUpdateLineNumbersState = false - @applyingChanges = false + @updating = false observeModel: -> @disposables.add @model.onDidChange => @@ -184,26 +184,25 @@ class TextEditorPresenter updateFocusedState: -> if @isBatching() @shouldUpdateFocusedState = true - @needsRefresh() else @state.focused = @focused + @needsRefresh() updateHeightState: -> if @isBatching() @shouldUpdateHeightState = true - @needsRefresh() else if @autoHeight @state.height = @contentHeight else @state.height = null + @needsRefresh() updateVerticalScrollState: -> if @isBatching() @shouldUpdateVerticalScrollState = true - @needsRefresh() else @state.content.scrollHeight = @scrollHeight @state.gutter.scrollHeight = @scrollHeight @@ -213,11 +212,11 @@ class TextEditorPresenter @state.gutter.scrollTop = @scrollTop @state.verticalScrollbar.scrollTop = @scrollTop + @needsRefresh() updateHorizontalScrollState: -> if @isBatching() @shouldUpdateHorizontalScrollState = true - @needsRefresh() else @state.content.scrollWidth = @scrollWidth @state.horizontalScrollbar.scrollWidth = @scrollWidth @@ -225,11 +224,11 @@ class TextEditorPresenter @state.content.scrollLeft = @scrollLeft @state.horizontalScrollbar.scrollLeft = @scrollLeft + @needsRefresh() updateScrollbarsState: -> if @isBatching() @shouldUpdateScrollbarsState = true - @needsRefresh() else @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight @@ -239,11 +238,11 @@ class TextEditorPresenter @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth @state.verticalScrollbar.bottom = @horizontalScrollbarHeight + @needsRefresh() updateHiddenInputState: -> if @isBatching() @shouldUpdateHiddenInputState = true - @needsRefresh() else return unless lastCursor = @model.getLastCursor() @@ -261,10 +260,11 @@ class TextEditorPresenter @state.hiddenInput.height = height @state.hiddenInput.width = Math.max(width, 2) + @needsRefresh() + updateContentState: -> if @isBatching() @shouldUpdateContentState = true - @needsRefresh() else @state.content.scrollWidth = @scrollWidth @state.content.scrollLeft = @scrollLeft @@ -272,11 +272,11 @@ class TextEditorPresenter @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null + @needsRefresh() updateLinesState: -> if @isBatching() @shouldUpdateLinesState = true - @needsRefresh() else return unless @startRow? and @endRow? and @lineHeight? @@ -302,6 +302,7 @@ class TextEditorPresenter unless visibleLineIds.hasOwnProperty(id) delete @state.content.lines[id] + @needsRefresh() updateLineState: (row, line) -> lineState = @state.content.lines[line.id] @@ -325,32 +326,28 @@ class TextEditorPresenter updateCursorsState: -> if @isBatching() @shouldUpdateCursorsState = true - @needsRefresh() else @state.content.cursors = {} - @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation + @needsRefresh() + updateCursorState: (cursor, destroyOnly = false) -> - if @isBatching() - @shouldUpdateCursorsState = true - @needsRefresh() - else - delete @state.content.cursors[cursor.id] + delete @state.content.cursors[cursor.id] - return if destroyOnly - return unless @startRow? and @endRow? and @hasPixelRectRequirements() and @baseCharacterWidth? - return unless cursor.isVisible() and @startRow <= cursor.getScreenRow() < @endRow + return if destroyOnly + return unless @startRow? and @endRow? and @hasPixelRectRequirements() and @baseCharacterWidth? + return unless cursor.isVisible() and @startRow <= cursor.getScreenRow() < @endRow - pixelRect = @pixelRectForScreenRange(cursor.getScreenRange()) - pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 - @state.content.cursors[cursor.id] = pixelRect + pixelRect = @pixelRectForScreenRange(cursor.getScreenRange()) + pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 + @state.content.cursors[cursor.id] = pixelRect + @needsRefresh() updateOverlaysState: -> if @isBatching() @shouldUpdateOverlaysState = true - @needsRefresh() else return unless @hasPixelRectRequirements() @@ -372,11 +369,11 @@ class TextEditorPresenter for id of @state.content.overlays delete @state.content.overlays[id] unless visibleDecorationIds[id] + @needsRefresh() updateGutterState: -> if @isBatching() @shouldUpdateGutterState = true - @needsRefresh() else @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length @@ -385,11 +382,11 @@ class TextEditorPresenter else @backgroundColor + @needsRefresh() updateLineNumbersState: -> if @isBatching() @shouldUpdateLineNumbersState = true - @needsRefresh() else return unless @startRow? and @endRow? and @lineHeight? @@ -433,6 +430,7 @@ class TextEditorPresenter for id of @state.gutter.lineNumbers delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] + @needsRefresh() updateStartRow: -> return unless @scrollTop? and @lineHeight? @@ -912,7 +910,6 @@ class TextEditorPresenter updateDecorations: -> if @isBatching() @shouldUpdateDecorations = true - @needsRefresh() else @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @@ -933,6 +930,8 @@ class TextEditorPresenter unless visibleHighlights[id] delete @state.content.highlights[id] + @needsRefresh() + removeFromLineDecorationCaches: (decoration, range) -> for row in [range.start.row..range.end.row] by 1 From fbec9e31c4761e433e1c4ad58b036398a9b195b3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Feb 2015 20:54:00 +0100 Subject: [PATCH 14/21] Let's call it needsUpdate :wink: --- src/text-editor-component.coffee | 2 +- src/text-editor-presenter.coffee | 58 +++++++++++++++++--------------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 50729cc95..5e597a3fb 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -52,7 +52,7 @@ class TextEditorComponent cursorBlinkResumeDelay: @cursorBlinkResumeDelay stoppedScrollingDelay: 200 - @presenter.onDidUpdateState(@requestUpdate) + @presenter.onNeedsUpdate(@requestUpdate) @domNode = document.createElement('div') if @useShadowDOM diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index a1ffcacf8..d2afd9efc 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -31,8 +31,12 @@ class TextEditorPresenter destroy: -> @disposables.dispose() - onDidUpdateState: (callback) -> - @emitter.on 'did-update-state', callback + # Calls your `callback` when some changes in the model occurred and the current state needs to be updated. + onNeedsUpdate: (callback) -> + @emitter.on 'needs-update', callback + + needsUpdate: -> + @emitter.emit "needs-update" unless @updating transferMeasurementsToModel: -> @model.setHeight(@explicitHeight) if @explicitHeight? @@ -44,12 +48,12 @@ class TextEditorPresenter @model.setVerticalScrollbarWidth(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth? @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? - needsRefresh: -> - @emitter.emit "did-update-state" unless @updating - + # Determines whether {TextEditorPresenter} is currently batching changes. + # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them. isBatching: -> @updating == false + # Updates the state, applying only those changes that occurred between this call and a previous call to this method. update: -> @updating = true @@ -187,7 +191,7 @@ class TextEditorPresenter else @state.focused = @focused - @needsRefresh() + @needsUpdate() updateHeightState: -> if @isBatching() @@ -198,7 +202,7 @@ class TextEditorPresenter else @state.height = null - @needsRefresh() + @needsUpdate() updateVerticalScrollState: -> if @isBatching() @@ -212,7 +216,7 @@ class TextEditorPresenter @state.gutter.scrollTop = @scrollTop @state.verticalScrollbar.scrollTop = @scrollTop - @needsRefresh() + @needsUpdate() updateHorizontalScrollState: -> if @isBatching() @@ -224,7 +228,7 @@ class TextEditorPresenter @state.content.scrollLeft = @scrollLeft @state.horizontalScrollbar.scrollLeft = @scrollLeft - @needsRefresh() + @needsUpdate() updateScrollbarsState: -> if @isBatching() @@ -238,7 +242,7 @@ class TextEditorPresenter @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth @state.verticalScrollbar.bottom = @horizontalScrollbarHeight - @needsRefresh() + @needsUpdate() updateHiddenInputState: -> if @isBatching() @@ -260,7 +264,7 @@ class TextEditorPresenter @state.hiddenInput.height = height @state.hiddenInput.width = Math.max(width, 2) - @needsRefresh() + @needsUpdate() updateContentState: -> if @isBatching() @@ -272,7 +276,7 @@ class TextEditorPresenter @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null - @needsRefresh() + @needsUpdate() updateLinesState: -> if @isBatching() @@ -302,7 +306,7 @@ class TextEditorPresenter unless visibleLineIds.hasOwnProperty(id) delete @state.content.lines[id] - @needsRefresh() + @needsUpdate() updateLineState: (row, line) -> lineState = @state.content.lines[line.id] @@ -330,7 +334,7 @@ class TextEditorPresenter @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation - @needsRefresh() + @needsUpdate() updateCursorState: (cursor, destroyOnly = false) -> delete @state.content.cursors[cursor.id] @@ -343,7 +347,7 @@ class TextEditorPresenter pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect - @needsRefresh() + @needsUpdate() updateOverlaysState: -> if @isBatching() @@ -369,7 +373,7 @@ class TextEditorPresenter for id of @state.content.overlays delete @state.content.overlays[id] unless visibleDecorationIds[id] - @needsRefresh() + @needsUpdate() updateGutterState: -> if @isBatching() @@ -382,7 +386,7 @@ class TextEditorPresenter else @backgroundColor - @needsRefresh() + @needsUpdate() updateLineNumbersState: -> if @isBatching() @@ -430,7 +434,7 @@ class TextEditorPresenter for id of @state.gutter.lineNumbers delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] - @needsRefresh() + @needsUpdate() updateStartRow: -> return unless @scrollTop? and @lineHeight? @@ -620,7 +624,7 @@ class TextEditorPresenter @stoppedScrollingTimeoutId = null @stoppedScrollingTimeoutId = setTimeout(@didStopScrolling.bind(this), @stoppedScrollingDelay) @state.content.scrollingVertically = true - @needsRefresh() + @needsUpdate() didStopScrolling: -> @state.content.scrollingVertically = false @@ -629,7 +633,7 @@ class TextEditorPresenter @updateLinesState() @updateLineNumbersState() else - @needsRefresh() + @needsUpdate() setScrollLeft: (scrollLeft) -> scrollLeft = @constrainScrollLeft(scrollLeft) @@ -893,7 +897,7 @@ class TextEditorPresenter decorationState.flashCount++ decorationState.flashClass = flash.class decorationState.flashDuration = flash.duration - @needsRefresh() + @needsUpdate() didAddDecoration: (decoration) -> @observeDecoration(decoration) @@ -930,7 +934,7 @@ class TextEditorPresenter unless visibleHighlights[id] delete @state.content.highlights[id] - @needsRefresh() + @needsUpdate() removeFromLineDecorationCaches: (decoration, range) -> @@ -971,7 +975,7 @@ class TextEditorPresenter if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) delete @state.content.highlights[decoration.id] - @needsRefresh() + @needsUpdate() return if range.start.row < @startRow @@ -983,7 +987,7 @@ class TextEditorPresenter if range.isEmpty() delete @state.content.highlights[decoration.id] - @needsRefresh() + @needsUpdate() return highlightState = @state.content.highlights[decoration.id] ?= { @@ -994,7 +998,7 @@ class TextEditorPresenter highlightState.class = properties.class highlightState.deprecatedRegionClass = properties.deprecatedRegionClass highlightState.regions = @buildHighlightRegions(range) - @needsRefresh() + @needsUpdate() true @@ -1081,10 +1085,10 @@ class TextEditorPresenter toggleCursorBlink: -> @state.content.cursorsVisible = not @state.content.cursorsVisible - @needsRefresh() + @needsUpdate() pauseCursorBlinking: -> @stopBlinkingCursors(true) @startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay()) @startBlinkingCursorsAfterDelay() - @needsRefresh() + @needsUpdate() From 290acb356c42da17dcc10c2125e1fa30f2536d27 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 27 Feb 2015 21:01:46 +0100 Subject: [PATCH 15/21] :art: Remove unnecessary newlines --- src/text-editor-presenter.coffee | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index d2afd9efc..6e4c8e928 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -36,7 +36,7 @@ class TextEditorPresenter @emitter.on 'needs-update', callback needsUpdate: -> - @emitter.emit "needs-update" unless @updating + @emitter.emit "needs-update" if @isBatching() transferMeasurementsToModel: -> @model.setHeight(@explicitHeight) if @explicitHeight? @@ -442,7 +442,6 @@ class TextEditorPresenter startRow = Math.floor(@scrollTop / @lineHeight) - @lineOverdrawMargin @startRow = Math.max(0, startRow) - updateEndRow: -> return unless @scrollTop? and @lineHeight? and @height? @@ -451,7 +450,6 @@ class TextEditorPresenter endRow = startRow + visibleLinesCount + @lineOverdrawMargin @endRow = Math.min(@model.getScreenLineCount(), endRow) - updateScrollWidth: -> return unless @contentWidth? and @clientWidth? @@ -492,7 +490,6 @@ class TextEditorPresenter @updateScrollbarDimensions() @updateScrollWidth() - updateClientHeight: -> return unless @height? and @horizontalScrollbarHeight? From 0dca5a5fcd398a2d9d2d3e1eb010d3b745a76927 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 28 Feb 2015 09:25:53 +0100 Subject: [PATCH 16/21] Encapsulate state update inside TextEditorPresenter#getState --- spec/text-editor-presenter-spec.coffee | 472 ++++++++++++------------- src/cursors-component.coffee | 7 +- src/gutter-component.coffee | 6 +- src/highlights-component.coffee | 6 +- src/input-component.coffee | 7 +- src/lines-component.coffee | 16 +- src/overlay-manager.coffee | 22 +- src/scrollbar-component.coffee | 10 +- src/scrollbar-corner-component.coffee | 10 +- src/text-editor-component.coffee | 30 +- src/text-editor-presenter.coffee | 5 +- 11 files changed, 288 insertions(+), 303 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 2ee966630..662b9d483 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -45,7 +45,7 @@ describe "TextEditorPresenter", -> expectStateUpdate = (presenter, fn) -> updatedState = false - disposable = presenter.onDidUpdateState -> + disposable = presenter.onNeedsUpdate -> updatedState = true disposable.dispose() fn() @@ -61,21 +61,20 @@ describe "TextEditorPresenter", -> lineHeight: 10 horizontalScrollbarHeight: 10 verticalScrollbarWidth: 10 - {state} = presenter - expect(state.horizontalScrollbar.visible).toBe false + expect(presenter.getState().horizontalScrollbar.visible).toBe false # ::contentFrameWidth itself is smaller than scrollWidth presenter.setContentFrameWidth(editor.getMaxScreenLineLength() * 10) - expect(state.horizontalScrollbar.visible).toBe true + expect(presenter.getState().horizontalScrollbar.visible).toBe true # restore... presenter.setContentFrameWidth(editor.getMaxScreenLineLength() * 10 + 1) - expect(state.horizontalScrollbar.visible).toBe false + expect(presenter.getState().horizontalScrollbar.visible).toBe false # visible vertical scrollbar makes the clientWidth smaller than the scrollWidth presenter.setExplicitHeight((editor.getLineCount() * 10) - 1) - expect(state.horizontalScrollbar.visible).toBe true + expect(presenter.getState().horizontalScrollbar.visible).toBe true it "is false if the editor is mini", -> presenter = buildPresenter @@ -83,18 +82,18 @@ describe "TextEditorPresenter", -> contentFrameWidth: editor.getMaxScreenLineLength() * 10 - 10 baseCharacterWidth: 10 - expect(presenter.state.horizontalScrollbar.visible).toBe true + expect(presenter.getState().horizontalScrollbar.visible).toBe true editor.setMini(true) - expect(presenter.state.horizontalScrollbar.visible).toBe false + expect(presenter.getState().horizontalScrollbar.visible).toBe false editor.setMini(false) - expect(presenter.state.horizontalScrollbar.visible).toBe true + expect(presenter.getState().horizontalScrollbar.visible).toBe true describe ".height", -> it "tracks the value of ::horizontalScrollbarHeight", -> presenter = buildPresenter(horizontalScrollbarHeight: 10) - expect(presenter.state.horizontalScrollbar.height).toBe 10 + expect(presenter.getState().horizontalScrollbar.height).toBe 10 expectStateUpdate presenter, -> presenter.setHorizontalScrollbarHeight(20) - expect(presenter.state.horizontalScrollbar.height).toBe 20 + expect(presenter.getState().horizontalScrollbar.height).toBe 20 describe ".right", -> it "is ::verticalScrollbarWidth if the vertical scrollbar is visible and 0 otherwise", -> @@ -105,37 +104,36 @@ describe "TextEditorPresenter", -> lineHeight: 10 horizontalScrollbarHeight: 10 verticalScrollbarWidth: 10 - {state} = presenter - expect(state.horizontalScrollbar.right).toBe 0 + expect(presenter.getState().horizontalScrollbar.right).toBe 0 presenter.setExplicitHeight((editor.getLineCount() * 10) - 1) - expect(state.horizontalScrollbar.right).toBe 10 + expect(presenter.getState().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 = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 presenter = buildPresenter(contentFrameWidth: 10 * maxLineLength + 20, baseCharacterWidth: 10) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20 it "updates when the ::contentFrameWidth changes", -> maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.setContentFrameWidth(10 * maxLineLength + 20) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 20 it "updates when the ::baseCharacterWidth changes", -> maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 15 * maxLineLength + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 15 * maxLineLength + 1 it "updates when the scoped character widths change", -> waitsForPromise -> atom.packages.activatePackage('language-javascript') @@ -144,58 +142,58 @@ describe "TextEditorPresenter", -> maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['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 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide it "updates when ::softWrapped changes on the editor", -> presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 expectStateUpdate presenter, -> editor.setSoftWrapped(true) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe presenter.clientWidth + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe presenter.clientWidth expectStateUpdate presenter, -> editor.setSoftWrapped(false) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 it "updates when the longest line changes", -> presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 expectStateUpdate presenter, -> editor.setCursorBufferPosition([editor.getLongestScreenRow(), 0]) expectStateUpdate presenter, -> editor.insertText('xyz') - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 describe ".scrollLeft", -> it "tracks the value of ::scrollLeft", -> presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe 10 + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 10 expectStateUpdate presenter, -> presenter.setScrollLeft(50) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe 50 + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 50 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) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> presenter.setContentFrameWidth(600) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> presenter.setVerticalScrollbarWidth(5) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> editor.getBuffer().delete([[6, 0], [6, Infinity]]) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth # Scroll top only gets smaller when needed as dimensions change, never bigger - scrollLeftBefore = presenter.state.horizontalScrollbar.scrollLeft + scrollLeftBefore = presenter.getState().horizontalScrollbar.scrollLeft expectStateUpdate presenter, -> editor.getBuffer().insert([6, 0], new Array(100).join('x')) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe scrollLeftBefore + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe scrollLeftBefore it "never goes negative", -> presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expectStateUpdate presenter, -> presenter.setScrollLeft(-300) - expect(presenter.state.horizontalScrollbar.scrollLeft).toBe 0 + expect(presenter.getState().horizontalScrollbar.scrollLeft).toBe 0 describe ".verticalScrollbar", -> describe ".visible", -> @@ -208,28 +206,27 @@ describe "TextEditorPresenter", -> lineHeight: 10 horizontalScrollbarHeight: 10 verticalScrollbarWidth: 10 - {state} = presenter - expect(state.verticalScrollbar.visible).toBe false + expect(presenter.getState().verticalScrollbar.visible).toBe false # ::explicitHeight itself is smaller than scrollWidth presenter.setExplicitHeight(editor.getLineCount() * 10 - 1) - expect(state.verticalScrollbar.visible).toBe true + expect(presenter.getState().verticalScrollbar.visible).toBe true # restore... presenter.setExplicitHeight(editor.getLineCount() * 10) - expect(state.verticalScrollbar.visible).toBe false + expect(presenter.getState().verticalScrollbar.visible).toBe false # visible horizontal scrollbar makes the clientHeight smaller than the scrollHeight presenter.setContentFrameWidth(editor.getMaxScreenLineLength() * 10) - expect(state.verticalScrollbar.visible).toBe true + expect(presenter.getState().verticalScrollbar.visible).toBe true describe ".width", -> it "is assigned based on ::verticalScrollbarWidth", -> presenter = buildPresenter(verticalScrollbarWidth: 10) - expect(presenter.state.verticalScrollbar.width).toBe 10 + expect(presenter.getState().verticalScrollbar.width).toBe 10 expectStateUpdate presenter, -> presenter.setVerticalScrollbarWidth(20) - expect(presenter.state.verticalScrollbar.width).toBe 20 + expect(presenter.getState().verticalScrollbar.width).toBe 20 describe ".bottom", -> it "is ::horizontalScrollbarHeight if the horizontal scrollbar is visible and 0 otherwise", -> @@ -240,131 +237,130 @@ describe "TextEditorPresenter", -> lineHeight: 10 horizontalScrollbarHeight: 10 verticalScrollbarWidth: 10 - {state} = presenter - expect(state.verticalScrollbar.bottom).toBe 0 + expect(presenter.getState().verticalScrollbar.bottom).toBe 0 presenter.setContentFrameWidth(editor.getMaxScreenLineLength() * 10) - expect(state.verticalScrollbar.bottom).toBe 10 + expect(presenter.getState().verticalScrollbar.bottom).toBe 10 describe ".scrollHeight", -> it "is initialized based on the lineHeight, the number of lines, and the height", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) - expect(presenter.state.verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 500) - expect(presenter.state.verticalScrollbar.scrollHeight).toBe 500 + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe 500 it "updates when the ::lineHeight changes", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expectStateUpdate presenter, -> presenter.setLineHeight(20) - expect(presenter.state.verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 20 + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 20 it "updates when the line count changes", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") - expect(presenter.state.verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe editor.getScreenLineCount() * 10 it "updates when ::explicitHeight changes", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expectStateUpdate presenter, -> presenter.setExplicitHeight(500) - expect(presenter.state.verticalScrollbar.scrollHeight).toBe 500 + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe 500 it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.verticalScrollbar.scrollHeight).toBe presenter.contentHeight + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe presenter.contentHeight expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true) - expect(presenter.state.verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) - expect(presenter.state.verticalScrollbar.scrollHeight).toBe presenter.contentHeight + expect(presenter.getState().verticalScrollbar.scrollHeight).toBe presenter.contentHeight describe ".scrollTop", -> it "tracks the value of ::scrollTop", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 20, horizontalScrollbarHeight: 10) - expect(presenter.state.verticalScrollbar.scrollTop).toBe 10 + expect(presenter.getState().verticalScrollbar.scrollTop).toBe 10 expectStateUpdate presenter, -> presenter.setScrollTop(50) - expect(presenter.state.verticalScrollbar.scrollTop).toBe 50 + expect(presenter.getState().verticalScrollbar.scrollTop).toBe 50 it "never exceeds the computed scrollHeight minus the computed clientHeight", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(100) - expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> presenter.setExplicitHeight(60) - expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> presenter.setHorizontalScrollbarHeight(5) - expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> editor.getBuffer().delete([[8, 0], [12, 0]]) - expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().verticalScrollbar.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight # Scroll top only gets smaller when needed as dimensions change, never bigger - scrollTopBefore = presenter.state.verticalScrollbar.scrollTop + scrollTopBefore = presenter.getState().verticalScrollbar.scrollTop expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n') - expect(presenter.state.verticalScrollbar.scrollTop).toBe scrollTopBefore + expect(presenter.getState().verticalScrollbar.scrollTop).toBe scrollTopBefore it "never goes negative", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(-100) - expect(presenter.state.verticalScrollbar.scrollTop).toBe 0 + expect(presenter.getState().verticalScrollbar.scrollTop).toBe 0 it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.contentHeight - presenter.clientHeight + expect(presenter.getState().verticalScrollbar.scrollTop).toBe presenter.contentHeight - presenter.clientHeight atom.config.set("editor.scrollPastEnd", true) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.contentHeight - (presenter.lineHeight * 3) + expect(presenter.getState().verticalScrollbar.scrollTop).toBe presenter.contentHeight - (presenter.lineHeight * 3) expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) - expect(presenter.state.verticalScrollbar.scrollTop).toBe presenter.contentHeight - presenter.clientHeight + expect(presenter.getState().verticalScrollbar.scrollTop).toBe presenter.contentHeight - presenter.clientHeight describe ".hiddenInput", -> describe ".top/.left", -> 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.state.hiddenInput, {top: 0, left: 0} + expectValues presenter.getState().hiddenInput, {top: 0, left: 0} expectStateUpdate presenter, -> presenter.setFocused(true) - expectValues presenter.state.hiddenInput, {top: 3 * 10, left: 6 * 10} + expectValues presenter.getState().hiddenInput, {top: 3 * 10, left: 6 * 10} expectStateUpdate presenter, -> presenter.setScrollTop(15) - expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: 6 * 10} + expectValues presenter.getState().hiddenInput, {top: (3 * 10) - 15, left: 6 * 10} expectStateUpdate presenter, -> presenter.setScrollLeft(35) - expectValues presenter.state.hiddenInput, {top: (3 * 10) - 15, left: (6 * 10) - 35} + expectValues presenter.getState().hiddenInput, {top: (3 * 10) - 15, left: (6 * 10) - 35} expectStateUpdate presenter, -> presenter.setScrollTop(40) - expectValues presenter.state.hiddenInput, {top: 0, left: (6 * 10) - 35} + expectValues presenter.getState().hiddenInput, {top: 0, left: (6 * 10) - 35} expectStateUpdate presenter, -> presenter.setScrollLeft(70) - expectValues presenter.state.hiddenInput, {top: 0, left: 0} + expectValues presenter.getState().hiddenInput, {top: 0, left: 0} expectStateUpdate presenter, -> editor.setCursorBufferPosition([11, 43]) - expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10} + expectValues presenter.getState().hiddenInput, {top: 50 - 10, left: 300 - 10} newCursor = null expectStateUpdate presenter, -> newCursor = editor.addCursorAtBufferPosition([6, 10]) - expectValues presenter.state.hiddenInput, {top: (6 * 10) - 40, left: (10 * 10) - 70} + expectValues presenter.getState().hiddenInput, {top: (6 * 10) - 40, left: (10 * 10) - 70} expectStateUpdate presenter, -> newCursor.destroy() - expectValues presenter.state.hiddenInput, {top: 50 - 10, left: 300 - 10} + expectValues presenter.getState().hiddenInput, {top: 50 - 10, left: 300 - 10} expectStateUpdate presenter, -> presenter.setFocused(false) - expectValues presenter.state.hiddenInput, {top: 0, left: 0} + expectValues presenter.getState().hiddenInput, {top: 0, left: 0} describe ".height", -> it "is assigned based on the line height", -> presenter = buildPresenter() - expect(presenter.state.hiddenInput.height).toBe 10 + expect(presenter.getState().hiddenInput.height).toBe 10 expectStateUpdate presenter, -> presenter.setLineHeight(20) - expect(presenter.state.hiddenInput.height).toBe 20 + expect(presenter.getState().hiddenInput.height).toBe 20 describe ".width", -> it "is assigned based on the width of the character following the cursor", -> @@ -373,93 +369,93 @@ describe "TextEditorPresenter", -> runs -> editor.setCursorBufferPosition([3, 6]) presenter = buildPresenter() - expect(presenter.state.hiddenInput.width).toBe 10 + expect(presenter.getState().hiddenInput.width).toBe 10 expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) - expect(presenter.state.hiddenInput.width).toBe 15 + expect(presenter.getState().hiddenInput.width).toBe 15 expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'storage.modifier.js'], 'r', 20) - expect(presenter.state.hiddenInput.width).toBe 20 + expect(presenter.getState().hiddenInput.width).toBe 20 it "is 2px at the end of lines", -> presenter = buildPresenter() editor.setCursorBufferPosition([3, Infinity]) - expect(presenter.state.hiddenInput.width).toBe 2 + expect(presenter.getState().hiddenInput.width).toBe 2 describe ".content", -> describe ".scrollingVertically", -> it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", -> presenter = buildPresenter(scrollTop: 10, stoppedScrollingDelay: 200, explicitHeight: 100) - expect(presenter.state.content.scrollingVertically).toBe false + expect(presenter.getState().content.scrollingVertically).toBe false expectStateUpdate presenter, -> presenter.setScrollTop(0) - expect(presenter.state.content.scrollingVertically).toBe true + expect(presenter.getState().content.scrollingVertically).toBe true advanceClock(100) - expect(presenter.state.content.scrollingVertically).toBe true + expect(presenter.getState().content.scrollingVertically).toBe true presenter.setScrollTop(10) advanceClock(100) - expect(presenter.state.content.scrollingVertically).toBe true + expect(presenter.getState().content.scrollingVertically).toBe true expectStateUpdate presenter, -> advanceClock(100) - expect(presenter.state.content.scrollingVertically).toBe false + expect(presenter.getState().content.scrollingVertically).toBe false describe ".scrollHeight", -> it "is initialized based on the lineHeight, the number of lines, and the height", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) - expect(presenter.state.content.scrollHeight).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().content.scrollHeight).toBe editor.getScreenLineCount() * 10 presenter = buildPresenter(scrollTop: 0, lineHeight: 10, explicitHeight: 500) - expect(presenter.state.content.scrollHeight).toBe 500 + expect(presenter.getState().content.scrollHeight).toBe 500 it "updates when the ::lineHeight changes", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expectStateUpdate presenter, -> presenter.setLineHeight(20) - expect(presenter.state.content.scrollHeight).toBe editor.getScreenLineCount() * 20 + expect(presenter.getState().content.scrollHeight).toBe editor.getScreenLineCount() * 20 it "updates when the line count changes", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") - expect(presenter.state.content.scrollHeight).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().content.scrollHeight).toBe editor.getScreenLineCount() * 10 it "updates when ::explicitHeight changes", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) expectStateUpdate presenter, -> presenter.setExplicitHeight(500) - expect(presenter.state.content.scrollHeight).toBe 500 + expect(presenter.getState().content.scrollHeight).toBe 500 it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.content.scrollHeight).toBe presenter.contentHeight + expect(presenter.getState().content.scrollHeight).toBe presenter.contentHeight expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true) - expect(presenter.state.content.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) + expect(presenter.getState().content.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) - expect(presenter.state.content.scrollHeight).toBe presenter.contentHeight + expect(presenter.getState().content.scrollHeight).toBe presenter.contentHeight describe ".scrollWidth", -> it "is initialized as the max of the computed clientWidth and the width of the longest line", -> maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(explicitHeight: 100, contentFrameWidth: 50, baseCharacterWidth: 10, verticalScrollbarWidth: 10) - expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1 presenter = buildPresenter(explicitHeight: 100, contentFrameWidth: 10 * maxLineLength + 20, baseCharacterWidth: 10, verticalScrollbarWidth: 10) - expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 20 - 10 # subtract vertical scrollbar width + expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 20 - 10 # subtract vertical scrollbar width it "updates when the ::contentFrameWidth changes", -> maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.setContentFrameWidth(10 * maxLineLength + 20) - expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 20 + expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 20 it "updates when the ::baseCharacterWidth changes", -> maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.setBaseCharacterWidth(15) - expect(presenter.state.content.scrollWidth).toBe 15 * maxLineLength + 1 + expect(presenter.getState().content.scrollWidth).toBe 15 * maxLineLength + 1 it "updates when the scoped character widths change", -> waitsForPromise -> atom.packages.activatePackage('language-javascript') @@ -468,184 +464,184 @@ describe "TextEditorPresenter", -> maxLineLength = editor.getMaxScreenLineLength() presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.content.scrollWidth).toBe 10 * maxLineLength + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * maxLineLength + 1 expectStateUpdate presenter, -> presenter.setScopedCharacterWidth(['source.js', 'support.function.js'], 'p', 20) - expect(presenter.state.content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide + expect(presenter.getState().content.scrollWidth).toBe (10 * (maxLineLength - 2)) + (20 * 2) + 1 # 2 of the characters are 20px wide now instead of 10px wide it "updates when ::softWrapped changes on the editor", -> presenter = buildPresenter(contentFrameWidth: 470, baseCharacterWidth: 10) - expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 expectStateUpdate presenter, -> editor.setSoftWrapped(true) - expect(presenter.state.horizontalScrollbar.scrollWidth).toBe presenter.clientWidth + expect(presenter.getState().horizontalScrollbar.scrollWidth).toBe presenter.clientWidth expectStateUpdate presenter, -> editor.setSoftWrapped(false) - expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 it "updates when the longest line changes", -> presenter = buildPresenter(contentFrameWidth: 50, baseCharacterWidth: 10) - expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 expectStateUpdate presenter, -> editor.setCursorBufferPosition([editor.getLongestScreenRow(), 0]) expectStateUpdate presenter, -> editor.insertText('xyz') - expect(presenter.state.content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 + expect(presenter.getState().content.scrollWidth).toBe 10 * editor.getMaxScreenLineLength() + 1 describe ".scrollTop", -> it "tracks the value of ::scrollTop", -> presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 20) - expect(presenter.state.content.scrollTop).toBe 10 + expect(presenter.getState().content.scrollTop).toBe 10 expectStateUpdate presenter, -> presenter.setScrollTop(50) - expect(presenter.state.content.scrollTop).toBe 50 + expect(presenter.getState().content.scrollTop).toBe 50 it "never exceeds the computed scroll height minus the computed client height", -> presenter = buildPresenter(scrollTop: 10, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(100) - expect(presenter.state.content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> presenter.setExplicitHeight(60) - expect(presenter.state.content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> presenter.setHorizontalScrollbarHeight(5) - expect(presenter.state.content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> editor.getBuffer().delete([[8, 0], [12, 0]]) - expect(presenter.state.content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().content.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight # Scroll top only gets smaller when needed as dimensions change, never bigger - scrollTopBefore = presenter.state.verticalScrollbar.scrollTop + scrollTopBefore = presenter.getState().verticalScrollbar.scrollTop expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n') - expect(presenter.state.content.scrollTop).toBe scrollTopBefore + expect(presenter.getState().content.scrollTop).toBe scrollTopBefore it "never goes negative", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(-100) - expect(presenter.state.content.scrollTop).toBe 0 + expect(presenter.getState().content.scrollTop).toBe 0 it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight + expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight atom.config.set("editor.scrollPastEnd", true) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.content.scrollTop).toBe presenter.contentHeight - (presenter.lineHeight * 3) + expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - (presenter.lineHeight * 3) expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) - expect(presenter.state.content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight + expect(presenter.getState().content.scrollTop).toBe presenter.contentHeight - presenter.clientHeight describe ".scrollLeft", -> it "tracks the value of ::scrollLeft", -> presenter = buildPresenter(scrollLeft: 10, lineHeight: 10, baseCharacterWidth: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) - expect(presenter.state.content.scrollLeft).toBe 10 + expect(presenter.getState().content.scrollLeft).toBe 10 expectStateUpdate presenter, -> presenter.setScrollLeft(50) - expect(presenter.state.content.scrollLeft).toBe 50 + expect(presenter.getState().content.scrollLeft).toBe 50 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) - expect(presenter.state.content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> presenter.setContentFrameWidth(600) - expect(presenter.state.content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> presenter.setVerticalScrollbarWidth(5) - expect(presenter.state.content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth expectStateUpdate presenter, -> editor.getBuffer().delete([[6, 0], [6, Infinity]]) - expect(presenter.state.content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth + expect(presenter.getState().content.scrollLeft).toBe presenter.scrollWidth - presenter.clientWidth # Scroll top only gets smaller when needed as dimensions change, never bigger - scrollLeftBefore = presenter.state.content.scrollLeft + scrollLeftBefore = presenter.getState().content.scrollLeft expectStateUpdate presenter, -> editor.getBuffer().insert([6, 0], new Array(100).join('x')) - expect(presenter.state.content.scrollLeft).toBe scrollLeftBefore + expect(presenter.getState().content.scrollLeft).toBe scrollLeftBefore it "never goes negative", -> presenter = buildPresenter(scrollLeft: 10, verticalScrollbarWidth: 10, contentFrameWidth: 500) expectStateUpdate presenter, -> presenter.setScrollLeft(-300) - expect(presenter.state.content.scrollLeft).toBe 0 + expect(presenter.getState().content.scrollLeft).toBe 0 describe ".indentGuidesVisible", -> it "is initialized based on the editor.showIndentGuide config setting", -> presenter = buildPresenter() - expect(presenter.state.content.indentGuidesVisible).toBe false + expect(presenter.getState().content.indentGuidesVisible).toBe false atom.config.set('editor.showIndentGuide', true) presenter = buildPresenter() - expect(presenter.state.content.indentGuidesVisible).toBe true + expect(presenter.getState().content.indentGuidesVisible).toBe true it "updates when the editor.showIndentGuide config setting changes", -> presenter = buildPresenter() - expect(presenter.state.content.indentGuidesVisible).toBe false + expect(presenter.getState().content.indentGuidesVisible).toBe false expectStateUpdate presenter, -> atom.config.set('editor.showIndentGuide', true) - expect(presenter.state.content.indentGuidesVisible).toBe true + expect(presenter.getState().content.indentGuidesVisible).toBe true expectStateUpdate presenter, -> atom.config.set('editor.showIndentGuide', false) - expect(presenter.state.content.indentGuidesVisible).toBe false + expect(presenter.getState().content.indentGuidesVisible).toBe false it "updates when the editor's grammar changes", -> atom.config.set('editor.showIndentGuide', true, scopeSelector: ".source.js") presenter = buildPresenter() - expect(presenter.state.content.indentGuidesVisible).toBe false + expect(presenter.getState().content.indentGuidesVisible).toBe false stateUpdated = false - presenter.onDidUpdateState -> stateUpdated = true + presenter.onNeedsUpdate -> stateUpdated = true waitsForPromise -> atom.packages.activatePackage('language-javascript') runs -> expect(stateUpdated).toBe true - expect(presenter.state.content.indentGuidesVisible).toBe true + expect(presenter.getState().content.indentGuidesVisible).toBe true expectStateUpdate presenter, -> editor.setGrammar(atom.grammars.selectGrammar('.txt')) - expect(presenter.state.content.indentGuidesVisible).toBe false + expect(presenter.getState().content.indentGuidesVisible).toBe false it "is always false when the editor is mini", -> atom.config.set('editor.showIndentGuide', true) editor.setMini(true) presenter = buildPresenter() - expect(presenter.state.content.indentGuidesVisible).toBe false + expect(presenter.getState().content.indentGuidesVisible).toBe false editor.setMini(false) - expect(presenter.state.content.indentGuidesVisible).toBe true + expect(presenter.getState().content.indentGuidesVisible).toBe true editor.setMini(true) - expect(presenter.state.content.indentGuidesVisible).toBe false + expect(presenter.getState().content.indentGuidesVisible).toBe false describe ".backgroundColor", -> it "is assigned to ::backgroundColor unless the editor is mini", -> presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)') - expect(presenter.state.content.backgroundColor).toBe '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)') - expect(presenter.state.content.backgroundColor).toBeNull() + expect(presenter.getState().content.backgroundColor).toBeNull() it "updates when ::backgroundColor changes", -> presenter = buildPresenter(backgroundColor: 'rgba(255, 0, 0, 0)') - expect(presenter.state.content.backgroundColor).toBe '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.state.content.backgroundColor).toBe '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)') - expect(presenter.state.content.backgroundColor).toBe 'rgba(255, 0, 0, 0)' + expect(presenter.getState().content.backgroundColor).toBe 'rgba(255, 0, 0, 0)' expectStateUpdate presenter, -> editor.setMini(true) - expect(presenter.state.content.backgroundColor).toBeNull() + expect(presenter.getState().content.backgroundColor).toBeNull() describe ".placeholderText", -> it "is present when the editor has no text", -> editor.setPlaceholderText("the-placeholder-text") presenter = buildPresenter() - expect(presenter.state.content.placeholderText).toBeNull() + expect(presenter.getState().content.placeholderText).toBeNull() expectStateUpdate presenter, -> editor.setText("") - expect(presenter.state.content.placeholderText).toBe "the-placeholder-text" + expect(presenter.getState().content.placeholderText).toBe "the-placeholder-text" expectStateUpdate presenter, -> editor.setPlaceholderText("new-placeholder-text") - expect(presenter.state.content.placeholderText).toBe "new-placeholder-text" + expect(presenter.getState().content.placeholderText).toBe "new-placeholder-text" describe ".lines", -> lineStateForScreenRow = (presenter, screenRow) -> - presenter.state.content.lines[presenter.model.tokenizedLineForScreenRow(screenRow).id] + presenter.getState().content.lines[presenter.model.tokenizedLineForScreenRow(screenRow).id] it "contains states for lines that are visible on screen, plus and minus the overdraw margin", -> presenter = buildPresenter(explicitHeight: 15, scrollTop: 50, lineHeight: 10, lineOverdrawMargin: 1) @@ -720,16 +716,16 @@ describe "TextEditorPresenter", -> it "is empty until all of the required measurements are assigned", -> presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null) - expect(presenter.state.content.lines).toEqual({}) + expect(presenter.getState().content.lines).toEqual({}) presenter.setExplicitHeight(25) - expect(presenter.state.content.lines).toEqual({}) + expect(presenter.getState().content.lines).toEqual({}) presenter.setLineHeight(10) - expect(presenter.state.content.lines).toEqual({}) + expect(presenter.getState().content.lines).toEqual({}) presenter.setScrollTop(0) - expect(presenter.state.content.lines).not.toEqual({}) + expect(presenter.getState().content.lines).not.toEqual({}) it "updates when ::scrollTop changes", -> presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1) @@ -836,8 +832,8 @@ describe "TextEditorPresenter", -> expectStateUpdate presenter, -> editor.getBuffer().insert([3, Infinity], 'xyz') newLine3 = editor.tokenizedLineForScreenRow(3) - expect(presenter.state.content.lines[oldLine3.id]).toBeUndefined() - expect(presenter.state.content.lines[newLine3.id]).toBeDefined() + expect(presenter.getState().content.lines[oldLine3.id]).toBeUndefined() + expect(presenter.getState().content.lines[newLine3.id]).toBeDefined() it "does not attempt to preserve lines corresponding to ::mouseWheelScreenRow if they have been deleted", -> presenter = buildPresenter(explicitHeight: 25, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 1, stoppedScrollingDelay: 200) @@ -984,7 +980,7 @@ describe "TextEditorPresenter", -> describe ".cursors", -> stateForCursor = (presenter, cursorIndex) -> - presenter.state.content.cursors[presenter.model.getCursors()[cursorIndex].id] + presenter.getState().content.cursors[presenter.model.getCursors()[cursorIndex].id] it "contains pixelRects for empty selections that are visible on screen", -> editor.setSelectedBufferRanges([ @@ -1004,22 +1000,22 @@ describe "TextEditorPresenter", -> it "is empty until all of the required measurements are assigned", -> presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null, horizontalScrollbarHeight: null) - expect(presenter.state.content.cursors).toEqual({}) + expect(presenter.getState().content.cursors).toEqual({}) presenter.setExplicitHeight(25) - expect(presenter.state.content.cursors).toEqual({}) + expect(presenter.getState().content.cursors).toEqual({}) presenter.setLineHeight(10) - expect(presenter.state.content.cursors).toEqual({}) + expect(presenter.getState().content.cursors).toEqual({}) presenter.setScrollTop(0) - expect(presenter.state.content.cursors).toEqual({}) + expect(presenter.getState().content.cursors).toEqual({}) presenter.setBaseCharacterWidth(8) - expect(presenter.state.content.cursors).toEqual({}) + expect(presenter.getState().content.cursors).toEqual({}) presenter.setHorizontalScrollbarHeight(10) - expect(presenter.state.content.cursors).not.toEqual({}) + expect(presenter.getState().content.cursors).not.toEqual({}) it "updates when ::scrollTop changes", -> editor.setSelectedBufferRanges([ @@ -1128,7 +1124,7 @@ describe "TextEditorPresenter", -> # destroying destroyedCursor = editor.getCursors()[2] expectStateUpdate presenter, -> destroyedCursor.destroy() - expect(presenter.state.content.cursors[destroyedCursor.id]).toBeUndefined() + expect(presenter.getState().content.cursors[destroyedCursor.id]).toBeUndefined() it "makes cursors as wide as the ::baseCharacterWidth if they're at the end of a line", -> editor.setCursorBufferPosition([1, Infinity]) @@ -1141,61 +1137,61 @@ describe "TextEditorPresenter", -> cursorBlinkResumeDelay = 200 presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true}) - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> presenter.setFocused(false) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false expectStateUpdate presenter, -> presenter.setFocused(true) - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false it "stops alternating for ::cursorBlinkResumeDelay when a cursor moves or a cursor is added", -> cursorBlinkPeriod = 100 cursorBlinkResumeDelay = 200 presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true}) - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false expectStateUpdate presenter, -> editor.moveRight() - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkResumeDelay) advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false expectStateUpdate presenter, -> editor.addCursorAtBufferPosition([1, 0]) - expect(presenter.state.content.cursorsVisible).toBe true + expect(presenter.getState().content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkResumeDelay) advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.cursorsVisible).toBe false + expect(presenter.getState().content.cursorsVisible).toBe false describe ".highlights", -> stateForHighlight = (presenter, decoration) -> - presenter.state.content.highlights[decoration.id] + presenter.getState().content.highlights[decoration.id] stateForSelection = (presenter, selectionIndex) -> selection = presenter.model.getSelections()[selectionIndex] @@ -1286,19 +1282,19 @@ describe "TextEditorPresenter", -> ]) presenter = buildPresenter(explicitHeight: null, lineHeight: null, scrollTop: null, baseCharacterWidth: null) - expect(presenter.state.content.highlights).toEqual({}) + expect(presenter.getState().content.highlights).toEqual({}) presenter.setExplicitHeight(25) - expect(presenter.state.content.highlights).toEqual({}) + expect(presenter.getState().content.highlights).toEqual({}) presenter.setLineHeight(10) - expect(presenter.state.content.highlights).toEqual({}) + expect(presenter.getState().content.highlights).toEqual({}) presenter.setScrollTop(0) - expect(presenter.state.content.highlights).toEqual({}) + expect(presenter.getState().content.highlights).toEqual({}) presenter.setBaseCharacterWidth(8) - expect(presenter.state.content.highlights).not.toEqual({}) + expect(presenter.getState().content.highlights).not.toEqual({}) it "does not include highlights for invalid markers", -> marker = editor.markBufferRange([[2, 2], [2, 4]], invalidate: 'touch') @@ -1481,7 +1477,7 @@ describe "TextEditorPresenter", -> describe ".overlays", -> stateForOverlay = (presenter, decoration) -> - presenter.state.content.overlays[decoration.id] + presenter.getState().content.overlays[decoration.id] it "contains state for overlay decorations both initially and when their markers move", -> item = {} @@ -1583,114 +1579,114 @@ describe "TextEditorPresenter", -> decoration = editor.decorateMarker(marker, {type: 'overlay', position: 'tail', item}) presenter = buildPresenter(baseCharacterWidth: null, lineHeight: null) - expect(presenter.state.content.overlays).toEqual({}) + expect(presenter.getState().content.overlays).toEqual({}) presenter.setBaseCharacterWidth(10) - expect(presenter.state.content.overlays).toEqual({}) + expect(presenter.getState().content.overlays).toEqual({}) presenter.setLineHeight(10) - expect(presenter.state.content.overlays).not.toEqual({}) + expect(presenter.getState().content.overlays).not.toEqual({}) describe ".gutter", -> describe ".scrollHeight", -> it "is initialized based on ::lineHeight, the number of lines, and ::explicitHeight", -> presenter = buildPresenter() - expect(presenter.state.gutter.scrollHeight).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().gutter.scrollHeight).toBe editor.getScreenLineCount() * 10 presenter = buildPresenter(explicitHeight: 500) - expect(presenter.state.gutter.scrollHeight).toBe 500 + expect(presenter.getState().gutter.scrollHeight).toBe 500 it "updates when the ::lineHeight changes", -> presenter = buildPresenter() expectStateUpdate presenter, -> presenter.setLineHeight(20) - expect(presenter.state.gutter.scrollHeight).toBe editor.getScreenLineCount() * 20 + expect(presenter.getState().gutter.scrollHeight).toBe editor.getScreenLineCount() * 20 it "updates when the line count changes", -> presenter = buildPresenter() expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") - expect(presenter.state.gutter.scrollHeight).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().gutter.scrollHeight).toBe editor.getScreenLineCount() * 10 it "updates when ::explicitHeight changes", -> presenter = buildPresenter() expectStateUpdate presenter, -> presenter.setExplicitHeight(500) - expect(presenter.state.gutter.scrollHeight).toBe 500 + expect(presenter.getState().gutter.scrollHeight).toBe 500 it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.gutter.scrollHeight).toBe presenter.contentHeight + expect(presenter.getState().gutter.scrollHeight).toBe presenter.contentHeight expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", true) - expect(presenter.state.gutter.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) + expect(presenter.getState().gutter.scrollHeight).toBe presenter.contentHeight + presenter.clientHeight - (presenter.lineHeight * 3) expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) - expect(presenter.state.gutter.scrollHeight).toBe presenter.contentHeight + expect(presenter.getState().gutter.scrollHeight).toBe presenter.contentHeight describe ".scrollTop", -> it "tracks the value of ::scrollTop", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 20) - expect(presenter.state.gutter.scrollTop).toBe 10 + expect(presenter.getState().gutter.scrollTop).toBe 10 expectStateUpdate presenter, -> presenter.setScrollTop(50) - expect(presenter.state.gutter.scrollTop).toBe 50 + expect(presenter.getState().gutter.scrollTop).toBe 50 it "never exceeds the computed scrollHeight minus the computed clientHeight", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(100) - expect(presenter.state.gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> presenter.setExplicitHeight(60) - expect(presenter.state.gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> presenter.setHorizontalScrollbarHeight(5) - expect(presenter.state.gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight expectStateUpdate presenter, -> editor.getBuffer().delete([[8, 0], [12, 0]]) - expect(presenter.state.gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight + expect(presenter.getState().gutter.scrollTop).toBe presenter.scrollHeight - presenter.clientHeight # Scroll top only gets smaller when needed as dimensions change, never bigger - scrollTopBefore = presenter.state.verticalScrollbar.scrollTop + scrollTopBefore = presenter.getState().verticalScrollbar.scrollTop expectStateUpdate presenter, -> editor.getBuffer().insert([9, Infinity], '\n\n\n') - expect(presenter.state.gutter.scrollTop).toBe scrollTopBefore + expect(presenter.getState().gutter.scrollTop).toBe scrollTopBefore it "never goes negative", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(-100) - expect(presenter.state.gutter.scrollTop).toBe 0 + expect(presenter.getState().gutter.scrollTop).toBe 0 it "adds the computed clientHeight to the computed scrollHeight if editor.scrollPastEnd is true", -> presenter = buildPresenter(scrollTop: 10, explicitHeight: 50, horizontalScrollbarHeight: 10) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.gutter.scrollTop).toBe presenter.contentHeight - presenter.clientHeight + expect(presenter.getState().gutter.scrollTop).toBe presenter.contentHeight - presenter.clientHeight atom.config.set("editor.scrollPastEnd", true) expectStateUpdate presenter, -> presenter.setScrollTop(300) - expect(presenter.state.gutter.scrollTop).toBe presenter.contentHeight - (presenter.lineHeight * 3) + expect(presenter.getState().gutter.scrollTop).toBe presenter.contentHeight - (presenter.lineHeight * 3) expectStateUpdate presenter, -> atom.config.set("editor.scrollPastEnd", false) - expect(presenter.state.gutter.scrollTop).toBe presenter.contentHeight - presenter.clientHeight + expect(presenter.getState().gutter.scrollTop).toBe presenter.contentHeight - presenter.clientHeight 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)") - expect(presenter.state.gutter.backgroundColor).toBe "rgba(0, 255, 0, 0)" + expect(presenter.getState().gutter.backgroundColor).toBe "rgba(0, 255, 0, 0)" expectStateUpdate presenter, -> presenter.setGutterBackgroundColor("rgba(0, 0, 255, 0)") - expect(presenter.state.gutter.backgroundColor).toBe "rgba(0, 0, 255, 0)" + expect(presenter.getState().gutter.backgroundColor).toBe "rgba(0, 0, 255, 0)" expectStateUpdate presenter, -> presenter.setGutterBackgroundColor("rgba(0, 0, 0, 0)") - expect(presenter.state.gutter.backgroundColor).toBe "rgba(255, 0, 0, 0)" + expect(presenter.getState().gutter.backgroundColor).toBe "rgba(255, 0, 0, 0)" expectStateUpdate presenter, -> presenter.setBackgroundColor("rgba(0, 0, 255, 0)") - expect(presenter.state.gutter.backgroundColor).toBe "rgba(0, 0, 255, 0)" + expect(presenter.getState().gutter.backgroundColor).toBe "rgba(0, 0, 255, 0)" describe ".maxLineNumberDigits", -> it "is set to the number of digits used by the greatest line number", -> presenter = buildPresenter() expect(editor.getLastBufferRow()).toBe 12 - expect(presenter.state.gutter.maxLineNumberDigits).toBe 2 + expect(presenter.getState().gutter.maxLineNumberDigits).toBe 2 editor.setText("1\n2\n3") - expect(presenter.state.gutter.maxLineNumberDigits).toBe 1 + expect(presenter.getState().gutter.maxLineNumberDigits).toBe 1 describe ".lineNumbers", -> lineNumberStateForScreenRow = (presenter, screenRow) -> @@ -1702,7 +1698,7 @@ describe "TextEditorPresenter", -> else key = bufferRow - presenter.state.gutter.lineNumbers[key] + presenter.getState().gutter.lineNumbers[key] it "contains states for line numbers that are visible on screen, plus and minus the overdraw margin", -> editor.foldBufferRow(4) @@ -1994,62 +1990,62 @@ describe "TextEditorPresenter", -> presenter = buildPresenter() expect(editor.isGutterVisible()).toBe true - expect(presenter.state.gutter.visible).toBe true + expect(presenter.getState().gutter.visible).toBe true expectStateUpdate presenter, -> editor.setMini(true) - expect(presenter.state.gutter.visible).toBe false + expect(presenter.getState().gutter.visible).toBe false expectStateUpdate presenter, -> editor.setMini(false) - expect(presenter.state.gutter.visible).toBe true + expect(presenter.getState().gutter.visible).toBe true expectStateUpdate presenter, -> editor.setGutterVisible(false) - expect(presenter.state.gutter.visible).toBe false + expect(presenter.getState().gutter.visible).toBe false expectStateUpdate presenter, -> editor.setGutterVisible(true) - expect(presenter.state.gutter.visible).toBe true + expect(presenter.getState().gutter.visible).toBe true expectStateUpdate presenter, -> atom.config.set('editor.showLineNumbers', false) - expect(presenter.state.gutter.visible).toBe false + expect(presenter.getState().gutter.visible).toBe false it "updates when the editor's grammar changes", -> presenter = buildPresenter() atom.config.set('editor.showLineNumbers', false, scopeSelector: '.source.js') - expect(presenter.state.gutter.visible).toBe true + expect(presenter.getState().gutter.visible).toBe true stateUpdated = false - presenter.onDidUpdateState -> stateUpdated = true + presenter.onNeedsUpdate -> stateUpdated = true waitsForPromise -> atom.packages.activatePackage('language-javascript') runs -> expect(stateUpdated).toBe true - expect(presenter.state.gutter.visible).toBe false + expect(presenter.getState().gutter.visible).toBe false describe ".height", -> it "tracks the computed content height if ::autoHeight is true so the editor auto-expands vertically", -> presenter = buildPresenter(explicitHeight: null, autoHeight: true) - expect(presenter.state.height).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().height).toBe editor.getScreenLineCount() * 10 expectStateUpdate presenter, -> presenter.setAutoHeight(false) - expect(presenter.state.height).toBe null + expect(presenter.getState().height).toBe null expectStateUpdate presenter, -> presenter.setAutoHeight(true) - expect(presenter.state.height).toBe editor.getScreenLineCount() * 10 + expect(presenter.getState().height).toBe editor.getScreenLineCount() * 10 expectStateUpdate presenter, -> presenter.setLineHeight(20) - expect(presenter.state.height).toBe editor.getScreenLineCount() * 20 + expect(presenter.getState().height).toBe editor.getScreenLineCount() * 20 expectStateUpdate presenter, -> editor.getBuffer().append("\n\n\n") - expect(presenter.state.height).toBe editor.getScreenLineCount() * 20 + expect(presenter.getState().height).toBe editor.getScreenLineCount() * 20 describe ".focused", -> it "tracks the value of ::focused", -> presenter = buildPresenter(focused: false) - expect(presenter.state.focused).toBe false + expect(presenter.getState().focused).toBe false expectStateUpdate presenter, -> presenter.setFocused(true) - expect(presenter.state.focused).toBe true + expect(presenter.getState().focused).toBe true expectStateUpdate presenter, -> presenter.setFocused(false) - expect(presenter.state.focused).toBe false + expect(presenter.getState().focused).toBe false # disabled until we fix an issue with display buffer markers not updating when # they are moved on screen but not in the buffer @@ -2106,7 +2102,7 @@ describe "TextEditorPresenter", -> expectValidState = -> presenterParams.scrollTop = presenter.scrollTop presenterParams.scrollLeft = presenter.scrollLeft - actualState = presenter.state + actualState = presenter.getState() expectedState = new TextEditorPresenter(presenterParams).state delete actualState.content.scrollingVertically delete expectedState.content.scrollingVertically diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index ae844bfe0..f4f5d749f 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -2,14 +2,13 @@ module.exports = class CursorsComponent oldState: null - constructor: (@presenter) -> + constructor: -> @cursorNodesById = {} @domNode = document.createElement('div') @domNode.classList.add('cursors') - @updateSync() - updateSync: -> - newState = @presenter.state.content + updateSync: (state) -> + newState = state.content @oldState ?= {cursors: {}} # update blink class diff --git a/src/gutter-component.coffee b/src/gutter-component.coffee index 3209fe806..182a187b0 100644 --- a/src/gutter-component.coffee +++ b/src/gutter-component.coffee @@ -18,10 +18,8 @@ class GutterComponent @domNode.addEventListener 'click', @onClick @domNode.addEventListener 'mousedown', @onMouseDown - @updateSync() - - updateSync: -> - @newState = @presenter.state.gutter + updateSync: (state) -> + @newState = state.gutter @oldState ?= {lineNumbers: {}} @appendDummyLineNumber() unless @dummyLineNumberNode? diff --git a/src/highlights-component.coffee b/src/highlights-component.coffee index 48d2cc70e..3bd5197fe 100644 --- a/src/highlights-component.coffee +++ b/src/highlights-component.coffee @@ -5,7 +5,7 @@ module.exports = class HighlightsComponent oldState: null - constructor: (@presenter) -> + constructor: -> @highlightNodesById = {} @regionNodesByHighlightId = {} @@ -17,8 +17,8 @@ class HighlightsComponent insertionPoint.setAttribute('select', '.underlayer') @domNode.appendChild(insertionPoint) - updateSync: -> - newState = @presenter.state.content.highlights + updateSync: (state) -> + newState = state.content.highlights @oldState ?= {} # remove highlights diff --git a/src/input-component.coffee b/src/input-component.coffee index 2037fbe48..f6f8917f6 100644 --- a/src/input-component.coffee +++ b/src/input-component.coffee @@ -1,16 +1,15 @@ module.exports = class InputComponent - constructor: (@presenter) -> + constructor: -> @domNode = document.createElement('input') @domNode.classList.add('hidden-input') @domNode.setAttribute('data-react-skip-selection-restoration', true) @domNode.style['-webkit-transform'] = 'translateZ(0)' @domNode.addEventListener 'paste', (event) -> event.preventDefault() - @updateSync() - updateSync: -> + updateSync: (state) -> @oldState ?= {} - newState = @presenter.state.hiddenInput + newState = state.hiddenInput if newState.top isnt @oldState.top @domNode.style.top = newState.top + 'px' diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 9619a175c..fa4ab23b0 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -42,15 +42,13 @@ class LinesComponent insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', 'atom-overlay') - @overlayManager = new OverlayManager(@hostElement) + @overlayManager = new OverlayManager(@presenter, @hostElement) @domNode.appendChild(insertionPoint) else - @overlayManager = new OverlayManager(@domNode) + @overlayManager = new OverlayManager(@presenter, @domNode) - @updateSync(visible) - - updateSync: -> - @newState = @presenter.state.content + updateSync: (state) -> + @newState = state.content @oldState ?= {lines: {}} if @newState.scrollHeight isnt @oldState.scrollHeight @@ -81,10 +79,10 @@ class LinesComponent @domNode.style.width = @newState.scrollWidth + 'px' @oldState.scrollWidth = @newState.scrollWidth - @cursorsComponent.updateSync() - @highlightsComponent.updateSync() + @cursorsComponent.updateSync(state) + @highlightsComponent.updateSync(state) - @overlayManager?.render(@presenter) + @overlayManager?.render(state) @oldState.indentGuidesVisible = @newState.indentGuidesVisible @oldState.scrollWidth = @newState.scrollWidth diff --git a/src/overlay-manager.coffee b/src/overlay-manager.coffee index 7d9c9d95d..c8b5da0e8 100644 --- a/src/overlay-manager.coffee +++ b/src/overlay-manager.coffee @@ -1,20 +1,20 @@ module.exports = class OverlayManager - constructor: (@container) -> + constructor: (@presenter, @container) -> @overlayNodesById = {} - render: (presenter) -> - for decorationId, {pixelPosition, item} of presenter.state.content.overlays - @renderOverlay(presenter, decorationId, item, pixelPosition) + render: (state) -> + for decorationId, {pixelPosition, item} of state.content.overlays + @renderOverlay(state, decorationId, item, pixelPosition) for id, overlayNode of @overlayNodesById - unless presenter.state.content.overlays.hasOwnProperty(id) + unless state.content.overlays.hasOwnProperty(id) delete @overlayNodesById[id] overlayNode.remove() return - renderOverlay: (presenter, decorationId, item, pixelPosition) -> + renderOverlay: (state, decorationId, item, pixelPosition) -> item = atom.views.getView(item) unless overlayNode = @overlayNodesById[decorationId] overlayNode = @overlayNodesById[decorationId] = document.createElement('atom-overlay') @@ -25,15 +25,15 @@ class OverlayManager itemHeight = item.offsetHeight - {scrollTop, scrollLeft} = presenter.state.content + {scrollTop, scrollLeft} = state.content left = pixelPosition.left - if left + itemWidth - scrollLeft > presenter.contentFrameWidth and left - itemWidth >= scrollLeft + if left + itemWidth - scrollLeft > @presenter.contentFrameWidth and left - itemWidth >= scrollLeft left -= itemWidth - top = pixelPosition.top + presenter.lineHeight - if top + itemHeight - scrollTop > presenter.height and top - itemHeight - presenter.lineHeight >= scrollTop - top -= itemHeight + presenter.lineHeight + top = pixelPosition.top + @presenter.lineHeight + if top + itemHeight - scrollTop > @presenter.height and top - itemHeight - @presenter.lineHeight >= scrollTop + top -= itemHeight + @presenter.lineHeight overlayNode.style.top = top + 'px' overlayNode.style.left = left + 'px' diff --git a/src/scrollbar-component.coffee b/src/scrollbar-component.coffee index 3e94a0708..3a63a33ed 100644 --- a/src/scrollbar-component.coffee +++ b/src/scrollbar-component.coffee @@ -1,6 +1,6 @@ module.exports = class ScrollbarComponent - constructor: ({@presenter, @orientation, @onScroll}) -> + constructor: ({@orientation, @onScroll}) -> @domNode = document.createElement('div') @domNode.classList.add "#{@orientation}-scrollbar" @domNode.style['-webkit-transform'] = 'translateZ(0)' # See atom/atom#3559 @@ -12,16 +12,14 @@ class ScrollbarComponent @domNode.addEventListener 'scroll', @onScrollCallback - @updateSync() - - updateSync: -> + updateSync: (state) -> @oldState ?= {} switch @orientation when 'vertical' - @newState = @presenter.state.verticalScrollbar + @newState = state.verticalScrollbar @updateVertical() when 'horizontal' - @newState = @presenter.state.horizontalScrollbar + @newState = state.horizontalScrollbar @updateHorizontal() if @newState.visible isnt @oldState.visible diff --git a/src/scrollbar-corner-component.coffee b/src/scrollbar-corner-component.coffee index 1a266afc5..c0fbdfd60 100644 --- a/src/scrollbar-corner-component.coffee +++ b/src/scrollbar-corner-component.coffee @@ -1,20 +1,18 @@ module.exports = class ScrollbarCornerComponent - constructor: (@presenter) -> + constructor: () -> @domNode = document.createElement('div') @domNode.classList.add('scrollbar-corner') @contentNode = document.createElement('div') @domNode.appendChild(@contentNode) - @updateSync() - - updateSync: -> + updateSync: (state) -> @oldState ?= {} @newState ?= {} - newHorizontalState = @presenter.state.horizontalScrollbar - newVerticalState = @presenter.state.verticalScrollbar + newHorizontalState = state.horizontalScrollbar + newVerticalState = state.verticalScrollbar @newState.visible = newHorizontalState.visible and newVerticalState.visible @newState.height = newHorizontalState.height @newState.width = newVerticalState.width diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 5e597a3fb..4c01cde56 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -64,21 +64,21 @@ class TextEditorComponent @scrollViewNode.classList.add('scroll-view') @domNode.appendChild(@scrollViewNode) - @mountGutterComponent() if @presenter.state.gutter.visible + @mountGutterComponent() if @presenter.getState().gutter.visible - @hiddenInputComponent = new InputComponent(@presenter) + @hiddenInputComponent = new InputComponent @scrollViewNode.appendChild(@hiddenInputComponent.domNode) @linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM}) @scrollViewNode.appendChild(@linesComponent.domNode) - @horizontalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'horizontal', onScroll: @onHorizontalScroll}) + @horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll}) @scrollViewNode.appendChild(@horizontalScrollbarComponent.domNode) - @verticalScrollbarComponent = new ScrollbarComponent({@presenter, orientation: 'vertical', onScroll: @onVerticalScroll}) + @verticalScrollbarComponent = new ScrollbarComponent({orientation: 'vertical', onScroll: @onVerticalScroll}) @domNode.appendChild(@verticalScrollbarComponent.domNode) - @scrollbarCornerComponent = new ScrollbarCornerComponent(@presenter) + @scrollbarCornerComponent = new ScrollbarCornerComponent @domNode.appendChild(@scrollbarCornerComponent.domNode) @observeEditor() @@ -103,10 +103,8 @@ class TextEditorComponent window.removeEventListener 'resize', @requestHeightAndWidthMeasurement updateSync: -> - @presenter.update() - @oldState ?= {} - @newState = @presenter.state + @newState = @presenter.getState() cursorMoved = @cursorMoved selectionChanged = @selectionChanged @@ -130,18 +128,18 @@ class TextEditorComponent else @domNode.style.height = '' - if @presenter.state.gutter.visible + if @newState.gutter.visible @mountGutterComponent() unless @gutterComponent? - @gutterComponent.updateSync() + @gutterComponent.updateSync(@newState) else @gutterComponent?.domNode?.remove() @gutterComponent = null - @hiddenInputComponent.updateSync() - @linesComponent.updateSync() - @horizontalScrollbarComponent.updateSync() - @verticalScrollbarComponent.updateSync() - @scrollbarCornerComponent.updateSync() + @hiddenInputComponent.updateSync(@newState) + @linesComponent.updateSync(@newState) + @horizontalScrollbarComponent.updateSync(@newState) + @verticalScrollbarComponent.updateSync(@newState) + @scrollbarCornerComponent.updateSync(@newState) if @editor.isAlive() @updateParentViewFocusedClassIfNeeded() @@ -154,7 +152,7 @@ class TextEditorComponent @linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically mountGutterComponent: -> - @gutterComponent = new GutterComponent({@presenter, @editor, onMouseDown: @onGutterMouseDown}) + @gutterComponent = new GutterComponent({@editor, onMouseDown: @onGutterMouseDown}) @domNode.insertBefore(@gutterComponent.domNode, @domNode.firstChild) becameVisible: -> diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 6e4c8e928..b7ba41419 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -53,8 +53,7 @@ class TextEditorPresenter isBatching: -> @updating == false - # Updates the state, applying only those changes that occurred between this call and a previous call to this method. - update: -> + getState: -> @updating = true @updateFocusedState() if @shouldUpdateFocusedState @@ -87,6 +86,8 @@ class TextEditorPresenter @updating = false + @state + observeModel: -> @disposables.add @model.onDidChange => @updateContentDimensions() From 436322fc410e0781da2db8c3fb40ad3c0bb2a7fb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 28 Feb 2015 09:36:56 +0100 Subject: [PATCH 17/21] Call it onDidUpdateState --- spec/text-editor-presenter-spec.coffee | 6 +-- src/text-editor-component.coffee | 2 +- src/text-editor-presenter.coffee | 54 +++++++++++++------------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 662b9d483..53dc41814 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -45,7 +45,7 @@ describe "TextEditorPresenter", -> expectStateUpdate = (presenter, fn) -> updatedState = false - disposable = presenter.onNeedsUpdate -> + disposable = presenter.onDidUpdateState -> updatedState = true disposable.dispose() fn() @@ -586,7 +586,7 @@ describe "TextEditorPresenter", -> expect(presenter.getState().content.indentGuidesVisible).toBe false stateUpdated = false - presenter.onNeedsUpdate -> stateUpdated = true + presenter.onDidUpdateState -> stateUpdated = true waitsForPromise -> atom.packages.activatePackage('language-javascript') @@ -2013,7 +2013,7 @@ describe "TextEditorPresenter", -> atom.config.set('editor.showLineNumbers', false, scopeSelector: '.source.js') expect(presenter.getState().gutter.visible).toBe true stateUpdated = false - presenter.onNeedsUpdate -> stateUpdated = true + presenter.onDidUpdateState -> stateUpdated = true waitsForPromise -> atom.packages.activatePackage('language-javascript') diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 4c01cde56..9536bcaad 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -52,7 +52,7 @@ class TextEditorComponent cursorBlinkResumeDelay: @cursorBlinkResumeDelay stoppedScrollingDelay: 200 - @presenter.onNeedsUpdate(@requestUpdate) + @presenter.onDidUpdateState(@requestUpdate) @domNode = document.createElement('div') if @useShadowDOM diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index b7ba41419..dbefad1fa 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -31,12 +31,12 @@ class TextEditorPresenter destroy: -> @disposables.dispose() - # Calls your `callback` when some changes in the model occurred and the current state needs to be updated. - onNeedsUpdate: (callback) -> - @emitter.on 'needs-update', callback + # Calls your `callback` when some changes in the model occurred and the current state has been updated. + onDidUpdateState: (callback) -> + @emitter.on 'did-update-state', callback - needsUpdate: -> - @emitter.emit "needs-update" if @isBatching() + emitDidUpdateState: -> + @emitter.emit "did-update-state" if @isBatching() transferMeasurementsToModel: -> @model.setHeight(@explicitHeight) if @explicitHeight? @@ -192,7 +192,7 @@ class TextEditorPresenter else @state.focused = @focused - @needsUpdate() + @emitDidUpdateState() updateHeightState: -> if @isBatching() @@ -203,7 +203,7 @@ class TextEditorPresenter else @state.height = null - @needsUpdate() + @emitDidUpdateState() updateVerticalScrollState: -> if @isBatching() @@ -217,7 +217,7 @@ class TextEditorPresenter @state.gutter.scrollTop = @scrollTop @state.verticalScrollbar.scrollTop = @scrollTop - @needsUpdate() + @emitDidUpdateState() updateHorizontalScrollState: -> if @isBatching() @@ -229,7 +229,7 @@ class TextEditorPresenter @state.content.scrollLeft = @scrollLeft @state.horizontalScrollbar.scrollLeft = @scrollLeft - @needsUpdate() + @emitDidUpdateState() updateScrollbarsState: -> if @isBatching() @@ -243,7 +243,7 @@ class TextEditorPresenter @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth @state.verticalScrollbar.bottom = @horizontalScrollbarHeight - @needsUpdate() + @emitDidUpdateState() updateHiddenInputState: -> if @isBatching() @@ -265,7 +265,7 @@ class TextEditorPresenter @state.hiddenInput.height = height @state.hiddenInput.width = Math.max(width, 2) - @needsUpdate() + @emitDidUpdateState() updateContentState: -> if @isBatching() @@ -277,7 +277,7 @@ class TextEditorPresenter @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null - @needsUpdate() + @emitDidUpdateState() updateLinesState: -> if @isBatching() @@ -307,7 +307,7 @@ class TextEditorPresenter unless visibleLineIds.hasOwnProperty(id) delete @state.content.lines[id] - @needsUpdate() + @emitDidUpdateState() updateLineState: (row, line) -> lineState = @state.content.lines[line.id] @@ -335,7 +335,7 @@ class TextEditorPresenter @state.content.cursors = {} @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation - @needsUpdate() + @emitDidUpdateState() updateCursorState: (cursor, destroyOnly = false) -> delete @state.content.cursors[cursor.id] @@ -348,7 +348,7 @@ class TextEditorPresenter pixelRect.width = @baseCharacterWidth if pixelRect.width is 0 @state.content.cursors[cursor.id] = pixelRect - @needsUpdate() + @emitDidUpdateState() updateOverlaysState: -> if @isBatching() @@ -374,7 +374,7 @@ class TextEditorPresenter for id of @state.content.overlays delete @state.content.overlays[id] unless visibleDecorationIds[id] - @needsUpdate() + @emitDidUpdateState() updateGutterState: -> if @isBatching() @@ -387,7 +387,7 @@ class TextEditorPresenter else @backgroundColor - @needsUpdate() + @emitDidUpdateState() updateLineNumbersState: -> if @isBatching() @@ -435,7 +435,7 @@ class TextEditorPresenter for id of @state.gutter.lineNumbers delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] - @needsUpdate() + @emitDidUpdateState() updateStartRow: -> return unless @scrollTop? and @lineHeight? @@ -622,7 +622,7 @@ class TextEditorPresenter @stoppedScrollingTimeoutId = null @stoppedScrollingTimeoutId = setTimeout(@didStopScrolling.bind(this), @stoppedScrollingDelay) @state.content.scrollingVertically = true - @needsUpdate() + @emitDidUpdateState() didStopScrolling: -> @state.content.scrollingVertically = false @@ -631,7 +631,7 @@ class TextEditorPresenter @updateLinesState() @updateLineNumbersState() else - @needsUpdate() + @emitDidUpdateState() setScrollLeft: (scrollLeft) -> scrollLeft = @constrainScrollLeft(scrollLeft) @@ -895,7 +895,7 @@ class TextEditorPresenter decorationState.flashCount++ decorationState.flashClass = flash.class decorationState.flashDuration = flash.duration - @needsUpdate() + @emitDidUpdateState() didAddDecoration: (decoration) -> @observeDecoration(decoration) @@ -932,7 +932,7 @@ class TextEditorPresenter unless visibleHighlights[id] delete @state.content.highlights[id] - @needsUpdate() + @emitDidUpdateState() removeFromLineDecorationCaches: (decoration, range) -> @@ -973,7 +973,7 @@ class TextEditorPresenter if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1) delete @state.content.highlights[decoration.id] - @needsUpdate() + @emitDidUpdateState() return if range.start.row < @startRow @@ -985,7 +985,7 @@ class TextEditorPresenter if range.isEmpty() delete @state.content.highlights[decoration.id] - @needsUpdate() + @emitDidUpdateState() return highlightState = @state.content.highlights[decoration.id] ?= { @@ -996,7 +996,7 @@ class TextEditorPresenter highlightState.class = properties.class highlightState.deprecatedRegionClass = properties.deprecatedRegionClass highlightState.regions = @buildHighlightRegions(range) - @needsUpdate() + @emitDidUpdateState() true @@ -1083,10 +1083,10 @@ class TextEditorPresenter toggleCursorBlink: -> @state.content.cursorsVisible = not @state.content.cursorsVisible - @needsUpdate() + @emitDidUpdateState() pauseCursorBlinking: -> @stopBlinkingCursors(true) @startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay()) @startBlinkingCursorsAfterDelay() - @needsUpdate() + @emitDidUpdateState() From 78a1a724db7ae770a29bf93465069f582869fe46 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 28 Feb 2015 09:55:49 +0100 Subject: [PATCH 18/21] :art: More expressive specs --- spec/text-editor-presenter-spec.coffee | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 53dc41814..4ad319cc1 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -8,7 +8,7 @@ TextEditorPresenter = require '../src/text-editor-presenter' 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 "::getState()", -> [buffer, editor] = [] beforeEach -> @@ -43,13 +43,22 @@ describe "TextEditorPresenter", -> for key, value of expected expect(actual[key]).toEqual value - expectStateUpdate = (presenter, fn) -> + expectStateUpdatedToBe = (value, presenter, fn) -> updatedState = false disposable = presenter.onDidUpdateState -> updatedState = true disposable.dispose() fn() - expect(updatedState).toBe true + expect(updatedState).toBe(value) + + expectStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(true, presenter, fn) + + expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn) + + describe "during state retrieval", -> + it "does not trigger onDidUpdateState events", -> + presenter = buildPresenter() + expectNoStateUpdate presenter, -> presenter.getState() describe ".horizontalScrollbar", -> describe ".visible", -> From ac01d3ec45e71a2f72adee5698ddc0640955bd67 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 28 Feb 2015 10:10:42 +0100 Subject: [PATCH 19/21] Remove newline --- src/text-editor-presenter.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index dbefad1fa..9d809e4cd 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -567,7 +567,6 @@ class TextEditorPresenter @verticalScrollbarWidth = verticalScrollbarWidth @updateClientWidth() - lineDecorationClassesForRow: (row) -> return null if @model.isMini() From d05fe8a7d8dbe1fdcc15e719f6f53abdbba13fdb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sat, 28 Feb 2015 12:31:46 +0100 Subject: [PATCH 20/21] :art: Remove repetitive batching code --- src/text-editor-presenter.coffee | 394 ++++++++++++++----------------- 1 file changed, 171 insertions(+), 223 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 9d809e4cd..156756feb 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -48,11 +48,24 @@ class TextEditorPresenter @model.setVerticalScrollbarWidth(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth? @model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight? - # Determines whether {TextEditorPresenter} is currently batching changes. + # Private: Determines whether {TextEditorPresenter} is currently batching changes. # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them. isBatching: -> @updating == false + # Private: Executes `fn` if `isBatching()` is false, otherwise sets `@[flagName]` to `true` for later processing. In either cases, it calls `emitDidUpdateState`. + # * `flagName` {String} name of a property of this presenter + # * `fn` {Function} to call when not batching. + batch: (flagName, fn) -> + if @isBatching() + @[flagName] = true + else + fn.apply(this) + + @emitDidUpdateState() + + # Public: Gets this presenter's state, updating it just in time before returning from this function. + # Returns a state {Object}, useful for rendering to screen. getState: -> @updating = true @@ -186,128 +199,88 @@ class TextEditorPresenter @updateGutterState() @updateLineNumbersState() - updateFocusedState: -> - if @isBatching() - @shouldUpdateFocusedState = true - else - @state.focused = @focused + updateFocusedState: -> @batch "shouldUpdateFocusedState", -> + @state.focused = @focused - @emitDidUpdateState() - - updateHeightState: -> - if @isBatching() - @shouldUpdateHeightState = true + updateHeightState: -> @batch "shouldUpdateHeightState", -> + if @autoHeight + @state.height = @contentHeight else - if @autoHeight - @state.height = @contentHeight + @state.height = null + + updateVerticalScrollState: -> @batch "shouldUpdateVerticalScrollState", -> + @state.content.scrollHeight = @scrollHeight + @state.gutter.scrollHeight = @scrollHeight + @state.verticalScrollbar.scrollHeight = @scrollHeight + + @state.content.scrollTop = @scrollTop + @state.gutter.scrollTop = @scrollTop + @state.verticalScrollbar.scrollTop = @scrollTop + + updateHorizontalScrollState: -> @batch "shouldUpdateHorizontalScrollState", -> + @state.content.scrollWidth = @scrollWidth + @state.horizontalScrollbar.scrollWidth = @scrollWidth + + @state.content.scrollLeft = @scrollLeft + @state.horizontalScrollbar.scrollLeft = @scrollLeft + + updateScrollbarsState: -> @batch "shouldUpdateScrollbarsState", -> + @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 + @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight + @state.horizontalScrollbar.right = @verticalScrollbarWidth + + @state.verticalScrollbar.visible = @verticalScrollbarWidth > 0 + @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth + @state.verticalScrollbar.bottom = @horizontalScrollbarHeight + + updateHiddenInputState: -> @batch "shouldUpdateHiddenInputState", -> + return unless lastCursor = @model.getLastCursor() + + {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange()) + + if @focused + top -= @scrollTop + left -= @scrollLeft + @state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0) + @state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0) + else + @state.hiddenInput.top = 0 + @state.hiddenInput.left = 0 + + @state.hiddenInput.height = height + @state.hiddenInput.width = Math.max(width, 2) + + updateContentState: -> @batch "shouldUpdateContentState", -> + @state.content.scrollWidth = @scrollWidth + @state.content.scrollLeft = @scrollLeft + @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide + @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor + @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null + + updateLinesState: -> @batch "shouldUpdateLinesState", -> + return unless @startRow? and @endRow? and @lineHeight? + + visibleLineIds = {} + row = @startRow + while row < @endRow + line = @model.tokenizedLineForScreenRow(row) + unless line? + throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") + + visibleLineIds[line.id] = true + if @state.content.lines.hasOwnProperty(line.id) + @updateLineState(row, line) else - @state.height = null + @buildLineState(row, line) + row++ - @emitDidUpdateState() + if @mouseWheelScreenRow? + if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow) + visibleLineIds[preservedLine.id] = true - updateVerticalScrollState: -> - if @isBatching() - @shouldUpdateVerticalScrollState = true - else - @state.content.scrollHeight = @scrollHeight - @state.gutter.scrollHeight = @scrollHeight - @state.verticalScrollbar.scrollHeight = @scrollHeight - - @state.content.scrollTop = @scrollTop - @state.gutter.scrollTop = @scrollTop - @state.verticalScrollbar.scrollTop = @scrollTop - - @emitDidUpdateState() - - updateHorizontalScrollState: -> - if @isBatching() - @shouldUpdateHorizontalScrollState = true - else - @state.content.scrollWidth = @scrollWidth - @state.horizontalScrollbar.scrollWidth = @scrollWidth - - @state.content.scrollLeft = @scrollLeft - @state.horizontalScrollbar.scrollLeft = @scrollLeft - - @emitDidUpdateState() - - updateScrollbarsState: -> - if @isBatching() - @shouldUpdateScrollbarsState = true - else - @state.horizontalScrollbar.visible = @horizontalScrollbarHeight > 0 - @state.horizontalScrollbar.height = @measuredHorizontalScrollbarHeight - @state.horizontalScrollbar.right = @verticalScrollbarWidth - - @state.verticalScrollbar.visible = @verticalScrollbarWidth > 0 - @state.verticalScrollbar.width = @measuredVerticalScrollbarWidth - @state.verticalScrollbar.bottom = @horizontalScrollbarHeight - - @emitDidUpdateState() - - updateHiddenInputState: -> - if @isBatching() - @shouldUpdateHiddenInputState = true - else - return unless lastCursor = @model.getLastCursor() - - {top, left, height, width} = @pixelRectForScreenRange(lastCursor.getScreenRange()) - - if @focused - top -= @scrollTop - left -= @scrollLeft - @state.hiddenInput.top = Math.max(Math.min(top, @clientHeight - height), 0) - @state.hiddenInput.left = Math.max(Math.min(left, @clientWidth - width), 0) - else - @state.hiddenInput.top = 0 - @state.hiddenInput.left = 0 - - @state.hiddenInput.height = height - @state.hiddenInput.width = Math.max(width, 2) - - @emitDidUpdateState() - - updateContentState: -> - if @isBatching() - @shouldUpdateContentState = true - else - @state.content.scrollWidth = @scrollWidth - @state.content.scrollLeft = @scrollLeft - @state.content.indentGuidesVisible = not @model.isMini() and @showIndentGuide - @state.content.backgroundColor = if @model.isMini() then null else @backgroundColor - @state.content.placeholderText = if @model.isEmpty() then @model.getPlaceholderText() else null - - @emitDidUpdateState() - - updateLinesState: -> - if @isBatching() - @shouldUpdateLinesState = true - else - return unless @startRow? and @endRow? and @lineHeight? - - visibleLineIds = {} - row = @startRow - while row < @endRow - line = @model.tokenizedLineForScreenRow(row) - unless line? - throw new Error("No line exists for row #{row}. Last screen row: #{@model.getLastScreenRow()}") - - visibleLineIds[line.id] = true - if @state.content.lines.hasOwnProperty(line.id) - @updateLineState(row, line) - else - @buildLineState(row, line) - row++ - - if @mouseWheelScreenRow? - if preservedLine = @model.tokenizedLineForScreenRow(@mouseWheelScreenRow) - visibleLineIds[preservedLine.id] = true - - for id, line of @state.content.lines - unless visibleLineIds.hasOwnProperty(id) - delete @state.content.lines[id] - - @emitDidUpdateState() + for id, line of @state.content.lines + unless visibleLineIds.hasOwnProperty(id) + delete @state.content.lines[id] updateLineState: (row, line) -> lineState = @state.content.lines[line.id] @@ -328,14 +301,9 @@ class TextEditorPresenter top: row * @lineHeight decorationClasses: @lineDecorationClassesForRow(row) - updateCursorsState: -> - if @isBatching() - @shouldUpdateCursorsState = true - else - @state.content.cursors = {} - @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation - - @emitDidUpdateState() + updateCursorsState: -> @batch "shouldUpdateCursorsState", -> + @state.content.cursors = {} + @updateCursorState(cursor) for cursor in @model.cursors # using property directly to avoid allocation updateCursorState: (cursor, destroyOnly = false) -> delete @state.content.cursors[cursor.id] @@ -350,92 +318,77 @@ class TextEditorPresenter @emitDidUpdateState() - updateOverlaysState: -> - if @isBatching() - @shouldUpdateOverlaysState = true + updateOverlaysState: -> @batch "shouldUpdateOverlaysState", -> + return unless @hasPixelRectRequirements() + + visibleDecorationIds = {} + + for decoration in @model.getOverlayDecorations() + continue unless decoration.getMarker().isValid() + + {item, position} = decoration.getProperties() + if position is 'tail' + screenPosition = decoration.getMarker().getTailScreenPosition() + else + screenPosition = decoration.getMarker().getHeadScreenPosition() + + @state.content.overlays[decoration.id] ?= {item} + @state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition) + visibleDecorationIds[decoration.id] = true + + for id of @state.content.overlays + delete @state.content.overlays[id] unless visibleDecorationIds[id] + + updateGutterState: -> @batch "shouldUpdateGutterState", -> + @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers + @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length + @state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)" + @gutterBackgroundColor else - return unless @hasPixelRectRequirements() + @backgroundColor - visibleDecorationIds = {} + updateLineNumbersState: -> @batch "shouldUpdateLineNumbersState", -> + return unless @startRow? and @endRow? and @lineHeight? - for decoration in @model.getOverlayDecorations() - continue unless decoration.getMarker().isValid() + visibleLineNumberIds = {} - {item, position} = decoration.getProperties() - if position is 'tail' - screenPosition = decoration.getMarker().getTailScreenPosition() + if @startRow > 0 + rowBeforeStartRow = @startRow - 1 + lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow) + wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow) + else + lastBufferRow = null + wrapCount = 0 + + if @endRow > @startRow + for bufferRow, i in @model.bufferRowsForScreenRows(@startRow, @endRow - 1) + if bufferRow is lastBufferRow + wrapCount++ + id = bufferRow + '-' + wrapCount + softWrapped = true else - screenPosition = decoration.getMarker().getHeadScreenPosition() + id = bufferRow + wrapCount = 0 + lastBufferRow = bufferRow + softWrapped = false - @state.content.overlays[decoration.id] ?= {item} - @state.content.overlays[decoration.id].pixelPosition = @pixelPositionForScreenPosition(screenPosition) - visibleDecorationIds[decoration.id] = true + screenRow = @startRow + i + top = screenRow * @lineHeight + decorationClasses = @lineNumberDecorationClassesForRow(screenRow) + foldable = @model.isFoldableAtScreenRow(screenRow) - for id of @state.content.overlays - delete @state.content.overlays[id] unless visibleDecorationIds[id] - - @emitDidUpdateState() - - updateGutterState: -> - if @isBatching() - @shouldUpdateGutterState = true - else - @state.gutter.visible = not @model.isMini() and (@model.isGutterVisible() ? true) and @showLineNumbers - @state.gutter.maxLineNumberDigits = @model.getLineCount().toString().length - @state.gutter.backgroundColor = if @gutterBackgroundColor isnt "rgba(0, 0, 0, 0)" - @gutterBackgroundColor - else - @backgroundColor - - @emitDidUpdateState() - - updateLineNumbersState: -> - if @isBatching() - @shouldUpdateLineNumbersState = true - else - return unless @startRow? and @endRow? and @lineHeight? - - visibleLineNumberIds = {} - - if @startRow > 0 - rowBeforeStartRow = @startRow - 1 - lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow) - wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow) - else - lastBufferRow = null - wrapCount = 0 - - if @endRow > @startRow - for bufferRow, i in @model.bufferRowsForScreenRows(@startRow, @endRow - 1) - if bufferRow is lastBufferRow - wrapCount++ - id = bufferRow + '-' + wrapCount - softWrapped = true - else - id = bufferRow - wrapCount = 0 - lastBufferRow = bufferRow - softWrapped = false - - screenRow = @startRow + i - top = screenRow * @lineHeight - decorationClasses = @lineNumberDecorationClassesForRow(screenRow) - foldable = @model.isFoldableAtScreenRow(screenRow) - - @state.gutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} - visibleLineNumberIds[id] = true - - if @mouseWheelScreenRow? - bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow) - wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow) - id = bufferRow - id += '-' + wrapCount if wrapCount > 0 + @state.gutter.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable} visibleLineNumberIds[id] = true - for id of @state.gutter.lineNumbers - delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] + if @mouseWheelScreenRow? + bufferRow = @model.bufferRowForScreenRow(@mouseWheelScreenRow) + wrapCount = @mouseWheelScreenRow - @model.screenRowForBufferRow(bufferRow) + id = bufferRow + id += '-' + wrapCount if wrapCount > 0 + visibleLineNumberIds[id] = true - @emitDidUpdateState() + for id of @state.gutter.lineNumbers + delete @state.gutter.lineNumbers[id] unless visibleLineNumberIds[id] updateStartRow: -> return unless @scrollTop? and @lineHeight? @@ -908,30 +861,25 @@ class TextEditorPresenter else if decoration.isType('overlay') @updateOverlaysState() - updateDecorations: -> - if @isBatching() - @shouldUpdateDecorations = true - else - @lineDecorationsByScreenRow = {} - @lineNumberDecorationsByScreenRow = {} - @highlightDecorationsById = {} + updateDecorations: -> @batch "shouldUpdateDecorations", -> + @lineDecorationsByScreenRow = {} + @lineNumberDecorationsByScreenRow = {} + @highlightDecorationsById = {} - visibleHighlights = {} - return unless 0 <= @startRow <= @endRow <= Infinity + visibleHighlights = {} + return unless 0 <= @startRow <= @endRow <= Infinity - for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1) - range = @model.getMarker(markerId).getScreenRange() - for decoration in decorations - if decoration.isType('line') or decoration.isType('line-number') - @addToLineDecorationCaches(decoration, range) - else if decoration.isType('highlight') - visibleHighlights[decoration.id] = @updateHighlightState(decoration) + for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1) + range = @model.getMarker(markerId).getScreenRange() + for decoration in decorations + if decoration.isType('line') or decoration.isType('line-number') + @addToLineDecorationCaches(decoration, range) + else if decoration.isType('highlight') + visibleHighlights[decoration.id] = @updateHighlightState(decoration) - for id of @state.content.highlights - unless visibleHighlights[id] - delete @state.content.highlights[id] - - @emitDidUpdateState() + for id of @state.content.highlights + unless visibleHighlights[id] + delete @state.content.highlights[id] removeFromLineDecorationCaches: (decoration, range) -> From 1e1eae475836d3a576ca98c56de3dadbec2a6f49 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 28 Feb 2015 10:57:32 -0700 Subject: [PATCH 21/21] :art: --- src/text-editor-presenter.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 156756feb..70eef98a9 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -51,7 +51,7 @@ class TextEditorPresenter # Private: Determines whether {TextEditorPresenter} is currently batching changes. # Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them. isBatching: -> - @updating == false + @updating is false # Private: Executes `fn` if `isBatching()` is false, otherwise sets `@[flagName]` to `true` for later processing. In either cases, it calls `emitDidUpdateState`. # * `flagName` {String} name of a property of this presenter