mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-20 07:28:08 +03:00
WIP: working on only rendering on-screen lines. still half-baked.
This commit is contained in:
parent
5ae40a3cb8
commit
1e2d333a15
@ -42,7 +42,7 @@ describe "editor.", ->
|
||||
editor.backspace()
|
||||
|
||||
describe "9000-line-file.", ->
|
||||
benchmark "opening", 1, ->
|
||||
fbenchmark "opening", 1, ->
|
||||
editor.setBuffer new Buffer(require.resolve('fixtures/huge.js'))
|
||||
|
||||
describe "at-end.", ->
|
||||
|
@ -9,7 +9,15 @@ _ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
|
||||
describe "Editor", ->
|
||||
[rootView, buffer, editor] = []
|
||||
[rootView, buffer, editor, cachedLineHeight] = []
|
||||
|
||||
getLineHeight = ->
|
||||
return cachedLineHeight if cachedLineHeight
|
||||
editorForMeasurement = new Editor()
|
||||
editorForMeasurement.attachToDom()
|
||||
cachedLineHeight = editorForMeasurement.lineHeight
|
||||
editorForMeasurement.remove()
|
||||
cachedLineHeight
|
||||
|
||||
beforeEach ->
|
||||
rootView = new RootView(pathToOpen: require.resolve('fixtures/sample.js'))
|
||||
@ -17,6 +25,11 @@ describe "Editor", ->
|
||||
editor = rootView.activeEditor()
|
||||
buffer = editor.buffer
|
||||
|
||||
editor.attachToDom = (options={}) ->
|
||||
heightInLines = options.heightInLines ? this.buffer.numLines()
|
||||
this.height(getLineHeight() * heightInLines)
|
||||
$('#jasmine-content').append(this)
|
||||
|
||||
editor.autoIndent = false
|
||||
editor.enableKeymap()
|
||||
editor.isFocused = true
|
||||
@ -62,125 +75,152 @@ describe "Editor", ->
|
||||
expect(openHandler).not.toHaveBeenCalled()
|
||||
|
||||
describe "text rendering", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
|
||||
it "creates a line element for each line in the buffer with the html-escaped text of the line", ->
|
||||
expect(editor.lines.find('.line').length).toEqual(buffer.numLines())
|
||||
expect(buffer.lineForRow(2)).toContain('<')
|
||||
expect(editor.lines.find('.line:eq(2)').html()).toContain '<'
|
||||
|
||||
# renders empty lines with a non breaking space
|
||||
expect(buffer.lineForRow(10)).toBe ''
|
||||
expect(editor.lines.find('.line:eq(10)').html()).toBe ' '
|
||||
|
||||
it "syntax highlights code based on the file type", ->
|
||||
line1 = editor.lines.find('.line:first')
|
||||
expect(line1.find('span:eq(0)')).toMatchSelector '.keyword.definition'
|
||||
expect(line1.find('span:eq(0)').text()).toBe 'var'
|
||||
expect(line1.find('span:eq(1)')).toMatchSelector '.text'
|
||||
expect(line1.find('span:eq(1)').text()).toBe ' '
|
||||
expect(line1.find('span:eq(2)')).toMatchSelector '.identifier'
|
||||
expect(line1.find('span:eq(2)').text()).toBe 'quicksort'
|
||||
expect(line1.find('span:eq(4)')).toMatchSelector '.operator'
|
||||
expect(line1.find('span:eq(4)').text()).toBe '='
|
||||
|
||||
line12 = editor.lines.find('.line:eq(11)')
|
||||
expect(line12.find('span:eq(1)')).toMatchSelector '.keyword'
|
||||
|
||||
describe "when lines are updated in the buffer", ->
|
||||
it "syntax highlights the updated lines", ->
|
||||
expect(editor.lines.find('.line:eq(0) span:eq(0)')).toMatchSelector '.keyword.definition'
|
||||
buffer.insert([0, 4], "g")
|
||||
expect(editor.lines.find('.line:eq(0) span:eq(0)')).toMatchSelector '.keyword.definition'
|
||||
|
||||
# verify that re-highlighting can occur below the changed line
|
||||
buffer.insert([5,0], "/* */")
|
||||
buffer.insert([1,0], "/*")
|
||||
expect(editor.lines.find('.line:eq(2) span:eq(0)')).toMatchSelector '.comment'
|
||||
|
||||
describe "when soft-wrap is enabled", ->
|
||||
describe "when all lines in the buffer are visible on screen", ->
|
||||
beforeEach ->
|
||||
setEditorWidthInChars(editor, 50)
|
||||
editor.setSoftWrap(true)
|
||||
expect(editor.renderer.maxLineLength).toBe 50
|
||||
editor.attachToDom()
|
||||
expect(editor.height()).toBe buffer.numLines() * editor.lineHeight
|
||||
|
||||
it "wraps lines that are too long to fit within the editor's width, adjusting cursor positioning accordingly", ->
|
||||
expect(editor.lines.find('.line').length).toBe 16
|
||||
expect(editor.lines.find('.line:eq(3)').text()).toBe " var pivot = items.shift(), current, left = [], "
|
||||
expect(editor.lines.find('.line:eq(4)').text()).toBe "right = [];"
|
||||
it "creates a line element for each line in the buffer with the html-escaped text of the line", ->
|
||||
expect(editor.lines.find('.line').length).toEqual(buffer.numLines())
|
||||
expect(buffer.lineForRow(2)).toContain('<')
|
||||
expect(editor.lines.find('.line:eq(2)').html()).toContain '<'
|
||||
|
||||
editor.setCursorBufferPosition([3, 51])
|
||||
expect(editor.find('.cursor').offset()).toEqual(editor.lines.find('.line:eq(4)').offset())
|
||||
# renders empty lines with a non breaking space
|
||||
expect(buffer.lineForRow(10)).toBe ''
|
||||
expect(editor.lines.find('.line:eq(10)').html()).toBe ' '
|
||||
|
||||
editor.setCursorBufferPosition([4, 0])
|
||||
expect(editor.find('.cursor').offset()).toEqual(editor.lines.find('.line:eq(5)').offset())
|
||||
it "syntax highlights code based on the file type", ->
|
||||
line1 = editor.lines.find('.line:first')
|
||||
expect(line1.find('span:eq(0)')).toMatchSelector '.keyword.definition'
|
||||
expect(line1.find('span:eq(0)').text()).toBe 'var'
|
||||
expect(line1.find('span:eq(1)')).toMatchSelector '.text'
|
||||
expect(line1.find('span:eq(1)').text()).toBe ' '
|
||||
expect(line1.find('span:eq(2)')).toMatchSelector '.identifier'
|
||||
expect(line1.find('span:eq(2)').text()).toBe 'quicksort'
|
||||
expect(line1.find('span:eq(4)')).toMatchSelector '.operator'
|
||||
expect(line1.find('span:eq(4)').text()).toBe '='
|
||||
|
||||
editor.getSelection().setBufferRange(new Range([6, 30], [6, 55]))
|
||||
[region1, region2] = editor.getSelection().regions
|
||||
expect(region1.offset().top).toBe(editor.lines.find('.line:eq(7)').offset().top)
|
||||
expect(region2.offset().top).toBe(editor.lines.find('.line:eq(8)').offset().top)
|
||||
line12 = editor.lines.find('.line:eq(11)')
|
||||
expect(line12.find('span:eq(1)')).toMatchSelector '.keyword'
|
||||
|
||||
it "handles changes to wrapped lines correctly", ->
|
||||
buffer.insert([6, 28], '1234567')
|
||||
expect(editor.lines.find('.line:eq(7)').text()).toBe ' current < pivot ? left1234567.push(current) '
|
||||
expect(editor.lines.find('.line:eq(8)').text()).toBe ': right.push(current);'
|
||||
expect(editor.lines.find('.line:eq(9)').text()).toBe ' }'
|
||||
describe "when lines are updated in the buffer", ->
|
||||
it "syntax highlights the updated lines", ->
|
||||
expect(editor.lines.find('.line:eq(0) span:eq(0)')).toMatchSelector '.keyword.definition'
|
||||
buffer.insert([0, 4], "g")
|
||||
expect(editor.lines.find('.line:eq(0) span:eq(0)')).toMatchSelector '.keyword.definition'
|
||||
|
||||
it "changes the max line length and repositions the cursor when the window size changes", ->
|
||||
editor.setCursorBufferPosition([3, 60])
|
||||
setEditorWidthInChars(editor, 40)
|
||||
$(window).trigger 'resize'
|
||||
expect(editor.lines.find('.line').length).toBe 19
|
||||
expect(editor.lines.find('.line:eq(4)').text()).toBe "left = [], right = [];"
|
||||
expect(editor.lines.find('.line:eq(5)').text()).toBe " while(items.length > 0) {"
|
||||
expect(editor.bufferPositionForScreenPosition(editor.getCursorScreenPosition())).toEqual [3, 60]
|
||||
# verify that re-highlighting can occur below the changed line
|
||||
buffer.insert([5,0], "/* */")
|
||||
buffer.insert([1,0], "/*")
|
||||
expect(editor.lines.find('.line:eq(2) span:eq(0)')).toMatchSelector '.comment'
|
||||
|
||||
it "wraps the lines of any newly assigned buffers", ->
|
||||
otherBuffer = new Buffer
|
||||
otherBuffer.setText([1..100].join(''))
|
||||
editor.setBuffer(otherBuffer)
|
||||
expect(editor.lines.find('.line').length).toBeGreaterThan(1)
|
||||
describe "when soft-wrap is enabled", ->
|
||||
beforeEach ->
|
||||
setEditorWidthInChars(editor, 50)
|
||||
editor.setSoftWrap(true)
|
||||
expect(editor.renderer.maxLineLength).toBe 50
|
||||
|
||||
it "unwraps lines and cancels window resize listener when softwrap is disabled", ->
|
||||
editor.toggleSoftWrap()
|
||||
expect(editor.lines.find('.line:eq(3)').text()).toBe ' var pivot = items.shift(), current, left = [], right = [];'
|
||||
it "wraps lines that are too long to fit within the editor's width, adjusting cursor positioning accordingly", ->
|
||||
expect(editor.lines.find('.line').length).toBe 16
|
||||
expect(editor.lines.find('.line:eq(3)').text()).toBe " var pivot = items.shift(), current, left = [], "
|
||||
expect(editor.lines.find('.line:eq(4)').text()).toBe "right = [];"
|
||||
|
||||
spyOn(editor, 'setMaxLineLength')
|
||||
$(window).trigger 'resize'
|
||||
expect(editor.setMaxLineLength).not.toHaveBeenCalled()
|
||||
editor.setCursorBufferPosition([3, 51])
|
||||
expect(editor.find('.cursor').offset()).toEqual(editor.lines.find('.line:eq(4)').offset())
|
||||
|
||||
it "allows the cursor to move down to the last line", ->
|
||||
_.times editor.getLastScreenRow(), -> editor.moveCursorDown()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [editor.getLastScreenRow(), 0]
|
||||
editor.moveCursorDown()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [editor.getLastScreenRow(), 2]
|
||||
editor.setCursorBufferPosition([4, 0])
|
||||
expect(editor.find('.cursor').offset()).toEqual(editor.lines.find('.line:eq(5)').offset())
|
||||
|
||||
it "allows the cursor to move up to a shorter soft wrapped line", ->
|
||||
editor.setCursorScreenPosition([11, 15])
|
||||
editor.moveCursorUp()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [10, 10]
|
||||
editor.moveCursorUp()
|
||||
editor.moveCursorUp()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [8, 15]
|
||||
editor.getSelection().setBufferRange(new Range([6, 30], [6, 55]))
|
||||
[region1, region2] = editor.getSelection().regions
|
||||
expect(region1.offset().top).toBe(editor.lines.find('.line:eq(7)').offset().top)
|
||||
expect(region2.offset().top).toBe(editor.lines.find('.line:eq(8)').offset().top)
|
||||
|
||||
it "it allows the cursor to wrap when moving horizontally past the beginning / end of a wrapped line", ->
|
||||
editor.setCursorScreenPosition([11, 0])
|
||||
editor.moveCursorLeft()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [10, 10]
|
||||
it "handles changes to wrapped lines correctly", ->
|
||||
buffer.insert([6, 28], '1234567')
|
||||
expect(editor.lines.find('.line:eq(7)').text()).toBe ' current < pivot ? left1234567.push(current) '
|
||||
expect(editor.lines.find('.line:eq(8)').text()).toBe ': right.push(current);'
|
||||
expect(editor.lines.find('.line:eq(9)').text()).toBe ' }'
|
||||
|
||||
editor.moveCursorRight()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [11, 0]
|
||||
it "changes the max line length and repositions the cursor when the window size changes", ->
|
||||
editor.setCursorBufferPosition([3, 60])
|
||||
setEditorWidthInChars(editor, 40)
|
||||
$(window).trigger 'resize'
|
||||
expect(editor.lines.find('.line').length).toBe 19
|
||||
expect(editor.lines.find('.line:eq(4)').text()).toBe "left = [], right = [];"
|
||||
expect(editor.lines.find('.line:eq(5)').text()).toBe " while(items.length > 0) {"
|
||||
expect(editor.bufferPositionForScreenPosition(editor.getCursorScreenPosition())).toEqual [3, 60]
|
||||
|
||||
it "calls .setMaxLineLength() when the editor is attached because now its dimensions are available to calculate it", ->
|
||||
otherEditor = new Editor()
|
||||
spyOn(otherEditor, 'setMaxLineLength')
|
||||
it "wraps the lines of any newly assigned buffers", ->
|
||||
otherBuffer = new Buffer
|
||||
otherBuffer.setText([1..100].join(''))
|
||||
editor.setBuffer(otherBuffer)
|
||||
expect(editor.lines.find('.line').length).toBeGreaterThan(1)
|
||||
|
||||
otherEditor.setSoftWrap(true)
|
||||
expect(otherEditor.setMaxLineLength).not.toHaveBeenCalled()
|
||||
it "unwraps lines and cancels window resize listener when softwrap is disabled", ->
|
||||
editor.toggleSoftWrap()
|
||||
expect(editor.lines.find('.line:eq(3)').text()).toBe ' var pivot = items.shift(), current, left = [], right = [];'
|
||||
|
||||
otherEditor.simulateDomAttachment()
|
||||
expect(otherEditor.setMaxLineLength).toHaveBeenCalled()
|
||||
spyOn(editor, 'setMaxLineLength')
|
||||
$(window).trigger 'resize'
|
||||
expect(editor.setMaxLineLength).not.toHaveBeenCalled()
|
||||
|
||||
it "allows the cursor to move down to the last line", ->
|
||||
_.times editor.getLastScreenRow(), -> editor.moveCursorDown()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [editor.getLastScreenRow(), 0]
|
||||
editor.moveCursorDown()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [editor.getLastScreenRow(), 2]
|
||||
|
||||
it "allows the cursor to move up to a shorter soft wrapped line", ->
|
||||
editor.setCursorScreenPosition([11, 15])
|
||||
editor.moveCursorUp()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [10, 10]
|
||||
editor.moveCursorUp()
|
||||
editor.moveCursorUp()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [8, 15]
|
||||
|
||||
it "it allows the cursor to wrap when moving horizontally past the beginning / end of a wrapped line", ->
|
||||
editor.setCursorScreenPosition([11, 0])
|
||||
editor.moveCursorLeft()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [10, 10]
|
||||
|
||||
editor.moveCursorRight()
|
||||
expect(editor.getCursorScreenPosition()).toEqual [11, 0]
|
||||
|
||||
it "calls .setMaxLineLength() when the editor is attached because now its dimensions are available to calculate it", ->
|
||||
otherEditor = new Editor()
|
||||
spyOn(otherEditor, 'setMaxLineLength')
|
||||
|
||||
otherEditor.setSoftWrap(true)
|
||||
expect(otherEditor.setMaxLineLength).not.toHaveBeenCalled()
|
||||
|
||||
otherEditor.simulateDomAttachment()
|
||||
expect(otherEditor.setMaxLineLength).toHaveBeenCalled()
|
||||
|
||||
fdescribe "when some lines at the end of the buffer are not visible on screen", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom(heightInLines: 5.5)
|
||||
|
||||
it "only renders the visible lines, giving the final line a margin-bottom to account for the missing lines", ->
|
||||
expect(editor.lines.find('.line').length).toBe 6
|
||||
expectedMarginBottom = (buffer.numLines() - 6) * editor.lineHeight
|
||||
expect(editor.lines.find('.line:last').css('margin-bottom')).toBe "#{expectedMarginBottom}px"
|
||||
|
||||
it "renders additional lines when the editor is scrolled down, adjusting margins appropriately", ->
|
||||
editor.scroller.scrollTop(editor.lineHeight * 2.5)
|
||||
editor.scroller.trigger 'scroll'
|
||||
|
||||
expect(editor.lines.find('.line').length).toBe 6
|
||||
expect(editor.lines.find('.line:first')).toHaveText buffer.lineForRow(2)
|
||||
expect(editor.lines.find('.line:last')).toHaveText buffer.lineForRow(7)
|
||||
|
||||
for line, index in editor.lines.find('.line')
|
||||
marginBottom = $(line).css('margin-bottom')
|
||||
if index == 5
|
||||
expectedMarginBottom = (buffer.numLines() - 8) * editor.lineHeight
|
||||
expect(marginBottom).toBe "#{expectedMarginBottom}px"
|
||||
else
|
||||
expect(marginBottom).toBe '0px'
|
||||
|
||||
describe "gutter rendering", ->
|
||||
it "creates a line number element for each line in the buffer", ->
|
||||
|
@ -203,6 +203,7 @@ class Editor extends View
|
||||
false
|
||||
|
||||
@scroller.on 'scroll', =>
|
||||
@updateLines()
|
||||
@gutter.scrollTop(@scroller.scrollTop())
|
||||
if @scroller.scrollLeft() == 0
|
||||
@gutter.removeClass('drop-shadow')
|
||||
@ -235,7 +236,36 @@ class Editor extends View
|
||||
renderLines: ->
|
||||
@lineCache = []
|
||||
@lines.find('.line').remove()
|
||||
@insertLineElements(0, @buildLineElements(0, @getLastScreenRow()))
|
||||
|
||||
lastVisibleRow = @getLastVisibleRow()
|
||||
@firstRenderedScreenRow = 0
|
||||
@lastRenderedScreenRow = lastVisibleRow
|
||||
|
||||
lineElements = @buildLineElements(0, lastVisibleRow)
|
||||
lineElements.last().css('margin-bottom', (@getLastScreenRow() - lastVisibleRow) * @lineHeight)
|
||||
@insertLineElements(0, lineElements)
|
||||
|
||||
updateLines: ->
|
||||
firstVisibleRow = @getFirstVisibleRow()
|
||||
lastVisibleRow = @getLastVisibleRow()
|
||||
|
||||
# if @firstRenderedScreenRow < firstVisibleRow
|
||||
# @lines.find('.line:first').remove() for row in [@firstRenderedScreenRow...firstVisibleRow]
|
||||
# @firstRenderedScreenRow = firstVisibleRow
|
||||
|
||||
if @lastRenderedScreenRow < lastVisibleRow
|
||||
@lines.find('.line:last').css('margin-bottom', 'inherit')
|
||||
console.log "building line elements for", @lastRenderedScreenRow + 1, lastVisibleRow
|
||||
lineElements = @buildLineElements(@lastRenderedScreenRow + 1, lastVisibleRow)
|
||||
lineElements.last().css('margin-bottom', (@getLastScreenRow() - lastVisibleRow) * @lineHeight)
|
||||
@insertLineElements(@lastRenderedScreenRow + 1, lineElements)
|
||||
@lastRenderedScreenRow = lastVisibleRow
|
||||
|
||||
getFirstVisibleRow: ->
|
||||
Math.floor(@scroller.scrollTop() / @lineHeight)
|
||||
|
||||
getLastVisibleRow: ->
|
||||
Math.ceil((@scroller.scrollTop() + @scroller.height()) / @lineHeight) - 1
|
||||
|
||||
getScreenLines: ->
|
||||
@renderer.getLines()
|
||||
@ -345,6 +375,7 @@ class Editor extends View
|
||||
@spliceLineElements(startRow, endRow - startRow + 1, lineElements)
|
||||
|
||||
spliceLineElements: (startRow, rowCount, lineElements) ->
|
||||
console.log "splice", startRow, rowCount, lineElements.length
|
||||
endRow = startRow + rowCount
|
||||
elementToInsertBefore = @lineCache[startRow]
|
||||
elementsToReplace = @lineCache[startRow...endRow]
|
||||
@ -357,6 +388,7 @@ class Editor extends View
|
||||
if elementToInsertBefore
|
||||
lines.insertBefore(fragment, elementToInsertBefore)
|
||||
else
|
||||
console.log "appending child at start row", startRow, lineElements.text()
|
||||
lines.appendChild(fragment)
|
||||
|
||||
elementsToReplace.forEach (element) =>
|
||||
|
Loading…
Reference in New Issue
Block a user