mirror of
https://github.com/atom/atom.git
synced 2026-01-24 14:28:14 -05:00
Merge pull request #9426 from atom/ns-editor-marker-layers
Optimize markers via new layers API and treap-based index
This commit is contained in:
@@ -52,7 +52,7 @@
|
||||
"service-hub": "^0.7.0",
|
||||
"source-map-support": "^0.3.2",
|
||||
"temp": "0.8.1",
|
||||
"text-buffer": "7.1.3",
|
||||
"text-buffer": "^8.0.3",
|
||||
"typescript-simple": "1.0.0",
|
||||
"underscore-plus": "^1.6.6",
|
||||
"yargs": "^3.23.0"
|
||||
@@ -87,7 +87,7 @@
|
||||
"dev-live-reload": "0.47.0",
|
||||
"encoding-selector": "0.21.0",
|
||||
"exception-reporting": "0.37.0",
|
||||
"find-and-replace": "0.190.0",
|
||||
"find-and-replace": "0.191.0",
|
||||
"fuzzy-finder": "0.93.0",
|
||||
"git-diff": "0.57.0",
|
||||
"go-to-line": "0.30.0",
|
||||
@@ -104,7 +104,7 @@
|
||||
"package-generator": "0.41.0",
|
||||
"release-notes": "0.53.0",
|
||||
"settings-view": "0.231.0",
|
||||
"snippets": "0.101.0",
|
||||
"snippets": "0.101.1",
|
||||
"spell-check": "0.62.0",
|
||||
"status-bar": "0.80.0",
|
||||
"styleguide": "0.45.0",
|
||||
|
||||
28
spec/async-spec-helpers.coffee
Normal file
28
spec/async-spec-helpers.coffee
Normal file
@@ -0,0 +1,28 @@
|
||||
exports.beforeEach = (fn) ->
|
||||
global.beforeEach ->
|
||||
result = fn()
|
||||
if result instanceof Promise
|
||||
waitsForPromise(-> result)
|
||||
|
||||
exports.afterEach = (fn) ->
|
||||
global.afterEach ->
|
||||
result = fn()
|
||||
if result instanceof Promise
|
||||
waitsForPromise(-> result)
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach (name) ->
|
||||
exports[name] = (description, fn) ->
|
||||
global[name] description, ->
|
||||
result = fn()
|
||||
if result instanceof Promise
|
||||
waitsForPromise(-> result)
|
||||
|
||||
waitsForPromise = (fn) ->
|
||||
promise = fn()
|
||||
waitsFor 'spec promise to resolve', 30000, (done) ->
|
||||
promise.then(
|
||||
done,
|
||||
(error) ->
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
)
|
||||
@@ -243,23 +243,6 @@ describe "AtomEnvironment", ->
|
||||
|
||||
atomEnvironment.destroy()
|
||||
|
||||
describe "::destroy()", ->
|
||||
it "unsubscribes from all buffers", ->
|
||||
atomEnvironment = new AtomEnvironment({applicationDelegate: atom.applicationDelegate, window, document})
|
||||
|
||||
waitsForPromise ->
|
||||
atomEnvironment.workspace.open("sample.js")
|
||||
|
||||
runs ->
|
||||
buffer = atomEnvironment.workspace.getActivePaneItem().buffer
|
||||
pane = atomEnvironment.workspace.getActivePane()
|
||||
pane.splitRight(copyActiveItem: true)
|
||||
expect(atomEnvironment.workspace.getTextEditors().length).toBe 2
|
||||
|
||||
atomEnvironment.destroy()
|
||||
|
||||
expect(buffer.getSubscriptionCount()).toBe 0
|
||||
|
||||
describe "::openLocations(locations) (called via IPC from browser process)", ->
|
||||
beforeEach ->
|
||||
spyOn(atom.workspace, 'open')
|
||||
|
||||
@@ -418,11 +418,11 @@ describe "DisplayBuffer", ->
|
||||
describe "when creating a fold where one already exists", ->
|
||||
it "returns existing fold and does't create new fold", ->
|
||||
fold = displayBuffer.createFold(0, 10)
|
||||
expect(displayBuffer.findMarkers(class: 'fold').length).toBe 1
|
||||
expect(displayBuffer.foldsMarkerLayer.getMarkers().length).toBe 1
|
||||
|
||||
newFold = displayBuffer.createFold(0, 10)
|
||||
expect(newFold).toBe fold
|
||||
expect(displayBuffer.findMarkers(class: 'fold').length).toBe 1
|
||||
expect(displayBuffer.foldsMarkerLayer.getMarkers().length).toBe 1
|
||||
|
||||
describe "when a fold is created inside an existing folded region", ->
|
||||
it "creates/destroys the fold, but does not trigger change event", ->
|
||||
@@ -829,7 +829,6 @@ describe "DisplayBuffer", ->
|
||||
it "unsubscribes all display buffer markers from their underlying buffer marker (regression)", ->
|
||||
marker = displayBuffer.markBufferPosition([12, 2])
|
||||
displayBuffer.destroy()
|
||||
expect(marker.bufferMarker.getSubscriptionCount()).toBe 0
|
||||
expect( -> buffer.insert([12, 2], '\n')).not.toThrow()
|
||||
|
||||
describe "markers", ->
|
||||
@@ -879,7 +878,7 @@ describe "DisplayBuffer", ->
|
||||
[markerChangedHandler, marker] = []
|
||||
|
||||
beforeEach ->
|
||||
marker = displayBuffer.markScreenRange([[5, 4], [5, 10]], maintainHistory: true)
|
||||
marker = displayBuffer.addMarkerLayer(maintainHistory: true).markScreenRange([[5, 4], [5, 10]])
|
||||
marker.onDidChange markerChangedHandler = jasmine.createSpy("markerChangedHandler")
|
||||
|
||||
it "triggers the 'changed' event whenever the markers head's screen position changes in the buffer or on screen", ->
|
||||
@@ -1016,7 +1015,7 @@ describe "DisplayBuffer", ->
|
||||
expect(markerChangedHandler).not.toHaveBeenCalled()
|
||||
|
||||
it "updates markers before emitting buffer change events, but does not notify their observers until the change event", ->
|
||||
marker2 = displayBuffer.markBufferRange([[8, 1], [8, 1]], maintainHistory: true)
|
||||
marker2 = displayBuffer.addMarkerLayer(maintainHistory: true).markBufferRange([[8, 1], [8, 1]])
|
||||
marker2.onDidChange marker2ChangedHandler = jasmine.createSpy("marker2ChangedHandler")
|
||||
displayBuffer.onDidChange changeHandler = jasmine.createSpy("changeHandler").andCallFake -> onDisplayBufferChange()
|
||||
|
||||
@@ -1237,11 +1236,6 @@ describe "DisplayBuffer", ->
|
||||
decoration.destroy()
|
||||
expect(displayBuffer.decorationForId(decoration.id)).not.toBeDefined()
|
||||
|
||||
it "does not leak disposables", ->
|
||||
disposablesSize = displayBuffer.disposables.disposables.size
|
||||
decoration.destroy()
|
||||
expect(displayBuffer.disposables.disposables.size).toBe(disposablesSize - 1)
|
||||
|
||||
describe "when a decoration is updated via Decoration::update()", ->
|
||||
it "emits an 'updated' event containing the new and old params", ->
|
||||
decoration.onDidChangeProperties updatedSpy = jasmine.createSpy()
|
||||
@@ -1249,7 +1243,7 @@ describe "DisplayBuffer", ->
|
||||
|
||||
{oldProperties, newProperties} = updatedSpy.mostRecentCall.args[0]
|
||||
expect(oldProperties).toEqual decorationProperties
|
||||
expect(newProperties).toEqual type: 'line-number', gutterName: 'line-number', class: 'two', id: decoration.id
|
||||
expect(newProperties).toEqual {type: 'line-number', gutterName: 'line-number', class: 'two'}
|
||||
|
||||
describe "::getDecorations(properties)", ->
|
||||
it "returns decorations matching the given optional properties", ->
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
undefined
|
||||
File diff suppressed because it is too large
Load Diff
4752
spec/text-editor-component-spec.js
Normal file
4752
spec/text-editor-component-spec.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,13 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectNoStateUpdate = (presenter, fn) -> expectStateUpdatedToBe(false, presenter, fn)
|
||||
|
||||
waitsForStateToUpdate = (presenter, fn) ->
|
||||
waitsFor "presenter state to update", 1000, (done) ->
|
||||
fn?()
|
||||
disposable = presenter.onDidUpdateState ->
|
||||
disposable.dispose()
|
||||
process.nextTick(done)
|
||||
|
||||
tiledContentContract = (stateFn) ->
|
||||
it "contains states for tiles that are visible on screen", ->
|
||||
presenter = buildPresenter(explicitHeight: 6, scrollTop: 0, lineHeight: 1, tileSize: 2)
|
||||
@@ -1147,55 +1154,62 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration1 = editor.decorateMarker(marker1, type: 'line', class: 'a')
|
||||
presenter = buildPresenter()
|
||||
marker2 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration2 = editor.decorateMarker(marker2, type: 'line', class: 'b')
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
runs ->
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.undo()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.undo()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> decoration1.destroy()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> decoration1.destroy()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker2.destroy()
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker2.destroy()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyEmpty' option on line decorations", ->
|
||||
presenter = buildPresenter()
|
||||
@@ -1206,11 +1220,12 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "honors the 'onlyNonEmpty' option on line decorations", ->
|
||||
presenter = buildPresenter()
|
||||
@@ -1221,40 +1236,49 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyHead' option on line decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a', onlyHead: true)
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
editor.decorateMarker(marker, type: 'line', class: 'a', onlyHead: true)
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "does not decorate the last line of a non-empty line decoration range if it ends at column 0", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker = editor.markBufferRange([[4, 0], [6, 0]])
|
||||
editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
it "does not apply line decorations to mini editors", ->
|
||||
editor.setMini(true)
|
||||
presenter = buildPresenter(explicitHeight: 10)
|
||||
marker = editor.markBufferRange([[0, 0], [0, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(false)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toEqual ['cursor-line', 'a']
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker = editor.markBufferRange([[0, 0], [0, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line', class: 'a')
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(true)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(false)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toEqual ['cursor-line', 'a']
|
||||
|
||||
expectStateUpdate presenter, -> editor.setMini(true)
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
|
||||
|
||||
it "only applies decorations to screen rows that are spanned by their marker when lines are soft-wrapped", ->
|
||||
editor.setText("a line that wraps, ok")
|
||||
@@ -1268,9 +1292,12 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
|
||||
marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
|
||||
runs ->
|
||||
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
|
||||
describe ".cursors", ->
|
||||
stateForCursor = (presenter, cursorIndex) ->
|
||||
@@ -1740,41 +1767,51 @@ describe "TextEditorPresenter", ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# moving into view
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# becoming empty
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].clear(autoscroll: false)
|
||||
runs ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# becoming non-empty
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[2, 4], [2, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 1, 2), {
|
||||
regions: [{top: 0, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# moving out of view
|
||||
expectStateUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[1].setBufferRange([[3, 4], [3, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectUndefinedStateForSelection(presenter, 1)
|
||||
|
||||
# adding
|
||||
expectStateUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.addSelectionForBufferRange([[1, 4], [1, 6]], autoscroll: false)
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 2 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# moving added selection
|
||||
expectStateUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 4 * 10, height: 10}]
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> editor.getSelections()[2].setBufferRange([[1, 4], [1, 8]], autoscroll: false)
|
||||
|
||||
# destroying
|
||||
destroyedSelection = editor.getSelections()[2]
|
||||
expectStateUpdate presenter, -> destroyedSelection.destroy()
|
||||
expectUndefinedStateForHighlight(presenter, destroyedSelection.decoration)
|
||||
destroyedSelection = null
|
||||
runs ->
|
||||
expectValues stateForSelectionInTile(presenter, 2, 0), {
|
||||
regions: [{top: 10, left: 4 * 10, width: 4 * 10, height: 10}]
|
||||
}
|
||||
|
||||
# destroying
|
||||
destroyedSelection = editor.getSelections()[2]
|
||||
|
||||
waitsForStateToUpdate presenter, -> destroyedSelection.destroy()
|
||||
runs ->
|
||||
expectUndefinedStateForHighlight(presenter, destroyedSelection.decoration)
|
||||
|
||||
it "updates when highlight decorations' properties are updated", ->
|
||||
marker = editor.markBufferPosition([2, 2])
|
||||
@@ -1784,44 +1821,45 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
expectUndefinedStateForHighlight(presenter, highlight)
|
||||
|
||||
expectStateUpdate presenter, ->
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker.setBufferRange([[2, 2], [2, 4]])
|
||||
highlight.setProperties(class: 'b', type: 'highlight')
|
||||
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {class: 'b'}
|
||||
runs ->
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {class: 'b'}
|
||||
|
||||
it "increments the .flashCount and sets the .flashClass and .flashDuration when the highlight model flashes", ->
|
||||
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20, tileSize: 2)
|
||||
|
||||
marker = editor.markBufferPosition([2, 2])
|
||||
highlight = editor.decorateMarker(marker, type: 'highlight', class: 'a')
|
||||
expectStateUpdate presenter, ->
|
||||
waitsForStateToUpdate presenter, ->
|
||||
marker.setBufferRange([[2, 2], [5, 2]])
|
||||
highlight.flash('b', 500)
|
||||
runs ->
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'b'
|
||||
flashDuration: 500
|
||||
flashCount: 1
|
||||
}
|
||||
|
||||
expectStateUpdate presenter, -> highlight.flash('c', 600)
|
||||
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> highlight.flash('c', 600)
|
||||
runs ->
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 2), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
expectValues stateForHighlightInTile(presenter, highlight, 4), {
|
||||
flashClass: 'c'
|
||||
flashDuration: 600
|
||||
flashCount: 2
|
||||
}
|
||||
|
||||
describe ".overlays", ->
|
||||
[item] = []
|
||||
@@ -1829,7 +1867,7 @@ describe "TextEditorPresenter", ->
|
||||
presenter.getState().content.overlays[decoration.id]
|
||||
|
||||
it "contains state for overlay decorations both initially and when their markers move", ->
|
||||
marker = editor.markBufferPosition([2, 13], invalidate: 'touch', maintainHistory: true)
|
||||
marker = editor.addMarkerLayer(maintainHistory: true).markBufferPosition([2, 13], invalidate: 'touch')
|
||||
decoration = editor.decorateMarker(marker, {type: 'overlay', item})
|
||||
presenter = buildPresenter(explicitHeight: 30, scrollTop: 20)
|
||||
|
||||
@@ -1840,40 +1878,47 @@ describe "TextEditorPresenter", ->
|
||||
}
|
||||
|
||||
# Change range
|
||||
expectStateUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]])
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]])
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
|
||||
# Valid -> invalid
|
||||
expectStateUpdate presenter, -> editor.getBuffer().insert([2, 14], 'x')
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
# Valid -> invalid
|
||||
waitsForStateToUpdate presenter, -> editor.getBuffer().insert([2, 14], 'x')
|
||||
runs ->
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
|
||||
# Invalid -> valid
|
||||
expectStateUpdate presenter, -> editor.undo()
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
# Invalid -> valid
|
||||
waitsForStateToUpdate presenter, -> editor.undo()
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 5 * 10 - presenter.state.content.scrollTop, left: 6 * 10}
|
||||
}
|
||||
|
||||
# Reverse direction
|
||||
expectStateUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]], reversed: true)
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
waitsForStateToUpdate presenter, -> marker.setBufferRange([[2, 13], [4, 6]], reversed: true)
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
|
||||
# Destroy
|
||||
decoration.destroy()
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
waitsForStateToUpdate presenter, -> decoration.destroy()
|
||||
runs ->
|
||||
expect(stateForOverlay(presenter, decoration)).toBeUndefined()
|
||||
|
||||
# Add
|
||||
decoration2 = editor.decorateMarker(marker, {type: 'overlay', item})
|
||||
expectValues stateForOverlay(presenter, decoration2), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
decoration2 = null
|
||||
waitsForStateToUpdate presenter, -> decoration2 = editor.decorateMarker(marker, {type: 'overlay', item})
|
||||
runs ->
|
||||
expectValues stateForOverlay(presenter, decoration2), {
|
||||
item: item
|
||||
pixelPosition: {top: 3 * 10 - presenter.state.content.scrollTop, left: 13 * 10}
|
||||
}
|
||||
|
||||
it "updates when character widths changes", ->
|
||||
scrollTop = 20
|
||||
@@ -2308,11 +2353,11 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
describe ".decorationClasses", ->
|
||||
it "adds decoration classes to the relevant line number state objects, both initially and when decorations change", ->
|
||||
marker1 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker1 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration1 = editor.decorateMarker(marker1, type: 'line-number', class: 'a')
|
||||
presenter = buildPresenter()
|
||||
marker2 = editor.markBufferRange([[4, 0], [6, 2]], invalidate: 'touch', maintainHistory: true)
|
||||
marker2 = editor.addMarkerLayer(maintainHistory: true).markBufferRange([[4, 0], [6, 2]], invalidate: 'touch')
|
||||
decoration2 = editor.decorateMarker(marker2, type: 'line-number', class: 'b')
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
@@ -2320,85 +2365,92 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.getBuffer().insert([5, 0], 'x')
|
||||
runs ->
|
||||
expect(marker1.isValid()).toBe false
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> editor.undo()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> editor.undo()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker1.setBufferRange([[2, 0], [4, 2]])
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a', 'b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> decoration1.destroy()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> decoration1.destroy()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['b']
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker2.destroy()
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
waitsForStateToUpdate presenter, -> marker2.destroy()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 2).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 3).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 7).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyEmpty' option on line-number decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 1]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyEmpty: true)
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "honors the 'onlyNonEmpty' option on line-number decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyNonEmpty: true)
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
expectStateUpdate presenter, -> marker.clearTail()
|
||||
waitsForStateToUpdate presenter, -> marker.clearTail()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toBeNull()
|
||||
|
||||
it "honors the 'onlyHead' option on line-number decorations", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 2]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a', onlyHead: true)
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toBeNull()
|
||||
expect(lineNumberStateForScreenRow(presenter, 6).decorationClasses).toEqual ['a']
|
||||
|
||||
it "does not decorate the last line of a non-empty line-number decoration range if it ends at column 0", ->
|
||||
presenter = buildPresenter()
|
||||
marker = editor.markBufferRange([[4, 0], [6, 0]])
|
||||
decoration = editor.decorateMarker(marker, type: 'line-number', class: 'a')
|
||||
presenter = buildPresenter()
|
||||
|
||||
expect(lineNumberStateForScreenRow(presenter, 4).decorationClasses).toEqual ['a']
|
||||
expect(lineNumberStateForScreenRow(presenter, 5).decorationClasses).toEqual ['a']
|
||||
@@ -2430,9 +2482,10 @@ describe "TextEditorPresenter", ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toBeNull()
|
||||
|
||||
marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
waitsForStateToUpdate presenter, -> marker.setBufferRange([[0, 0], [0, Infinity]])
|
||||
runs ->
|
||||
expect(lineNumberStateForScreenRow(presenter, 0).decorationClasses).toContain 'a'
|
||||
expect(lineNumberStateForScreenRow(presenter, 1).decorationClasses).toContain 'a'
|
||||
|
||||
describe ".foldable", ->
|
||||
it "marks line numbers at the start of a foldable region as foldable", ->
|
||||
@@ -2565,14 +2618,15 @@ describe "TextEditorPresenter", ->
|
||||
|
||||
it "updates when a decoration's marker is modified", ->
|
||||
# This update will move decoration1 out of view.
|
||||
expectStateUpdate presenter, ->
|
||||
waitsForStateToUpdate presenter, ->
|
||||
newRange = new Range([13, 0], [14, 0])
|
||||
marker1.setBufferRange(newRange)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
describe "when a decoration's properties are modified", ->
|
||||
it "updates the item applied to the decoration, if the decoration item is changed", ->
|
||||
@@ -2584,12 +2638,14 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter'
|
||||
class: 'test-class'
|
||||
item: newItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].item).toBe newItem
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].item).toBe newItem
|
||||
expect(decorationState[decoration2.id].item).toBe decorationItem
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates the class applied to the decoration, if the decoration class is changed", ->
|
||||
# This changes the decoration item. The visibility of the decoration should not be affected.
|
||||
@@ -2598,12 +2654,13 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter'
|
||||
class: 'new-test-class'
|
||||
item: decorationItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].class).toBe 'new-test-class'
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id].class).toBe 'new-test-class'
|
||||
expect(decorationState[decoration2.id].class).toBe 'test-class'
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates the type of the decoration, if the decoration type is changed", ->
|
||||
# This changes the type of the decoration. This should remove the decoration from the gutter.
|
||||
@@ -2612,12 +2669,13 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter' # This is an invalid/meaningless option here, but it shouldn't matter.
|
||||
class: 'test-class'
|
||||
item: decorationItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates the gutter the decoration targets, if the decoration gutterName is changed", ->
|
||||
# This changes which gutter this decoration applies to. Since this gutter does not exist,
|
||||
@@ -2627,24 +2685,25 @@ describe "TextEditorPresenter", ->
|
||||
gutterName: 'test-gutter-2'
|
||||
class: 'new-test-class'
|
||||
item: decorationItem
|
||||
expectStateUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
waitsForStateToUpdate presenter, -> decoration1.setProperties(newDecorationParams)
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
runs ->
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id].top).toBeDefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
# After adding the targeted gutter, the decoration will appear in the state for that gutter,
|
||||
# since it should be visible.
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
newGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(newGutterDecorationState[decoration1.id].top).toBeDefined()
|
||||
expect(newGutterDecorationState[decoration2.id]).toBeUndefined()
|
||||
expect(newGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
oldGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(oldGutterDecorationState[decoration1.id]).toBeUndefined()
|
||||
expect(oldGutterDecorationState[decoration2.id].top).toBeDefined()
|
||||
expect(oldGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
# After adding the targeted gutter, the decoration will appear in the state for that gutter,
|
||||
# since it should be visible.
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
newGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(newGutterDecorationState[decoration1.id].top).toBeDefined()
|
||||
expect(newGutterDecorationState[decoration2.id]).toBeUndefined()
|
||||
expect(newGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
oldGutterDecorationState = getContentForGutterWithName(presenter, 'test-gutter')
|
||||
expect(oldGutterDecorationState[decoration1.id]).toBeUndefined()
|
||||
expect(oldGutterDecorationState[decoration2.id].top).toBeDefined()
|
||||
expect(oldGutterDecorationState[decoration3.id]).toBeUndefined()
|
||||
|
||||
it "updates when the editor's mini state changes, and is cleared when the editor is mini", ->
|
||||
expectStateUpdate presenter, -> editor.setMini(true)
|
||||
@@ -2679,13 +2738,17 @@ describe "TextEditorPresenter", ->
|
||||
class: 'test-class'
|
||||
marker4 = editor.markBufferRange([[0, 0], [1, 0]])
|
||||
decoration4 = editor.decorateMarker(marker4, decorationParams)
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id]).toBeUndefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
expect(decorationState[decoration4.id].top).toBeDefined()
|
||||
waitsForStateToUpdate presenter
|
||||
|
||||
runs ->
|
||||
expectStateUpdate presenter, -> editor.addGutter({name: 'test-gutter-2'})
|
||||
|
||||
decorationState = getContentForGutterWithName(presenter, 'test-gutter-2')
|
||||
expect(decorationState[decoration1.id]).toBeUndefined()
|
||||
expect(decorationState[decoration2.id]).toBeUndefined()
|
||||
expect(decorationState[decoration3.id]).toBeUndefined()
|
||||
expect(decorationState[decoration4.id].top).toBeDefined()
|
||||
|
||||
it "updates when editor lines are folded", ->
|
||||
oldDimensionsForDecoration1 =
|
||||
|
||||
@@ -4589,7 +4589,10 @@ describe "TextEditor", ->
|
||||
expect(buffer.getLineCount()).toBe(count - 1)
|
||||
|
||||
describe "when the line being deleted preceeds a fold, and the command is undone", ->
|
||||
it "restores the line and preserves the fold", ->
|
||||
# TODO: This seemed to have only been passing due to an accident in the text
|
||||
# buffer implementation. Once we moved selections to a different layer it
|
||||
# broke. We need to revisit our representation of folds and then reenable it.
|
||||
xit "restores the line and preserves the fold", ->
|
||||
editor.setCursorBufferPosition([4])
|
||||
editor.foldCurrentRow()
|
||||
expect(editor.isFoldedAtScreenRow(4)).toBeTruthy()
|
||||
@@ -5057,11 +5060,12 @@ describe "TextEditor", ->
|
||||
expect(coffeeEditor.lineTextForBufferRow(2)).toBe ""
|
||||
|
||||
describe ".destroy()", ->
|
||||
it "destroys all markers associated with the edit session", ->
|
||||
editor.foldAll()
|
||||
expect(buffer.getMarkerCount()).toBeGreaterThan 0
|
||||
it "destroys marker layers associated with the text editor", ->
|
||||
selectionsMarkerLayerId = editor.selectionsMarkerLayer.id
|
||||
foldsMarkerLayerId = editor.displayBuffer.foldsMarkerLayer.id
|
||||
editor.destroy()
|
||||
expect(buffer.getMarkerCount()).toBe 0
|
||||
expect(buffer.getMarkerLayer(selectionsMarkerLayerId)).toBeUndefined()
|
||||
expect(buffer.getMarkerLayer(foldsMarkerLayerId)).toBeUndefined()
|
||||
|
||||
it "notifies ::onDidDestroy observers when the editor is destroyed", ->
|
||||
destroyObserverCalled = false
|
||||
@@ -5500,101 +5504,189 @@ describe "TextEditor", ->
|
||||
it "does not allow a custom gutter with the 'line-number' name.", ->
|
||||
expect(editor.addGutter.bind(editor, {name: 'line-number'})).toThrow()
|
||||
|
||||
describe '::decorateMarker', ->
|
||||
[marker] = []
|
||||
describe '::decorateMarker', ->
|
||||
[marker] = []
|
||||
|
||||
beforeEach ->
|
||||
marker = editor.markBufferRange([[1, 0], [1, 0]])
|
||||
beforeEach ->
|
||||
marker = editor.markBufferRange([[1, 0], [1, 0]])
|
||||
|
||||
it 'reflects an added decoration when one of its custom gutters is decorated.', ->
|
||||
gutter = editor.addGutter {'name': 'custom-gutter'}
|
||||
decoration = gutter.decorateMarker marker, {class: 'custom-class'}
|
||||
gutterDecorations = editor.getDecorations
|
||||
type: 'gutter'
|
||||
gutterName: 'custom-gutter'
|
||||
class: 'custom-class'
|
||||
expect(gutterDecorations.length).toBe 1
|
||||
expect(gutterDecorations[0]).toBe decoration
|
||||
it 'reflects an added decoration when one of its custom gutters is decorated.', ->
|
||||
gutter = editor.addGutter {'name': 'custom-gutter'}
|
||||
decoration = gutter.decorateMarker marker, {class: 'custom-class'}
|
||||
gutterDecorations = editor.getDecorations
|
||||
type: 'gutter'
|
||||
gutterName: 'custom-gutter'
|
||||
class: 'custom-class'
|
||||
expect(gutterDecorations.length).toBe 1
|
||||
expect(gutterDecorations[0]).toBe decoration
|
||||
|
||||
it 'reflects an added decoration when its line-number gutter is decorated.', ->
|
||||
decoration = editor.gutterWithName('line-number').decorateMarker marker, {class: 'test-class'}
|
||||
gutterDecorations = editor.getDecorations
|
||||
type: 'line-number'
|
||||
gutterName: 'line-number'
|
||||
class: 'test-class'
|
||||
expect(gutterDecorations.length).toBe 1
|
||||
expect(gutterDecorations[0]).toBe decoration
|
||||
it 'reflects an added decoration when its line-number gutter is decorated.', ->
|
||||
decoration = editor.gutterWithName('line-number').decorateMarker marker, {class: 'test-class'}
|
||||
gutterDecorations = editor.getDecorations
|
||||
type: 'line-number'
|
||||
gutterName: 'line-number'
|
||||
class: 'test-class'
|
||||
expect(gutterDecorations.length).toBe 1
|
||||
expect(gutterDecorations[0]).toBe decoration
|
||||
|
||||
describe '::observeGutters', ->
|
||||
[payloads, callback] = []
|
||||
describe '::observeGutters', ->
|
||||
[payloads, callback] = []
|
||||
|
||||
beforeEach ->
|
||||
payloads = []
|
||||
callback = (payload) ->
|
||||
payloads.push(payload)
|
||||
beforeEach ->
|
||||
payloads = []
|
||||
callback = (payload) ->
|
||||
payloads.push(payload)
|
||||
|
||||
it 'calls the callback immediately with each existing gutter, and with each added gutter after that.', ->
|
||||
lineNumberGutter = editor.gutterWithName('line-number')
|
||||
editor.observeGutters(callback)
|
||||
expect(payloads).toEqual [lineNumberGutter]
|
||||
gutter1 = editor.addGutter({name: 'test-gutter-1'})
|
||||
expect(payloads).toEqual [lineNumberGutter, gutter1]
|
||||
gutter2 = editor.addGutter({name: 'test-gutter-2'})
|
||||
expect(payloads).toEqual [lineNumberGutter, gutter1, gutter2]
|
||||
it 'calls the callback immediately with each existing gutter, and with each added gutter after that.', ->
|
||||
lineNumberGutter = editor.gutterWithName('line-number')
|
||||
editor.observeGutters(callback)
|
||||
expect(payloads).toEqual [lineNumberGutter]
|
||||
gutter1 = editor.addGutter({name: 'test-gutter-1'})
|
||||
expect(payloads).toEqual [lineNumberGutter, gutter1]
|
||||
gutter2 = editor.addGutter({name: 'test-gutter-2'})
|
||||
expect(payloads).toEqual [lineNumberGutter, gutter1, gutter2]
|
||||
|
||||
it 'does not call the callback when a gutter is removed.', ->
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
editor.observeGutters(callback)
|
||||
payloads = []
|
||||
gutter.destroy()
|
||||
expect(payloads).toEqual []
|
||||
it 'does not call the callback when a gutter is removed.', ->
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
editor.observeGutters(callback)
|
||||
payloads = []
|
||||
gutter.destroy()
|
||||
expect(payloads).toEqual []
|
||||
|
||||
it 'does not call the callback after the subscription has been disposed.', ->
|
||||
subscription = editor.observeGutters(callback)
|
||||
payloads = []
|
||||
subscription.dispose()
|
||||
editor.addGutter({name: 'test-gutter'})
|
||||
expect(payloads).toEqual []
|
||||
it 'does not call the callback after the subscription has been disposed.', ->
|
||||
subscription = editor.observeGutters(callback)
|
||||
payloads = []
|
||||
subscription.dispose()
|
||||
editor.addGutter({name: 'test-gutter'})
|
||||
expect(payloads).toEqual []
|
||||
|
||||
describe '::onDidAddGutter', ->
|
||||
[payloads, callback] = []
|
||||
describe '::onDidAddGutter', ->
|
||||
[payloads, callback] = []
|
||||
|
||||
beforeEach ->
|
||||
payloads = []
|
||||
callback = (payload) ->
|
||||
payloads.push(payload)
|
||||
beforeEach ->
|
||||
payloads = []
|
||||
callback = (payload) ->
|
||||
payloads.push(payload)
|
||||
|
||||
it 'calls the callback with each newly-added gutter, but not with existing gutters.', ->
|
||||
editor.onDidAddGutter(callback)
|
||||
expect(payloads).toEqual []
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
expect(payloads).toEqual [gutter]
|
||||
it 'calls the callback with each newly-added gutter, but not with existing gutters.', ->
|
||||
editor.onDidAddGutter(callback)
|
||||
expect(payloads).toEqual []
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
expect(payloads).toEqual [gutter]
|
||||
|
||||
it 'does not call the callback after the subscription has been disposed.', ->
|
||||
subscription = editor.onDidAddGutter(callback)
|
||||
payloads = []
|
||||
subscription.dispose()
|
||||
editor.addGutter({name: 'test-gutter'})
|
||||
expect(payloads).toEqual []
|
||||
it 'does not call the callback after the subscription has been disposed.', ->
|
||||
subscription = editor.onDidAddGutter(callback)
|
||||
payloads = []
|
||||
subscription.dispose()
|
||||
editor.addGutter({name: 'test-gutter'})
|
||||
expect(payloads).toEqual []
|
||||
|
||||
describe '::onDidRemoveGutter', ->
|
||||
[payloads, callback] = []
|
||||
describe '::onDidRemoveGutter', ->
|
||||
[payloads, callback] = []
|
||||
|
||||
beforeEach ->
|
||||
payloads = []
|
||||
callback = (payload) ->
|
||||
payloads.push(payload)
|
||||
beforeEach ->
|
||||
payloads = []
|
||||
callback = (payload) ->
|
||||
payloads.push(payload)
|
||||
|
||||
it 'calls the callback when a gutter is removed.', ->
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
editor.onDidRemoveGutter(callback)
|
||||
expect(payloads).toEqual []
|
||||
gutter.destroy()
|
||||
expect(payloads).toEqual ['test-gutter']
|
||||
it 'calls the callback when a gutter is removed.', ->
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
editor.onDidRemoveGutter(callback)
|
||||
expect(payloads).toEqual []
|
||||
gutter.destroy()
|
||||
expect(payloads).toEqual ['test-gutter']
|
||||
|
||||
it 'does not call the callback after the subscription has been disposed.', ->
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
subscription = editor.onDidRemoveGutter(callback)
|
||||
subscription.dispose()
|
||||
gutter.destroy()
|
||||
expect(payloads).toEqual []
|
||||
it 'does not call the callback after the subscription has been disposed.', ->
|
||||
gutter = editor.addGutter({name: 'test-gutter'})
|
||||
subscription = editor.onDidRemoveGutter(callback)
|
||||
subscription.dispose()
|
||||
gutter.destroy()
|
||||
expect(payloads).toEqual []
|
||||
|
||||
describe "decorations", ->
|
||||
describe "::decorateMarker", ->
|
||||
it "includes the decoration in the object returned from ::decorationsStateForScreenRowRange", ->
|
||||
marker = editor.markBufferRange([[2, 4], [6, 8]])
|
||||
decoration = editor.decorateMarker(marker, type: 'highlight', class: 'foo')
|
||||
expect(editor.decorationsStateForScreenRowRange(0, 5)[decoration.id]).toEqual {
|
||||
properties: {type: 'highlight', class: 'foo'}
|
||||
screenRange: marker.getScreenRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
describe "::decorateMarkerLayer", ->
|
||||
it "based on the markers in the layer, includes multiple decoration objects with the same properties and different ranges in the object returned from ::decorationsStateForScreenRowRange", ->
|
||||
layer1 = editor.getBuffer().addMarkerLayer()
|
||||
marker1 = layer1.markRange([[2, 4], [6, 8]])
|
||||
marker2 = layer1.markRange([[11, 0], [11, 12]])
|
||||
layer2 = editor.getBuffer().addMarkerLayer()
|
||||
marker3 = layer2.markRange([[8, 0], [9, 0]])
|
||||
|
||||
layer1Decoration1 = editor.decorateMarkerLayer(layer1, type: 'highlight', class: 'foo')
|
||||
layer1Decoration2 = editor.decorateMarkerLayer(layer1, type: 'highlight', class: 'bar')
|
||||
layer2Decoration = editor.decorateMarkerLayer(layer2, type: 'highlight', class: 'baz')
|
||||
|
||||
decorationState = editor.decorationsStateForScreenRowRange(0, 13)
|
||||
|
||||
expect(decorationState["#{layer1Decoration1.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'foo'},
|
||||
screenRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration1.id}-#{marker2.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'foo'},
|
||||
screenRange: marker2.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker2.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker2.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer2Decoration.id}-#{marker3.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'baz'},
|
||||
screenRange: marker3.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
layer1Decoration1.destroy()
|
||||
|
||||
decorationState = editor.decorationsStateForScreenRowRange(0, 12)
|
||||
expect(decorationState["#{layer1Decoration1.id}-#{marker1.id}"]).toBeUndefined()
|
||||
expect(decorationState["#{layer1Decoration1.id}-#{marker2.id}"]).toBeUndefined()
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker2.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker2.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
expect(decorationState["#{layer2Decoration.id}-#{marker3.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'baz'},
|
||||
screenRange: marker3.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
layer1Decoration2.setPropertiesForMarker(marker1, {type: 'highlight', class: 'quux'})
|
||||
decorationState = editor.decorationsStateForScreenRowRange(0, 12)
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'quux'},
|
||||
screenRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
layer1Decoration2.setPropertiesForMarker(marker1, null)
|
||||
decorationState = editor.decorationsStateForScreenRowRange(0, 12)
|
||||
expect(decorationState["#{layer1Decoration2.id}-#{marker1.id}"]).toEqual {
|
||||
properties: {type: 'highlight', class: 'bar'},
|
||||
screenRange: marker1.getRange(),
|
||||
rangeIsReversed: false
|
||||
}
|
||||
|
||||
@@ -209,3 +209,21 @@ describe "ViewRegistry", ->
|
||||
window.dispatchEvent(new UIEvent('resize'))
|
||||
|
||||
expect(events).toEqual ['poll 1', 'poll 2']
|
||||
|
||||
describe "::getNextUpdatePromise()", ->
|
||||
it "returns a promise that resolves at the end of the next update cycle", ->
|
||||
updateCalled = false
|
||||
readCalled = false
|
||||
pollCalled = false
|
||||
|
||||
waitsFor 'getNextUpdatePromise to resolve', (done) ->
|
||||
registry.getNextUpdatePromise().then ->
|
||||
expect(updateCalled).toBe true
|
||||
expect(readCalled).toBe true
|
||||
expect(pollCalled).toBe true
|
||||
done()
|
||||
|
||||
registry.updateDocument -> updateCalled = true
|
||||
registry.readDocument -> readCalled = true
|
||||
registry.pollDocument -> pollCalled = true
|
||||
registry.pollAfterNextUpdate()
|
||||
|
||||
@@ -7,7 +7,7 @@ Model = require './model'
|
||||
# where text can be inserted.
|
||||
#
|
||||
# Cursors belong to {TextEditor}s and have some metadata attached in the form
|
||||
# of a {Marker}.
|
||||
# of a {TextEditorMarker}.
|
||||
module.exports =
|
||||
class Cursor extends Model
|
||||
screenPosition: null
|
||||
@@ -127,7 +127,7 @@ class Cursor extends Model
|
||||
Section: Cursor Position Details
|
||||
###
|
||||
|
||||
# Public: Returns the underlying {Marker} for the cursor.
|
||||
# Public: Returns the underlying {TextEditorMarker} for the cursor.
|
||||
# Useful with overlay {Decoration}s.
|
||||
getMarker: -> @marker
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
decorationParams.gutterName = 'line-number'
|
||||
decorationParams
|
||||
|
||||
# Essential: Represents a decoration that follows a {Marker}. A decoration is
|
||||
# Essential: Represents a decoration that follows a {TextEditorMarker}. A decoration is
|
||||
# basically a visual representation of a marker. It allows you to add CSS
|
||||
# classes to line numbers in the gutter, lines, and add selection-line regions
|
||||
# around marked ranges of text.
|
||||
@@ -25,7 +25,7 @@ translateDecorationParamsOldToNew = (decorationParams) ->
|
||||
# decoration = editor.decorateMarker(marker, {type: 'line', class: 'my-line-class'})
|
||||
# ```
|
||||
#
|
||||
# Best practice for destroying the decoration is by destroying the {Marker}.
|
||||
# Best practice for destroying the decoration is by destroying the {TextEditorMarker}.
|
||||
#
|
||||
# ```coffee
|
||||
# marker.destroy()
|
||||
@@ -67,20 +67,19 @@ class Decoration
|
||||
@emitter = new Emitter
|
||||
@id = nextId()
|
||||
@setProperties properties
|
||||
@properties.id = @id
|
||||
@flashQueue = null
|
||||
@destroyed = false
|
||||
@markerDestroyDisposable = @marker.onDidDestroy => @destroy()
|
||||
|
||||
# Essential: Destroy this marker.
|
||||
#
|
||||
# If you own the marker, you should use {Marker::destroy} which will destroy
|
||||
# If you own the marker, you should use {TextEditorMarker::destroy} which will destroy
|
||||
# this decoration.
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@markerDestroyDisposable.dispose()
|
||||
@markerDestroyDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyDecoration(this)
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
@@ -150,9 +149,9 @@ class Decoration
|
||||
return if @destroyed
|
||||
oldProperties = @properties
|
||||
@properties = translateDecorationParamsOldToNew(newProperties)
|
||||
@properties.id = @id
|
||||
if newProperties.type?
|
||||
@displayBuffer.decorationDidChangeType(this)
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
|
||||
|
||||
###
|
||||
@@ -165,15 +164,10 @@ class Decoration
|
||||
return false if @properties[key] isnt value
|
||||
true
|
||||
|
||||
onDidFlash: (callback) ->
|
||||
@emitter.on 'did-flash', callback
|
||||
|
||||
flash: (klass, duration=500) ->
|
||||
flashObject = {class: klass, duration}
|
||||
@flashQueue ?= []
|
||||
@flashQueue.push(flashObject)
|
||||
@properties.flashCount ?= 0
|
||||
@properties.flashCount++
|
||||
@properties.flashClass = klass
|
||||
@properties.flashDuration = duration
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-flash'
|
||||
|
||||
consumeNextFlash: ->
|
||||
return @flashQueue.shift() if @flashQueue?.length > 0
|
||||
null
|
||||
|
||||
@@ -7,7 +7,8 @@ Fold = require './fold'
|
||||
Model = require './model'
|
||||
Token = require './token'
|
||||
Decoration = require './decoration'
|
||||
Marker = require './marker'
|
||||
LayerDecoration = require './layer-decoration'
|
||||
TextEditorMarkerLayer = require './text-editor-marker-layer'
|
||||
|
||||
class BufferToScreenConversionError extends Error
|
||||
constructor: (@message, @metadata) ->
|
||||
@@ -25,9 +26,12 @@ class DisplayBuffer extends Model
|
||||
defaultCharWidth: null
|
||||
height: null
|
||||
width: null
|
||||
didUpdateDecorationsEventScheduled: false
|
||||
updatedSynchronously: false
|
||||
|
||||
@deserialize: (state, atomEnvironment) ->
|
||||
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer, atomEnvironment)
|
||||
state.foldsMarkerLayer = state.tokenizedBuffer.buffer.getMarkerLayer(state.foldsMarkerLayerId)
|
||||
state.config = atomEnvironment.config
|
||||
state.assert = atomEnvironment.assert
|
||||
state.grammarRegistry = atomEnvironment.grammars
|
||||
@@ -38,8 +42,8 @@ class DisplayBuffer extends Model
|
||||
super
|
||||
|
||||
{
|
||||
tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles,
|
||||
@largeFileMode, @config, @assert, @grammarRegistry, @packageManager
|
||||
tabLength, @editorWidthInChars, @tokenizedBuffer, @foldsMarkerLayer, buffer,
|
||||
ignoreInvisibles, @largeFileMode, @config, @assert, @grammarRegistry, @packageManager
|
||||
} = params
|
||||
|
||||
@emitter = new Emitter
|
||||
@@ -51,17 +55,22 @@ class DisplayBuffer extends Model
|
||||
})
|
||||
@buffer = @tokenizedBuffer.buffer
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@defaultMarkerLayer = new TextEditorMarkerLayer(this, @buffer.getDefaultMarkerLayer(), true)
|
||||
@customMarkerLayersById = {}
|
||||
@foldsByMarkerId = {}
|
||||
@decorationsById = {}
|
||||
@decorationsByMarkerId = {}
|
||||
@overlayDecorationsById = {}
|
||||
@layerDecorationsByMarkerLayerId = {}
|
||||
@decorationCountsByLayerId = {}
|
||||
@layerUpdateDisposablesByLayerId = {}
|
||||
|
||||
@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()))
|
||||
@disposables.add @buffer.onDidCreateMarker @didCreateDefaultLayerMarker
|
||||
|
||||
@foldsMarkerLayer ?= @buffer.addMarkerLayer()
|
||||
folds = (new Fold(this, marker) for marker in @foldsMarkerLayer.getMarkers())
|
||||
@updateAllScreenLines()
|
||||
@decorateFold(fold) for fold in folds
|
||||
|
||||
@@ -107,17 +116,15 @@ class DisplayBuffer extends Model
|
||||
editorWidthInChars: @editorWidthInChars
|
||||
tokenizedBuffer: @tokenizedBuffer.serialize()
|
||||
largeFileMode: @largeFileMode
|
||||
foldsMarkerLayerId: @foldsMarkerLayer.id
|
||||
|
||||
copy: ->
|
||||
newDisplayBuffer = new DisplayBuffer({
|
||||
foldsMarkerLayer = @foldsMarkerLayer.copy()
|
||||
new DisplayBuffer({
|
||||
@buffer, tabLength: @getTabLength(), @largeFileMode, @config, @assert,
|
||||
@grammarRegistry, @packageManager
|
||||
@grammarRegistry, @packageManager, foldsMarkerLayer
|
||||
})
|
||||
|
||||
for marker in @findMarkers(displayBufferId: @id)
|
||||
marker.copy(displayBufferId: newDisplayBuffer.id)
|
||||
newDisplayBuffer
|
||||
|
||||
updateAllScreenLines: ->
|
||||
@maxLineLength = 0
|
||||
@screenLines = []
|
||||
@@ -158,6 +165,9 @@ class DisplayBuffer extends Model
|
||||
onDidUpdateMarkers: (callback) ->
|
||||
@emitter.on 'did-update-markers', callback
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@emitter.on 'did-update-decorations', callback
|
||||
|
||||
emitDidChange: (eventProperties, refreshMarkers=true) ->
|
||||
@emitter.emit 'did-change', eventProperties
|
||||
if refreshMarkers
|
||||
@@ -177,6 +187,8 @@ class DisplayBuffer extends Model
|
||||
# visible - A {Boolean} indicating of the tokenized buffer is shown
|
||||
setVisible: (visible) -> @tokenizedBuffer.setVisible(visible)
|
||||
|
||||
setUpdatedSynchronously: (@updatedSynchronously) ->
|
||||
|
||||
getVerticalScrollMargin: ->
|
||||
maxScrollMargin = Math.floor(((@getHeight() / @getLineHeightInPixels()) - 1) / 2)
|
||||
Math.min(@verticalScrollMargin, maxScrollMargin)
|
||||
@@ -386,10 +398,14 @@ class DisplayBuffer extends Model
|
||||
# Returns the new {Fold}.
|
||||
createFold: (startRow, endRow) ->
|
||||
unless @largeFileMode
|
||||
foldMarker =
|
||||
@findFoldMarker({startRow, endRow}) ?
|
||||
@buffer.markRange([[startRow, 0], [endRow, Infinity]], @getFoldMarkerAttributes())
|
||||
@foldForMarker(foldMarker)
|
||||
if foldMarker = @findFoldMarker({startRow, endRow})
|
||||
@foldForMarker(foldMarker)
|
||||
else
|
||||
foldMarker = @foldsMarkerLayer.markRange([[startRow, 0], [endRow, Infinity]])
|
||||
fold = new Fold(this, foldMarker)
|
||||
fold.updateDisplayBuffer()
|
||||
@decorateFold(fold)
|
||||
fold
|
||||
|
||||
isFoldedAtBufferRow: (bufferRow) ->
|
||||
@largestFoldContainingBufferRow(bufferRow)?
|
||||
@@ -769,52 +785,68 @@ class DisplayBuffer extends Model
|
||||
decorationsByMarkerId[marker.id] = decorations
|
||||
decorationsByMarkerId
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
decorationsState = {}
|
||||
|
||||
for layerId of @decorationCountsByLayerId
|
||||
layer = @getMarkerLayer(layerId)
|
||||
|
||||
for marker in layer.findMarkers(intersectsScreenRowRange: [startScreenRow, endScreenRow]) when marker.isValid()
|
||||
screenRange = marker.getScreenRange()
|
||||
rangeIsReversed = marker.isReversed()
|
||||
|
||||
if decorations = @decorationsByMarkerId[marker.id]
|
||||
for decoration in decorations
|
||||
decorationsState[decoration.id] = {
|
||||
properties: decoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
if layerDecorations = @layerDecorationsByMarkerLayerId[layerId]
|
||||
for layerDecoration in layerDecorations
|
||||
decorationsState["#{layerDecoration.id}-#{marker.id}"] = {
|
||||
properties: layerDecoration.overridePropertiesByMarkerId[marker.id] ? layerDecoration.properties
|
||||
screenRange, rangeIsReversed
|
||||
}
|
||||
|
||||
decorationsState
|
||||
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
marker = @getMarker(marker.id)
|
||||
marker = @getMarkerLayer(marker.layer.id).getMarker(marker.id)
|
||||
decoration = new Decoration(marker, this, decorationParams)
|
||||
decorationDestroyedDisposable = decoration.onDidDestroy =>
|
||||
@removeDecoration(decoration)
|
||||
@disposables.remove(decorationDestroyedDisposable)
|
||||
@disposables.add(decorationDestroyedDisposable)
|
||||
@decorationsByMarkerId[marker.id] ?= []
|
||||
@decorationsByMarkerId[marker.id].push(decoration)
|
||||
@overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
|
||||
@decorationsById[decoration.id] = decoration
|
||||
@observeDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
@emitter.emit 'did-add-decoration', decoration
|
||||
decoration
|
||||
|
||||
removeDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
decoration = new LayerDecoration(markerLayer, this, decorationParams)
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id] ?= []
|
||||
@layerDecorationsByMarkerLayerId[markerLayer.id].push(decoration)
|
||||
@observeDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
decoration
|
||||
|
||||
decorationsForMarkerId: (markerId) ->
|
||||
@decorationsByMarkerId[markerId]
|
||||
|
||||
# Retrieves a {Marker} based on its id.
|
||||
# Retrieves a {TextEditorMarker} based on its id.
|
||||
#
|
||||
# id - A {Number} representing a marker id
|
||||
#
|
||||
# Returns the {Marker} (if it exists).
|
||||
# Returns the {TextEditorMarker} (if it exists).
|
||||
getMarker: (id) ->
|
||||
unless marker = @markers[id]
|
||||
if bufferMarker = @buffer.getMarker(id)
|
||||
marker = new Marker({bufferMarker, displayBuffer: this})
|
||||
@markers[id] = marker
|
||||
marker
|
||||
@defaultMarkerLayer.getMarker(id)
|
||||
|
||||
# Retrieves the active markers in the buffer.
|
||||
#
|
||||
# Returns an {Array} of existing {Marker}s.
|
||||
# Returns an {Array} of existing {TextEditorMarker}s.
|
||||
getMarkers: ->
|
||||
@buffer.getMarkers().map ({id}) => @getMarker(id)
|
||||
@defaultMarkerLayer.getMarkers()
|
||||
|
||||
getMarkerCount: ->
|
||||
@buffer.getMarkerCount()
|
||||
@@ -822,54 +854,46 @@ class DisplayBuffer extends Model
|
||||
# Public: Constructs a new marker at the given screen range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenRange: (args...) ->
|
||||
bufferRange = @bufferRangeForScreenRange(args.shift())
|
||||
@markBufferRange(bufferRange, args...)
|
||||
markScreenRange: (screenRange, options) ->
|
||||
@defaultMarkerLayer.markScreenRange(screenRange, options)
|
||||
|
||||
# Public: Constructs a new marker at the given buffer range.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferRange: (range, options) ->
|
||||
@getMarker(@buffer.markRange(range, options).id)
|
||||
markBufferRange: (bufferRange, options) ->
|
||||
@defaultMarkerLayer.markBufferRange(bufferRange, options)
|
||||
|
||||
# Public: Constructs a new marker at the given screen position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markScreenPosition: (screenPosition, options) ->
|
||||
@markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options)
|
||||
@defaultMarkerLayer.markScreenPosition(screenPosition, options)
|
||||
|
||||
# Public: Constructs a new marker at the given buffer position.
|
||||
#
|
||||
# range - The marker {Range} (representing the distance between the head and tail)
|
||||
# options - Options to pass to the {Marker} constructor
|
||||
# options - Options to pass to the {TextEditorMarker} constructor
|
||||
#
|
||||
# Returns a {Number} representing the new marker's ID.
|
||||
markBufferPosition: (bufferPosition, options) ->
|
||||
@getMarker(@buffer.markPosition(bufferPosition, options).id)
|
||||
|
||||
# Public: Removes the marker with the given id.
|
||||
#
|
||||
# id - The {Number} of the ID to remove
|
||||
destroyMarker: (id) ->
|
||||
@buffer.destroyMarker(id)
|
||||
delete @markers[id]
|
||||
@defaultMarkerLayer.markBufferPosition(bufferPosition, options)
|
||||
|
||||
# Finds the first marker satisfying the given attributes
|
||||
#
|
||||
# Refer to {DisplayBuffer::findMarkers} for details.
|
||||
#
|
||||
# Returns a {Marker} or null
|
||||
# Returns a {TextEditorMarker} or null
|
||||
findMarker: (params) ->
|
||||
@findMarkers(params)[0]
|
||||
@defaultMarkerLayer.findMarkers(params)[0]
|
||||
|
||||
# Public: Find all markers satisfying a set of parameters.
|
||||
#
|
||||
@@ -888,69 +912,36 @@ class DisplayBuffer extends Model
|
||||
# :containedInBufferRange - A {Range} or range-compatible {Array}. Only
|
||||
# returns markers contained within this range.
|
||||
#
|
||||
# Returns an {Array} of {Marker}s
|
||||
# Returns an {Array} of {TextEditorMarker}s
|
||||
findMarkers: (params) ->
|
||||
params = @translateToBufferMarkerParams(params)
|
||||
@buffer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id)
|
||||
@defaultMarkerLayer.findMarkers(params)
|
||||
|
||||
translateToBufferMarkerParams: (params) ->
|
||||
bufferMarkerParams = {}
|
||||
for key, value of params
|
||||
switch key
|
||||
when 'startBufferRow'
|
||||
key = 'startRow'
|
||||
when 'endBufferRow'
|
||||
key = 'endRow'
|
||||
when 'startScreenRow'
|
||||
key = 'startRow'
|
||||
value = @bufferRowForScreenRow(value)
|
||||
when 'endScreenRow'
|
||||
key = 'endRow'
|
||||
value = @bufferRowForScreenRow(value)
|
||||
when 'intersectsBufferRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
when 'intersectsScreenRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
[startRow, endRow] = value
|
||||
value = [@bufferRowForScreenRow(startRow), @bufferRowForScreenRow(endRow)]
|
||||
when 'containsBufferRange'
|
||||
key = 'containsRange'
|
||||
when 'containsBufferPosition'
|
||||
key = 'containsPosition'
|
||||
when 'containedInBufferRange'
|
||||
key = 'containedInRange'
|
||||
when 'containedInScreenRange'
|
||||
key = 'containedInRange'
|
||||
value = @bufferRangeForScreenRange(value)
|
||||
when 'intersectsBufferRange'
|
||||
key = 'intersectsRange'
|
||||
when 'intersectsScreenRange'
|
||||
key = 'intersectsRange'
|
||||
value = @bufferRangeForScreenRange(value)
|
||||
bufferMarkerParams[key] = value
|
||||
addMarkerLayer: (options) ->
|
||||
bufferLayer = @buffer.addMarkerLayer(options)
|
||||
@getMarkerLayer(bufferLayer.id)
|
||||
|
||||
bufferMarkerParams
|
||||
getMarkerLayer: (id) ->
|
||||
if layer = @customMarkerLayersById[id]
|
||||
layer
|
||||
else if bufferLayer = @buffer.getMarkerLayer(id)
|
||||
@customMarkerLayersById[id] = new TextEditorMarkerLayer(this, bufferLayer)
|
||||
|
||||
findFoldMarker: (attributes) ->
|
||||
@findFoldMarkers(attributes)[0]
|
||||
getDefaultMarkerLayer: -> @defaultMarkerLayer
|
||||
|
||||
findFoldMarkers: (attributes) ->
|
||||
@buffer.findMarkers(@getFoldMarkerAttributes(attributes))
|
||||
findFoldMarker: (params) ->
|
||||
@findFoldMarkers(params)[0]
|
||||
|
||||
getFoldMarkerAttributes: (attributes) ->
|
||||
if attributes
|
||||
_.extend(attributes, @foldMarkerAttributes)
|
||||
else
|
||||
@foldMarkerAttributes
|
||||
findFoldMarkers: (params) ->
|
||||
@foldsMarkerLayer.findMarkers(params)
|
||||
|
||||
refreshMarkerScreenPositions: ->
|
||||
for marker in @getMarkers()
|
||||
marker.notifyObservers(textChanged: false)
|
||||
@defaultMarkerLayer.refreshMarkerScreenPositions()
|
||||
layer.refreshMarkerScreenPositions() for id, layer of @customMarkerLayersById
|
||||
return
|
||||
|
||||
destroyed: ->
|
||||
fold.destroy() for markerId, fold of @foldsByMarkerId
|
||||
marker.disposables.dispose() for id, marker of @markers
|
||||
@defaultMarkerLayer.destroy()
|
||||
@foldsMarkerLayer.destroy()
|
||||
@scopedConfigSubscriptions.dispose()
|
||||
@disposables.dispose()
|
||||
@tokenizedBuffer.destroy()
|
||||
@@ -1072,17 +1063,23 @@ class DisplayBuffer extends Model
|
||||
@longestScreenRow = screenRow
|
||||
@maxLineLength = length
|
||||
|
||||
handleBufferMarkerCreated: (textBufferMarker) =>
|
||||
if textBufferMarker.matchesParams(@getFoldMarkerAttributes())
|
||||
fold = new Fold(this, textBufferMarker)
|
||||
fold.updateDisplayBuffer()
|
||||
@decorateFold(fold)
|
||||
|
||||
didCreateDefaultLayerMarker: (textBufferMarker) =>
|
||||
if marker = @getMarker(textBufferMarker.id)
|
||||
# The marker might have been removed in some other handler called before
|
||||
# this one. Only emit when the marker still exists.
|
||||
@emitter.emit 'did-create-marker', marker
|
||||
|
||||
scheduleUpdateDecorationsEvent: ->
|
||||
if @updatedSynchronously
|
||||
@emitter.emit 'did-update-decorations'
|
||||
return
|
||||
|
||||
unless @didUpdateDecorationsEventScheduled
|
||||
@didUpdateDecorationsEventScheduled = true
|
||||
process.nextTick =>
|
||||
@didUpdateDecorationsEventScheduled = false
|
||||
@emitter.emit 'did-update-decorations'
|
||||
|
||||
decorateFold: (fold) ->
|
||||
@decorateMarker(fold.marker, type: 'line-number', class: 'folded')
|
||||
|
||||
@@ -1095,6 +1092,42 @@ class DisplayBuffer extends Model
|
||||
else
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
{marker} = decoration
|
||||
return unless decorations = @decorationsByMarkerId[marker.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @decorationsById[decoration.id]
|
||||
@emitter.emit 'did-remove-decoration', decoration
|
||||
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
|
||||
delete @overlayDecorationsById[decoration.id]
|
||||
@unobserveDecoratedLayer(marker.layer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
didDestroyLayerDecoration: (decoration) ->
|
||||
{markerLayer} = decoration
|
||||
return unless decorations = @layerDecorationsByMarkerLayerId[markerLayer.id]
|
||||
index = decorations.indexOf(decoration)
|
||||
|
||||
if index > -1
|
||||
decorations.splice(index, 1)
|
||||
delete @layerDecorationsByMarkerLayerId[markerLayer.id] if decorations.length is 0
|
||||
@unobserveDecoratedLayer(markerLayer)
|
||||
@scheduleUpdateDecorationsEvent()
|
||||
|
||||
observeDecoratedLayer: (layer) ->
|
||||
@decorationCountsByLayerId[layer.id] ?= 0
|
||||
if ++@decorationCountsByLayerId[layer.id] is 1
|
||||
@layerUpdateDisposablesByLayerId[layer.id] = layer.onDidUpdate(@scheduleUpdateDecorationsEvent.bind(this))
|
||||
|
||||
unobserveDecoratedLayer: (layer) ->
|
||||
if --@decorationCountsByLayerId[layer.id] is 0
|
||||
@layerUpdateDisposablesByLayerId[layer.id].dispose()
|
||||
delete @decorationCountsByLayerId[layer.id]
|
||||
delete @layerUpdateDisposablesByLayerId[layer.id]
|
||||
|
||||
checkScreenLinesInvariant: ->
|
||||
return if @isSoftWrapped()
|
||||
return if _.size(@foldsByMarkerId) > 0
|
||||
|
||||
@@ -71,13 +71,13 @@ class Gutter
|
||||
isVisible: ->
|
||||
@visible
|
||||
|
||||
# Essential: Add a decoration that tracks a {Marker}. When the marker moves,
|
||||
# Essential: Add a decoration that tracks a {TextEditorMarker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect
|
||||
# the marker's state.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
# * `marker` A {Marker} you want this decoration to follow.
|
||||
# * `marker` A {TextEditorMarker} you want this decoration to follow.
|
||||
# * `decorationParams` An {Object} representing the decoration. It is passed
|
||||
# to {TextEditor::decorateMarker} as its `decorationParams` and so supports
|
||||
# all options documented there.
|
||||
|
||||
61
src/layer-decoration.coffee
Normal file
61
src/layer-decoration.coffee
Normal file
@@ -0,0 +1,61 @@
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
idCounter = 0
|
||||
nextId = -> idCounter++
|
||||
|
||||
# Essential: Represents a decoration that applies to every marker on a given
|
||||
# layer. Created via {TextEditor::decorateMarkerLayer}.
|
||||
module.exports =
|
||||
class LayerDecoration
|
||||
constructor: (@markerLayer, @displayBuffer, @properties) ->
|
||||
@id = nextId()
|
||||
@destroyed = false
|
||||
@markerLayerDestroyedDisposable = @markerLayer.onDidDestroy => @destroy()
|
||||
@overridePropertiesByMarkerId = {}
|
||||
|
||||
# Essential: Destroys the decoration.
|
||||
destroy: ->
|
||||
return if @destroyed
|
||||
@markerLayerDestroyedDisposable.dispose()
|
||||
@markerLayerDestroyedDisposable = null
|
||||
@destroyed = true
|
||||
@displayBuffer.didDestroyLayerDecoration(this)
|
||||
|
||||
# Essential: Determine whether this decoration is destroyed.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isDestroyed: -> @destroyed
|
||||
|
||||
getId: -> @id
|
||||
|
||||
getMarkerLayer: -> @markerLayer
|
||||
|
||||
# Essential: Get this decoration's properties.
|
||||
#
|
||||
# Returns an {Object}.
|
||||
getProperties: ->
|
||||
@properties
|
||||
|
||||
# Essential: Set this decoration's properties.
|
||||
#
|
||||
# * `newProperties` See {TextEditor::decorateMarker} for more information on
|
||||
# the properties. The `type` of `gutter` and `overlay` are not supported on
|
||||
# layer decorations.
|
||||
setProperties: (newProperties) ->
|
||||
return if @destroyed
|
||||
@properties = newProperties
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
|
||||
# Essential: Override the decoration properties for a specific marker.
|
||||
#
|
||||
# * `marker` The {TextEditorMarker} or {Marker} for which to override
|
||||
# properties.
|
||||
# * `properties` An {Object} containing properties to apply to this marker.
|
||||
# Pass `null` to clear the override.
|
||||
setPropertiesForMarker: (marker, properties) ->
|
||||
return if @destroyed
|
||||
if properties?
|
||||
@overridePropertiesByMarkerId[marker.id] = properties
|
||||
else
|
||||
delete @overridePropertiesByMarkerId[marker.id]
|
||||
@displayBuffer.scheduleUpdateDecorationsEvent()
|
||||
@@ -220,7 +220,7 @@ class TextEditorComponent
|
||||
@updatesPaused = false
|
||||
if @updateRequestedWhilePaused and @canUpdate()
|
||||
@updateRequestedWhilePaused = false
|
||||
@updateSync()
|
||||
@requestUpdate()
|
||||
|
||||
getTopmostDOMNode: ->
|
||||
@hostElement
|
||||
|
||||
@@ -103,6 +103,7 @@ class TextEditorElement extends HTMLElement
|
||||
return if model.isDestroyed()
|
||||
|
||||
@model = model
|
||||
@model.setUpdatedSynchronously(@isUpdatedSynchronously())
|
||||
@initializeContent()
|
||||
@mountComponent()
|
||||
@addGrammarScopeAttribute()
|
||||
@@ -194,7 +195,9 @@ class TextEditorElement extends HTMLElement
|
||||
hasFocus: ->
|
||||
this is document.activeElement or @contains(document.activeElement)
|
||||
|
||||
setUpdatedSynchronously: (@updatedSynchronously) -> @updatedSynchronously
|
||||
setUpdatedSynchronously: (@updatedSynchronously) ->
|
||||
@model?.setUpdatedSynchronously(@updatedSynchronously)
|
||||
@updatedSynchronously
|
||||
|
||||
isUpdatedSynchronously: -> @updatedSynchronously
|
||||
|
||||
|
||||
192
src/text-editor-marker-layer.coffee
Normal file
192
src/text-editor-marker-layer.coffee
Normal file
@@ -0,0 +1,192 @@
|
||||
TextEditorMarker = require './text-editor-marker'
|
||||
|
||||
# Public: *Experimental:* A container for a related set of markers at the
|
||||
# {TextEditor} level. Wraps an underlying {MarkerLayer} on the editor's
|
||||
# {TextBuffer}.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
module.exports =
|
||||
class TextEditorMarkerLayer
|
||||
constructor: (@displayBuffer, @bufferMarkerLayer, @isDefaultLayer) ->
|
||||
@id = @bufferMarkerLayer.id
|
||||
@markersById = {}
|
||||
|
||||
###
|
||||
Section: Lifecycle
|
||||
###
|
||||
|
||||
# Essential: Destroy this layer.
|
||||
destroy: ->
|
||||
if @isDefaultLayer
|
||||
marker.destroy() for id, marker of @markersById
|
||||
else
|
||||
@bufferMarkerLayer.destroy()
|
||||
|
||||
###
|
||||
Section: Querying
|
||||
###
|
||||
|
||||
# Essential: Get an existing marker by its id.
|
||||
#
|
||||
# Returns a {TextEditorMarker}.
|
||||
getMarker: (id) ->
|
||||
if editorMarker = @markersById[id]
|
||||
editorMarker
|
||||
else if bufferMarker = @bufferMarkerLayer.getMarker(id)
|
||||
@markersById[id] = new TextEditorMarker(this, bufferMarker)
|
||||
|
||||
# Essential: Get all markers in the layer.
|
||||
#
|
||||
# Returns an {Array} of {TextEditorMarker}s.
|
||||
getMarkers: ->
|
||||
@bufferMarkerLayer.getMarkers().map ({id}) => @getMarker(id)
|
||||
|
||||
# Public: Get the number of markers in the marker layer.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getMarkerCount: ->
|
||||
@bufferMarkerLayer.getMarkerCount()
|
||||
|
||||
# Public: Find markers in the layer conforming to the given parameters.
|
||||
#
|
||||
# See the documentation for {TextEditor::findMarkers}.
|
||||
findMarkers: (params) ->
|
||||
params = @translateToBufferMarkerParams(params)
|
||||
@bufferMarkerLayer.findMarkers(params).map (stringMarker) => @getMarker(stringMarker.id)
|
||||
|
||||
###
|
||||
Section: Marker creation
|
||||
###
|
||||
|
||||
# Essential: Create a marker on this layer with the given range in buffer
|
||||
# coordinates.
|
||||
#
|
||||
# See the documentation for {TextEditor::markBufferRange}
|
||||
markBufferRange: (bufferRange, options) ->
|
||||
@getMarker(@bufferMarkerLayer.markRange(bufferRange, options).id)
|
||||
|
||||
# Essential: Create a marker on this layer with the given range in screen
|
||||
# coordinates.
|
||||
#
|
||||
# See the documentation for {TextEditor::markScreenRange}
|
||||
markScreenRange: (screenRange, options) ->
|
||||
bufferRange = @displayBuffer.bufferRangeForScreenRange(screenRange)
|
||||
@markBufferRange(bufferRange, options)
|
||||
|
||||
# Public: Create a marker on this layer with the given buffer position and no
|
||||
# tail.
|
||||
#
|
||||
# See the documentation for {TextEditor::markBufferPosition}
|
||||
markBufferPosition: (bufferPosition, options) ->
|
||||
@getMarker(@bufferMarkerLayer.markPosition(bufferPosition, options).id)
|
||||
|
||||
# Public: Create a marker on this layer with the given screen position and no
|
||||
# tail.
|
||||
#
|
||||
# See the documentation for {TextEditor::markScreenPosition}
|
||||
markScreenPosition: (screenPosition, options) ->
|
||||
bufferPosition = @displayBuffer.bufferPositionForScreenPosition(screenPosition)
|
||||
@markBufferPosition(bufferPosition, options)
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Public: Subscribe to be notified asynchronously whenever markers are
|
||||
# created, updated, or destroyed on this layer. *Prefer this method for
|
||||
# optimal performance when interacting with layers that could contain large
|
||||
# numbers of markers.*
|
||||
#
|
||||
# * `callback` A {Function} that will be called with no arguments when changes
|
||||
# occur on this layer.
|
||||
#
|
||||
# Subscribers are notified once, asynchronously when any number of changes
|
||||
# occur in a given tick of the event loop. You should re-query the layer
|
||||
# to determine the state of markers in which you're interested in. It may
|
||||
# be counter-intuitive, but this is much more efficient than subscribing to
|
||||
# events on individual markers, which are expensive to deliver.
|
||||
#
|
||||
# Returns a {Disposable}.
|
||||
onDidUpdate: (callback) ->
|
||||
@bufferMarkerLayer.onDidUpdate(callback)
|
||||
|
||||
# Public: Subscribe to be notified synchronously whenever markers are created
|
||||
# on this layer. *Avoid this method for optimal performance when interacting
|
||||
# with layers that could contain large numbers of markers.*
|
||||
#
|
||||
# * `callback` A {Function} that will be called with a {TextEditorMarker}
|
||||
# whenever a new marker is created.
|
||||
#
|
||||
# You should prefer {onDidUpdate} when synchronous notifications aren't
|
||||
# absolutely necessary.
|
||||
#
|
||||
# Returns a {Disposable}.
|
||||
onDidCreateMarker: (callback) ->
|
||||
@bufferMarkerLayer.onDidCreateMarker (bufferMarker) =>
|
||||
callback(@getMarker(bufferMarker.id))
|
||||
|
||||
# Public: Subscribe to be notified synchronously when this layer is destroyed.
|
||||
#
|
||||
# Returns a {Disposable}.
|
||||
onDidDestroy: (callback) ->
|
||||
@bufferMarkerLayer.onDidDestroy(callback)
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
refreshMarkerScreenPositions: ->
|
||||
for marker in @getMarkers()
|
||||
marker.notifyObservers(textChanged: false)
|
||||
return
|
||||
|
||||
didDestroyMarker: (marker) ->
|
||||
delete @markersById[marker.id]
|
||||
|
||||
translateToBufferMarkerParams: (params) ->
|
||||
bufferMarkerParams = {}
|
||||
for key, value of params
|
||||
switch key
|
||||
when 'startBufferPosition'
|
||||
key = 'startPosition'
|
||||
when 'endBufferPosition'
|
||||
key = 'endPosition'
|
||||
when 'startScreenPosition'
|
||||
key = 'startPosition'
|
||||
value = @displayBuffer.bufferPositionForScreenPosition(value)
|
||||
when 'endScreenPosition'
|
||||
key = 'endPosition'
|
||||
value = @displayBuffer.bufferPositionForScreenPosition(value)
|
||||
when 'startBufferRow'
|
||||
key = 'startRow'
|
||||
when 'endBufferRow'
|
||||
key = 'endRow'
|
||||
when 'startScreenRow'
|
||||
key = 'startRow'
|
||||
value = @displayBuffer.bufferRowForScreenRow(value)
|
||||
when 'endScreenRow'
|
||||
key = 'endRow'
|
||||
value = @displayBuffer.bufferRowForScreenRow(value)
|
||||
when 'intersectsBufferRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
when 'intersectsScreenRowRange'
|
||||
key = 'intersectsRowRange'
|
||||
[startRow, endRow] = value
|
||||
value = [@displayBuffer.bufferRowForScreenRow(startRow), @displayBuffer.bufferRowForScreenRow(endRow)]
|
||||
when 'containsBufferRange'
|
||||
key = 'containsRange'
|
||||
when 'containsBufferPosition'
|
||||
key = 'containsPosition'
|
||||
when 'containedInBufferRange'
|
||||
key = 'containedInRange'
|
||||
when 'containedInScreenRange'
|
||||
key = 'containedInRange'
|
||||
value = @displayBuffer.bufferRangeForScreenRange(value)
|
||||
when 'intersectsBufferRange'
|
||||
key = 'intersectsRange'
|
||||
when 'intersectsScreenRange'
|
||||
key = 'intersectsRange'
|
||||
value = @displayBuffer.bufferRangeForScreenRange(value)
|
||||
bufferMarkerParams[key] = value
|
||||
|
||||
bufferMarkerParams
|
||||
@@ -6,7 +6,7 @@ _ = require 'underscore-plus'
|
||||
# targets, misspelled words, and anything else that needs to track a logical
|
||||
# location in the buffer over time.
|
||||
#
|
||||
# ### Marker Creation
|
||||
# ### TextEditorMarker Creation
|
||||
#
|
||||
# Use {TextEditor::markBufferRange} rather than creating Markers directly.
|
||||
#
|
||||
@@ -40,7 +40,7 @@ _ = require 'underscore-plus'
|
||||
#
|
||||
# See {TextEditor::markBufferRange} for usage.
|
||||
module.exports =
|
||||
class Marker
|
||||
class TextEditorMarker
|
||||
bufferMarkerSubscription: null
|
||||
oldHeadBufferPosition: null
|
||||
oldHeadScreenPosition: null
|
||||
@@ -53,7 +53,8 @@ class Marker
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: ({@bufferMarker, @displayBuffer}) ->
|
||||
constructor: (@layer, @bufferMarker) ->
|
||||
{@displayBuffer} = @layer
|
||||
@emitter = new Emitter
|
||||
@disposables = new CompositeDisposable
|
||||
@id = @bufferMarker.id
|
||||
@@ -66,7 +67,7 @@ class Marker
|
||||
@bufferMarker.destroy()
|
||||
@disposables.dispose()
|
||||
|
||||
# Essential: Creates and returns a new {Marker} with the same properties as
|
||||
# Essential: Creates and returns a new {TextEditorMarker} with the same properties as
|
||||
# this marker.
|
||||
#
|
||||
# {Selection} markers (markers with a custom property `type: "selection"`)
|
||||
@@ -79,9 +80,9 @@ class Marker
|
||||
# marker. The new marker's properties are computed by extending this marker's
|
||||
# properties with `properties`.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
copy: (properties) ->
|
||||
@displayBuffer.getMarker(@bufferMarker.copy(properties).id)
|
||||
@layer.getMarker(@bufferMarker.copy(properties).id)
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
@@ -129,7 +130,7 @@ class Marker
|
||||
@emitter.on 'did-destroy', callback
|
||||
|
||||
###
|
||||
Section: Marker Details
|
||||
Section: TextEditorMarker Details
|
||||
###
|
||||
|
||||
# Essential: Returns a {Boolean} indicating whether the marker is valid. Markers can be
|
||||
@@ -140,7 +141,7 @@ class Marker
|
||||
# Essential: Returns a {Boolean} indicating whether the marker has been destroyed. A marker
|
||||
# can be invalid without being destroyed, in which case undoing the invalidating
|
||||
# operation would restore the marker. Once a marker is destroyed by calling
|
||||
# {Marker::destroy}, no undo/redo operation can ever bring it back.
|
||||
# {TextEditorMarker::destroy}, no undo/redo operation can ever bring it back.
|
||||
isDestroyed: ->
|
||||
@bufferMarker.isDestroyed()
|
||||
|
||||
@@ -169,7 +170,7 @@ class Marker
|
||||
@bufferMarker.setProperties(properties)
|
||||
|
||||
matchesProperties: (attributes) ->
|
||||
attributes = @displayBuffer.translateToBufferMarkerParams(attributes)
|
||||
attributes = @layer.translateToBufferMarkerParams(attributes)
|
||||
@bufferMarker.matchesParams(attributes)
|
||||
|
||||
###
|
||||
@@ -179,14 +180,14 @@ class Marker
|
||||
# Essential: Returns a {Boolean} indicating whether this marker is equivalent to
|
||||
# another marker, meaning they have the same range and options.
|
||||
#
|
||||
# * `other` {Marker} other marker
|
||||
# * `other` {TextEditorMarker} other marker
|
||||
isEqual: (other) ->
|
||||
return false unless other instanceof @constructor
|
||||
@bufferMarker.isEqual(other.bufferMarker)
|
||||
|
||||
# Essential: Compares this marker to another based on their ranges.
|
||||
#
|
||||
# * `other` {Marker}
|
||||
# * `other` {TextEditorMarker}
|
||||
#
|
||||
# Returns a {Number}
|
||||
compare: (other) ->
|
||||
@@ -225,28 +226,28 @@ class Marker
|
||||
@setBufferRange(@displayBuffer.bufferRangeForScreenRange(screenRange), options)
|
||||
|
||||
# Essential: Retrieves the buffer position of the marker's start. This will always be
|
||||
# less than or equal to the result of {Marker::getEndBufferPosition}.
|
||||
# less than or equal to the result of {TextEditorMarker::getEndBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartBufferPosition: ->
|
||||
@bufferMarker.getStartPosition()
|
||||
|
||||
# Essential: Retrieves the screen position of the marker's start. This will always be
|
||||
# less than or equal to the result of {Marker::getEndScreenPosition}.
|
||||
# less than or equal to the result of {TextEditorMarker::getEndScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Essential: Retrieves the buffer position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {Marker::getStartBufferPosition}.
|
||||
# greater than or equal to the result of {TextEditorMarker::getStartBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndBufferPosition: ->
|
||||
@bufferMarker.getEndPosition()
|
||||
|
||||
# Essential: Retrieves the screen position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {Marker::getStartScreenPosition}.
|
||||
# greater than or equal to the result of {TextEditorMarker::getStartScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndScreenPosition: ->
|
||||
@@ -330,10 +331,10 @@ class Marker
|
||||
|
||||
# Returns a {String} representation of the marker
|
||||
inspect: ->
|
||||
"Marker(id: #{@id}, bufferRange: #{@getBufferRange()})"
|
||||
"TextEditorMarker(id: #{@id}, bufferRange: #{@getBufferRange()})"
|
||||
|
||||
destroyed: ->
|
||||
delete @displayBuffer.markers[@id]
|
||||
@layer.didDestroyMarker(this)
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
@@ -28,10 +28,9 @@ class TextEditorPresenter
|
||||
@emitter = new Emitter
|
||||
@visibleHighlights = {}
|
||||
@characterWidthsByScope = {}
|
||||
@rangesByDecorationId = {}
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow = {}
|
||||
@customGutterDecorationsByGutterName = {}
|
||||
@screenRowsToMeasure = []
|
||||
@transferMeasurementsToModel()
|
||||
@transferMeasurementsFromModel()
|
||||
@@ -49,6 +48,9 @@ class TextEditorPresenter
|
||||
|
||||
destroy: ->
|
||||
@disposables.dispose()
|
||||
clearTimeout(@stoppedScrollingTimeoutId) if @stoppedScrollingTimeoutId?
|
||||
clearInterval(@reflowingInterval) if @reflowingInterval?
|
||||
@stopBlinkingCursors()
|
||||
|
||||
# Calls your `callback` when some changes in the model occurred and the current state has been updated.
|
||||
onDidUpdateState: (callback) ->
|
||||
@@ -185,7 +187,7 @@ class TextEditorPresenter
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add @model.onDidUpdateMarkers =>
|
||||
@disposables.add @model.onDidUpdateDecorations =>
|
||||
@shouldUpdateLinesState = true
|
||||
@shouldUpdateLineNumbersState = true
|
||||
@shouldUpdateDecorations = true
|
||||
@@ -214,10 +216,8 @@ class TextEditorPresenter
|
||||
@shouldUpdateGutterOrderState = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
|
||||
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
|
||||
@disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this))
|
||||
@observeDecoration(decoration) for decoration in @model.getDecorations()
|
||||
@observeCursor(cursor) for cursor in @model.getCursors()
|
||||
@disposables.add @model.onDidAddGutter(@didAddGutter.bind(this))
|
||||
return
|
||||
@@ -626,16 +626,14 @@ class TextEditorPresenter
|
||||
@clearDecorationsForCustomGutterName(gutterName)
|
||||
else
|
||||
@customGutterDecorations[gutterName] = {}
|
||||
continue if not @gutterIsVisible(gutter)
|
||||
|
||||
relevantDecorations = @customGutterDecorationsInRange(gutterName, @startRow, @endRow - 1)
|
||||
relevantDecorations.forEach (decoration) =>
|
||||
decorationRange = decoration.getMarker().getScreenRange()
|
||||
@customGutterDecorations[gutterName][decoration.id] =
|
||||
top: @lineHeight * decorationRange.start.row
|
||||
height: @lineHeight * decorationRange.getRowCount()
|
||||
item: decoration.getProperties().item
|
||||
class: decoration.getProperties().class
|
||||
continue unless @gutterIsVisible(gutter)
|
||||
for decorationId, {properties, screenRange} of @customGutterDecorationsByGutterName[gutterName]
|
||||
@customGutterDecorations[gutterName][decorationId] =
|
||||
top: @lineHeight * screenRange.start.row
|
||||
height: @lineHeight * screenRange.getRowCount()
|
||||
item: properties.item
|
||||
class: properties.class
|
||||
|
||||
clearAllCustomGutterDecorations: ->
|
||||
allGutterNames = Object.keys(@customGutterDecorations)
|
||||
@@ -850,32 +848,20 @@ class TextEditorPresenter
|
||||
return null if @model.isMini()
|
||||
|
||||
decorationClasses = null
|
||||
for id, decoration of @lineDecorationsByScreenRow[row]
|
||||
for id, properties of @lineDecorationsByScreenRow[row]
|
||||
decorationClasses ?= []
|
||||
decorationClasses.push(decoration.getProperties().class)
|
||||
decorationClasses.push(properties.class)
|
||||
decorationClasses
|
||||
|
||||
lineNumberDecorationClassesForRow: (row) ->
|
||||
return null if @model.isMini()
|
||||
|
||||
decorationClasses = null
|
||||
for id, decoration of @lineNumberDecorationsByScreenRow[row]
|
||||
for id, properties of @lineNumberDecorationsByScreenRow[row]
|
||||
decorationClasses ?= []
|
||||
decorationClasses.push(decoration.getProperties().class)
|
||||
decorationClasses.push(properties.class)
|
||||
decorationClasses
|
||||
|
||||
# Returns a {Set} of {Decoration}s on the given custom gutter from startRow to endRow (inclusive).
|
||||
customGutterDecorationsInRange: (gutterName, startRow, endRow) ->
|
||||
decorations = new Set
|
||||
|
||||
return decorations if @model.isMini() or gutterName is 'line-number' or
|
||||
not @customGutterDecorationsByGutterNameAndScreenRow[gutterName]
|
||||
|
||||
for screenRow in [@startRow..@endRow - 1]
|
||||
for id, decoration of @customGutterDecorationsByGutterNameAndScreenRow[gutterName][screenRow]
|
||||
decorations.add(decoration)
|
||||
decorations
|
||||
|
||||
getCursorBlinkPeriod: -> @cursorBlinkPeriod
|
||||
|
||||
getCursorBlinkResumeDelay: -> @cursorBlinkResumeDelay
|
||||
@@ -1183,93 +1169,32 @@ class TextEditorPresenter
|
||||
|
||||
rect
|
||||
|
||||
observeDecoration: (decoration) ->
|
||||
decorationDisposables = new CompositeDisposable
|
||||
if decoration.isType('highlight')
|
||||
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)
|
||||
|
||||
decorationPropertiesDidChange: (decoration, {oldProperties}) ->
|
||||
@shouldUpdateDecorations = true
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
if decoration.isType('line') or Decoration.isType(oldProperties, 'line')
|
||||
@shouldUpdateLinesState = true
|
||||
if decoration.isType('line-number') or Decoration.isType(oldProperties, 'line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
if (decoration.isType('gutter') and not decoration.isType('line-number')) or
|
||||
(Decoration.isType(oldProperties, 'gutter') and not Decoration.isType(oldProperties, 'line-number'))
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
else if decoration.isType('overlay')
|
||||
@shouldUpdateOverlaysState = true
|
||||
@emitDidUpdateState()
|
||||
|
||||
didDestroyDecoration: (decoration) ->
|
||||
@shouldUpdateDecorations = true
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
if decoration.isType('overlay')
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
didAddDecoration: (decoration) ->
|
||||
@observeDecoration(decoration)
|
||||
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@shouldUpdateDecorations = true
|
||||
@shouldUpdateLinesState = true if decoration.isType('line')
|
||||
if decoration.isType('line-number')
|
||||
@shouldUpdateLineNumbersState = true
|
||||
else if decoration.isType('gutter')
|
||||
@shouldUpdateCustomGutterDecorationState = true
|
||||
else if decoration.isType('highlight')
|
||||
@shouldUpdateDecorations = true
|
||||
else if decoration.isType('overlay')
|
||||
@shouldUpdateOverlaysState = true
|
||||
|
||||
@emitDidUpdateState()
|
||||
|
||||
fetchDecorations: ->
|
||||
@decorations = []
|
||||
|
||||
return unless 0 <= @startRow <= @endRow <= Infinity
|
||||
|
||||
for markerId, decorations of @model.decorationsForScreenRowRange(@startRow, @endRow - 1)
|
||||
range = @model.getMarker(markerId).getScreenRange()
|
||||
for decoration in decorations
|
||||
@decorations.push({decoration, range})
|
||||
@decorations = @model.decorationsStateForScreenRowRange(@startRow, @endRow - 1)
|
||||
|
||||
updateLineDecorations: ->
|
||||
@rangesByDecorationId = {}
|
||||
@lineDecorationsByScreenRow = {}
|
||||
@lineNumberDecorationsByScreenRow = {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow = {}
|
||||
@customGutterDecorationsByGutterName = {}
|
||||
|
||||
for {decoration, range} in @decorations
|
||||
if decoration.isType('line') or decoration.isType('gutter')
|
||||
@addToLineDecorationCaches(decoration, range)
|
||||
for decorationId, decorationState of @decorations
|
||||
{properties, screenRange, rangeIsReversed} = decorationState
|
||||
if Decoration.isType(properties, 'line') or Decoration.isType(properties, 'line-number')
|
||||
@addToLineDecorationCaches(decorationId, properties, screenRange, rangeIsReversed)
|
||||
|
||||
else if Decoration.isType(properties, 'gutter') and properties.gutterName?
|
||||
@customGutterDecorationsByGutterName[properties.gutterName] ?= {}
|
||||
@customGutterDecorationsByGutterName[properties.gutterName][decorationId] = decorationState
|
||||
|
||||
return
|
||||
|
||||
updateHighlightDecorations: ->
|
||||
@visibleHighlights = {}
|
||||
|
||||
for {decoration, range} in @decorations
|
||||
if decoration.isType('highlight')
|
||||
@updateHighlightState(decoration, range)
|
||||
for decorationId, {properties, screenRange} of @decorations
|
||||
if Decoration.isType(properties, 'highlight')
|
||||
@updateHighlightState(decorationId, properties, screenRange)
|
||||
|
||||
for tileId, tileState of @state.content.tiles
|
||||
for id, highlight of tileState.highlights
|
||||
@@ -1277,50 +1202,29 @@ class TextEditorPresenter
|
||||
|
||||
return
|
||||
|
||||
removeFromLineDecorationCaches: (decoration) ->
|
||||
@removePropertiesFromLineDecorationCaches(decoration.id, decoration.getProperties())
|
||||
|
||||
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()
|
||||
properties = decoration.getProperties()
|
||||
|
||||
return unless marker.isValid()
|
||||
|
||||
if range.isEmpty()
|
||||
addToLineDecorationCaches: (decorationId, properties, screenRange, rangeIsReversed) ->
|
||||
if screenRange.isEmpty()
|
||||
return if properties.onlyNonEmpty
|
||||
else
|
||||
return if properties.onlyEmpty
|
||||
omitLastRow = range.end.column is 0
|
||||
omitLastRow = screenRange.end.column is 0
|
||||
|
||||
@rangesByDecorationId[decoration.id] = range
|
||||
if rangeIsReversed
|
||||
headPosition = screenRange.start
|
||||
else
|
||||
headPosition = screenRange.end
|
||||
|
||||
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
|
||||
for row in [screenRange.start.row..screenRange.end.row] by 1
|
||||
continue if properties.onlyHead and row isnt headPosition.row
|
||||
continue if omitLastRow and row is screenRange.end.row
|
||||
|
||||
if decoration.isType('line')
|
||||
if Decoration.isType(properties, 'line')
|
||||
@lineDecorationsByScreenRow[row] ?= {}
|
||||
@lineDecorationsByScreenRow[row][decoration.id] = decoration
|
||||
@lineDecorationsByScreenRow[row][decorationId] = properties
|
||||
|
||||
if decoration.isType('line-number')
|
||||
if Decoration.isType(properties, 'line-number')
|
||||
@lineNumberDecorationsByScreenRow[row] ?= {}
|
||||
@lineNumberDecorationsByScreenRow[row][decoration.id] = decoration
|
||||
else if decoration.isType('gutter')
|
||||
gutterName = decoration.getProperties().gutterName
|
||||
@customGutterDecorationsByGutterNameAndScreenRow[gutterName] ?= {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow[gutterName][row] ?= {}
|
||||
@customGutterDecorationsByGutterNameAndScreenRow[gutterName][row][decoration.id] = decoration
|
||||
@lineNumberDecorationsByScreenRow[row][decorationId] = properties
|
||||
|
||||
return
|
||||
|
||||
@@ -1340,46 +1244,34 @@ class TextEditorPresenter
|
||||
|
||||
intersectingRange
|
||||
|
||||
updateHighlightState: (decoration, range) ->
|
||||
updateHighlightState: (decorationId, properties, screenRange) ->
|
||||
return unless @startRow? and @endRow? and @lineHeight? and @hasPixelPositionRequirements()
|
||||
|
||||
properties = decoration.getProperties()
|
||||
marker = decoration.getMarker()
|
||||
return if screenRange.isEmpty()
|
||||
|
||||
if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(@startRow, @endRow - 1)
|
||||
return
|
||||
if screenRange.start.row < @startRow
|
||||
screenRange.start.row = @startRow
|
||||
screenRange.start.column = 0
|
||||
if screenRange.end.row >= @endRow
|
||||
screenRange.end.row = @endRow
|
||||
screenRange.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
|
||||
return if screenRange.isEmpty()
|
||||
|
||||
return if range.isEmpty()
|
||||
|
||||
flash = decoration.consumeNextFlash()
|
||||
|
||||
startTile = @tileForRow(range.start.row)
|
||||
endTile = @tileForRow(range.end.row)
|
||||
startTile = @tileForRow(screenRange.start.row)
|
||||
endTile = @tileForRow(screenRange.end.row)
|
||||
|
||||
for tileStartRow in [startTile..endTile] by @tileSize
|
||||
rangeWithinTile = @intersectRangeWithTile(range, tileStartRow)
|
||||
rangeWithinTile = @intersectRangeWithTile(screenRange, tileStartRow)
|
||||
|
||||
continue if rangeWithinTile.isEmpty()
|
||||
|
||||
tileState = @state.content.tiles[tileStartRow] ?= {highlights: {}}
|
||||
highlightState = tileState.highlights[decoration.id] ?= {
|
||||
flashCount: 0
|
||||
flashDuration: null
|
||||
flashClass: null
|
||||
}
|
||||
|
||||
if flash?
|
||||
highlightState.flashCount++
|
||||
highlightState.flashClass = flash.class
|
||||
highlightState.flashDuration = flash.duration
|
||||
highlightState = tileState.highlights[decorationId] ?= {}
|
||||
|
||||
highlightState.flashCount = properties.flashCount
|
||||
highlightState.flashClass = properties.flashClass
|
||||
highlightState.flashDuration = properties.flashDuration
|
||||
highlightState.class = properties.class
|
||||
highlightState.deprecatedRegionClass = properties.deprecatedRegionClass
|
||||
highlightState.regions = @buildHighlightRegions(rangeWithinTile)
|
||||
@@ -1388,7 +1280,7 @@ class TextEditorPresenter
|
||||
@repositionRegionWithinTile(region, tileStartRow)
|
||||
|
||||
@visibleHighlights[tileStartRow] ?= {}
|
||||
@visibleHighlights[tileStartRow][decoration.id] = true
|
||||
@visibleHighlights[tileStartRow][decorationId] = true
|
||||
|
||||
true
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ class TextEditor extends Model
|
||||
throw error
|
||||
|
||||
state.displayBuffer = displayBuffer
|
||||
state.selectionsMarkerLayer = displayBuffer.getMarkerLayer(state.selectionsMarkerLayerId)
|
||||
state.config = atomEnvironment.config
|
||||
state.notificationManager = atomEnvironment.notifications
|
||||
state.packageManager = atomEnvironment.packages
|
||||
@@ -90,9 +91,10 @@ class TextEditor extends Model
|
||||
|
||||
{
|
||||
@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength,
|
||||
softWrapped, @displayBuffer, buffer, suppressCursorCreation, @mini, @placeholderText,
|
||||
lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager,
|
||||
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
|
||||
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
|
||||
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
|
||||
@project, @assert, @applicationDelegate
|
||||
} = params
|
||||
|
||||
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
|
||||
@@ -115,8 +117,9 @@ class TextEditor extends Model
|
||||
@config, @assert, @grammarRegistry, @packageManager
|
||||
})
|
||||
@buffer = @displayBuffer.buffer
|
||||
@selectionsMarkerLayer ?= @addMarkerLayer(maintainHistory: true)
|
||||
|
||||
for marker in @findMarkers(@getSelectionMarkerAttributes())
|
||||
for marker in @selectionsMarkerLayer.getMarkers()
|
||||
marker.setProperties(preserveFolds: true)
|
||||
@addSelection(marker)
|
||||
|
||||
@@ -146,6 +149,7 @@ class TextEditor extends Model
|
||||
scrollRow: @getScrollRow()
|
||||
scrollColumn: @getScrollColumn()
|
||||
displayBuffer: @displayBuffer.serialize()
|
||||
selectionsMarkerLayerId: @selectionsMarkerLayer.id
|
||||
|
||||
subscribeToBuffer: ->
|
||||
@buffer.retain()
|
||||
@@ -161,9 +165,9 @@ class TextEditor extends Model
|
||||
@preserveCursorPositionOnBufferReload()
|
||||
|
||||
subscribeToDisplayBuffer: ->
|
||||
@disposables.add @displayBuffer.onDidCreateMarker @handleMarkerCreated
|
||||
@disposables.add @displayBuffer.onDidChangeGrammar => @handleGrammarChange()
|
||||
@disposables.add @displayBuffer.onDidTokenize => @handleTokenization()
|
||||
@disposables.add @selectionsMarkerLayer.onDidCreateMarker @addSelection.bind(this)
|
||||
@disposables.add @displayBuffer.onDidChangeGrammar @handleGrammarChange.bind(this)
|
||||
@disposables.add @displayBuffer.onDidTokenize @handleTokenization.bind(this)
|
||||
@disposables.add @displayBuffer.onDidChange (e) =>
|
||||
@mergeIntersectingSelections()
|
||||
@emitter.emit 'did-change', e
|
||||
@@ -177,6 +181,7 @@ class TextEditor extends Model
|
||||
@disposables.dispose()
|
||||
@tabTypeSubscription.dispose()
|
||||
selection.destroy() for selection in @selections.slice()
|
||||
@selectionsMarkerLayer.destroy()
|
||||
@buffer.release()
|
||||
@displayBuffer.destroy()
|
||||
@languageMode.destroy()
|
||||
@@ -468,6 +473,9 @@ class TextEditor extends Model
|
||||
onDidUpdateMarkers: (callback) ->
|
||||
@displayBuffer.onDidUpdateMarkers(callback)
|
||||
|
||||
onDidUpdateDecorations: (callback) ->
|
||||
@displayBuffer.onDidUpdateDecorations(callback)
|
||||
|
||||
# Essential: Retrieves the current {TextBuffer}.
|
||||
getBuffer: -> @buffer
|
||||
|
||||
@@ -477,14 +485,13 @@ class TextEditor extends Model
|
||||
# Create an {TextEditor} with its initial state based on this object
|
||||
copy: ->
|
||||
displayBuffer = @displayBuffer.copy()
|
||||
selectionsMarkerLayer = displayBuffer.getMarkerLayer(@buffer.getMarkerLayer(@selectionsMarkerLayer.id).copy().id)
|
||||
softTabs = @getSoftTabs()
|
||||
newEditor = new TextEditor({
|
||||
@buffer, displayBuffer, @tabLength, softTabs, suppressCursorCreation: true,
|
||||
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
|
||||
@grammarRegistry, @project, @assert, @applicationDelegate
|
||||
@buffer, displayBuffer, selectionsMarkerLayer, @tabLength, softTabs,
|
||||
suppressCursorCreation: true, @config, @notificationManager, @packageManager,
|
||||
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
|
||||
})
|
||||
for marker in @findMarkers(editorId: @id)
|
||||
marker.copy(editorId: newEditor.id, preserveFolds: true)
|
||||
newEditor
|
||||
|
||||
# Controls visibility based on the given {Boolean}.
|
||||
@@ -499,6 +506,9 @@ class TextEditor extends Model
|
||||
|
||||
isMini: -> @mini
|
||||
|
||||
setUpdatedSynchronously: (updatedSynchronously) ->
|
||||
@displayBuffer.setUpdatedSynchronously(updatedSynchronously)
|
||||
|
||||
onDidChangeMini: (callback) ->
|
||||
@emitter.on 'did-change-mini', callback
|
||||
|
||||
@@ -1393,9 +1403,9 @@ class TextEditor extends Model
|
||||
Section: Decorations
|
||||
###
|
||||
|
||||
# Essential: Adds a decoration that tracks a {Marker}. When the marker moves,
|
||||
# is invalidated, or is destroyed, the decoration will be updated to reflect
|
||||
# the marker's state.
|
||||
# Essential: Add a decoration that tracks a {TextEditorMarker}. When the
|
||||
# marker moves, is invalidated, or is destroyed, the decoration will be
|
||||
# updated to reflect the marker's state.
|
||||
#
|
||||
# The following are the supported decorations types:
|
||||
#
|
||||
@@ -1414,28 +1424,28 @@ class TextEditor extends Model
|
||||
# </div>
|
||||
# ```
|
||||
# * __overlay__: Positions the view associated with the given item at the head
|
||||
# or tail of the given `Marker`.
|
||||
# * __gutter__: A decoration that tracks a {Marker} in a {Gutter}. Gutter
|
||||
# or tail of the given `TextEditorMarker`.
|
||||
# * __gutter__: A decoration that tracks a {TextEditorMarker} in a {Gutter}. Gutter
|
||||
# decorations are created by calling {Gutter::decorateMarker} on the
|
||||
# desired `Gutter` instance.
|
||||
#
|
||||
# ## Arguments
|
||||
#
|
||||
# * `marker` A {Marker} you want this decoration to follow.
|
||||
# * `marker` A {TextEditorMarker} you want this decoration to follow.
|
||||
# * `decorationParams` An {Object} representing the decoration e.g.
|
||||
# `{type: 'line-number', class: 'linter-error'}`
|
||||
# * `type` There are several supported decoration types. The behavior of the
|
||||
# types are as follows:
|
||||
# * `line` Adds the given `class` to the lines overlapping the rows
|
||||
# spanned by the `Marker`.
|
||||
# spanned by the `TextEditorMarker`.
|
||||
# * `line-number` Adds the given `class` to the line numbers overlapping
|
||||
# the rows spanned by the `Marker`.
|
||||
# the rows spanned by the `TextEditorMarker`.
|
||||
# * `highlight` Creates a `.highlight` div with the nested class with up
|
||||
# to 3 nested regions that fill the area spanned by the `Marker`.
|
||||
# to 3 nested regions that fill the area spanned by the `TextEditorMarker`.
|
||||
# * `overlay` Positions the view associated with the given item at the
|
||||
# head or tail of the given `Marker`, depending on the `position`
|
||||
# head or tail of the given `TextEditorMarker`, depending on the `position`
|
||||
# property.
|
||||
# * `gutter` Tracks a {Marker} in a {Gutter}. Created by calling
|
||||
# * `gutter` Tracks a {TextEditorMarker} in a {Gutter}. Created by calling
|
||||
# {Gutter::decorateMarker} on the desired `Gutter` instance.
|
||||
# * `class` This CSS class will be applied to the decorated line number,
|
||||
# line, highlight, or overlay.
|
||||
@@ -1443,35 +1453,53 @@ class TextEditor extends Model
|
||||
# corresponding view registered. Only applicable to the `gutter` and
|
||||
# `overlay` types.
|
||||
# * `onlyHead` (optional) If `true`, the decoration will only be applied to
|
||||
# the head of the `Marker`. Only applicable to the `line` and
|
||||
# the head of the `TextEditorMarker`. Only applicable to the `line` and
|
||||
# `line-number` types.
|
||||
# * `onlyEmpty` (optional) If `true`, the decoration will only be applied if
|
||||
# the associated `Marker` is empty. Only applicable to the `gutter`,
|
||||
# the associated `TextEditorMarker` is empty. Only applicable to the `gutter`,
|
||||
# `line`, and `line-number` types.
|
||||
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
|
||||
# if the associated `Marker` is non-empty. Only applicable to the
|
||||
# if the associated `TextEditorMarker` is non-empty. Only applicable to the
|
||||
# `gutter`, `line`, and `line-number` types.
|
||||
# * `position` (optional) Only applicable to decorations of type `overlay`,
|
||||
# controls where the overlay view is positioned relative to the `Marker`.
|
||||
# controls where the overlay view is positioned relative to the `TextEditorMarker`.
|
||||
# Values can be `'head'` (the default), or `'tail'`.
|
||||
#
|
||||
# Returns a {Decoration} object
|
||||
decorateMarker: (marker, decorationParams) ->
|
||||
@displayBuffer.decorateMarker(marker, decorationParams)
|
||||
|
||||
# Essential: Get all the decorations within a screen row range.
|
||||
# Essential: *Experimental:* Add a decoration to every marker in the given
|
||||
# marker layer. Can be used to decorate a large number of markers without
|
||||
# having to create and manage many individual decorations.
|
||||
#
|
||||
# * `markerLayer` A {TextEditorMarkerLayer} or {MarkerLayer} to decorate.
|
||||
# * `decorationParams` The same parameters that are passed to
|
||||
# {decorateMarker}, except the `type` cannot be `overlay` or `gutter`.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {LayerDecoration}.
|
||||
decorateMarkerLayer: (markerLayer, decorationParams) ->
|
||||
@displayBuffer.decorateMarkerLayer(markerLayer, decorationParams)
|
||||
|
||||
# Deprecated: Get all the decorations within a screen row range on the default
|
||||
# layer.
|
||||
#
|
||||
# * `startScreenRow` the {Number} beginning screen row
|
||||
# * `endScreenRow` the {Number} end screen row (inclusive)
|
||||
#
|
||||
# Returns an {Object} of decorations in the form
|
||||
# `{1: [{id: 10, type: 'line-number', class: 'someclass'}], 2: ...}`
|
||||
# where the keys are {Marker} IDs, and the values are an array of decoration
|
||||
# where the keys are {TextEditorMarker} IDs, and the values are an array of decoration
|
||||
# params objects attached to the marker.
|
||||
# Returns an empty object when no decorations are found
|
||||
decorationsForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
decorationsStateForScreenRowRange: (startScreenRow, endScreenRow) ->
|
||||
@displayBuffer.decorationsStateForScreenRowRange(startScreenRow, endScreenRow)
|
||||
|
||||
# Extended: Get all decorations.
|
||||
#
|
||||
# * `propertyFilter` (optional) An {Object} containing key value pairs that
|
||||
@@ -1527,10 +1555,10 @@ class TextEditor extends Model
|
||||
Section: Markers
|
||||
###
|
||||
|
||||
# Essential: Create a marker with the given range in buffer coordinates. This
|
||||
# marker will maintain its logical location as the buffer is changed, so if
|
||||
# you mark a particular word, the marker will remain over that word even if
|
||||
# the word's location in the buffer changes.
|
||||
# Essential: Create a marker on the default marker layer with the given range
|
||||
# in buffer coordinates. This marker will maintain its logical location as the
|
||||
# buffer is changed, so if you mark a particular word, the marker will remain
|
||||
# over that word even if the word's location in the buffer changes.
|
||||
#
|
||||
# * `range` A {Range} or range-compatible {Array}
|
||||
# * `properties` A hash of key-value pairs to associate with the marker. There
|
||||
@@ -1558,14 +1586,14 @@ class TextEditor extends Model
|
||||
# region in any way, including changes that end at the marker's
|
||||
# start or start at the marker's end. This is the most fragile strategy.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markBufferRange: (args...) ->
|
||||
@displayBuffer.markBufferRange(args...)
|
||||
|
||||
# Essential: Create a marker with the given range in screen coordinates. This
|
||||
# marker will maintain its logical location as the buffer is changed, so if
|
||||
# you mark a particular word, the marker will remain over that word even if
|
||||
# the word's location in the buffer changes.
|
||||
# Essential: Create a marker on the default marker layer with the given range
|
||||
# in screen coordinates. This marker will maintain its logical location as the
|
||||
# buffer is changed, so if you mark a particular word, the marker will remain
|
||||
# over that word even if the word's location in the buffer changes.
|
||||
#
|
||||
# * `range` A {Range} or range-compatible {Array}
|
||||
# * `properties` A hash of key-value pairs to associate with the marker. There
|
||||
@@ -1593,29 +1621,32 @@ class TextEditor extends Model
|
||||
# region in any way, including changes that end at the marker's
|
||||
# start or start at the marker's end. This is the most fragile strategy.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markScreenRange: (args...) ->
|
||||
@displayBuffer.markScreenRange(args...)
|
||||
|
||||
# Essential: Mark the given position in buffer coordinates.
|
||||
# Essential: Mark the given position in buffer coordinates on the default
|
||||
# marker layer.
|
||||
#
|
||||
# * `position` A {Point} or {Array} of `[row, column]`.
|
||||
# * `options` (optional) See {TextBuffer::markRange}.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markBufferPosition: (args...) ->
|
||||
@displayBuffer.markBufferPosition(args...)
|
||||
|
||||
# Essential: Mark the given position in screen coordinates.
|
||||
# Essential: Mark the given position in screen coordinates on the default
|
||||
# marker layer.
|
||||
#
|
||||
# * `position` A {Point} or {Array} of `[row, column]`.
|
||||
# * `options` (optional) See {TextBuffer::markRange}.
|
||||
#
|
||||
# Returns a {Marker}.
|
||||
# Returns a {TextEditorMarker}.
|
||||
markScreenPosition: (args...) ->
|
||||
@displayBuffer.markScreenPosition(args...)
|
||||
|
||||
# Essential: Find all {Marker}s that match the given properties.
|
||||
# Essential: Find all {TextEditorMarker}s on the default marker layer that
|
||||
# match the given properties.
|
||||
#
|
||||
# This method finds markers based on the given properties. Markers can be
|
||||
# associated with custom properties that will be compared with basic equality.
|
||||
@@ -1637,44 +1668,60 @@ 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.
|
||||
# Extended: Get the {TextEditorMarker} on the default layer for the given
|
||||
# marker id.
|
||||
#
|
||||
# * `id` {Number} id of the marker
|
||||
getMarker: (id) ->
|
||||
@displayBuffer.getMarker(id)
|
||||
|
||||
# Extended: Get all {Marker}s. Consider using {::findMarkers}
|
||||
# Extended: Get all {TextEditorMarker}s on the default marker layer. Consider
|
||||
# using {::findMarkers}
|
||||
getMarkers: ->
|
||||
@displayBuffer.getMarkers()
|
||||
|
||||
# Extended: Get the number of markers in this editor's buffer.
|
||||
# Extended: Get the number of markers in the default marker layer.
|
||||
#
|
||||
# Returns a {Number}.
|
||||
getMarkerCount: ->
|
||||
@buffer.getMarkerCount()
|
||||
|
||||
# {Delegates to: DisplayBuffer.destroyMarker}
|
||||
destroyMarker: (args...) ->
|
||||
@displayBuffer.destroyMarker(args...)
|
||||
destroyMarker: (id) ->
|
||||
@getMarker(id)?.destroy()
|
||||
|
||||
# Extended: *Experimental:* Create a marker layer to group related markers.
|
||||
#
|
||||
# * `options` An {Object} containing the following keys:
|
||||
# * `maintainHistory` A {Boolean} indicating whether marker state should be
|
||||
# restored on undo/redo. Defaults to `false`.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {TextEditorMarkerLayer}.
|
||||
addMarkerLayer: (options) ->
|
||||
@displayBuffer.addMarkerLayer(options)
|
||||
|
||||
# Public: *Experimental:* Get a {TextEditorMarkerLayer} by id.
|
||||
#
|
||||
# * `id` The id of the marker layer to retrieve.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {MarkerLayer} or `undefined` if no layer exists with the given
|
||||
# id.
|
||||
getMarkerLayer: (id) ->
|
||||
@displayBuffer.getMarkerLayer(id)
|
||||
|
||||
# Public: *Experimental:* Get the default {TextEditorMarkerLayer}.
|
||||
#
|
||||
# All marker APIs not tied to an explicit layer interact with this default
|
||||
# layer.
|
||||
#
|
||||
# This API is experimental and subject to change on any release.
|
||||
#
|
||||
# Returns a {TextEditorMarkerLayer}.
|
||||
getDefaultMarkerLayer: ->
|
||||
@displayBuffer.getDefaultMarkerLayer()
|
||||
|
||||
###
|
||||
Section: Cursors
|
||||
@@ -1744,7 +1791,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Cursor}.
|
||||
addCursorAtBufferPosition: (bufferPosition, options) ->
|
||||
@markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
|
||||
@selectionsMarkerLayer.markBufferPosition(bufferPosition, @getSelectionMarkerAttributes())
|
||||
@getLastSelection().cursor.autoscroll() unless options?.autoscroll is false
|
||||
@getLastSelection().cursor
|
||||
|
||||
@@ -1754,7 +1801,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns a {Cursor}.
|
||||
addCursorAtScreenPosition: (screenPosition, options) ->
|
||||
@markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
|
||||
@selectionsMarkerLayer.markScreenPosition(screenPosition, @getSelectionMarkerAttributes())
|
||||
@getLastSelection().cursor.autoscroll() unless options?.autoscroll is false
|
||||
@getLastSelection().cursor
|
||||
|
||||
@@ -1879,7 +1926,7 @@ class TextEditor extends Model
|
||||
getCursorsOrderedByBufferPosition: ->
|
||||
@getCursors().sort (a, b) -> a.compare(b)
|
||||
|
||||
# Add a cursor based on the given {Marker}.
|
||||
# Add a cursor based on the given {TextEditorMarker}.
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editor: this, marker: marker, config: @config)
|
||||
@cursors.push(cursor)
|
||||
@@ -2032,7 +2079,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns the added {Selection}.
|
||||
addSelectionForBufferRange: (bufferRange, options={}) ->
|
||||
@markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@selectionsMarkerLayer.markBufferRange(bufferRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@getLastSelection().autoscroll() unless options.autoscroll is false
|
||||
@getLastSelection()
|
||||
|
||||
@@ -2045,7 +2092,7 @@ class TextEditor extends Model
|
||||
#
|
||||
# Returns the added {Selection}.
|
||||
addSelectionForScreenRange: (screenRange, options={}) ->
|
||||
@markScreenRange(screenRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@selectionsMarkerLayer.markScreenRange(screenRange, _.defaults(@getSelectionMarkerAttributes(), options))
|
||||
@getLastSelection().autoscroll() unless options.autoscroll is false
|
||||
@getLastSelection()
|
||||
|
||||
@@ -2228,7 +2275,7 @@ class TextEditor extends Model
|
||||
|
||||
# Extended: Select the range of the given marker if it is valid.
|
||||
#
|
||||
# * `marker` A {Marker}
|
||||
# * `marker` A {TextEditorMarker}
|
||||
#
|
||||
# Returns the selected {Range} or `undefined` if the marker is invalid.
|
||||
selectMarker: (marker) ->
|
||||
@@ -2354,9 +2401,9 @@ class TextEditor extends Model
|
||||
_.reduce(tail, reducer, [head])
|
||||
return result if fn?
|
||||
|
||||
# Add a {Selection} based on the given {Marker}.
|
||||
# Add a {Selection} based on the given {TextEditorMarker}.
|
||||
#
|
||||
# * `marker` The {Marker} to highlight
|
||||
# * `marker` The {TextEditorMarker} to highlight
|
||||
# * `options` (optional) An {Object} that pertains to the {Selection} constructor.
|
||||
#
|
||||
# Returns the new {Selection}.
|
||||
@@ -3064,10 +3111,6 @@ class TextEditor extends Model
|
||||
@subscribeToTabTypeConfig()
|
||||
@emitter.emit 'did-change-grammar', @getGrammar()
|
||||
|
||||
handleMarkerCreated: (marker) =>
|
||||
if marker.matchesProperties(@getSelectionMarkerAttributes())
|
||||
@addSelection(marker)
|
||||
|
||||
###
|
||||
Section: TextEditor Rendering
|
||||
###
|
||||
@@ -3104,7 +3147,7 @@ class TextEditor extends Model
|
||||
@viewRegistry.getView(this).pixelPositionForScreenPosition(screenPosition)
|
||||
|
||||
getSelectionMarkerAttributes: ->
|
||||
{type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true}
|
||||
{type: 'selection', invalidate: 'never'}
|
||||
|
||||
getVerticalScrollMargin: -> @displayBuffer.getVerticalScrollMargin()
|
||||
setVerticalScrollMargin: (verticalScrollMargin) -> @displayBuffer.setVerticalScrollMargin(verticalScrollMargin)
|
||||
|
||||
@@ -43,7 +43,7 @@ _ = require 'underscore-plus'
|
||||
# ```
|
||||
module.exports =
|
||||
class ViewRegistry
|
||||
documentUpdateRequested: false
|
||||
animationFrameRequest: null
|
||||
documentReadInProgress: false
|
||||
performDocumentPollAfterUpdate: false
|
||||
debouncedPerformDocumentPoll: null
|
||||
@@ -195,20 +195,30 @@ class ViewRegistry
|
||||
pollAfterNextUpdate: ->
|
||||
@performDocumentPollAfterUpdate = true
|
||||
|
||||
getNextUpdatePromise: ->
|
||||
@nextUpdatePromise ?= new Promise (resolve) =>
|
||||
@resolveNextUpdatePromise = resolve
|
||||
|
||||
clearDocumentRequests: ->
|
||||
@documentReaders = []
|
||||
@documentWriters = []
|
||||
@documentPollers = []
|
||||
@documentUpdateRequested = false
|
||||
@nextUpdatePromise = null
|
||||
@resolveNextUpdatePromise = null
|
||||
if @animationFrameRequest?
|
||||
cancelAnimationFrame(@animationFrameRequest)
|
||||
@animationFrameRequest = null
|
||||
@stopPollingDocument()
|
||||
|
||||
requestDocumentUpdate: ->
|
||||
unless @documentUpdateRequested
|
||||
@documentUpdateRequested = true
|
||||
requestAnimationFrame(@performDocumentUpdate)
|
||||
@animationFrameRequest ?= requestAnimationFrame(@performDocumentUpdate)
|
||||
|
||||
performDocumentUpdate: =>
|
||||
@documentUpdateRequested = false
|
||||
resolveNextUpdatePromise = @resolveNextUpdatePromise
|
||||
@animationFrameRequest = null
|
||||
@nextUpdatePromise = null
|
||||
@resolveNextUpdatePromise = null
|
||||
|
||||
writer() while writer = @documentWriters.shift()
|
||||
|
||||
@documentReadInProgress = true
|
||||
@@ -220,6 +230,8 @@ class ViewRegistry
|
||||
# process updates requested as a result of reads
|
||||
writer() while writer = @documentWriters.shift()
|
||||
|
||||
resolveNextUpdatePromise?()
|
||||
|
||||
startPollingDocument: ->
|
||||
window.addEventListener('resize', @requestDocumentPoll)
|
||||
@observer.observe(document, {subtree: true, childList: true, attributes: true})
|
||||
@@ -229,7 +241,7 @@ class ViewRegistry
|
||||
@observer.disconnect()
|
||||
|
||||
requestDocumentPoll: =>
|
||||
if @documentUpdateRequested
|
||||
if @animationFrameRequest?
|
||||
@performDocumentPollAfterUpdate = true
|
||||
else
|
||||
@debouncedPerformDocumentPoll()
|
||||
|
||||
Reference in New Issue
Block a user