mirror of
https://github.com/atom/atom.git
synced 2026-01-24 06:18:03 -05:00
Merge pull request #2507 from atom/bo-gutter-api
Add an API for decorations; render stuff in the gutter again
This commit is contained in:
@@ -165,7 +165,7 @@ describe "EditorComponent", ->
|
||||
beforeEach ->
|
||||
editor.setText "a line that wraps "
|
||||
editor.setSoftWrap(true)
|
||||
node.style.width = 15 * charWidth + 'px'
|
||||
node.style.width = 16 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
it "doesn't show end of line invisibles at the end of wrapped lines", ->
|
||||
@@ -228,6 +228,13 @@ describe "EditorComponent", ->
|
||||
[node]
|
||||
|
||||
describe "gutter rendering", ->
|
||||
[lineNumberHasClass, gutter] = []
|
||||
|
||||
beforeEach ->
|
||||
{gutter} = component.refs
|
||||
lineNumberHasClass = (screenRow, klass) ->
|
||||
component.lineNumberNodeForScreenRow(screenRow).classList.contains(klass)
|
||||
|
||||
it "renders the currently-visible line numbers", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
@@ -302,6 +309,267 @@ describe "EditorComponent", ->
|
||||
expect(component.lineNumberNodeForScreenRow(9).textContent).toBe "10"
|
||||
expect(gutterNode.offsetWidth).toBe initialGutterWidth
|
||||
|
||||
describe "fold decorations", ->
|
||||
describe "rendering fold decorations", ->
|
||||
it "adds the foldable class to line numbers when the line is foldable", ->
|
||||
expect(lineNumberHasClass(0, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(1, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(2, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(3, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(4, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(5, 'foldable')).toBe false
|
||||
|
||||
it "updates the foldable class on the correct line numbers when the foldable positions change", ->
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
expect(lineNumberHasClass(0, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(1, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(2, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(3, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(4, 'foldable')).toBe false
|
||||
expect(lineNumberHasClass(5, 'foldable')).toBe true
|
||||
expect(lineNumberHasClass(6, 'foldable')).toBe false
|
||||
|
||||
it "updates the foldable class on a line number that becomes foldable", ->
|
||||
expect(lineNumberHasClass(11, 'foldable')).toBe false
|
||||
|
||||
editor.getBuffer().insert([11, 44], '\n fold me')
|
||||
expect(lineNumberHasClass(11, 'foldable')).toBe true
|
||||
|
||||
editor.undo()
|
||||
expect(lineNumberHasClass(11, 'foldable')).toBe false
|
||||
|
||||
it "adds, updates and removes the folded class on the correct line number nodes", ->
|
||||
editor.foldBufferRow(4)
|
||||
expect(lineNumberHasClass(4, 'folded')).toBe true
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
expect(lineNumberHasClass(4, 'folded')).toBe false
|
||||
expect(lineNumberHasClass(5, 'folded')).toBe true
|
||||
|
||||
editor.unfoldBufferRow(5)
|
||||
expect(lineNumberHasClass(5, 'folded')).toBe false
|
||||
|
||||
describe "mouse interactions with fold indicators", ->
|
||||
[gutterNode] = []
|
||||
|
||||
buildClickEvent = (target) ->
|
||||
buildMouseEvent('click', {target})
|
||||
|
||||
beforeEach ->
|
||||
gutterNode = node.querySelector('.gutter')
|
||||
|
||||
it "folds and unfolds the block represented by the fold indicator when clicked", ->
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe false
|
||||
|
||||
lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
target = lineNumber.querySelector('.icon-right')
|
||||
|
||||
target.dispatchEvent(buildClickEvent(target))
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe true
|
||||
|
||||
lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
target = lineNumber.querySelector('.icon-right')
|
||||
|
||||
target.dispatchEvent(buildClickEvent(target))
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe false
|
||||
|
||||
it "does not fold when the line number node is clicked", ->
|
||||
lineNumber = component.lineNumberNodeForScreenRow(1)
|
||||
lineNumber.dispatchEvent(buildClickEvent(lineNumber))
|
||||
expect(lineNumberHasClass(1, 'folded')).toBe false
|
||||
|
||||
describe "cursor-line decorations", ->
|
||||
cursor = null
|
||||
beforeEach ->
|
||||
cursor = editor.getCursor()
|
||||
|
||||
it "modifies the cursor-line decoration when the cursor moves", ->
|
||||
cursor.setScreenPosition([0, 0])
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe true
|
||||
|
||||
cursor.setScreenPosition([1, 0])
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(1, 'cursor-line')).toBe true
|
||||
|
||||
it "updates cursor-line decorations for multiple cursors", ->
|
||||
cursor.setScreenPosition([2, 0])
|
||||
cursor2 = editor.addCursorAtScreenPosition([8, 0])
|
||||
cursor3 = editor.addCursorAtScreenPosition([10, 0])
|
||||
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(8, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(10, 'cursor-line')).toBe true
|
||||
|
||||
cursor2.destroy()
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(8, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(10, 'cursor-line')).toBe true
|
||||
|
||||
cursor3.destroy()
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(8, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(10, 'cursor-line')).toBe false
|
||||
|
||||
it "adds cursor-line decorations to multiple lines when a selection is performed", ->
|
||||
cursor.setScreenPosition([1, 0])
|
||||
editor.selectDown(2)
|
||||
expect(lineNumberHasClass(0, 'cursor-line')).toBe false
|
||||
expect(lineNumberHasClass(1, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(2, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(3, 'cursor-line')).toBe true
|
||||
expect(lineNumberHasClass(4, 'cursor-line')).toBe false
|
||||
|
||||
describe "when decorations are used", ->
|
||||
describe "when decorations are applied to buffer rows", ->
|
||||
it "renders line number classes based on the decorations on their buffer row", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
expect(component.lineNumberNodeForScreenRow(9)).not.toBeDefined()
|
||||
|
||||
editor.addDecorationToBufferRow(9, type: 'gutter', class: 'fancy-class')
|
||||
editor.addDecorationToBufferRow(9, type: 'someother-type', class: 'nope-class')
|
||||
|
||||
verticalScrollbarNode.scrollTop = 2.5 * lineHeightInPixels
|
||||
verticalScrollbarNode.dispatchEvent(new UIEvent('scroll'))
|
||||
|
||||
expect(lineNumberHasClass(9, 'fancy-class')).toBe true
|
||||
expect(lineNumberHasClass(9, 'nope-class')).toBe false
|
||||
|
||||
it "renders updates to gutter decorations", ->
|
||||
editor.addDecorationToBufferRow(2, type: 'gutter', class: 'fancy-class')
|
||||
editor.addDecorationToBufferRow(2, type: 'someother-type', class: 'nope-class')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'fancy-class')).toBe true
|
||||
expect(lineNumberHasClass(2, 'nope-class')).toBe false
|
||||
|
||||
editor.removeDecorationFromBufferRow(2, type: 'gutter', class: 'fancy-class')
|
||||
editor.removeDecorationFromBufferRow(2, type: 'someother-type', class: 'nope-class')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'fancy-class')).toBe false
|
||||
expect(lineNumberHasClass(2, 'nope-class')).toBe false
|
||||
|
||||
it "renders decorations on soft-wrapped line numbers when softWrap is true", ->
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'wrap-me', softWrap: true)
|
||||
|
||||
editor.setSoftWrap(true)
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
node.style.width = 30 * charWidth + 'px'
|
||||
component.measureScrollView()
|
||||
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe true
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe true
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe true
|
||||
|
||||
# should remove the wrapped decorations
|
||||
editor.removeDecorationFromBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.removeDecorationFromBufferRow(1, type: 'gutter', class: 'wrap-me')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe false
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe false
|
||||
|
||||
# should add them back when the nodes are not recreated
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'no-wrap')
|
||||
editor.addDecorationToBufferRow(1, type: 'gutter', class: 'wrap-me', softWrap: true)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'no-wrap')).toBe true
|
||||
expect(lineNumberHasClass(2, 'wrap-me')).toBe true
|
||||
expect(lineNumberHasClass(3, 'no-wrap')).toBe false
|
||||
expect(lineNumberHasClass(3, 'wrap-me')).toBe true
|
||||
|
||||
describe "when decorations are applied to markers", ->
|
||||
{marker, decoration} = {}
|
||||
beforeEach ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], class: 'my-marker', invalidate: 'inside')
|
||||
decoration = {type: 'gutter', class: 'someclass'}
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
|
||||
it "updates line number classes when the marker moves", ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(5, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().deleteRows(0, 1)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(0, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes line number classes when a decoration's marker is invalidated", ->
|
||||
editor.getBuffer().insert([3, 2], 'n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
|
||||
expect(marker.isValid()).toBe false
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().undo()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(marker.isValid()).toBe true
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe true
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
it "removes the classes and unsubscribes from the marker when decoration is removed", ->
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
|
||||
it "removes the line number classes when the decoration's marker is destroyed", ->
|
||||
marker.destroy()
|
||||
|
||||
waitsFor -> not component.decorationChangedImmediate?
|
||||
runs ->
|
||||
expect(lineNumberHasClass(1, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(2, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(3, 'someclass')).toBe false
|
||||
expect(lineNumberHasClass(4, 'someclass')).toBe false
|
||||
|
||||
describe "cursor rendering", ->
|
||||
it "renders the currently visible cursors, translated relative to the scroll position", ->
|
||||
cursor1 = editor.getCursor()
|
||||
@@ -630,12 +898,6 @@ describe "EditorComponent", ->
|
||||
clientY = scrollViewClientRect.top + positionOffset.top - editor.getScrollTop()
|
||||
{clientX, clientY}
|
||||
|
||||
buildMouseEvent = (type, properties...) ->
|
||||
properties = extend({bubbles: true, cancelable: true}, properties...)
|
||||
event = new MouseEvent(type, properties)
|
||||
Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
|
||||
event
|
||||
|
||||
describe "focus handling", ->
|
||||
inputNode = null
|
||||
|
||||
@@ -654,6 +916,22 @@ describe "EditorComponent", ->
|
||||
inputNode.blur()
|
||||
expect(node.classList.contains('is-focused')).toBe false
|
||||
|
||||
describe "selection handling", ->
|
||||
cursor = null
|
||||
|
||||
beforeEach ->
|
||||
cursor = editor.getCursor()
|
||||
cursor.setScreenPosition([0, 0])
|
||||
|
||||
it "adds the 'has-selection' class to the editor when there is a selection", ->
|
||||
expect(node.classList.contains('has-selection')).toBe false
|
||||
|
||||
editor.selectDown()
|
||||
expect(node.classList.contains('has-selection')).toBe true
|
||||
|
||||
cursor.moveDown()
|
||||
expect(node.classList.contains('has-selection')).toBe false
|
||||
|
||||
describe "scrolling", ->
|
||||
it "updates the vertical scrollbar when the scrollTop is changed in the model", ->
|
||||
node.style.height = 4.5 * lineHeightInPixels + 'px'
|
||||
@@ -940,3 +1218,12 @@ describe "EditorComponent", ->
|
||||
editor.setCursorBufferPosition([0, Infinity])
|
||||
wrapperView.show()
|
||||
expect(node.querySelector('.cursor').style['-webkit-transform']).toBe "translate3d(#{9 * charWidth}px, 0px, 0px)"
|
||||
|
||||
buildMouseEvent = (type, properties...) ->
|
||||
properties = extend({bubbles: true, cancelable: true}, properties...)
|
||||
event = new MouseEvent(type, properties)
|
||||
Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
|
||||
if properties.target?
|
||||
Object.defineProperty(event, 'target', get: -> properties.target)
|
||||
Object.defineProperty(event, 'srcObject', get: -> properties.target)
|
||||
event
|
||||
|
||||
@@ -1622,7 +1622,7 @@ describe "Editor", ->
|
||||
editor.setCursorBufferPosition([9,2])
|
||||
editor.insertNewline()
|
||||
expect(editor.lineForBufferRow(10)).toBe ' };'
|
||||
|
||||
|
||||
describe ".backspace()", ->
|
||||
describe "when there is a single cursor", ->
|
||||
changeScreenRangeHandler = null
|
||||
@@ -3205,3 +3205,114 @@ describe "Editor", ->
|
||||
|
||||
editor.pageUp()
|
||||
expect(editor.getScrollTop()).toBe 0
|
||||
|
||||
describe "decorations", ->
|
||||
decoration = null
|
||||
beforeEach ->
|
||||
decoration = {type: 'gutter', class: 'one'}
|
||||
|
||||
it "can add decorations to buffer rows and remove them", ->
|
||||
editor.addDecorationToBufferRow(2, decoration)
|
||||
editor.addDecorationToBufferRow(2, decoration)
|
||||
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toHaveLength 1
|
||||
expect(decorations).toContain decoration
|
||||
|
||||
editor.removeDecorationFromBufferRow(2, decoration)
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toHaveLength 0
|
||||
|
||||
it "can add decorations to buffer row ranges and remove them", ->
|
||||
editor.addDecorationToBufferRowRange(2, 4, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
|
||||
editor.removeDecorationFromBufferRowRange(3, 5, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
|
||||
it "can add decorations associated with markers and remove them", ->
|
||||
marker = editor.displayBuffer.markBufferRange([[2, 13], [3, 15]], class: 'my-marker', invalidate: 'inside')
|
||||
|
||||
editor.addDecorationForMarker(marker, decoration)
|
||||
expect(editor.decorationsForBufferRow 1).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 2).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
|
||||
editor.getBuffer().insert([0, 0], '\n')
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.getBuffer().insert([4, 2], 'n')
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.getBuffer().undo()
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
editor.removeDecorationForMarker(marker, decoration)
|
||||
expect(editor.decorationsForBufferRow 2).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 3).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 4).not.toContain decoration
|
||||
expect(editor.decorationsForBufferRow 5).not.toContain decoration
|
||||
|
||||
describe "decorationsForBufferRow", ->
|
||||
one = {type: 'one', class: 'one'}
|
||||
two = {type: 'two', class: 'two'}
|
||||
typeless = {class: 'typeless'}
|
||||
|
||||
beforeEach ->
|
||||
editor.addDecorationToBufferRow(2, one)
|
||||
editor.addDecorationToBufferRow(2, two)
|
||||
editor.addDecorationToBufferRow(2, typeless)
|
||||
|
||||
it "returns all decorations with no decorationType specified", ->
|
||||
decorations = editor.decorationsForBufferRow(2)
|
||||
expect(decorations).toContain one
|
||||
expect(decorations).toContain two
|
||||
expect(decorations).toContain typeless
|
||||
|
||||
it "returns typeless decorations with all decorationTypes", ->
|
||||
decorations = editor.decorationsForBufferRow(2, 'one')
|
||||
expect(decorations).toContain one
|
||||
expect(decorations).not.toContain two
|
||||
expect(decorations).toContain typeless
|
||||
|
||||
describe "decorationsForBufferRowRange", ->
|
||||
one = {type: 'one', class: 'one'}
|
||||
two = {type: 'two', class: 'two'}
|
||||
typeless = {class: 'typeless'}
|
||||
|
||||
it "returns an object of decorations based on the decorationType", ->
|
||||
editor.addDecorationToBufferRow(2, one)
|
||||
editor.addDecorationToBufferRow(3, one)
|
||||
editor.addDecorationToBufferRow(5, one)
|
||||
|
||||
editor.addDecorationToBufferRow(3, two)
|
||||
editor.addDecorationToBufferRow(4, two)
|
||||
|
||||
editor.addDecorationToBufferRow(3, typeless)
|
||||
editor.addDecorationToBufferRow(5, typeless)
|
||||
|
||||
decorations = editor.decorationsForBufferRowRange(2, 5, 'one')
|
||||
expect(decorations[2]).toContain one
|
||||
|
||||
expect(decorations[3]).toContain one
|
||||
expect(decorations[3]).not.toContain two
|
||||
expect(decorations[3]).toContain typeless
|
||||
|
||||
expect(decorations[4]).toHaveLength 0
|
||||
|
||||
expect(decorations[5]).toContain one
|
||||
expect(decorations[5]).toContain typeless
|
||||
|
||||
@@ -111,6 +111,34 @@ class DisplayBufferMarker
|
||||
setTailBufferPosition: (bufferPosition) ->
|
||||
@bufferMarker.setTailPosition(bufferPosition)
|
||||
|
||||
# Retrieves the screen position of the marker's start. This will always be
|
||||
# less than or equal to the result of {DisplayBufferMarker::getEndScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getStartBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Retrieves the buffer position of the marker's start. This will always be
|
||||
# less than or equal to the result of {DisplayBufferMarker::getEndBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getStartBufferPosition: ->
|
||||
@bufferMarker.getStartPosition()
|
||||
|
||||
# Retrieves the screen position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {DisplayBufferMarker::getStartScreenPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndScreenPosition: ->
|
||||
@displayBuffer.screenPositionForBufferPosition(@getEndBufferPosition(), wrapAtSoftNewlines: true)
|
||||
|
||||
# Retrieves the buffer position of the marker's end. This will always be
|
||||
# greater than or equal to the result of {DisplayBufferMarker::getStartBufferPosition}.
|
||||
#
|
||||
# Returns a {Point}.
|
||||
getEndBufferPosition: ->
|
||||
@bufferMarker.getEndPosition()
|
||||
|
||||
# Sets the marker's tail to the same position as the marker's head.
|
||||
#
|
||||
# This only works if there isn't already a tail position.
|
||||
|
||||
@@ -43,6 +43,8 @@ class DisplayBuffer extends Model
|
||||
@charWidthsByScope = {}
|
||||
@markers = {}
|
||||
@foldsByMarkerId = {}
|
||||
@decorations = {}
|
||||
@decorationMarkerSubscriptions = {}
|
||||
@updateAllScreenLines()
|
||||
@createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes())
|
||||
@subscribe @tokenizedBuffer, 'grammar-changed', (grammar) => @emit 'grammar-changed', grammar
|
||||
@@ -718,6 +720,97 @@ class DisplayBuffer extends Model
|
||||
rangeForAllLines: ->
|
||||
new Range([0, 0], @clipScreenPosition([Infinity, Infinity]))
|
||||
|
||||
decorationsForBufferRow: (bufferRow, decorationType) ->
|
||||
decorations = @decorations[bufferRow] ? []
|
||||
decorations = (dec for dec in decorations when not dec.type? or dec.type is decorationType) if decorationType?
|
||||
decorations
|
||||
|
||||
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
|
||||
decorations = {}
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
decorations[bufferRow] = @decorationsForBufferRow(bufferRow, decorationType)
|
||||
decorations
|
||||
|
||||
addDecorationToBufferRow: (bufferRow, decoration) ->
|
||||
@decorations[bufferRow] ?= []
|
||||
for current in @decorations[bufferRow]
|
||||
return if _.isEqual(current, decoration)
|
||||
@decorations[bufferRow].push(decoration)
|
||||
@emit 'decoration-changed', {bufferRow, decoration, action: 'add'}
|
||||
|
||||
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
|
||||
return unless decorations = @decorations[bufferRow]
|
||||
|
||||
removed = []
|
||||
i = decorations.length - 1
|
||||
while i >= 0
|
||||
if @decorationMatchesPattern(decorations[i], decorationPattern)
|
||||
removed.push decorations[i]
|
||||
decorations.splice(i, 1)
|
||||
i--
|
||||
|
||||
delete @decorations[bufferRow] unless @decorations[bufferRow]?
|
||||
|
||||
for decoration in removed
|
||||
@emit 'decoration-changed', {bufferRow, decoration, action: 'remove'}
|
||||
|
||||
removed
|
||||
|
||||
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
@addDecorationToBufferRow(bufferRow, decoration)
|
||||
return
|
||||
|
||||
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
for bufferRow in [startBufferRow..endBufferRow]
|
||||
@removeDecorationFromBufferRow(bufferRow, decoration)
|
||||
return
|
||||
|
||||
decorationMatchesPattern: (decoration, decorationPattern) ->
|
||||
return false unless decoration? and decorationPattern?
|
||||
for key, value of decorationPattern
|
||||
return false if decoration[key] != value
|
||||
true
|
||||
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
startRow = marker.getStartBufferPosition().row
|
||||
endRow = marker.getEndBufferPosition().row
|
||||
@addDecorationToBufferRowRange(startRow, endRow, decoration)
|
||||
|
||||
changedSubscription = @subscribe marker, 'changed', (e) =>
|
||||
oldStartRow = e.oldHeadBufferPosition.row
|
||||
oldEndRow = e.oldTailBufferPosition.row
|
||||
newStartRow = e.newHeadBufferPosition.row
|
||||
newEndRow = e.newTailBufferPosition.row
|
||||
|
||||
# swap so head is always <= than tail
|
||||
[oldEndRow, oldStartRow] = [oldStartRow, oldEndRow] if oldStartRow > oldEndRow
|
||||
[newEndRow, newStartRow] = [newStartRow, newEndRow] if newStartRow > newEndRow
|
||||
|
||||
@removeDecorationFromBufferRowRange(oldStartRow, oldEndRow, decoration)
|
||||
@addDecorationToBufferRowRange(newStartRow, newEndRow, decoration) if e.isValid
|
||||
|
||||
destroyedSubscription = @subscribe marker, 'destroyed', (e) =>
|
||||
@removeDecorationForMarker(marker, decoration)
|
||||
|
||||
@decorationMarkerSubscriptions[marker.id] ?= []
|
||||
@decorationMarkerSubscriptions[marker.id].push {decoration, changedSubscription, destroyedSubscription}
|
||||
|
||||
removeDecorationForMarker: (marker, decorationPattern) ->
|
||||
return unless @decorationMarkerSubscriptions[marker.id]?
|
||||
|
||||
startRow = marker.getStartBufferPosition().row
|
||||
endRow = marker.getEndBufferPosition().row
|
||||
@removeDecorationFromBufferRowRange(startRow, endRow, decorationPattern)
|
||||
|
||||
for subscription in _.clone(@decorationMarkerSubscriptions[marker.id])
|
||||
if @decorationMatchesPattern(subscription.decoration, decorationPattern)
|
||||
subscription.changedSubscription.off()
|
||||
subscription.destroyedSubscription.off()
|
||||
@decorationMarkerSubscriptions[marker.id] = _.without(@decorationMarkerSubscriptions[marker.id], subscription)
|
||||
|
||||
return
|
||||
|
||||
# Retrieves a {DisplayBufferMarker} based on its id.
|
||||
#
|
||||
# id - A {Number} representing a marker id
|
||||
@@ -961,6 +1054,8 @@ class DisplayBuffer extends Model
|
||||
@emit 'marker-created', @getMarker(marker.id)
|
||||
|
||||
createFoldForMarker: (marker) ->
|
||||
bufferMarker = new DisplayBufferMarker({bufferMarker: marker, displayBuffer: this})
|
||||
@addDecorationForMarker(bufferMarker, type: 'gutter', class: 'folded')
|
||||
new Fold(this, marker)
|
||||
|
||||
foldForMarker: (marker) ->
|
||||
|
||||
@@ -43,12 +43,14 @@ EditorComponent = React.createClass
|
||||
{editor, cursorBlinkPeriod, cursorBlinkResumeDelay} = @props
|
||||
maxLineNumberDigits = editor.getScreenLineCount().toString().length
|
||||
invisibles = if showInvisibles then @state.invisibles else {}
|
||||
hasSelection = editor.getSelection()? and !editor.getSelection().isEmpty()
|
||||
|
||||
if @isMounted()
|
||||
renderedRowRange = @getRenderedRowRange()
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
cursorScreenRanges = @getCursorScreenRanges(renderedRowRange)
|
||||
selectionScreenRanges = @getSelectionScreenRanges(renderedRowRange)
|
||||
decorations = @getGutterDecorations(renderedRowRange)
|
||||
scrollHeight = editor.getScrollHeight()
|
||||
scrollWidth = editor.getScrollWidth()
|
||||
scrollTop = editor.getScrollTop()
|
||||
@@ -67,11 +69,13 @@ EditorComponent = React.createClass
|
||||
|
||||
className = 'editor-contents editor-colors'
|
||||
className += ' is-focused' if focused
|
||||
className += ' has-selection' if hasSelection
|
||||
|
||||
div className: className, style: {fontSize, lineHeight, fontFamily}, tabIndex: -1,
|
||||
GutterComponent {
|
||||
ref: 'gutter', editor, renderedRowRange, maxLineNumberDigits, scrollTop,
|
||||
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow
|
||||
scrollHeight, lineHeightInPixels, @pendingChanges, mouseWheelScreenRow,
|
||||
decorations
|
||||
}
|
||||
|
||||
div ref: 'scrollView', className: 'scroll-view', onMouseDown: @onMouseDown,
|
||||
@@ -225,6 +229,18 @@ EditorComponent = React.createClass
|
||||
|
||||
selectionScreenRanges
|
||||
|
||||
getGutterDecorations: (renderedRowRange) ->
|
||||
{editor} = @props
|
||||
[renderedStartRow, renderedEndRow] = renderedRowRange
|
||||
|
||||
bufferRows = editor.bufferRowsForScreenRows(renderedStartRow, renderedEndRow - 1)
|
||||
|
||||
decorations = {}
|
||||
for bufferRow in bufferRows
|
||||
decorations[bufferRow] = editor.decorationsForBufferRow(bufferRow, 'gutter')
|
||||
decorations[bufferRow].push {class: 'foldable'} if editor.isFoldableAtBufferRow(bufferRow)
|
||||
decorations
|
||||
|
||||
observeEditor: ->
|
||||
{editor} = @props
|
||||
@subscribe editor, 'batched-updates-started', @onBatchedUpdatesStarted
|
||||
@@ -233,6 +249,7 @@ EditorComponent = React.createClass
|
||||
@subscribe editor, 'cursors-moved', @onCursorsMoved
|
||||
@subscribe editor, 'selection-removed selection-screen-range-changed', @onSelectionChanged
|
||||
@subscribe editor, 'selection-added', @onSelectionAdded
|
||||
@subscribe editor, 'decoration-changed', @onDecorationChanged
|
||||
@subscribe editor.$scrollTop.changes, @onScrollTopChanged
|
||||
@subscribe editor.$scrollLeft.changes, @requestUpdate
|
||||
@subscribe editor.$height.changes, @requestUpdate
|
||||
@@ -510,6 +527,11 @@ EditorComponent = React.createClass
|
||||
onCursorsMoved: ->
|
||||
@cursorsMoved = true
|
||||
|
||||
onDecorationChanged: ->
|
||||
@decorationChangedImmediate ?= setImmediate =>
|
||||
@requestUpdate()
|
||||
@decorationChangedImmediate = null
|
||||
|
||||
selectToMousePositionUntilMouseUp: (event) ->
|
||||
{editor} = @props
|
||||
dragging = false
|
||||
|
||||
@@ -214,6 +214,7 @@ class Editor extends Model
|
||||
@subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
|
||||
@subscribe @displayBuffer, 'tokenized', => @handleTokenization()
|
||||
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
|
||||
@subscribe @displayBuffer, "decoration-changed", (e) => @emit 'decoration-changed', e
|
||||
|
||||
getViewClass: ->
|
||||
if atom.config.get('core.useReactEditor')
|
||||
@@ -1057,6 +1058,106 @@ class Editor extends Model
|
||||
selection.insertText(fn(text))
|
||||
selection.setBufferRange(range)
|
||||
|
||||
# Public: Get all the decorations for a buffer row.
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decorationType - the {String} decoration type to filter by eg. 'gutter'
|
||||
#
|
||||
# Returns an {Array} of decorations in the form `[{type: 'gutter', class: 'someclass'}, ...]`
|
||||
# Returns an empty array when no decorations are found
|
||||
decorationsForBufferRow: (bufferRow, decorationType) ->
|
||||
@displayBuffer.decorationsForBufferRow(bufferRow, decorationType)
|
||||
|
||||
# Public: Get all the decorations for a range of buffer rows (inclusive)
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decorationType - the {String} decoration type to filter by eg. 'gutter'
|
||||
#
|
||||
# Returns an {Object} of decorations in the form `{23: [{type: 'gutter', class: 'someclass'}, ...], 24: [...]}`
|
||||
# Returns an {Object} with keyed with all buffer rows in the range containing empty {Array}s when no decorations are found
|
||||
decorationsForBufferRowRange: (startBufferRow, endBufferRow, decorationType) ->
|
||||
@displayBuffer.decorationsForBufferRowRange(startBufferRow, endBufferRow, decorationType)
|
||||
|
||||
# Public: Adds a decoration to a buffer row. For example, use to mark a gutter
|
||||
# line number with a class by using the form `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationToBufferRow: (bufferRow, decoration) ->
|
||||
@displayBuffer.addDecorationToBufferRow(bufferRow, decoration)
|
||||
|
||||
# Public: Removes a decoration from a buffer row.
|
||||
#
|
||||
# ```coffee
|
||||
# editor.removeDecorationFromBufferRow(2, {type: 'gutter', class: 'linter-error'})
|
||||
# ```
|
||||
#
|
||||
# All decorations matching a pattern will be removed. For example, you might
|
||||
# have decorations with a namespace like this attached to a row:
|
||||
#
|
||||
# ```coffee
|
||||
# [
|
||||
# {type: 'gutter', namespace: 'myns', class: 'something'},
|
||||
# {type: 'gutter', namespace: 'myns', class: 'something-else'}
|
||||
# ]
|
||||
# ```
|
||||
#
|
||||
# You can remove both with:
|
||||
#
|
||||
# ```coffee
|
||||
# editor.removeDecorationFromBufferRow(2, {namespace: 'myns'})
|
||||
# ```
|
||||
#
|
||||
# bufferRow - the {int} buffer row
|
||||
# decorationPattern - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns an {Array} of the removed decorations
|
||||
removeDecorationFromBufferRow: (bufferRow, decorationPattern) ->
|
||||
@displayBuffer.removeDecorationFromBufferRow(bufferRow, decorationPattern)
|
||||
|
||||
# Public: Adds a decoration to line numbers in a buffer row range
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationToBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
@displayBuffer.addDecorationToBufferRowRange(startBufferRow, endBufferRow, decoration)
|
||||
|
||||
# Public: Removes a decoration from line numbers in a buffer row range
|
||||
#
|
||||
# startBufferRow - the {int} start of the buffer row range
|
||||
# endBufferRow - the {int} end of the buffer row range (inclusive)
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
removeDecorationFromBufferRowRange: (startBufferRow, endBufferRow, decoration) ->
|
||||
@displayBuffer.removeDecorationFromBufferRowRange(startBufferRow, endBufferRow, decoration)
|
||||
|
||||
# Public: 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.
|
||||
#
|
||||
# marker - the {Marker} you want this decoration to follow
|
||||
# decoration - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
addDecorationForMarker: (marker, decoration) ->
|
||||
@displayBuffer.addDecorationForMarker(marker, decoration)
|
||||
|
||||
# Public: Removes all decorations associated with a {Marker} that match a
|
||||
# `decorationPattern` and stop tracking the {Marker}.
|
||||
#
|
||||
# marker - the {Marker} to detach from
|
||||
# decorationPattern - the {Object} decoration type to filter by eg. `{type: 'gutter', class: 'linter-error'}`
|
||||
#
|
||||
# Returns nothing
|
||||
removeDecorationForMarker: (marker, decorationPattern) ->
|
||||
@displayBuffer.removeDecorationForMarker(marker, decorationPattern)
|
||||
|
||||
# Public: Get the {DisplayBufferMarker} for the given marker id.
|
||||
getMarker: (id) ->
|
||||
@displayBuffer.getMarker(id)
|
||||
@@ -1162,6 +1263,7 @@ class Editor extends Model
|
||||
addCursor: (marker) ->
|
||||
cursor = new Cursor(editor: this, marker: marker)
|
||||
@cursors.push(cursor)
|
||||
@addDecorationForMarker(marker, {class: 'cursor-line'})
|
||||
@emit 'cursor-added', cursor
|
||||
cursor
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
_ = require 'underscore-plus'
|
||||
React = require 'react-atom-fork'
|
||||
{div} = require 'reactionary-atom-fork'
|
||||
{isEqual, isEqualForProperties, multiplyString, toArray} = require 'underscore-plus'
|
||||
@@ -15,7 +16,7 @@ GutterComponent = React.createClass
|
||||
render: ->
|
||||
{scrollHeight, scrollTop} = @props
|
||||
|
||||
div className: 'gutter',
|
||||
div className: 'gutter', onClick: @onClick,
|
||||
div className: 'line-numbers', ref: 'lineNumbers', style:
|
||||
height: scrollHeight
|
||||
WebkitTransform: "translate3d(0px, #{-scrollTop}px, 0px)"
|
||||
@@ -24,6 +25,7 @@ GutterComponent = React.createClass
|
||||
@lineNumberNodesById = {}
|
||||
@lineNumberIdsByScreenRow = {}
|
||||
@screenRowsByLineNumberId = {}
|
||||
@previousDecorations = {}
|
||||
|
||||
componentDidMount: ->
|
||||
@appendDummyLineNumber()
|
||||
@@ -36,10 +38,12 @@ GutterComponent = React.createClass
|
||||
'renderedRowRange', 'scrollTop', 'lineHeightInPixels', 'mouseWheelScreenRow'
|
||||
)
|
||||
|
||||
{renderedRowRange, pendingChanges} = newProps
|
||||
{renderedRowRange, pendingChanges, decorations} = newProps
|
||||
for change in pendingChanges when Math.abs(change.screenDelta) > 0 or Math.abs(change.bufferDelta) > 0
|
||||
return true unless change.end <= renderedRowRange.start or renderedRowRange.end <= change.start
|
||||
|
||||
return true unless _.isEqual(@previousDecorations, decorations)
|
||||
|
||||
false
|
||||
|
||||
componentDidUpdate: (oldProps) ->
|
||||
@@ -70,7 +74,7 @@ GutterComponent = React.createClass
|
||||
@removeLineNumberNodes(lineNumberIdsToPreserve)
|
||||
|
||||
appendOrUpdateVisibleLineNumberNodes: ->
|
||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits} = @props
|
||||
{editor, renderedRowRange, scrollTop, maxLineNumberDigits, decorations} = @props
|
||||
[startRow, endRow] = renderedRowRange
|
||||
|
||||
newLineNumberIds = null
|
||||
@@ -91,12 +95,12 @@ GutterComponent = React.createClass
|
||||
visibleLineNumberIds.add(id)
|
||||
|
||||
if @hasLineNumberNode(id)
|
||||
@updateLineNumberNode(id, screenRow)
|
||||
@updateLineNumberNode(id, bufferRow, screenRow, wrapCount > 0, decorations[bufferRow])
|
||||
else
|
||||
newLineNumberIds ?= []
|
||||
newLineNumbersHTML ?= ""
|
||||
newLineNumberIds.push(id)
|
||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow)
|
||||
newLineNumbersHTML += @buildLineNumberHTML(bufferRow, wrapCount > 0, maxLineNumberDigits, screenRow, decorations[bufferRow])
|
||||
@screenRowsByLineNumberId[id] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = id
|
||||
|
||||
@@ -110,6 +114,7 @@ GutterComponent = React.createClass
|
||||
@lineNumberNodesById[lineNumberId] = lineNumberNode
|
||||
node.appendChild(lineNumberNode)
|
||||
|
||||
@previousDecorations = decorations
|
||||
visibleLineNumberIds
|
||||
|
||||
removeLineNumberNodes: (lineNumberIdsToPreserve) ->
|
||||
@@ -123,7 +128,7 @@ GutterComponent = React.createClass
|
||||
delete @screenRowsByLineNumberId[lineNumberId]
|
||||
node.removeChild(lineNumberNode)
|
||||
|
||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow) ->
|
||||
buildLineNumberHTML: (bufferRow, softWrapped, maxLineNumberDigits, screenRow, decorations) ->
|
||||
if screenRow?
|
||||
{lineHeightInPixels} = @props
|
||||
style = "position: absolute; top: #{screenRow * lineHeightInPixels}px;"
|
||||
@@ -131,7 +136,13 @@ GutterComponent = React.createClass
|
||||
style = "visibility: hidden;"
|
||||
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped, maxLineNumberDigits)
|
||||
|
||||
"<div class=\"line-number\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
classes = ''
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
classes += decoration.class + ' ' if not softWrapped or softWrapped and decoration.softWrap
|
||||
classes += 'line-number'
|
||||
|
||||
"<div class=\"#{classes}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
|
||||
|
||||
buildLineNumberInnerHTML: (bufferRow, softWrapped, maxLineNumberDigits) ->
|
||||
if softWrapped
|
||||
@@ -143,11 +154,23 @@ GutterComponent = React.createClass
|
||||
iconHTML = '<div class="icon-right"></div>'
|
||||
padding + lineNumber + iconHTML
|
||||
|
||||
updateLineNumberNode: (lineNumberId, screenRow) ->
|
||||
updateLineNumberNode: (lineNumberId, bufferRow, screenRow, softWrapped, decorations) ->
|
||||
node = @lineNumberNodesById[lineNumberId]
|
||||
previousDecorations = @previousDecorations[bufferRow]
|
||||
|
||||
if previousDecorations?
|
||||
for decoration in previousDecorations
|
||||
node.classList.remove(decoration.class) if not contains(decorations, decoration)
|
||||
|
||||
if decorations?
|
||||
for decoration in decorations
|
||||
if not contains(previousDecorations, decoration) and (not softWrapped or softWrapped and decoration.softWrap)
|
||||
node.classList.add(decoration.class)
|
||||
|
||||
unless @screenRowsByLineNumberId[lineNumberId] is screenRow
|
||||
{lineHeightInPixels} = @props
|
||||
@lineNumberNodesById[lineNumberId].style.top = screenRow * lineHeightInPixels + 'px'
|
||||
@lineNumberNodesById[lineNumberId].dataset.screenRow = screenRow
|
||||
node.style.top = screenRow * lineHeightInPixels + 'px'
|
||||
node.dataset.screenRow = screenRow
|
||||
@screenRowsByLineNumberId[lineNumberId] = screenRow
|
||||
@lineNumberIdsByScreenRow[screenRow] = lineNumberId
|
||||
|
||||
@@ -156,3 +179,22 @@ GutterComponent = React.createClass
|
||||
|
||||
lineNumberNodeForScreenRow: (screenRow) ->
|
||||
@lineNumberNodesById[@lineNumberIdsByScreenRow[screenRow]]
|
||||
|
||||
onClick: (event) ->
|
||||
{editor} = @props
|
||||
{target} = event
|
||||
lineNumber = target.parentNode
|
||||
|
||||
if target.classList.contains('icon-right') and lineNumber.classList.contains('foldable')
|
||||
bufferRow = parseInt(lineNumber.getAttribute('data-buffer-row'))
|
||||
if lineNumber.classList.contains('folded')
|
||||
editor.unfoldBufferRow(bufferRow)
|
||||
else
|
||||
editor.foldBufferRow(bufferRow)
|
||||
|
||||
# Created because underscore uses === not _.isEqual, which we need
|
||||
contains = (array, target) ->
|
||||
return false unless array?
|
||||
for object in array
|
||||
return true if _.isEqual(object, target)
|
||||
false
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{View, $} = require 'space-pen'
|
||||
Grim = require 'Grim'
|
||||
React = require 'react-atom-fork'
|
||||
EditorComponent = require './editor-component'
|
||||
{defaults} = require 'underscore-plus'
|
||||
@@ -53,9 +54,11 @@ class ReactEditorView extends View
|
||||
|
||||
@gutter = $(node).find('.gutter')
|
||||
@gutter.removeClassFromAllLines = (klass) =>
|
||||
Grim.deprecate 'You no longer need to manually add and remove classes. Use `Editor::removeDecorationFromBufferRow()` and related functions'
|
||||
@gutter.find('.line-number').removeClass(klass)
|
||||
|
||||
@gutter.addClassToLine = (bufferRow, klass) =>
|
||||
Grim.deprecate 'You no longer need to manually add and remove classes. Use `Editor::addDecorationToBufferRow()` and related functions'
|
||||
lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
|
||||
lines.addClass(klass)
|
||||
lines.length > 0
|
||||
|
||||
@@ -69,11 +69,13 @@
|
||||
.gutter {
|
||||
.line-number {
|
||||
white-space: nowrap;
|
||||
padding: 0 .5em;
|
||||
padding-left: .5em;
|
||||
|
||||
.icon-right {
|
||||
padding: 0;
|
||||
padding-left: .1em;
|
||||
padding: 0 .4em;
|
||||
&:before {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +123,7 @@
|
||||
visibility: hidden;
|
||||
padding-left: .1em;
|
||||
padding-right: .5em;
|
||||
opacity: .7;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.editor .gutter:hover .line-number.foldable .icon-right {
|
||||
|
||||
Reference in New Issue
Block a user