Add TextEditorPresenter::state.content.cursors

This commit is contained in:
Nathan Sobo 2015-01-22 10:55:01 -07:00
parent b7b0ec067c
commit c8b58761ba
2 changed files with 193 additions and 9 deletions

View File

@ -385,3 +385,132 @@ describe "TextEditorPresenter", ->
editor.setMini(true)
expect(lineStateForScreenRow(presenter, 0).decorationClasses).toBeNull()
describe ".cursors", ->
stateForCursor = (presenter, cursorIndex) ->
presenter.state.content.cursors[presenter.model.getCursors()[cursorIndex].id]
it "contains pixelRects for empty selections that are visible on screen", ->
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
[[3, 4], [3, 5]]
[[5, 12], [5, 12]],
[[8, 4], [8, 4]]
])
presenter = new TextEditorPresenter(model: editor, clientHeight: 30, scrollTop: 20, lineHeight: 10, lineOverdrawMargin: 0, baseCharacterWidth: 10)
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 4)).toBeUndefined()
it "updates when ::scrollTop changes", ->
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
[[3, 4], [3, 5]]
[[5, 12], [5, 12]],
[[8, 4], [8, 4]]
])
presenter = new TextEditorPresenter(model: editor, clientHeight: 30, scrollTop: 20, lineHeight: 10, lineOverdrawMargin: 0, baseCharacterWidth: 10)
presenter.setScrollTop(5 * 10)
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toBeUndefined()
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 10, left: 4 * 10, width: 10, height: 10}
it "updates when ::clientHeight changes", ->
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
[[3, 4], [3, 5]]
[[5, 12], [5, 12]],
[[8, 4], [8, 4]]
])
presenter = new TextEditorPresenter(model: editor, clientHeight: 20, scrollTop: 20, lineHeight: 10, lineOverdrawMargin: 0, baseCharacterWidth: 10)
presenter.setClientHeight(30)
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 10, left: 12 * 10, width: 10, height: 10}
expect(stateForCursor(presenter, 4)).toBeUndefined()
it "updates when ::lineHeight changes", ->
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[2, 4], [2, 4]],
[[3, 4], [3, 5]]
[[5, 12], [5, 12]],
[[8, 4], [8, 4]]
])
presenter = new TextEditorPresenter(model: editor, clientHeight: 20, scrollTop: 20, lineHeight: 10, lineOverdrawMargin: 0, baseCharacterWidth: 10)
presenter.setLineHeight(5)
expect(stateForCursor(presenter, 0)).toBeUndefined()
expect(stateForCursor(presenter, 1)).toBeUndefined()
expect(stateForCursor(presenter, 2)).toBeUndefined()
expect(stateForCursor(presenter, 3)).toEqual {top: 5 * 5, left: 12 * 10, width: 10, height: 5}
expect(stateForCursor(presenter, 4)).toEqual {top: 8 * 5, left: 4 * 10, width: 10, height: 5}
it "updates when ::baseCharacterWidth changes", ->
editor.setCursorBufferPosition([2, 4])
presenter = new TextEditorPresenter(model: editor, clientHeight: 20, scrollTop: 20, lineHeight: 10, lineOverdrawMargin: 0, baseCharacterWidth: 10)
presenter.setBaseCharacterWidth(20)
expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 20, width: 20, height: 10}
it "updates when scoped character widths change", ->
waitsForPromise ->
atom.packages.activatePackage('language-javascript')
runs ->
editor.setCursorBufferPosition([1, 4])
presenter = new TextEditorPresenter(model: editor, clientHeight: 20, scrollTop: 0, lineHeight: 10, lineOverdrawMargin: 0, baseCharacterWidth: 10)
presenter.setScopedCharWidth(['source.js', 'storage.modifier.js'], 'v', 20)
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 10, height: 10}
presenter.setScopedCharWidth(['source.js', 'storage.modifier.js'], 'r', 20)
expect(stateForCursor(presenter, 0)).toEqual {top: 1 * 10, left: (3 * 10) + 20, width: 20, height: 10}
it "updates when cursors are added, moved, hidden, shown, or destroyed", ->
editor.setSelectedBufferRanges([
[[1, 2], [1, 2]],
[[3, 4], [3, 5]]
])
presenter = new TextEditorPresenter(model: editor, clientHeight: 20, scrollTop: 20, lineHeight: 10, lineOverdrawMargin: 0, baseCharacterWidth: 10)
# moving into view
expect(stateForCursor(presenter, 0)).toBeUndefined()
editor.getCursors()[0].setBufferPosition([2, 4])
expect(stateForCursor(presenter, 0)).toEqual {top: 2 * 10, left: 4 * 10, width: 10, height: 10}
# showing
editor.getSelections()[1].clear()
expect(stateForCursor(presenter, 1)).toEqual {top: 3 * 10, left: 5 * 10, width: 10, height: 10}
# hiding
editor.getSelections()[1].setBufferRange([[3, 4], [3, 5]])
expect(stateForCursor(presenter, 1)).toBeUndefined()
# moving out of view
editor.getCursors()[0].setBufferPosition([10, 4])
expect(stateForCursor(presenter, 0)).toBeUndefined()
# adding
editor.addCursorAtBufferPosition([4, 4])
expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 4 * 10, width: 10, height: 10}
# moving added cursor
editor.getCursors()[2].setBufferPosition([4, 6])
expect(stateForCursor(presenter, 2)).toEqual {top: 4 * 10, left: 6 * 10, width: 10, height: 10}
# destroying
destroyedCursor = editor.getCursors()[2]
destroyedCursor.destroy()
expect(presenter.state.content.cursors[destroyedCursor.id]).toBeUndefined()

