From 224575fc46757e22f6206e64aa28a1b43e91177f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 29 May 2015 16:32:19 -0700 Subject: [PATCH 01/12] Always fall back on default lineOverdrawMargin --- src/text-editor-component.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-editor-component.coffee b/src/text-editor-component.coffee index 431966910..45a8a9720 100644 --- a/src/text-editor-component.coffee +++ b/src/text-editor-component.coffee @@ -47,7 +47,7 @@ class TextEditorComponent model: @editor scrollTop: @editor.getScrollTop() scrollLeft: @editor.getScrollLeft() - lineOverdrawMargin: lineOverdrawMargin + lineOverdrawMargin: @lineOverdrawMargin cursorBlinkPeriod: @cursorBlinkPeriod cursorBlinkResumeDelay: @cursorBlinkResumeDelay stoppedScrollingDelay: 200 From b59bec5f9db1314053a4704c359a0fda1ab3eb45 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 29 May 2015 17:18:48 -0700 Subject: [PATCH 02/12] Add Set::isEqual method in specs This allows us to use the .toEqual matcher to compare Set objects. --- spec/spec-helper.coffee | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 15874ff6f..5ba54a242 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -49,6 +49,25 @@ Object.defineProperty document, 'title', get: -> documentTitle set: (title) -> documentTitle = title +Set.prototype.jasmineToString = -> + result = "Set {" + first = true + @forEach (element) -> + result += ", " unless first + result += element.toString() + first = false + result + "}" + +Set.prototype.isEqual = (other) -> + if other instanceof Set + return false if @size isnt other.size + values = @values() + until (next = values.next()).done + return false unless other.has(next.value) + true + else + false + jasmine.getEnv().addEqualityTester(_.isEqual) # Use underscore's definition of equality for toEqual assertions if process.env.CI From 6f553f234cdedb6f6720e2fd767a9c8bfcbaf20f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 29 May 2015 17:21:23 -0700 Subject: [PATCH 03/12] Add TextEditor::observeMarkers, use it in presenter Signed-off-by: Nathan Sobo --- spec/display-buffer-spec.coffee | 44 +++++++++++++ src/display-buffer.coffee | 7 +++ src/marker-observation-window.coffee | 12 ++++ src/text-editor-presenter.coffee | 94 +++++++++++++++++----------- src/text-editor.coffee | 22 +++++++ 5 files changed, 141 insertions(+), 38 deletions(-) create mode 100644 src/marker-observation-window.coffee diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 140ca2f9e..4c48607a5 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1207,6 +1207,50 @@ describe "DisplayBuffer", -> expect(markerCreated1).toHaveBeenCalled() expect(markerCreated2).not.toHaveBeenCalled() + describe "::observeMarkers(callback)", -> + [observationWindow, events] = [] + + beforeEach -> + events = [] + observationWindow = displayBuffer.observeMarkers (event) -> events.push(event) + displayBuffer.unfoldBufferRow(4, 7) + + it "calls the callback when markers enter, leave, or move within the screen range", -> + expect(events).toHaveLength 0 + + observationWindow.setScreenRange([[0, 0], [4, 0]]) + expect(events).toHaveLength 0 + + marker1 = displayBuffer.markScreenPosition([4, 2]) + expect(events).toHaveLength 0 + + observationWindow.setScreenRange([[0, 0], [5, 0]]) + expect(events).toHaveLength 1 + expect(events[0]).toEqual { + insert: new Set([marker1.id]) + update: new Set + remove: new Set + } + + marker2 = displayBuffer.markScreenPosition([5, 2]) + expect(events).toHaveLength 1 + + observationWindow.setBufferRange([[1, 0], [6, 0]]) + expect(events).toHaveLength 2 + expect(events[1]).toEqual { + insert: new Set([marker2.id]) + update: new Set([marker1.id]) + remove: new Set + } + + marker1.destroy() + expect(events).toHaveLength 3 + expect(events[2]).toEqual { + insert: new Set + update: new Set + remove: new Set([marker1.id]) + } + describe "decorations", -> [marker, decoration, decorationProperties] = [] beforeEach -> diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index b2460addc..b361e416b 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -10,6 +10,7 @@ Model = require './model' Token = require './token' Decoration = require './decoration' Marker = require './marker' +MarkerObservationWindow = require './marker-observation-window' class BufferToScreenConversionError extends Error constructor: (@message, @metadata) -> @@ -950,6 +951,9 @@ class DisplayBuffer extends Model @emitter.emit 'did-remove-decoration', decoration delete @decorationsByMarkerId[marker.id] if decorations.length is 0 + decorationsForMarkerId: (markerId) -> + @decorationsByMarkerId[markerId] + # Retrieves a {Marker} based on its id. # # id - A {Number} representing a marker id @@ -1045,6 +1049,9 @@ class DisplayBuffer extends Model params = @translateToBufferMarkerParams(params) @buffer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id) + observeMarkers: (callback) -> + new MarkerObservationWindow(this, @buffer.observeMarkers(callback)) + translateToBufferMarkerParams: (params) -> bufferMarkerParams = {} for key, value of params diff --git a/src/marker-observation-window.coffee b/src/marker-observation-window.coffee new file mode 100644 index 000000000..aa7b71f69 --- /dev/null +++ b/src/marker-observation-window.coffee @@ -0,0 +1,12 @@ +module.exports = +class MarkerObservationWindow + constructor: (@displayBuffer, @bufferWindow) -> + + setScreenRange: (range) -> + @bufferWindow.setRange(@displayBuffer.bufferRangeForScreenRange(range)) + + setBufferRange: (range) -> + @bufferWindow.setRange(range) + + destroy: -> + @bufferWindow.destroy() diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 3aea57f29..e3e3ad5a7 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1,4 +1,4 @@ -{CompositeDisposable, Emitter} = require 'event-kit' +{CompositeDisposable, Disposable, Emitter} = require 'event-kit' {Point, Range} = require 'text-buffer' _ = require 'underscore-plus' Decoration = require './decoration' @@ -24,6 +24,11 @@ class TextEditorPresenter @disposables = new CompositeDisposable @emitter = new Emitter @characterWidthsByScope = {} + @rangesByDecorationId = {} + @lineDecorationsByScreenRow = {} + @lineNumberDecorationsByScreenRow = {} + @customGutterDecorationsByGutterNameAndScreenRow = {} + @highlightDecorationsById = {} @transferMeasurementsToModel() @observeModel() @observeConfig() @@ -121,6 +126,10 @@ class TextEditorPresenter @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() + + @markerObservationWindow = @model.observeMarkers(@markersInRangeDidChange.bind(this)) + @disposables.add new Disposable => @markerObservationWindow.destroy() + @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) @disposables.add @model.onDidChangePlaceholderText => @shouldUpdateContentState = true @@ -579,6 +588,7 @@ class TextEditorPresenter visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 endRow = startRow + visibleLinesCount + @lineOverdrawMargin @endRow = Math.min(@model.getScreenLineCount(), endRow) + @markerObservationWindow.setScreenRange(Range(Point(@startRow, 0), Point(@endRow, 0))) updateScrollWidth: -> return unless @contentWidth? and @clientWidth? @@ -1055,7 +1065,6 @@ class TextEditorPresenter observeDecoration: (decoration) -> decorationDisposables = new CompositeDisposable - decorationDisposables.add decoration.getMarker().onDidChange(@decorationMarkerDidChange.bind(this, decoration)) if decoration.isType('highlight') decorationDisposables.add decoration.onDidFlash(@highlightDidFlash.bind(this, decoration)) decorationDisposables.add decoration.onDidChangeProperties(@decorationPropertiesDidChange.bind(this, decoration)) @@ -1065,38 +1074,41 @@ class TextEditorPresenter @didDestroyDecoration(decoration) @disposables.add(decorationDisposables) - decorationMarkerDidChange: (decoration, change) -> - if decoration.isType('line') or decoration.isType('gutter') - return if change.textChanged - - intersectsVisibleRowRange = false - oldRange = new Range(change.oldTailScreenPosition, change.oldHeadScreenPosition) - newRange = new Range(change.newTailScreenPosition, change.newHeadScreenPosition) - - if oldRange.intersectsRowRange(@startRow, @endRow - 1) - @removeFromLineDecorationCaches(decoration, oldRange) - intersectsVisibleRowRange = true - - if newRange.intersectsRowRange(@startRow, @endRow - 1) - @addToLineDecorationCaches(decoration, newRange) - intersectsVisibleRowRange = true - - if intersectsVisibleRowRange - @shouldUpdateLinesState = true if decoration.isType('line') - if decoration.isType('line-number') - @shouldUpdateLineNumbersState = true - else if decoration.isType('gutter') - @shouldUpdateCustomGutterDecorationState = true + markersInRangeDidChange: (event) -> + event.insert.forEach (markerId) => + range = @model.getMarker(markerId).getScreenRange() + if decorations = @model.decorationsForMarkerId(markerId) + for decoration in decorations + @decorationMarkerDidChange(decoration) + if decoration.isType('line') or decoration.isType('gutter') + @addToLineDecorationCaches(decoration, range) + event.update.forEach (markerId) => + range = @model.getMarker(markerId).getScreenRange() + if decorations = @model.decorationsForMarkerId(markerId) + for decoration in decorations + @decorationMarkerDidChange(decoration) + if decoration.isType('line') or decoration.isType('gutter') + @removeFromLineDecorationCaches(decoration) + @addToLineDecorationCaches(decoration, range) + event.remove.forEach (markerId) => + if decorations = @model.decorationsForMarkerId(markerId) + for decoration in decorations + @decorationMarkerDidChange(decoration) + if decoration.isType('line') or decoration.isType('gutter') + @removeFromLineDecorationCaches(decoration) + @emitDidUpdateState() + decorationMarkerDidChange: (decoration) -> if decoration.isType('highlight') - return if change.textChanged - @updateHighlightState(decoration) - if decoration.isType('overlay') @shouldUpdateOverlaysState = true - - @emitDidUpdateState() + if decoration.isType('line') + @shouldUpdateLinesState = true + if decoration.isType('line-number') + @shouldUpdateLineNumbersState = true + else if decoration.isType('gutter') + @shouldUpdateCustomGutterDecorationState = true decorationPropertiesDidChange: (decoration, event) -> {oldProperties} = event @@ -1161,6 +1173,7 @@ class TextEditorPresenter @emitDidUpdateState() updateDecorations: -> + @rangesByDecorationId = {} @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterNameAndScreenRow = {} @@ -1183,16 +1196,19 @@ class TextEditorPresenter return - removeFromLineDecorationCaches: (decoration, range) -> - @removePropertiesFromLineDecorationCaches(decoration.id, decoration.getProperties(), range) + removeFromLineDecorationCaches: (decoration) -> + @removePropertiesFromLineDecorationCaches(decoration.id, decoration.getProperties()) - removePropertiesFromLineDecorationCaches: (decorationId, decorationProperties, range) -> - gutterName = decorationProperties.gutterName - for row in [range.start.row..range.end.row] by 1 - delete @lineDecorationsByScreenRow[row]?[decorationId] - delete @lineNumberDecorationsByScreenRow[row]?[decorationId] - delete @customGutterDecorationsByGutterNameAndScreenRow[gutterName]?[row]?[decorationId] if gutterName - return + removePropertiesFromLineDecorationCaches: (decorationId, decorationProperties) -> + if range = @rangesByDecorationId[decorationId] + delete @rangesByDecorationId[decorationId] + + gutterName = decorationProperties.gutterName + for row in [range.start.row..range.end.row] by 1 + delete @lineDecorationsByScreenRow[row]?[decorationId] + delete @lineNumberDecorationsByScreenRow[row]?[decorationId] + delete @customGutterDecorationsByGutterNameAndScreenRow[gutterName]?[row]?[decorationId] if gutterName + return addToLineDecorationCaches: (decoration, range) -> marker = decoration.getMarker() @@ -1206,6 +1222,8 @@ class TextEditorPresenter return if properties.onlyEmpty omitLastRow = range.end.column is 0 + @rangesByDecorationId[decoration.id] = range + for row in [range.start.row..range.end.row] by 1 continue if properties.onlyHead and row isnt marker.getHeadScreenPosition().row continue if omitLastRow and row is range.end.row diff --git a/src/text-editor.coffee b/src/text-editor.coffee index 6d979fdf7..edfc17e0d 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -1378,6 +1378,9 @@ class TextEditor extends Model decorationForId: (id) -> @displayBuffer.decorationForId(id) + decorationsForMarkerId: (id) -> + @displayBuffer.decorationsForMarkerId(id) + ### Section: Markers ### @@ -1480,6 +1483,25 @@ class TextEditor extends Model findMarkers: (properties) -> @displayBuffer.findMarkers(properties) + # Extended: Observe changes in the set of markers that intersect a particular + # region of the editor. + # + # * `callback` A {Function} to call whenever one or more {Marker}s appears, + # disappears, or moves within the given region. + # * `event` An {Object} with the following keys: + # * `insert` A {Set} containing the ids of all markers that appeared + # in the range. + # * `update` A {Set} containing the ids of all markers that moved within + # the region. + # * `remove` A {Set} containing the ids of all markers that disappeared + # from the region. + # + # Returns a {MarkerObservationWindow}, which allows you to specify the region + # of interest by calling {MarkerObservationWindow::setBufferRange} or + # {MarkerObservationWindow::setScreenRange}. + observeMarkers: (callback) -> + @displayBuffer.observeMarkers(callback) + # Extended: Get the {Marker} for the given marker id. # # * `id` {Number} id of the marker From eed1ecbbdcbc1c571be7ed439cddafe737a6b823 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 29 May 2015 17:29:03 -0700 Subject: [PATCH 04/12] Wait to observe buffer markers until Marker::onDidChange called Signed-off-by: Nathan Sobo --- src/marker.coffee | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/marker.coffee b/src/marker.coffee index a0d8bda00..536edfb63 100644 --- a/src/marker.coffee +++ b/src/marker.coffee @@ -48,6 +48,7 @@ class Marker oldTailBufferPosition: null oldTailScreenPosition: null wasValid: true + hasChangeObservers: false ### Section: Construction and Destruction @@ -57,14 +58,8 @@ class Marker @emitter = new Emitter @disposables = new CompositeDisposable @id = @bufferMarker.id - @oldHeadBufferPosition = @getHeadBufferPosition() - @oldHeadScreenPosition = @getHeadScreenPosition() - @oldTailBufferPosition = @getTailBufferPosition() - @oldTailScreenPosition = @getTailScreenPosition() - @wasValid = @isValid() @disposables.add @bufferMarker.onDidDestroy => @destroyed() - @disposables.add @bufferMarker.onDidChange (event) => @notifyObservers(event) # Essential: Destroys the marker, causing it to emit the 'destroyed' event. Once # destroyed, a marker cannot be restored by undo/redo operations. @@ -102,6 +97,14 @@ class Marker # # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidChange: (callback) -> + unless @hasChangeObservers + @oldHeadBufferPosition = @getHeadBufferPosition() + @oldHeadScreenPosition = @getHeadScreenPosition() + @oldTailBufferPosition = @getTailBufferPosition() + @oldTailScreenPosition = @getTailScreenPosition() + @wasValid = @isValid() + @disposables.add @bufferMarker.onDidChange (event) => @notifyObservers(event) + @hasChangeObservers = true @emitter.on 'did-change', callback # Essential: Invoke the given callback when the marker is destroyed. From 0db71fa11578acad9a278db6b5c646da1ae15392 Mon Sep 17 00:00:00 2001 From: Jessica Lord Date: Wed, 3 Jun 2015 10:20:34 -0700 Subject: [PATCH 05/12] Add 'Show Preferences' command to open Settings --- src/workspace-element.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/workspace-element.coffee b/src/workspace-element.coffee index 20dad16e1..6a276a7fc 100644 --- a/src/workspace-element.coffee +++ b/src/workspace-element.coffee @@ -126,6 +126,7 @@ atom.commands.add 'atom-workspace', 'application:about': -> ipc.send('command', 'application:about') 'application:run-all-specs': -> ipc.send('command', 'application:run-all-specs') 'application:run-benchmarks': -> ipc.send('command', 'application:run-benchmarks') + 'application:show-preferences': -> ipc.send('command', 'application:show-settings') 'application:show-settings': -> ipc.send('command', 'application:show-settings') 'application:quit': -> ipc.send('command', 'application:quit') 'application:hide': -> ipc.send('command', 'application:hide') From f5895d8b0b09e68391d034fd458007e9913f09d7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 Jun 2015 17:58:44 -0700 Subject: [PATCH 06/12] presenter: use 'markers-updated' event for state updates Signed-off-by: Nathan Sobo --- spec/display-buffer-spec.coffee | 44 ------------- spec/text-editor-presenter-spec.coffee | 16 +++-- src/display-buffer.coffee | 5 +- src/text-editor-presenter.coffee | 91 +++++++------------------- src/text-editor.coffee | 5 +- 5 files changed, 41 insertions(+), 120 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 4c48607a5..140ca2f9e 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -1207,50 +1207,6 @@ describe "DisplayBuffer", -> expect(markerCreated1).toHaveBeenCalled() expect(markerCreated2).not.toHaveBeenCalled() - describe "::observeMarkers(callback)", -> - [observationWindow, events] = [] - - beforeEach -> - events = [] - observationWindow = displayBuffer.observeMarkers (event) -> events.push(event) - displayBuffer.unfoldBufferRow(4, 7) - - it "calls the callback when markers enter, leave, or move within the screen range", -> - expect(events).toHaveLength 0 - - observationWindow.setScreenRange([[0, 0], [4, 0]]) - expect(events).toHaveLength 0 - - marker1 = displayBuffer.markScreenPosition([4, 2]) - expect(events).toHaveLength 0 - - observationWindow.setScreenRange([[0, 0], [5, 0]]) - expect(events).toHaveLength 1 - expect(events[0]).toEqual { - insert: new Set([marker1.id]) - update: new Set - remove: new Set - } - - marker2 = displayBuffer.markScreenPosition([5, 2]) - expect(events).toHaveLength 1 - - observationWindow.setBufferRange([[1, 0], [6, 0]]) - expect(events).toHaveLength 2 - expect(events[1]).toEqual { - insert: new Set([marker2.id]) - update: new Set([marker1.id]) - remove: new Set - } - - marker1.destroy() - expect(events).toHaveLength 3 - expect(events[2]).toEqual { - insert: new Set - update: new Set - remove: new Set([marker1.id]) - } - describe "decorations", -> [marker, decoration, decorationProperties] = [] beforeEach -> diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 142026913..fc52c4828 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1532,21 +1532,27 @@ describe "TextEditorPresenter", -> expect(stateForHighlight(presenter, destroyedSelection.decoration)).toBeUndefined() it "updates when highlight decorations' properties are updated", -> - marker = editor.markBufferRange([[2, 2], [2, 4]]) + marker = editor.markBufferPosition([2, 2]) highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a') presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) - expectValues stateForHighlight(presenter, highlight), {class: 'a'} - expectStateUpdate presenter, -> highlight.setProperties(class: 'b', type: 'highlight') + expect(stateForHighlight(presenter, highlight)).toBeUndefined() + + expectStateUpdate presenter, -> + marker.setBufferRange([[2, 2], [2, 4]]) + highlight.setProperties(class: 'b', type: 'highlight') + expectValues stateForHighlight(presenter, highlight), {class: 'b'} it "increments the .flashCount and sets the .flashClass and .flashDuration when the highlight model flashes", -> presenter = buildPresenter(explicitHeight: 30, scrollTop: 20) - marker = editor.markBufferRange([[2, 2], [2, 4]]) + marker = editor.markBufferPosition([2, 2]) highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a') - expectStateUpdate presenter, -> highlight.flash('b', 500) + expectStateUpdate presenter, -> + marker.setBufferRange([[2, 2], [2, 4]]) + highlight.flash('b', 500) expectValues stateForHighlight(presenter, highlight), { flashClass: 'b' diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index b361e416b..aa6abb7b7 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -10,7 +10,6 @@ Model = require './model' Token = require './token' Decoration = require './decoration' Marker = require './marker' -MarkerObservationWindow = require './marker-observation-window' class BufferToScreenConversionError extends Error constructor: (@message, @metadata) -> @@ -41,6 +40,7 @@ class DisplayBuffer extends Model @disposables.add @tokenizedBuffer.observeGrammar @subscribeToScopedConfigSettings @disposables.add @tokenizedBuffer.onDidChange @handleTokenizedBufferChange @disposables.add @buffer.onDidCreateMarker @handleBufferMarkerCreated + @disposables.add @buffer.onDidUpdateMarkers => @emitter.emit 'did-update-markers' @foldMarkerAttributes = Object.freeze({class: 'fold', displayBufferId: @id}) folds = (new Fold(this, marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())) @updateAllScreenLines() @@ -1049,9 +1049,6 @@ class DisplayBuffer extends Model params = @translateToBufferMarkerParams(params) @buffer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id) - observeMarkers: (callback) -> - new MarkerObservationWindow(this, @buffer.observeMarkers(callback)) - translateToBufferMarkerParams: (params) -> bufferMarkerParams = {} for key, value of params diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index c2a1d88a5..03cfd5a21 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -29,7 +29,6 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterNameAndScreenRow = {} - @highlightDecorationsById = {} @transferMeasurementsToModel() @observeModel() @observeConfig() @@ -126,17 +125,21 @@ class TextEditorPresenter @shouldUpdateLineNumbersState = true @shouldUpdateGutterOrderState = true @shouldUpdateCustomGutterDecorationState = true - @emitDidUpdateState() - @markerObservationWindow = @model.observeMarkers(@markersInRangeDidChange.bind(this)) - @disposables.add new Disposable => @markerObservationWindow.destroy() + @model.onDidUpdateMarkers => + @shouldUpdateTilesState = true + @shouldUpdateLineNumbersState = true + @shouldUpdateDecorations = true + @shouldUpdateOverlaysState = true + @shouldUpdateCustomGutterDecorationState = true + @emitDidUpdateState() @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) @disposables.add @model.onDidChangePlaceholderText => @shouldUpdateContentState = true - @emitDidUpdateState() + @disposables.add @model.onDidChangeMini => @shouldUpdateScrollbarsState = true @shouldUpdateContentState = true @@ -148,14 +151,14 @@ class TextEditorPresenter @shouldUpdateCustomGutterDecorationState = true @updateScrollbarDimensions() @updateCommonGutterState() - @emitDidUpdateState() + @disposables.add @model.onDidChangeLineNumberGutterVisible => @shouldUpdateLineNumberGutterState = true @shouldUpdateGutterOrderState = true @updateCommonGutterState() - @emitDidUpdateState() + @disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this)) @@ -614,7 +617,6 @@ class TextEditorPresenter visibleLinesCount = Math.ceil(@height / @lineHeight) + 1 endRow = startRow + visibleLinesCount @endRow = Math.min(@model.getScreenLineCount(), endRow) - @markerObservationWindow.setScreenRange(Range(Point(@startRow, 0), Point(@endRow, 0))) updateScrollWidth: -> return unless @contentWidth? and @clientWidth? @@ -1098,58 +1100,21 @@ class TextEditorPresenter observeDecoration: (decoration) -> decorationDisposables = new CompositeDisposable if decoration.isType('highlight') - decorationDisposables.add decoration.onDidFlash(@highlightDidFlash.bind(this, decoration)) - decorationDisposables.add decoration.onDidChangeProperties(@decorationPropertiesDidChange.bind(this, decoration)) + decorationDisposables.add decoration.onDidFlash => + @shouldUpdateDecorations = true + @emitDidUpdateState() + + decorationDisposables.add decoration.onDidChangeProperties (event) => + @decorationPropertiesDidChange(decoration, event) decorationDisposables.add decoration.onDidDestroy => @disposables.remove(decorationDisposables) decorationDisposables.dispose() @didDestroyDecoration(decoration) @disposables.add(decorationDisposables) - markersInRangeDidChange: (event) -> - event.insert.forEach (markerId) => - range = @model.getMarker(markerId).getScreenRange() - if decorations = @model.decorationsForMarkerId(markerId) - for decoration in decorations - @decorationMarkerDidChange(decoration) - if decoration.isType('line') or decoration.isType('gutter') - @addToLineDecorationCaches(decoration, range) - event.update.forEach (markerId) => - range = @model.getMarker(markerId).getScreenRange() - if decorations = @model.decorationsForMarkerId(markerId) - for decoration in decorations - @decorationMarkerDidChange(decoration) - if decoration.isType('line') or decoration.isType('gutter') - @removeFromLineDecorationCaches(decoration) - @addToLineDecorationCaches(decoration, range) - event.remove.forEach (markerId) => - if decorations = @model.decorationsForMarkerId(markerId) - for decoration in decorations - @decorationMarkerDidChange(decoration) - if decoration.isType('line') or decoration.isType('gutter') - @removeFromLineDecorationCaches(decoration) - @emitDidUpdateState() - - decorationMarkerDidChange: (decoration) -> - if decoration.isType('highlight') - @updateHighlightState(decoration) - if decoration.isType('overlay') - @shouldUpdateOverlaysState = true - if decoration.isType('line') - @shouldUpdateTilesState = true - if decoration.isType('line-number') - @shouldUpdateLineNumbersState = true - else if decoration.isType('gutter') - @shouldUpdateCustomGutterDecorationState = true - - decorationPropertiesDidChange: (decoration, event) -> - {oldProperties} = event + decorationPropertiesDidChange: (decoration, {oldProperties}) -> + @shouldUpdateDecorations = true if decoration.isType('line') or decoration.isType('gutter') - @removePropertiesFromLineDecorationCaches( - decoration.id, - oldProperties, - decoration.getMarker().getScreenRange()) - @addToLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) if decoration.isType('line') or Decoration.isType(oldProperties, 'line') @shouldUpdateTilesState = true if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number') @@ -1159,14 +1124,11 @@ class TextEditorPresenter @shouldUpdateCustomGutterDecorationState = true else if decoration.isType('overlay') @shouldUpdateOverlaysState = true - else if decoration.isType('highlight') - @updateHighlightState(decoration, event) - @emitDidUpdateState() didDestroyDecoration: (decoration) -> + @shouldUpdateDecorations = true if decoration.isType('line') or decoration.isType('gutter') - @removeFromLineDecorationCaches(decoration, decoration.getMarker().getScreenRange()) @shouldUpdateTilesState = true if decoration.isType('line') if decoration.isType('line-number') @shouldUpdateLineNumbersState = true @@ -1179,14 +1141,6 @@ class TextEditorPresenter @emitDidUpdateState() - highlightDidFlash: (decoration) -> - flash = decoration.consumeNextFlash() - if decorationState = @state.content.highlights[decoration.id] - decorationState.flashCount++ - decorationState.flashClass = flash.class - decorationState.flashDuration = flash.duration - @emitDidUpdateState() - didAddDecoration: (decoration) -> @observeDecoration(decoration) @@ -1209,7 +1163,6 @@ class TextEditorPresenter @lineDecorationsByScreenRow = {} @lineNumberDecorationsByScreenRow = {} @customGutterDecorationsByGutterNameAndScreenRow = {} - @highlightDecorationsById = {} visibleHighlights = {} return unless 0 <= @startRow <= @endRow <= Infinity @@ -1304,6 +1257,12 @@ class TextEditorPresenter flashDuration: null flashClass: null } + + if flash = decoration.consumeNextFlash() + highlightState.flashCount++ + highlightState.flashClass = flash.class + highlightState.flashDuration = flash.duration + highlightState.class = properties.class highlightState.deprecatedRegionClass = properties.deprecatedRegionClass highlightState.regions = @buildHighlightRegions(range) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index f2d8445aa..4aacb3dfa 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -163,10 +163,10 @@ class TextEditor extends Model subscribeToDisplayBuffer: -> @disposables.add @displayBuffer.onDidCreateMarker @handleMarkerCreated - @disposables.add @displayBuffer.onDidUpdateMarkers => @mergeIntersectingSelections() @disposables.add @displayBuffer.onDidChangeGrammar => @handleGrammarChange() @disposables.add @displayBuffer.onDidTokenize => @handleTokenization() @disposables.add @displayBuffer.onDidChange (e) => + @mergeIntersectingSelections() @emit 'screen-lines-changed', e if includeDeprecatedAPIs @emitter.emit 'did-change', e @@ -461,6 +461,9 @@ class TextEditor extends Model onDidChangeIcon: (callback) -> @emitter.on 'did-change-icon', callback + onDidUpdateMarkers: (callback) -> + @displayBuffer.onDidUpdateMarkers(callback) + # Public: Retrieves the current {TextBuffer}. getBuffer: -> @buffer From b80c480d863e8ac9a8f98659445741f4b1684fe1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jun 2015 10:27:36 -0700 Subject: [PATCH 07/12] Store overlay decorations to optimize ::getOverlayDecorations() --- src/decoration.coffee | 2 ++ src/display-buffer.coffee | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/decoration.coffee b/src/decoration.coffee index bc3a21748..d8f0b5edf 100644 --- a/src/decoration.coffee +++ b/src/decoration.coffee @@ -153,6 +153,8 @@ class Decoration oldProperties = @properties @properties = translateDecorationParamsOldToNew(newProperties) @properties.id = @id + if newProperties.type? + @displayBuffer.decorationDidChangeType(this) @emit 'updated', {oldParams: oldProperties, newParams: newProperties} if Grim.includeDeprecatedAPIs @emitter.emit 'did-change-properties', {oldProperties, newProperties} diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index aa6abb7b7..d98f93fe3 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -37,6 +37,7 @@ class DisplayBuffer extends Model @foldsByMarkerId = {} @decorationsById = {} @decorationsByMarkerId = {} + @overlayDecorationsById = {} @disposables.add @tokenizedBuffer.observeGrammar @subscribeToScopedConfigSettings @disposables.add @tokenizedBuffer.onDidChange @handleTokenizedBufferChange @disposables.add @buffer.onDidCreateMarker @handleBufferMarkerCreated @@ -919,7 +920,16 @@ class DisplayBuffer extends Model @getDecorations(propertyFilter).filter (decoration) -> decoration.isType('highlight') getOverlayDecorations: (propertyFilter) -> - @getDecorations(propertyFilter).filter (decoration) -> decoration.isType('overlay') + result = [] + for id, decoration of @overlayDecorationsById + result.push(decoration) + if propertyFilter? + result.filter (decoration) -> + for key, value of propertyFilter + return false unless decoration.properties[key] is value + true + else + result decorationsForScreenRowRange: (startScreenRow, endScreenRow) -> decorationsByMarkerId = {} @@ -934,6 +944,7 @@ class DisplayBuffer extends Model @disposables.add decoration.onDidDestroy => @removeDecoration(decoration) @decorationsByMarkerId[marker.id] ?= [] @decorationsByMarkerId[marker.id].push(decoration) + @overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay') @decorationsById[decoration.id] = decoration @emit 'decoration-added', decoration if Grim.includeDeprecatedAPIs @emitter.emit 'did-add-decoration', decoration @@ -950,6 +961,7 @@ class DisplayBuffer extends Model @emit 'decoration-removed', decoration if Grim.includeDeprecatedAPIs @emitter.emit 'did-remove-decoration', decoration delete @decorationsByMarkerId[marker.id] if decorations.length is 0 + delete @overlayDecorationsById[decoration.id] decorationsForMarkerId: (markerId) -> @decorationsByMarkerId[markerId] @@ -1246,6 +1258,12 @@ class DisplayBuffer extends Model foldForMarker: (marker) -> @foldsByMarkerId[marker.id] + decorationDidChangeType: (decoration) -> + if decoration.isType('overlay') + @overlayDecorationsById[decoration.id] = decoration + else + delete @overlayDecorationsById[decoration.id] + if Grim.includeDeprecatedAPIs DisplayBuffer.properties softWrapped: null From ee3cd3994d005099428b2c7943dba3e45ce5a6c8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jun 2015 10:38:44 -0700 Subject: [PATCH 08/12] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74a07659d..83ff9b06c 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "space-pen": "3.8.2", "stacktrace-parser": "0.1.1", "temp": "0.8.1", - "text-buffer": "6.1.3", + "text-buffer": "6.2.0", "theorist": "^1.0.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", From bb680fc942f8ab71f2bba5093aa83b3d49624829 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 4 Jun 2015 10:46:52 -0700 Subject: [PATCH 09/12] :arrow_up: apm@0.170 --- apm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apm/package.json b/apm/package.json index 108e3c206..1dde8b9a6 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.169.0" + "atom-package-manager": "0.170.0" } } From 91e3a76d17fa87b6245e79d4eedb47bfa19ad173 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 4 Jun 2015 10:53:12 -0700 Subject: [PATCH 10/12] Update spec for new deprecation data --- spec/package-manager-spec.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 6f424465c..f09f43824 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -70,10 +70,10 @@ describe "PackageManager", -> it "returns null", -> grim.includeDeprecatedAPIs = false expect(atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'packages', 'wordcount'))).toBeNull() - expect(atom.packages.isDeprecatedPackage('wordcount', '2.0.9')).toBe true - expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.0')).toBe true - expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.1')).toBe false - expect(atom.packages.getDeprecatedPackageMetadata('wordcount').version).toBe '<=2.1.0' + expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.9')).toBe true + expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.0')).toBe true + expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.1')).toBe false + expect(atom.packages.getDeprecatedPackageMetadata('wordcount').version).toBe '<=2.2.0' it "invokes ::onDidLoadPackage listeners with the loaded package", -> loadedPackage = null From 63fbf7382e7e4bdd32904b1cd6cd4813bfe61d64 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jun 2015 10:57:31 -0700 Subject: [PATCH 11/12] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a39bf2c7f..79ae74c6a 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "dev-live-reload": "0.46.0", "encoding-selector": "0.20.0", "exception-reporting": "0.24.0", - "find-and-replace": "0.170.0", + "find-and-replace": "0.171.0", "fuzzy-finder": "0.87.0", "git-diff": "0.55.0", "go-to-line": "0.30.0", From 65831f1a4dff9dbcf68dbaf9b1d78b2617ed229a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 4 Jun 2015 11:22:11 -0700 Subject: [PATCH 12/12] :arrow_up: language-html@0.39 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79ae74c6a..dd5ffbbec 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "language-gfm": "0.77.0", "language-git": "0.10.0", "language-go": "0.26.0", - "language-html": "0.38.0", + "language-html": "0.39.0", "language-hyperlink": "0.13.0", "language-java": "0.15.0", "language-javascript": "0.78.0",