mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 23:48:05 +03:00
Add TextEditorPresenter::state.content.cursors
This commit is contained in:
parent
b7b0ec067c
commit
c8b58761ba
@ -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()
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user