mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge pull request #97 from github/batch-screen-updates
Update the screen once per keystroke, and make typing faster
This commit is contained in:
@@ -143,12 +143,6 @@ describe 'Buffer', ->
|
||||
waitsFor "file to be removed", ->
|
||||
not bufferToDelete.getPath()
|
||||
|
||||
describe ".isModified()", ->
|
||||
beforeEach ->
|
||||
buffer.destroy()
|
||||
waitsFor "file to be removed", ->
|
||||
not bufferToDelete.getPath()
|
||||
|
||||
describe ".isModified()", ->
|
||||
it "returns true when user changes buffer", ->
|
||||
expect(buffer.isModified()).toBeFalsy()
|
||||
@@ -733,3 +727,23 @@ describe 'Buffer', ->
|
||||
expect(buffer.isEmpty()).toBeFalsy()
|
||||
buffer.setText('\n')
|
||||
expect(buffer.isEmpty()).toBeFalsy()
|
||||
|
||||
describe "stopped-changing event", ->
|
||||
it "fires 'stoppedChangingDelay' ms after the last buffer change", ->
|
||||
delay = buffer.stoppedChangingDelay
|
||||
stoppedChangingHandler = jasmine.createSpy("stoppedChangingHandler")
|
||||
buffer.on 'stopped-changing', stoppedChangingHandler
|
||||
|
||||
buffer.insert([0, 0], 'a')
|
||||
expect(stoppedChangingHandler).not.toHaveBeenCalled()
|
||||
|
||||
advanceClock(delay / 2)
|
||||
|
||||
buffer.insert([0, 0], 'b')
|
||||
expect(stoppedChangingHandler).not.toHaveBeenCalled()
|
||||
|
||||
advanceClock(delay / 2)
|
||||
expect(stoppedChangingHandler).not.toHaveBeenCalled()
|
||||
|
||||
advanceClock(delay / 2)
|
||||
expect(stoppedChangingHandler).toHaveBeenCalled()
|
||||
|
||||
@@ -238,6 +238,7 @@ describe "Editor", ->
|
||||
editor.setSelectedBufferRange([[40, 0], [43, 1]])
|
||||
expect(editor.getSelection().getScreenRange()).toEqual [[40, 0], [43, 1]]
|
||||
previousScrollHeight = editor.verticalScrollbar.prop('scrollHeight')
|
||||
|
||||
editor.scrollTop(750)
|
||||
expect(editor.scrollTop()).toBe 750
|
||||
|
||||
@@ -483,6 +484,8 @@ describe "Editor", ->
|
||||
describe "when the font size changes on the view", ->
|
||||
it "updates the font sizes of editors and recalculates dimensions critical to cursor positioning", ->
|
||||
rootView.attachToDom()
|
||||
rootView.height(200)
|
||||
rootView.width(200)
|
||||
rootView.setFontSize(10)
|
||||
lineHeightBefore = editor.lineHeight
|
||||
charWidthBefore = editor.charWidth
|
||||
@@ -656,13 +659,11 @@ describe "Editor", ->
|
||||
it "places an additional cursor", ->
|
||||
editor.attachToDom()
|
||||
setEditorHeightInLines(editor, 5)
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [3, 0])
|
||||
editor.setCursorBufferPosition([3, 0])
|
||||
editor.scrollTop(editor.lineHeight * 6)
|
||||
|
||||
spyOn(editor, "scrollTo").andCallThrough()
|
||||
|
||||
editor.renderedLines.trigger mousedownEvent(editor: editor, point: [6, 0], metaKey: true)
|
||||
expect(editor.scrollTo.callCount).toBe 1
|
||||
expect(editor.scrollTop()).toBe editor.lineHeight * (6 - editor.vScrollMargin)
|
||||
|
||||
[cursor1, cursor2] = editor.getCursorViews()
|
||||
expect(cursor1.position()).toEqual(top: 3 * editor.lineHeight, left: 0)
|
||||
@@ -819,8 +820,8 @@ describe "Editor", ->
|
||||
expect(region1.position().top).toBeCloseTo(2 * lineHeight)
|
||||
expect(region1.position().left).toBeCloseTo(7 * charWidth)
|
||||
expect(region1.height()).toBeCloseTo lineHeight
|
||||
expect(region1.width()).toBeCloseTo(editor.renderedLines.outerWidth() - region1.position().left)
|
||||
|
||||
expect(region1.width()).toBeCloseTo(editor.renderedLines.outerWidth() - region1.position().left)
|
||||
region2 = selectionView.regions[1]
|
||||
expect(region2.position().top).toBeCloseTo(3 * lineHeight)
|
||||
expect(region2.position().left).toBeCloseTo(0)
|
||||
@@ -837,8 +838,8 @@ describe "Editor", ->
|
||||
expect(region1.position().top).toBeCloseTo(2 * lineHeight)
|
||||
expect(region1.position().left).toBeCloseTo(7 * charWidth)
|
||||
expect(region1.height()).toBeCloseTo lineHeight
|
||||
expect(region1.width()).toBeCloseTo(editor.renderedLines.outerWidth() - region1.position().left)
|
||||
|
||||
expect(region1.width()).toBeCloseTo(editor.renderedLines.outerWidth() - region1.position().left)
|
||||
region2 = selectionView.regions[1]
|
||||
expect(region2.position().top).toBeCloseTo(3 * lineHeight)
|
||||
expect(region2.position().left).toBeCloseTo(0)
|
||||
@@ -861,7 +862,7 @@ describe "Editor", ->
|
||||
expect(selectionView.regions.length).toBe 3
|
||||
expect(selectionView.find('.selection').length).toBe 3
|
||||
|
||||
selectionView.updateAppearance()
|
||||
selectionView.updateDisplay()
|
||||
expect(selectionView.regions.length).toBe 3
|
||||
expect(selectionView.find('.selection').length).toBe 3
|
||||
|
||||
@@ -906,7 +907,7 @@ describe "Editor", ->
|
||||
editor.setCursorScreenPosition(row: 2, column: 2)
|
||||
expect(editor.getCursorView().position()).toEqual(top: 2 * editor.lineHeight, left: 2 * editor.charWidth)
|
||||
|
||||
it "removes the idle class while moving, then adds it back when it stops", ->
|
||||
xit "removes the idle class while moving, then adds it back when it stops", ->
|
||||
cursorView = editor.getCursorView()
|
||||
advanceClock(200)
|
||||
|
||||
@@ -934,11 +935,11 @@ describe "Editor", ->
|
||||
|
||||
editor.setSelectedBufferRange([[0, 0], [3, 0]])
|
||||
expect(editor.getSelection().isEmpty()).toBeFalsy()
|
||||
expect(cursorView).not.toBeVisible()
|
||||
expect(cursorView.css('visibility')).toBe 'hidden'
|
||||
|
||||
editor.setCursorBufferPosition([1, 3])
|
||||
expect(editor.getSelection().isEmpty()).toBeTruthy()
|
||||
expect(cursorView).toBeVisible()
|
||||
expect(cursorView.css('visibility')).toBe 'visible'
|
||||
|
||||
describe "auto-scrolling", ->
|
||||
it "only auto-scrolls when the last cursor is moved", ->
|
||||
@@ -1339,7 +1340,7 @@ describe "Editor", ->
|
||||
editor.attachToDom(heightInLines: 5)
|
||||
spyOn(editor, "scrollTo")
|
||||
|
||||
describe "when the change the precedes the first rendered row", ->
|
||||
describe "when the change precedes the first rendered row", ->
|
||||
it "inserts and removes rendered lines to account for upstream change", ->
|
||||
editor.scrollToBottom()
|
||||
expect(editor.renderedLines.find(".line").length).toBe 7
|
||||
@@ -1347,9 +1348,9 @@ describe "Editor", ->
|
||||
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
buffer.change([[1,0], [3,0]], "1\n2\n3\n")
|
||||
expect(editor.renderedLines.find(".line").length).toBe 8
|
||||
expect(editor.renderedLines.find(".line").length).toBe 7
|
||||
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(13)
|
||||
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
describe "when the change straddles the first rendered row", ->
|
||||
it "doesn't render rows that were not previously rendered", ->
|
||||
@@ -1360,9 +1361,9 @@ describe "Editor", ->
|
||||
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
buffer.change([[2,0], [7,0]], "2\n3\n4\n5\n6\n7\n8\n9\n")
|
||||
expect(editor.renderedLines.find(".line").length).toBe 9
|
||||
expect(editor.renderedLines.find(".line").length).toBe 7
|
||||
expect(editor.renderedLines.find(".line:first").text()).toBe buffer.lineForRow(6)
|
||||
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(14)
|
||||
expect(editor.renderedLines.find(".line:last").text()).toBe buffer.lineForRow(12)
|
||||
|
||||
describe "when the change straddles the last rendered row", ->
|
||||
it "doesn't render rows that were not previously rendered", ->
|
||||
@@ -1382,7 +1383,7 @@ describe "Editor", ->
|
||||
maxLineLength = editor.maxScreenLineLength()
|
||||
setEditorWidthInChars(editor, maxLineLength)
|
||||
widthBefore = editor.renderedLines.width()
|
||||
expect(widthBefore).toBe editor.scrollView.width()
|
||||
expect(widthBefore).toBe editor.scrollView.width() + 20
|
||||
buffer.change([[12,0], [12,0]], [1..maxLineLength*2].join(''))
|
||||
expect(editor.renderedLines.width()).toBeGreaterThan widthBefore
|
||||
|
||||
@@ -1398,7 +1399,7 @@ describe "Editor", ->
|
||||
expect(editor.renderedLines.width()).toBeGreaterThan editor.scrollView.width()
|
||||
widthBefore = editor.renderedLines.width()
|
||||
buffer.delete([[12, 0], [12, Infinity]])
|
||||
expect(editor.renderedLines.width()).toBe editor.scrollView.width()
|
||||
expect(editor.renderedLines.width()).toBe editor.scrollView.width() + 20
|
||||
|
||||
describe "when the change the precedes the first rendered row", ->
|
||||
it "removes rendered lines to account for upstream change", ->
|
||||
@@ -1497,63 +1498,12 @@ describe "Editor", ->
|
||||
buffer.insert([0, 0], "–")
|
||||
expect(editor.find('.line:eq(0)').outerHeight()).toBe editor.find('.line:eq(1)').outerHeight()
|
||||
|
||||
describe ".spliceLineElements(startRow, rowCount, lineElements)", ->
|
||||
elements = null
|
||||
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
elements = $$ ->
|
||||
@div "A", class: 'line'
|
||||
@div "B", class: 'line'
|
||||
|
||||
describe "when the start row is 0", ->
|
||||
describe "when the row count is 0", ->
|
||||
it "inserts the given elements before the first row", ->
|
||||
editor.spliceLineElements 0, 0, elements
|
||||
|
||||
expect(editor.renderedLines.find('.line:eq(0)').text()).toBe 'A'
|
||||
expect(editor.renderedLines.find('.line:eq(1)').text()).toBe 'B'
|
||||
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe 'var quicksort = function () {'
|
||||
|
||||
describe "when the row count is > 0", ->
|
||||
it "replaces the initial rows with the given elements", ->
|
||||
editor.spliceLineElements 0, 2, elements
|
||||
|
||||
expect(editor.renderedLines.find('.line:eq(0)').text()).toBe 'A'
|
||||
expect(editor.renderedLines.find('.line:eq(1)').text()).toBe 'B'
|
||||
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe ' if (items.length <= 1) return items;'
|
||||
|
||||
describe "when the start row is less than the last row", ->
|
||||
describe "when the row count is 0", ->
|
||||
it "inserts the elements at the specified location", ->
|
||||
editor.spliceLineElements 2, 0, elements
|
||||
|
||||
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe 'A'
|
||||
expect(editor.renderedLines.find('.line:eq(3)').text()).toBe 'B'
|
||||
expect(editor.renderedLines.find('.line:eq(4)').text()).toBe ' if (items.length <= 1) return items;'
|
||||
|
||||
describe "when the row count is > 0", ->
|
||||
it "replaces the elements at the specified location", ->
|
||||
editor.spliceLineElements 2, 2, elements
|
||||
|
||||
expect(editor.renderedLines.find('.line:eq(2)').text()).toBe 'A'
|
||||
expect(editor.renderedLines.find('.line:eq(3)').text()).toBe 'B'
|
||||
expect(editor.renderedLines.find('.line:eq(4)').text()).toBe ' while(items.length > 0) {'
|
||||
|
||||
describe "when the start row is the last row", ->
|
||||
it "appends the elements to the end of the lines", ->
|
||||
editor.spliceLineElements 13, 0, elements
|
||||
|
||||
expect(editor.renderedLines.find('.line:eq(12)').text()).toBe '};'
|
||||
expect(editor.renderedLines.find('.line:eq(13)').text()).toBe 'A'
|
||||
expect(editor.renderedLines.find('.line:eq(14)').text()).toBe 'B'
|
||||
expect(editor.renderedLines.find('.line:eq(15)')).not.toExist()
|
||||
|
||||
describe "when editor.setShowInvisibles is called", ->
|
||||
it "displays spaces as •, tabs as ▸ and newlines as ¬ when true", ->
|
||||
editor.attachToDom()
|
||||
editor.setInvisibles(rootView.getInvisibles())
|
||||
editor.setText " a line with tabs\tand spaces "
|
||||
|
||||
expect(editor.showInvisibles).toBeFalsy()
|
||||
expect(editor.renderedLines.find('.line').text()).toBe " a line with tabs and spaces "
|
||||
editor.setShowInvisibles(true)
|
||||
@@ -1844,15 +1794,15 @@ describe "Editor", ->
|
||||
|
||||
editor.setCursorScreenPosition([2,0])
|
||||
expect(editor.lineElementForScreenRow(2)).toMatchSelector('.fold.selected')
|
||||
expect(editor.find('.cursor').css('display')).toBe 'none'
|
||||
expect(editor.find('.cursor').css('visibility')).toBe 'hidden'
|
||||
|
||||
editor.setCursorScreenPosition([3,0])
|
||||
expect(editor.find('.cursor').css('display')).toBe 'block'
|
||||
expect(editor.find('.cursor').css('visibility')).toBe 'visible'
|
||||
|
||||
describe "when a selected fold is scrolled into view (and the fold line was not previously rendered)", ->
|
||||
it "renders the fold's line element with the 'selected' class", ->
|
||||
setEditorHeightInLines(editor, 5)
|
||||
editor.renderLines() # re-render lines so certain lines are not rendered
|
||||
editor.resetDisplay()
|
||||
|
||||
editor.createFold(2, 4)
|
||||
editor.setSelectedBufferRange([[1, 0], [5, 0]], preserveFolds: true)
|
||||
|
||||
@@ -15,10 +15,6 @@ describe "StatusBar", ->
|
||||
statusBar = rootView.find('.status-bar').view()
|
||||
buffer = editor.getBuffer()
|
||||
|
||||
# updating the status bar is asynchronous for performance reasons
|
||||
# for testing purposes, make it synchronous
|
||||
spyOn(_, 'delay').andCallFake (fn) -> fn()
|
||||
|
||||
afterEach ->
|
||||
rootView.remove()
|
||||
|
||||
@@ -56,6 +52,7 @@ describe "StatusBar", ->
|
||||
it "enables the buffer modified indicator", ->
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
editor.insertText("\n")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe '*'
|
||||
editor.backspace()
|
||||
|
||||
@@ -66,6 +63,7 @@ describe "StatusBar", ->
|
||||
rootView.open(path)
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
editor.insertText("\n")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe '*'
|
||||
editor.save()
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
@@ -73,15 +71,19 @@ describe "StatusBar", ->
|
||||
it "disables the buffer modified indicator if the content matches again", ->
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
editor.insertText("\n")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe '*'
|
||||
editor.backspace()
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
|
||||
it "disables the buffer modified indicator when the change is undone", ->
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
editor.insertText("\n")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe '*'
|
||||
editor.undo()
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
|
||||
describe "when the buffer changes", ->
|
||||
@@ -89,6 +91,7 @@ describe "StatusBar", ->
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
rootView.open(require.resolve('fixtures/sample.txt'))
|
||||
editor.insertText("\n")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe '*'
|
||||
|
||||
it "doesn't update the buffer modified indicator for the old buffer", ->
|
||||
@@ -96,6 +99,7 @@ describe "StatusBar", ->
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
rootView.open(require.resolve('fixtures/sample.txt'))
|
||||
oldBuffer.setText("new text")
|
||||
advanceClock(buffer.stoppedChangingDelay)
|
||||
expect(statusBar.bufferModified.text()).toBe ''
|
||||
|
||||
describe "when the associated editor's cursor position changes", ->
|
||||
|
||||
@@ -7,6 +7,7 @@ Project = require 'project'
|
||||
Directory = require 'directory'
|
||||
File = require 'file'
|
||||
RootView = require 'root-view'
|
||||
Editor = require 'editor'
|
||||
TextMateBundle = require 'text-mate-bundle'
|
||||
TextMateTheme = require 'text-mate-theme'
|
||||
fs = require 'fs'
|
||||
@@ -22,6 +23,9 @@ beforeEach ->
|
||||
window.resetTimeouts()
|
||||
pathsWithSubscriptions = []
|
||||
|
||||
# make editor display updates synchronous
|
||||
spyOn(Editor.prototype, 'requestDisplayUpdate').andCallFake -> @updateDisplay()
|
||||
|
||||
afterEach ->
|
||||
delete window.rootView if window.rootView
|
||||
$('#jasmine-content').empty()
|
||||
|
||||
@@ -41,6 +41,8 @@ class BufferChangeOperation
|
||||
|
||||
event = { oldRange, newRange, oldText, newText }
|
||||
@buffer.trigger 'change', event
|
||||
@buffer.scheduleStoppedChangingEvent()
|
||||
|
||||
anchor.handleBufferChange(event) for anchor in @buffer.getAnchors()
|
||||
@buffer.trigger 'update-anchors-after-change'
|
||||
newRange
|
||||
|
||||
@@ -13,6 +13,8 @@ Git = require 'git'
|
||||
module.exports =
|
||||
class Buffer
|
||||
@idCounter = 1
|
||||
stoppedChangingDelay: 300
|
||||
stoppedChangingTimeout: null
|
||||
undoManager: null
|
||||
cachedDiskContents: null
|
||||
cachedMemoryContents: null
|
||||
@@ -378,4 +380,11 @@ class Buffer
|
||||
if @git?.checkoutHead(path)
|
||||
@trigger 'git-status-change'
|
||||
|
||||
scheduleStoppedChangingEvent: ->
|
||||
clearTimeout(@stoppedChangingTimeout) if @stoppedChangingTimeout
|
||||
stoppedChangingCallback = =>
|
||||
@stoppedChangingTimeout = null
|
||||
@trigger 'stopped-changing'
|
||||
@stoppedChangingTimeout = setTimeout(stoppedChangingCallback, @stoppedChangingDelay)
|
||||
|
||||
_.extend(Buffer.prototype, EventEmitter)
|
||||
|
||||
@@ -9,52 +9,79 @@ class CursorView extends View
|
||||
@content: ->
|
||||
@pre class: 'cursor idle', => @raw ' '
|
||||
|
||||
blinkPeriod: 800
|
||||
editor: null
|
||||
visible: true
|
||||
|
||||
needsUpdate: true
|
||||
needsAutoscroll: true
|
||||
needsRemoval: false
|
||||
shouldPauseBlinking: false
|
||||
|
||||
initialize: (@cursor, @editor) ->
|
||||
@cursor.on 'change-screen-position.cursor-view', (screenPosition, { bufferChange, autoscroll }) =>
|
||||
@updateAppearance({autoscroll})
|
||||
@removeIdleClassTemporarily() unless bufferChange
|
||||
@trigger 'cursor-move', {bufferChange}
|
||||
@cursor.on 'change-screen-position.cursor-view', (screenPosition, { autoscroll }) =>
|
||||
@needsUpdate = true
|
||||
@shouldPauseBlinking = true
|
||||
@needsAutoscroll = (autoscroll ? true) and @cursor?.isLastCursor()
|
||||
@editor.requestDisplayUpdate()
|
||||
|
||||
@cursor.on 'change-visibility.cursor-view', (visible) => @setVisible(visible)
|
||||
@cursor.on 'destroy.cursor-view', => @remove()
|
||||
@cursor.on 'change-visibility.cursor-view', (visible) =>
|
||||
@needsUpdate = true
|
||||
@needsAutoscroll = visible and @cursor.isLastCursor()
|
||||
@editor.requestDisplayUpdate()
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
return unless onDom
|
||||
@updateAppearance()
|
||||
@editor.syncCursorAnimations()
|
||||
@cursor.on 'destroy.cursor-view', =>
|
||||
@needsRemoval = true
|
||||
@editor.requestDisplayUpdate()
|
||||
|
||||
remove: ->
|
||||
@editor.removeCursorView(this)
|
||||
@cursor.off('.cursor-view')
|
||||
super
|
||||
|
||||
updateAppearance: (options={}) ->
|
||||
autoscroll = options.autoscroll ? true
|
||||
updateDisplay: ->
|
||||
screenPosition = @getScreenPosition()
|
||||
pixelPosition = @getPixelPosition()
|
||||
@css(pixelPosition)
|
||||
@autoscroll() if @cursor.isLastCursor() and autoscroll
|
||||
|
||||
unless _.isEqual(@lastPixelPosition, pixelPosition)
|
||||
changedPosition = true
|
||||
@css(pixelPosition)
|
||||
@trigger 'cursor-move'
|
||||
|
||||
if @shouldPauseBlinking
|
||||
@resetBlinking()
|
||||
else if !@startBlinkingTimeout
|
||||
@startBlinking()
|
||||
|
||||
@setVisible(@cursor.isVisible() and not @editor.isFoldedAtScreenRow(screenPosition.row))
|
||||
|
||||
getPixelPosition: ->
|
||||
@editor.pixelPositionForScreenPosition(@getScreenPosition())
|
||||
|
||||
autoscroll: ->
|
||||
pixelPosition =
|
||||
@editor.scrollTo(@getPixelPosition())
|
||||
|
||||
setVisible: (visible) ->
|
||||
return if visible == @visible
|
||||
@visible = visible
|
||||
unless @visible == visible
|
||||
@visible = visible
|
||||
if @visible
|
||||
@css('visibility', '')
|
||||
else
|
||||
@css('visibility', 'hidden')
|
||||
|
||||
if @visible
|
||||
@show()
|
||||
@autoscroll()
|
||||
else
|
||||
@hide()
|
||||
toggleVisible: ->
|
||||
@setVisible(not @visible) if @cursor.isVisible
|
||||
|
||||
stopBlinking: ->
|
||||
clearInterval(@blinkInterval) if @blinkInterval
|
||||
@blinkInterval = null
|
||||
@setVisible(true) if @cursor.isVisible
|
||||
|
||||
startBlinking: ->
|
||||
return if @blinkInterval?
|
||||
blink = => @toggleVisible()
|
||||
@blinkInterval = setInterval(blink, @blinkPeriod / 2)
|
||||
|
||||
resetBlinking: ->
|
||||
@stopBlinking()
|
||||
@startBlinking()
|
||||
|
||||
getBufferPosition: ->
|
||||
@cursor.getBufferPosition()
|
||||
|
||||
@@ -207,7 +207,7 @@ class DisplayBuffer
|
||||
@trigger 'change',
|
||||
oldRange: oldScreenRange
|
||||
newRange: newScreenRange
|
||||
bufferChanged: true
|
||||
bufferChange: e.bufferChange
|
||||
lineNumbersChanged: !e.oldRange.coversSameRows(newRange) or !oldScreenRange.coversSameRows(newScreenRange)
|
||||
|
||||
buildLineForBufferRow: (bufferRow) ->
|
||||
|
||||
@@ -60,9 +60,8 @@ class EditSession
|
||||
@mergeCursors()
|
||||
|
||||
@displayBuffer.on "change.edit-session-#{@id}", (e) =>
|
||||
@refreshAnchorScreenPositions() unless e.bufferChange
|
||||
@trigger 'screen-lines-change', e
|
||||
unless e.bufferChanged
|
||||
anchor.refreshScreenPosition() for anchor in @getAnchors()
|
||||
|
||||
destroy: ->
|
||||
throw new Error("Edit session already destroyed") if @destroyed
|
||||
@@ -263,7 +262,7 @@ class EditSession
|
||||
@setCursorBufferPosition([fold.startRow, 0])
|
||||
|
||||
isFoldedAtScreenRow: (screenRow) ->
|
||||
@lineForScreenRow(screenRow).fold?
|
||||
@lineForScreenRow(screenRow)?.fold?
|
||||
|
||||
largestFoldContainingBufferRow: (bufferRow) ->
|
||||
@displayBuffer.largestFoldContainingBufferRow(bufferRow)
|
||||
@@ -332,6 +331,9 @@ class EditSession
|
||||
removeAnchor: (anchor) ->
|
||||
_.remove(@anchors, anchor)
|
||||
|
||||
refreshAnchorScreenPositions: ->
|
||||
anchor.refreshScreenPosition() for anchor in @getAnchors()
|
||||
|
||||
removeAnchorRange: (anchorRange) ->
|
||||
_.remove(@anchorRanges, anchorRange)
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ class Editor extends View
|
||||
@subview 'gutter', new Gutter
|
||||
@input class: 'hidden-input', outlet: 'hiddenInput'
|
||||
@div class: 'scroll-view', outlet: 'scrollView', =>
|
||||
@div class: 'lines', outlet: 'renderedLines', =>
|
||||
@div class: 'overlayer', outlet: 'overlayer'
|
||||
@div class: 'lines', outlet: 'renderedLines'
|
||||
@div class: 'underlayer', outlet: 'underlayer'
|
||||
@div class: 'vertical-scrollbar', outlet: 'verticalScrollbar', =>
|
||||
@div outlet: 'verticalScrollbarContent'
|
||||
|
||||
@@ -42,6 +44,9 @@ class Editor extends View
|
||||
editSessions: null
|
||||
attached: false
|
||||
lineOverdraw: 100
|
||||
pendingChanges: null
|
||||
newCursors: null
|
||||
newSelections: null
|
||||
|
||||
@deserialize: (state, rootView) ->
|
||||
editSessions = state.editSessions.map (state) -> EditSession.deserialize(state, rootView.project)
|
||||
@@ -60,6 +65,9 @@ class Editor extends View
|
||||
@cursorViews = []
|
||||
@selectionViews = []
|
||||
@editSessions = []
|
||||
@pendingChanges = []
|
||||
@newCursors = []
|
||||
@newSelections = []
|
||||
|
||||
if editSession?
|
||||
@editSessions.push editSession
|
||||
@@ -269,10 +277,10 @@ class Editor extends View
|
||||
setShowInvisibles: (showInvisibles) ->
|
||||
return if showInvisibles == @showInvisibles
|
||||
@showInvisibles = showInvisibles
|
||||
@renderLines()
|
||||
@resetDisplay()
|
||||
|
||||
setInvisibles: (@invisibles={}) ->
|
||||
@renderLines()
|
||||
@resetDisplay()
|
||||
|
||||
checkoutHead: -> @getBuffer().checkoutHead()
|
||||
setText: (text) -> @getBuffer().setText(text)
|
||||
@@ -349,8 +357,6 @@ class Editor extends View
|
||||
else
|
||||
@gutter.addClass('drop-shadow')
|
||||
|
||||
@on 'selection-change', => @highlightCursorLine()
|
||||
|
||||
selectOnMousemoveUntilMouseup: ->
|
||||
moveHandler = (e) => @selectToScreenPosition(@screenPositionFromMouseEvent(e))
|
||||
@on 'mousemove', moveHandler
|
||||
@@ -364,17 +370,15 @@ class Editor extends View
|
||||
afterAttach: (onDom) ->
|
||||
return if @attached or not onDom
|
||||
@attached = true
|
||||
@clearRenderedLines()
|
||||
@subscribeToFontSize()
|
||||
@calculateDimensions()
|
||||
@hiddenInput.width(@charWidth)
|
||||
@setSoftWrapColumn() if @activeEditSession.getSoftWrap()
|
||||
@invisibles = @rootView()?.getInvisibles()
|
||||
$(window).on "resize.editor#{@id}", =>
|
||||
@updateRenderedLines()
|
||||
$(window).on "resize.editor#{@id}", => @requestDisplayUpdate()
|
||||
@focus() if @isFocused
|
||||
|
||||
@renderWhenAttached()
|
||||
@resetDisplay()
|
||||
|
||||
@trigger 'editor-open', [this]
|
||||
|
||||
@@ -425,11 +429,8 @@ class Editor extends View
|
||||
@activeEditSession.on "buffer-path-change", =>
|
||||
@trigger 'editor-path-change'
|
||||
|
||||
@activeEditSession.getSelection().on 'change-screen-range', =>
|
||||
@trigger 'selection-change'
|
||||
|
||||
@trigger 'editor-path-change'
|
||||
@renderWhenAttached()
|
||||
@resetDisplay()
|
||||
|
||||
if @attached and @activeEditSession.buffer.isInConflict()
|
||||
@showBufferConflictAlert(@activeEditSession)
|
||||
@@ -452,17 +453,18 @@ class Editor extends View
|
||||
getOpenBufferPaths: ->
|
||||
editSession.buffer.getPath() for editSession in @editSessions when editSession.buffer.getPath()?
|
||||
|
||||
scrollTop: (scrollTop, options) ->
|
||||
scrollTop: (scrollTop, options={}) ->
|
||||
return @cachedScrollTop or 0 unless scrollTop?
|
||||
|
||||
maxScrollTop = @verticalScrollbar.prop('scrollHeight') - @verticalScrollbar.height()
|
||||
scrollTop = Math.floor(Math.max(0, Math.min(maxScrollTop, scrollTop)))
|
||||
return if scrollTop == @cachedScrollTop
|
||||
@cachedScrollTop = scrollTop
|
||||
|
||||
@updateRenderedLines() if @attached
|
||||
@updateDisplay() if @attached
|
||||
|
||||
@renderedLines.css('top', -scrollTop)
|
||||
@underlayer.css('top', -scrollTop)
|
||||
@overlayer.css('top', -scrollTop)
|
||||
@gutter.lineNumbers.css('top', -scrollTop)
|
||||
if options?.adjustVerticalScrollbar ? true
|
||||
@verticalScrollbar.scrollTop(scrollTop)
|
||||
@@ -573,10 +575,8 @@ class Editor extends View
|
||||
@css('font-size', fontSize + 'px')
|
||||
@calculateDimensions()
|
||||
@updatePaddingOfRenderedLines()
|
||||
@handleScrollHeightChange()
|
||||
@updateCursorViews()
|
||||
@updateSelectionViews()
|
||||
@updateRenderedLines()
|
||||
@updateLayerDimensions()
|
||||
@requestDisplayUpdate()
|
||||
|
||||
newSplitEditor: ->
|
||||
new Editor { editSession: @activeEditSession.copy(), @showInvisibles }
|
||||
@@ -633,22 +633,6 @@ class Editor extends View
|
||||
for session in @getEditSessions()
|
||||
session.destroy()
|
||||
|
||||
renderWhenAttached: ->
|
||||
return unless @attached
|
||||
|
||||
@removeAllCursorAndSelectionViews()
|
||||
@addCursorView(cursor) for cursor in @activeEditSession.getCursors()
|
||||
@addSelectionView(selection) for selection in @activeEditSession.getSelections()
|
||||
@activeEditSession.on 'add-cursor', (cursor) => @addCursorView(cursor)
|
||||
@activeEditSession.on 'add-selection', (selection) => @addSelectionView(selection)
|
||||
|
||||
@prepareForScrolling()
|
||||
@setScrollPositionFromActiveEditSession()
|
||||
|
||||
@renderLines()
|
||||
@highlightCursorLine()
|
||||
@activeEditSession.on 'screen-lines-change', (e) => @handleDisplayBufferChange(e)
|
||||
|
||||
getCursorView: (index) ->
|
||||
index ?= @cursorViews.length - 1
|
||||
@cursorViews[index]
|
||||
@@ -656,27 +640,15 @@ class Editor extends View
|
||||
getCursorViews: ->
|
||||
new Array(@cursorViews...)
|
||||
|
||||
addCursorView: (cursor) ->
|
||||
cursorView = new CursorView(cursor, this)
|
||||
addCursorView: (cursor, options) ->
|
||||
cursorView = new CursorView(cursor, this, options)
|
||||
@cursorViews.push(cursorView)
|
||||
@appendToLinesView(cursorView)
|
||||
@overlayer.append(cursorView)
|
||||
cursorView
|
||||
|
||||
removeCursorView: (cursorView) ->
|
||||
_.remove(@cursorViews, cursorView)
|
||||
|
||||
updateCursorViews: ->
|
||||
for cursorView in @getCursorViews()
|
||||
cursorView.updateAppearance()
|
||||
|
||||
updateSelectionViews: ->
|
||||
for selectionView in @getSelectionViews()
|
||||
selectionView.updateAppearance()
|
||||
|
||||
syncCursorAnimations: ->
|
||||
for cursorView in @getCursorViews()
|
||||
do (cursorView) -> cursorView.resetCursorAnimation()
|
||||
|
||||
getSelectionView: (index) ->
|
||||
index ?= @selectionViews.length - 1
|
||||
@selectionViews[index]
|
||||
@@ -687,7 +659,7 @@ class Editor extends View
|
||||
addSelectionView: (selection) ->
|
||||
selectionView = new SelectionView({editor: this, selection})
|
||||
@selectionViews.push(selectionView)
|
||||
@appendToLinesView(selectionView)
|
||||
@underlayer.append(selectionView)
|
||||
selectionView
|
||||
|
||||
removeSelectionView: (selectionView) ->
|
||||
@@ -698,11 +670,11 @@ class Editor extends View
|
||||
selectionView.remove() for selectionView in @getSelectionViews()
|
||||
|
||||
appendToLinesView: (view) ->
|
||||
@renderedLines.append(view)
|
||||
@overlayer.append(view)
|
||||
|
||||
calculateDimensions: ->
|
||||
fragment = $('<pre class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
|
||||
@appendToLinesView(fragment)
|
||||
@renderedLines.append(fragment)
|
||||
|
||||
lineRect = fragment[0].getBoundingClientRect()
|
||||
charRect = fragment.find('span')[0].getBoundingClientRect()
|
||||
@@ -712,66 +684,203 @@ class Editor extends View
|
||||
@height(@lineHeight) if @mini
|
||||
fragment.remove()
|
||||
|
||||
updateLayerDimensions: ->
|
||||
@gutter.calculateWidth()
|
||||
|
||||
prepareForScrolling: ->
|
||||
@adjustHeightOfRenderedLines()
|
||||
@adjustMinWidthOfRenderedLines()
|
||||
height = @lineHeight * @screenLineCount()
|
||||
unless @layerHeight == height
|
||||
@renderedLines.height(height)
|
||||
@underlayer.height(height)
|
||||
@overlayer.height(height)
|
||||
@layerHeight = height
|
||||
|
||||
adjustHeightOfRenderedLines: ->
|
||||
heightOfRenderedLines = @lineHeight * @screenLineCount()
|
||||
@verticalScrollbarContent.height(heightOfRenderedLines)
|
||||
@renderedLines.css('padding-bottom', heightOfRenderedLines)
|
||||
@verticalScrollbarContent.height(height)
|
||||
@scrollBottom(height) if @scrollBottom() > height
|
||||
|
||||
adjustMinWidthOfRenderedLines: ->
|
||||
minWidth = @charWidth * @maxScreenLineLength()
|
||||
unless @renderedLines.cachedMinWidth == minWidth
|
||||
minWidth = @charWidth * @maxScreenLineLength() + 20
|
||||
unless @layerMinWidth == minWidth
|
||||
@renderedLines.css('min-width', minWidth)
|
||||
@renderedLines.cachedMinWidth = minWidth
|
||||
|
||||
handleScrollHeightChange: ->
|
||||
scrollHeight = @lineHeight * @screenLineCount()
|
||||
@verticalScrollbarContent.height(scrollHeight)
|
||||
@scrollBottom(scrollHeight) if @scrollBottom() > scrollHeight
|
||||
|
||||
renderLines: ->
|
||||
@clearRenderedLines()
|
||||
@updateRenderedLines()
|
||||
@underlayer.css('min-width', minWidth)
|
||||
@overlayer.css('min-width', minWidth)
|
||||
@layerMinWidth = minWidth
|
||||
|
||||
clearRenderedLines: ->
|
||||
@lineCache = []
|
||||
@renderedLines.find('.line').remove()
|
||||
@renderedLines.empty()
|
||||
@firstRenderedScreenRow = null
|
||||
@lastRenderedScreenRow = null
|
||||
|
||||
@firstRenderedScreenRow = -1
|
||||
@lastRenderedScreenRow = -1
|
||||
resetDisplay: ->
|
||||
return unless @attached
|
||||
|
||||
@clearRenderedLines()
|
||||
@removeAllCursorAndSelectionViews()
|
||||
@updateLayerDimensions()
|
||||
@setScrollPositionFromActiveEditSession()
|
||||
|
||||
@activeEditSession.on 'add-selection', (selection) =>
|
||||
@newCursors.push(selection.cursor)
|
||||
@newSelections.push(selection)
|
||||
@requestDisplayUpdate()
|
||||
|
||||
@activeEditSession.on 'screen-lines-change', (e) => @handleDisplayBufferChange(e)
|
||||
|
||||
@newCursors = @activeEditSession.getCursors()
|
||||
@newSelections = @activeEditSession.getSelections()
|
||||
@updateDisplay(suppressAutoScroll: true)
|
||||
|
||||
requestDisplayUpdate: ()->
|
||||
return if @pendingDisplayUpdate
|
||||
@pendingDisplayUpdate = true
|
||||
_.nextTick =>
|
||||
@updateDisplay()
|
||||
@pendingDisplayUpdate = false
|
||||
|
||||
updateDisplay: (options={}) ->
|
||||
return unless @attached
|
||||
@updateRenderedLines()
|
||||
@highlightCursorLine()
|
||||
@updateCursorViews()
|
||||
@updateSelectionViews()
|
||||
@autoscroll(options)
|
||||
|
||||
updateCursorViews: ->
|
||||
if @newCursors.length > 0
|
||||
@addCursorView(cursor) for cursor in @newCursors
|
||||
@syncCursorAnimations()
|
||||
@newCursors = []
|
||||
|
||||
for cursorView in @getCursorViews()
|
||||
if cursorView.needsRemoval
|
||||
cursorView.remove()
|
||||
else if cursorView.needsUpdate
|
||||
cursorView.updateDisplay()
|
||||
|
||||
updateSelectionViews: ->
|
||||
if @newSelections.length > 0
|
||||
@addSelectionView(selection) for selection in @newSelections
|
||||
@newSelections = []
|
||||
|
||||
for selectionView in @getSelectionViews()
|
||||
if selectionView.destroyed
|
||||
selectionView.remove()
|
||||
else
|
||||
selectionView.updateDisplay()
|
||||
|
||||
syncCursorAnimations: ->
|
||||
for cursorView in @getCursorViews()
|
||||
do (cursorView) -> cursorView.resetBlinking()
|
||||
|
||||
autoscroll: (options={}) ->
|
||||
for cursorView in @getCursorViews() when cursorView.needsAutoscroll
|
||||
@scrollTo(cursorView.getPixelPosition()) unless options.suppressAutoScroll
|
||||
cursorView.needsAutoscroll = false
|
||||
|
||||
updateRenderedLines: ->
|
||||
firstVisibleScreenRow = @getFirstVisibleScreenRow()
|
||||
lastVisibleScreenRow = @getLastVisibleScreenRow()
|
||||
renderFrom = Math.max(0, firstVisibleScreenRow - @lineOverdraw)
|
||||
renderTo = Math.min(@getLastScreenRow(), lastVisibleScreenRow + @lineOverdraw)
|
||||
|
||||
if firstVisibleScreenRow < @firstRenderedScreenRow
|
||||
@removeLineElements(Math.max(@firstRenderedScreenRow, renderTo + 1), @lastRenderedScreenRow)
|
||||
@lastRenderedScreenRow = renderTo
|
||||
newLines = @buildLineElements(renderFrom, Math.min(@firstRenderedScreenRow - 1, renderTo))
|
||||
@insertLineElements(renderFrom, newLines)
|
||||
@firstRenderedScreenRow = renderFrom
|
||||
renderedLines = true
|
||||
if @firstRenderedScreenRow? and firstVisibleScreenRow >= @firstRenderedScreenRow and lastVisibleScreenRow <= @lastRenderedScreenRow
|
||||
renderFrom = @firstRenderedScreenRow
|
||||
renderTo = Math.min(@getLastScreenRow(), @lastRenderedScreenRow)
|
||||
else
|
||||
renderFrom = Math.max(0, firstVisibleScreenRow - @lineOverdraw)
|
||||
renderTo = Math.min(@getLastScreenRow(), lastVisibleScreenRow + @lineOverdraw)
|
||||
|
||||
if lastVisibleScreenRow > @lastRenderedScreenRow
|
||||
if 0 <= @firstRenderedScreenRow < renderFrom
|
||||
@removeLineElements(@firstRenderedScreenRow, Math.min(@lastRenderedScreenRow, renderFrom - 1))
|
||||
@firstRenderedScreenRow = renderFrom
|
||||
startRowOfNewLines = Math.max(@lastRenderedScreenRow + 1, renderFrom)
|
||||
newLines = @buildLineElements(startRowOfNewLines, renderTo)
|
||||
@insertLineElements(startRowOfNewLines, newLines)
|
||||
@lastRenderedScreenRow = renderTo
|
||||
renderedLines = true
|
||||
if @pendingChanges.length == 0 and @firstRenderedScreenRow and @firstRenderedScreenRow <= renderFrom and renderTo <= @lastRenderedScreenRow
|
||||
return
|
||||
|
||||
if renderedLines
|
||||
@gutter.renderLineNumbers(renderFrom, renderTo)
|
||||
@updatePaddingOfRenderedLines()
|
||||
@gutter.updateLineNumbers(@pendingChanges, renderFrom, renderTo)
|
||||
intactRanges = @computeIntactRanges()
|
||||
@pendingChanges = []
|
||||
@truncateIntactRanges(intactRanges, renderFrom, renderTo)
|
||||
@clearDirtyRanges(intactRanges)
|
||||
@fillDirtyRanges(intactRanges, renderFrom, renderTo)
|
||||
@firstRenderedScreenRow = renderFrom
|
||||
@lastRenderedScreenRow = renderTo
|
||||
@updateLayerDimensions()
|
||||
@updatePaddingOfRenderedLines()
|
||||
|
||||
computeIntactRanges: ->
|
||||
return [] if !@firstRenderedScreenRow? and !@lastRenderedScreenRow?
|
||||
|
||||
intactRanges = [{from: @firstRenderedScreenRow, to: @lastRenderedScreenRow, domStart: 0}]
|
||||
for change in @pendingChanges
|
||||
newIntactRanges = []
|
||||
delta = change.delta
|
||||
for range in intactRanges
|
||||
if change.to < range.from and change.delta != 0
|
||||
newIntactRanges.push(
|
||||
from: range.from + delta
|
||||
to: range.to + delta
|
||||
domStart: range.domStart
|
||||
)
|
||||
else if change.to < range.from or change.from > range.to
|
||||
newIntactRanges.push(range)
|
||||
else
|
||||
if change.from > range.from
|
||||
newIntactRanges.push(
|
||||
from: range.from
|
||||
to: change.from - 1
|
||||
domStart: range.domStart)
|
||||
if change.to < range.to
|
||||
newIntactRanges.push(
|
||||
from: change.to + delta + 1
|
||||
to: range.to + delta
|
||||
domStart: range.domStart + change.to + 1 - range.from
|
||||
)
|
||||
intactRanges = newIntactRanges
|
||||
@pendingChanges = []
|
||||
intactRanges
|
||||
|
||||
truncateIntactRanges: (intactRanges, renderFrom, renderTo) ->
|
||||
i = 0
|
||||
while i < intactRanges.length
|
||||
range = intactRanges[i]
|
||||
if range.from < renderFrom
|
||||
range.domStart += renderFrom - range.from
|
||||
range.from = renderFrom
|
||||
if range.to > renderTo
|
||||
range.to = renderTo
|
||||
if range.from >= range.to
|
||||
intactRanges.splice(i--, 1)
|
||||
i++
|
||||
intactRanges.sort (a, b) -> a.domStart - b.domStart
|
||||
|
||||
clearDirtyRanges: (intactRanges) ->
|
||||
renderedLines = @renderedLines[0]
|
||||
killLine = (line) ->
|
||||
next = line.nextSibling
|
||||
renderedLines.removeChild(line)
|
||||
next
|
||||
|
||||
if intactRanges.length == 0
|
||||
@renderedLines.empty()
|
||||
else
|
||||
domPosition = 0
|
||||
currentLine = renderedLines.firstChild
|
||||
for intactRange in intactRanges
|
||||
while intactRange.domStart > domPosition
|
||||
currentLine = killLine(currentLine)
|
||||
domPosition++
|
||||
for i in [intactRange.from..intactRange.to]
|
||||
currentLine = currentLine.nextSibling
|
||||
domPosition++
|
||||
while currentLine
|
||||
currentLine = killLine(currentLine)
|
||||
|
||||
fillDirtyRanges: (intactRanges, renderFrom, renderTo) ->
|
||||
renderedLines = @renderedLines[0]
|
||||
nextIntact = intactRanges.shift()
|
||||
currentLine = renderedLines.firstChild
|
||||
screenRow = renderFrom
|
||||
for row in [renderFrom..renderTo]
|
||||
if row == nextIntact?.to + 1
|
||||
nextIntact = intactRanges.shift()
|
||||
if !nextIntact or row < nextIntact.from
|
||||
lineElement = @buildLineElementForScreenRow(row)
|
||||
renderedLines.insertBefore(lineElement, currentLine)
|
||||
else
|
||||
currentLine = currentLine.nextSibling
|
||||
|
||||
updatePaddingOfRenderedLines: ->
|
||||
paddingTop = @firstRenderedScreenRow * @lineHeight
|
||||
@@ -786,66 +895,24 @@ class Editor extends View
|
||||
Math.floor(@scrollTop() / @lineHeight)
|
||||
|
||||
getLastVisibleScreenRow: ->
|
||||
Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1
|
||||
Math.max(0, Math.ceil((@scrollTop() + @scrollView.height()) / @lineHeight) - 1)
|
||||
|
||||
handleDisplayBufferChange: (e) ->
|
||||
oldScreenRange = e.oldRange
|
||||
newScreenRange = e.newRange
|
||||
{ oldRange, newRange } = e
|
||||
from = oldRange.start.row
|
||||
to = oldRange.end.row
|
||||
delta = newRange.end.row - oldRange.end.row
|
||||
|
||||
if @attached
|
||||
@handleScrollHeightChange() unless newScreenRange.coversSameRows(oldScreenRange)
|
||||
@adjustMinWidthOfRenderedLines()
|
||||
if bufferChange = e.bufferChange
|
||||
bufferDelta = bufferChange.newRange.end.row - bufferChange.oldRange.end.row
|
||||
|
||||
return if oldScreenRange.start.row > @lastRenderedScreenRow
|
||||
@pendingChanges.push({from, to, delta, bufferDelta})
|
||||
@requestDisplayUpdate()
|
||||
|
||||
maxEndRow = Math.max(@getLastVisibleScreenRow() + @lineOverdraw, @lastRenderedScreenRow)
|
||||
@gutter.renderLineNumbers(@firstRenderedScreenRow, maxEndRow) if e.lineNumbersChanged
|
||||
|
||||
newScreenRange = newScreenRange.copy()
|
||||
oldScreenRange = oldScreenRange.copy()
|
||||
endOfShortestRange = Math.min(oldScreenRange.end.row, newScreenRange.end.row)
|
||||
|
||||
delta = @firstRenderedScreenRow - endOfShortestRange
|
||||
if delta > 0
|
||||
newScreenRange.start.row += delta
|
||||
newScreenRange.end.row += delta
|
||||
oldScreenRange.start.row += delta
|
||||
oldScreenRange.end.row += delta
|
||||
|
||||
oldScreenRange.start.row = Math.max(oldScreenRange.start.row, @firstRenderedScreenRow)
|
||||
oldScreenRange.end.row = Math.min(oldScreenRange.end.row, @lastRenderedScreenRow)
|
||||
newScreenRange.start.row = Math.max(newScreenRange.start.row, @firstRenderedScreenRow)
|
||||
newScreenRange.end.row = Math.min(newScreenRange.end.row, maxEndRow)
|
||||
|
||||
lineElements = @buildLineElements(newScreenRange.start.row, newScreenRange.end.row)
|
||||
@replaceLineElements(oldScreenRange.start.row, oldScreenRange.end.row, lineElements)
|
||||
|
||||
rowDelta = newScreenRange.end.row - oldScreenRange.end.row
|
||||
@lastRenderedScreenRow += rowDelta
|
||||
@updateRenderedLines() if rowDelta < 0
|
||||
|
||||
if @lastRenderedScreenRow > maxEndRow
|
||||
@removeLineElements(maxEndRow + 1, @lastRenderedScreenRow)
|
||||
@lastRenderedScreenRow = maxEndRow
|
||||
@updatePaddingOfRenderedLines()
|
||||
|
||||
@highlightCursorLine()
|
||||
|
||||
buildLineElements: (startRow, endRow) ->
|
||||
charWidth = @charWidth
|
||||
charHeight = @charHeight
|
||||
lines = @activeEditSession.linesForScreenRows(startRow, endRow)
|
||||
activeEditSession = @activeEditSession
|
||||
cursorScreenRow = @getCursorScreenPosition().row
|
||||
mini = @mini
|
||||
|
||||
buildLineHtml = (line) => @buildLineHtml(line)
|
||||
|
||||
$$ ->
|
||||
row = startRow
|
||||
for line in lines
|
||||
@raw(buildLineHtml(line))
|
||||
row++
|
||||
buildLineElementForScreenRow: (screenRow) ->
|
||||
div = document.createElement('div')
|
||||
div.innerHTML = @buildLineHtml(@activeEditSession.lineForScreenRow(screenRow))
|
||||
div.firstChild
|
||||
|
||||
buildLineHtml: (screenLine) ->
|
||||
scopeStack = []
|
||||
@@ -908,44 +975,8 @@ class Editor extends View
|
||||
line.push('</pre>')
|
||||
line.join('')
|
||||
|
||||
insertLineElements: (row, lineElements) ->
|
||||
@spliceLineElements(row, 0, lineElements)
|
||||
|
||||
replaceLineElements: (startRow, endRow, lineElements) ->
|
||||
@spliceLineElements(startRow, endRow - startRow + 1, lineElements)
|
||||
|
||||
removeLineElements: (startRow, endRow) ->
|
||||
@spliceLineElements(startRow, endRow - startRow + 1)
|
||||
|
||||
spliceLineElements: (startScreenRow, rowCount, lineElements) ->
|
||||
throw new Error("Splicing at a negative start row: #{startScreenRow}") if startScreenRow < 0
|
||||
|
||||
if startScreenRow < @firstRenderedScreenRow
|
||||
startRow = 0
|
||||
else
|
||||
startRow = startScreenRow - @firstRenderedScreenRow
|
||||
|
||||
endRow = startRow + rowCount
|
||||
|
||||
elementToInsertBefore = @lineCache[startRow]
|
||||
elementsToReplace = @lineCache[startRow...endRow]
|
||||
@lineCache[startRow...endRow] = lineElements?.toArray() or []
|
||||
|
||||
lines = @renderedLines[0]
|
||||
if lineElements
|
||||
fragment = document.createDocumentFragment()
|
||||
lineElements.each -> fragment.appendChild(this)
|
||||
if elementToInsertBefore
|
||||
lines.insertBefore(fragment, elementToInsertBefore)
|
||||
else
|
||||
lines.appendChild(fragment)
|
||||
|
||||
elementsToReplace.forEach (element) =>
|
||||
lines.removeChild(element)
|
||||
|
||||
lineElementForScreenRow: (screenRow) ->
|
||||
element = @lineCache[screenRow - @firstRenderedScreenRow]
|
||||
$(element)
|
||||
@renderedLines.children(":eq(#{screenRow - @firstRenderedScreenRow})")
|
||||
|
||||
logScreenLines: (start, end) ->
|
||||
@activeEditSession.logScreenLines(start, end)
|
||||
|
||||
@@ -9,7 +9,8 @@ class Gutter extends View
|
||||
@div class: 'gutter', =>
|
||||
@div outlet: 'lineNumbers', class: 'line-numbers'
|
||||
|
||||
firstScreenRow: -1
|
||||
firstScreenRow: Infinity
|
||||
lastScreenRow: -1
|
||||
highestNumberWidth: null
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
@@ -33,9 +34,18 @@ class Gutter extends View
|
||||
widthTesterElement.remove()
|
||||
lineNumberPadding
|
||||
|
||||
updateLineNumbers: (changes, renderFrom, renderTo) ->
|
||||
if renderFrom < @firstScreenRow or renderTo > @lastScreenRow
|
||||
performUpdate = true
|
||||
else
|
||||
for change in changes
|
||||
if change.delta != 0 or (change.bufferDelta? and change.bufferDelta != 0)
|
||||
performUpdate = true
|
||||
break
|
||||
|
||||
@renderLineNumbers(renderFrom, renderTo) if performUpdate
|
||||
|
||||
renderLineNumbers: (startScreenRow, endScreenRow) ->
|
||||
@firstScreenRow = startScreenRow
|
||||
lastScreenRow = -1
|
||||
rows = @editor().bufferRowsForScreenRows(startScreenRow, endScreenRow)
|
||||
|
||||
cursorScreenRow = @editor().getCursorScreenPosition().row
|
||||
@@ -49,6 +59,8 @@ class Gutter extends View
|
||||
lastScreenRow = row
|
||||
|
||||
@calculateWidth()
|
||||
@firstScreenRow = startScreenRow
|
||||
@lastScreenRow = endScreenRow
|
||||
@highlightedRow = null
|
||||
@highlightCursorLine()
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class LineMap
|
||||
@maxScreenLineLength = Math.max(@maxScreenLineLength, screenLine.text.length)
|
||||
|
||||
lineForScreenRow: (row) ->
|
||||
@linesForScreenRows(row, row)[0]
|
||||
@screenLines[row]
|
||||
|
||||
linesForScreenRows: (startRow, endRow) ->
|
||||
@screenLines[startRow..endRow]
|
||||
|
||||
@@ -9,17 +9,20 @@ class SelectionView extends View
|
||||
@div()
|
||||
|
||||
regions: null
|
||||
destroyed: false
|
||||
|
||||
initialize: ({@editor, @selection} = {}) ->
|
||||
@regions = []
|
||||
@selection.on 'change-screen-range', => @updateAppearance()
|
||||
@selection.on 'destroy', => @remove()
|
||||
@updateAppearance()
|
||||
@selection.on 'change-screen-range', => @editor.requestDisplayUpdate()
|
||||
@selection.on 'destroy', =>
|
||||
@destroyed = true
|
||||
@editor.requestDisplayUpdate()
|
||||
|
||||
updateAppearance: ->
|
||||
updateDisplay: ->
|
||||
@clearRegions()
|
||||
range = @getScreenRange()
|
||||
|
||||
@trigger 'selection-change'
|
||||
@editor.highlightFoldsContainingBufferRange(@getBufferRange())
|
||||
return if range.isEmpty()
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class Selection
|
||||
@cursor.selection = this
|
||||
|
||||
@cursor.on 'change-screen-position.selection', (e) =>
|
||||
@screenRangeChanged() unless e.bufferChanged
|
||||
@screenRangeChanged() unless e.bufferChange
|
||||
|
||||
@cursor.on 'destroy.selection', =>
|
||||
@cursor = null
|
||||
|
||||
@@ -42,9 +42,9 @@ class StatusBar extends View
|
||||
subscribeToBuffer: ->
|
||||
@buffer?.off '.status-bar'
|
||||
@buffer = @editor.getBuffer()
|
||||
@buffer.on 'change.status-bar', => _.delay (=> @updateBufferModifiedText()), 50
|
||||
@buffer.on 'after-save.status-bar', => _.delay (=> @updateStatusBar()), 50
|
||||
@buffer.on 'git-status-change.status-bar', => _.delay (=> @updateStatusBar()), 50
|
||||
@buffer.on 'stopped-changing.status-bar', => @updateBufferModifiedText()
|
||||
@buffer.on 'after-save.status-bar', => @updateStatusBar()
|
||||
@buffer.on 'git-status-change.status-bar', => @updateStatusBar()
|
||||
@updateStatusBar()
|
||||
|
||||
updateStatusBar: ->
|
||||
|
||||
@@ -360,7 +360,7 @@ describe "Autocomplete", ->
|
||||
beforeEach ->
|
||||
editor.attachToDom()
|
||||
setEditorHeightInLines(editor, 13)
|
||||
editor.renderLines() # Ensures the editor only has 13 lines visible
|
||||
editor.resetDisplay() # Ensures the editor only has 13 lines visible
|
||||
|
||||
editor.setCursorBufferPosition [1, 1]
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ describe "WrapGuide", ->
|
||||
describe "@initialize", ->
|
||||
it "appends a wrap guide to all existing and new editors", ->
|
||||
expect(rootView.panes.find('.pane').length).toBe 1
|
||||
expect(rootView.panes.find('.lines > .wrap-guide').length).toBe 1
|
||||
expect(rootView.panes.find('.underlayer > .wrap-guide').length).toBe 1
|
||||
editor.splitRight()
|
||||
expect(rootView.find('.pane').length).toBe 2
|
||||
expect(rootView.panes.find('.lines > .wrap-guide').length).toBe 2
|
||||
expect(rootView.panes.find('.underlayer > .wrap-guide').length).toBe 2
|
||||
|
||||
describe "@updateGuide", ->
|
||||
it "positions the guide at the configured column", ->
|
||||
|
||||
@@ -13,8 +13,8 @@ class WrapGuide extends View
|
||||
@appendToEditorPane(rootView, editor, config)
|
||||
|
||||
@appendToEditorPane: (rootView, editor, config) ->
|
||||
if lines = editor.pane()?.find('.lines')
|
||||
lines.append(new WrapGuide(rootView, editor, config))
|
||||
if underlayer = editor.pane()?.find('.underlayer')
|
||||
underlayer.append(new WrapGuide(rootView, editor, config))
|
||||
|
||||
@content: ->
|
||||
@div class: 'wrap-guide'
|
||||
|
||||
@@ -71,4 +71,14 @@ _.mixin
|
||||
inverted
|
||||
|
||||
multiplyString: (string, n) ->
|
||||
new Array(1 + n).join(string)
|
||||
new Array(1 + n).join(string)
|
||||
|
||||
nextTick: (fn) ->
|
||||
unless @messageChannel
|
||||
@pendingNextTickFns = []
|
||||
@messageChannel = new MessageChannel
|
||||
@messageChannel.port1.onmessage = =>
|
||||
fn() while fn = @pendingNextTickFns.shift()
|
||||
|
||||
@pendingNextTickFns.push(fn)
|
||||
@messageChannel.port2.postMessage(0)
|
||||
|
||||
@@ -73,36 +73,37 @@
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
|
||||
.editor .underlayer, .editor .lines, .editor .overlayer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.editor .underlayer {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.editor .lines {
|
||||
position: relative;
|
||||
display: table;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/*overflow: hidden; i'm worried this is causing rendering problems */
|
||||
padding-right: 2em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.editor .overlayer {
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.editor .line span {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@-webkit-keyframes blink {
|
||||
0% { opacity: 1; }
|
||||
60% { opacity: 1; }
|
||||
61% { opacity: 0; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.editor .cursor {
|
||||
position: absolute;
|
||||
border-left: 2px solid;
|
||||
}
|
||||
|
||||
.editor.focused .cursor.idle {
|
||||
-webkit-animation: blink 0.8s;
|
||||
-webkit-animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.editor .hidden-input {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
Reference in New Issue
Block a user