View File

@ -17,9 +17,11 @@ class TextEditorPresenter
@disposables.add @model.onDidChange(@updateState.bind(this))
@disposables.add @model.onDidChangeSoftWrapped(@updateState.bind(this))
@disposables.add @model.onDidChangeGrammar(@updateContentState.bind(this))
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
@disposables.add @model.onDidChangeMini(@updateLinesState.bind(this))
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
@observeDecoration(decoration) for decoration in @model.getLineDecorations()
@observeCursor(cursor) for cursor in @model.getCursors()
observeConfig: ->
@disposables.add atom.config.onDidChange 'editor.showIndentGuide', scope: @model.getRootScopeDescriptor(), @updateContentState.bind(this)
@ -28,6 +30,7 @@ class TextEditorPresenter
@state = {}
@buildContentState()
@buildLinesState()
@buildCursorsState()
buildContentState: ->
@state.content = {}
@ -37,6 +40,10 @@ class TextEditorPresenter
@state.content.lines = {}
@updateLinesState()
buildCursorsState: ->
@state.content.cursors = {}
@updateCursorsState()
updateState: ->
@updateContentState()
@updateLinesState()
@ -85,6 +92,19 @@ class TextEditorPresenter
top: row * @getLineHeight()
decorationClasses: @lineDecorationClassesForRow(row)
updateCursorsState: ->
startRow = @getStartRow()
endRow = @getEndRow()
visibleCursors = {}
for cursor in @model.getCursors()
if cursor.isVisible() and startRow <= cursor.getScreenRow() < endRow
@state.content.cursors[cursor.id] = @pixelRectForScreenRange(cursor.getScreenRange())
visibleCursors[cursor.id] = true
for id of @state.content.cursors
delete @state.content.cursors[id] unless visibleCursors.hasOwnProperty(id)
getStartRow: ->
startRow = Math.floor(@getScrollTop() / @getLineHeight()) - @lineOverdrawMargin
Math.max(0, startRow)
@ -126,6 +146,7 @@ class TextEditorPresenter
setScrollTop: (@scrollTop) ->
@updateContentState()
@updateLinesState()
@updateCursorsState()
getScrollTop: -> @scrollTop
@ -136,6 +157,7 @@ class TextEditorPresenter
setClientHeight: (@clientHeight) ->
@updateLinesState()
@updateCursorsState()
getClientHeight: ->
@clientHeight ? @model.getScreenLineCount() * @getLineHeight()
@ -149,12 +171,14 @@ class TextEditorPresenter
setLineHeight: (@lineHeight) ->
@updateContentState()
@updateLinesState()
@updateCursorsState()
getLineHeight: -> @lineHeight
setBaseCharacterWidth: (@baseCharacterWidth) ->
@updateContentState()
@updateLinesState()
@updateCursorsState()
getBaseCharacterWidth: -> @baseCharacterWidth
@ -184,6 +208,7 @@ class TextEditorPresenter
characterWidthsChanged: ->
@updateContentState()
@updateLinesState()
@updateCursorsState()
clearScopedCharWidths: ->
@charWidthsByScope = {}
@ -194,9 +219,9 @@ class TextEditorPresenter
targetRow = screenPosition.row
targetColumn = screenPosition.column
baseCharacterWidth = @baseCharacterWidth
baseCharacterWidth = @getBaseCharacterWidth()
top = targetRow * @lineHeightInPixels
top = targetRow * @getLineHeight()
left = 0
column = 0
for token in @model.tokenizedLineForScreenRow(targetRow).tokens
@ -219,17 +244,47 @@ class TextEditorPresenter
column += charLength
{top, left}
pixelRectForScreenRange: (screenRange) ->
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * @getLineHeight()
width = @getScrollWidth()
else
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false)
height = @getLineHeight()
width = @pixelPositionForScreenPosition(screenRange.end, false).left - left
{top, left, width, height}
observeDecoration: (decoration) ->
markerChangeDisposable = decoration.getMarker().onDidChange(@updateLinesState.bind(this))
destroyDisposable = decoration.onDidDestroy =>
@disposables.remove(markerChangeDisposable)
@disposables.remove(destroyDisposable)
markerDidChangeDisposable = decoration.getMarker().onDidChange(@updateLinesState.bind(this))
didDestroyDisposable = decoration.onDidDestroy =>
@disposables.remove(markerDidChangeDisposable)
@disposables.remove(didDestroyDisposable)
@updateLinesState()
@disposables.add(markerChangeDisposable)
@disposables.add(destroyDisposable)
@disposables.add(markerDidChangeDisposable)
@disposables.add(didDestroyDisposable)
didAddDecoration: (decoration) ->
if decoration.isType('line')
@observeDecoration(decoration)
@updateLinesState()
observeCursor: (cursor) ->
didChangePositionDisposable = cursor.onDidChangePosition(@updateCursorsState.bind(this))
didChangeVisibilityDisposable = cursor.onDidChangeVisibility(@updateCursorsState.bind(this))
didDestroyDisposable = cursor.onDidDestroy =>
@disposables.remove(didChangePositionDisposable)
@disposables.remove(didChangeVisibilityDisposable)
@disposables.remove(didDestroyDisposable)
@updateCursorsState()
@disposables.add(didChangePositionDisposable)
@disposables.add(didChangeVisibilityDisposable)
@disposables.add(didDestroyDisposable)
didAddCursor: (cursor) ->
@observeCursor(cursor)
@updateCursorsState()