Merge branch 'master' into chrome

This commit is contained in:
Corey Johnson
2012-02-27 14:09:24 -08:00
26 changed files with 980 additions and 853 deletions

View File

@@ -19,16 +19,16 @@ describe "Cursor", ->
advanceClock(200)
expect(cursor).toHaveClass 'idle'
cursor.setPosition([1, 2])
cursor.setScreenPosition([1, 2])
expect(cursor).not.toHaveClass 'idle'
window.advanceClock(200)
expect(cursor).toHaveClass 'idle'
cursor.setPosition([1, 3])
cursor.setScreenPosition([1, 3])
advanceClock(100)
cursor.setPosition([1, 4])
cursor.setScreenPosition([1, 4])
advanceClock(100)
expect(cursor).not.toHaveClass 'idle'
@@ -37,9 +37,9 @@ describe "Cursor", ->
describe ".isOnEOL()", ->
it "only returns true when cursor is on the end of a line", ->
cursor.setPosition([1,29])
cursor.setScreenPosition([1,29])
expect(cursor.isOnEOL()).toBeFalsy()
cursor.setPosition([1,30])
cursor.setScreenPosition([1,30])
expect(cursor.isOnEOL()).toBeTruthy()

View File

@@ -65,10 +65,10 @@ describe "Editor", ->
expect(editor.lines.find('pre:eq(3)').text()).toBe " var pivot = items.shift(), current, left = [], "
expect(editor.lines.find('pre:eq(4)').text()).toBe "right = [];"
editor.cursor.setPosition([3, 51])
editor.cursor.setScreenPosition([3, 51])
expect(editor.cursor.position()).toEqual(editor.lines.find('pre:eq(4)').position())
editor.cursor.setPosition([4, 0])
editor.cursor.setScreenPosition([4, 0])
expect(editor.cursor.position()).toEqual(editor.lines.find('pre:eq(5)').position())
editor.selection.setRange(new Range([6, 30], [6, 55]))
@@ -99,10 +99,10 @@ describe "Editor", ->
expect(editor.setMaxLineLength).not.toHaveBeenCalled()
describe "cursor movement", ->
describe ".setCursorPosition({row, column})", ->
describe ".setCursorScreenPosition({row, column})", ->
beforeEach ->
editor.attachToDom()
editor.setCursorPosition(row: 2, column: 2)
editor.setCursorScreenPosition(row: 2, column: 2)
it "moves the cursor to cover the character at the given row and column", ->
expect(editor.getCursor().position().top).toBe(2 * editor.lineHeight)
@@ -115,16 +115,16 @@ describe "Editor", ->
describe "when the arrow keys are pressed", ->
it "moves the cursor by a single row/column", ->
editor.trigger keydownEvent('right')
expect(editor.getCursorPosition()).toEqual(row: 0, column: 1)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 1)
editor.trigger keydownEvent('down')
expect(editor.getCursorPosition()).toEqual(row: 1, column: 1)
expect(editor.getCursorScreenPosition()).toEqual(row: 1, column: 1)
editor.trigger keydownEvent('left')
expect(editor.getCursorPosition()).toEqual(row: 1, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 1, column: 0)
editor.trigger keydownEvent('up')
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
describe "vertical movement", ->
describe "auto-scrolling", ->
@@ -173,50 +173,50 @@ describe "Editor", ->
it "retains the goal column when moving up", ->
expect(lineLengths[6]).toBeGreaterThan(32)
editor.setCursorPosition(row: 6, column: 32)
editor.setCursorScreenPosition(row: 6, column: 32)
editor.moveCursorUp()
expect(editor.getCursorPosition().column).toBe lineLengths[5]
expect(editor.getCursorScreenPosition().column).toBe lineLengths[5]
editor.moveCursorUp()
expect(editor.getCursorPosition().column).toBe lineLengths[4]
expect(editor.getCursorScreenPosition().column).toBe lineLengths[4]
editor.moveCursorUp()
expect(editor.getCursorPosition().column).toBe 32
expect(editor.getCursorScreenPosition().column).toBe 32
it "retains the goal column when moving down", ->
editor.setCursorPosition(row: 3, column: lineLengths[3])
editor.setCursorScreenPosition(row: 3, column: lineLengths[3])
editor.moveCursorDown()
expect(editor.getCursorPosition().column).toBe lineLengths[4]
expect(editor.getCursorScreenPosition().column).toBe lineLengths[4]
editor.moveCursorDown()
expect(editor.getCursorPosition().column).toBe lineLengths[5]
expect(editor.getCursorScreenPosition().column).toBe lineLengths[5]
editor.moveCursorDown()
expect(editor.getCursorPosition().column).toBe lineLengths[3]
expect(editor.getCursorScreenPosition().column).toBe lineLengths[3]
it "clears the goal column when the cursor is set", ->
# set a goal column by moving down
editor.setCursorPosition(row: 3, column: lineLengths[3])
editor.setCursorScreenPosition(row: 3, column: lineLengths[3])
editor.moveCursorDown()
expect(editor.getCursorPosition().column).not.toBe 6
expect(editor.getCursorScreenPosition().column).not.toBe 6
# clear the goal column by explicitly setting the cursor position
editor.setCursorColumn(6)
expect(editor.getCursorPosition().column).toBe 6
expect(editor.getCursorScreenPosition().column).toBe 6
editor.moveCursorDown()
expect(editor.getCursorPosition().column).toBe 6
expect(editor.getCursorScreenPosition().column).toBe 6
describe "when up is pressed on the first line", ->
it "moves the cursor to the beginning of the line, but retains the goal column", ->
editor.setCursorPosition(row: 0, column: 4)
editor.setCursorScreenPosition(row: 0, column: 4)
editor.moveCursorUp()
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
editor.moveCursorDown()
expect(editor.getCursorPosition()).toEqual(row: 1, column: 4)
expect(editor.getCursorScreenPosition()).toEqual(row: 1, column: 4)
describe "when down is pressed on the last line", ->
it "moves the cursor to the end of line, but retains the goal column", ->
@@ -224,22 +224,22 @@ describe "Editor", ->
lastLine = buffer.getLine(lastLineIndex)
expect(lastLine.length).toBeGreaterThan(0)
editor.setCursorPosition(row: lastLineIndex, column: 1)
editor.setCursorScreenPosition(row: lastLineIndex, column: 1)
editor.moveCursorDown()
expect(editor.getCursorPosition()).toEqual(row: lastLineIndex, column: lastLine.length)
expect(editor.getCursorScreenPosition()).toEqual(row: lastLineIndex, column: lastLine.length)
editor.moveCursorUp()
expect(editor.getCursorPosition().column).toBe 1
expect(editor.getCursorScreenPosition().column).toBe 1
it "retains a goal column of 0", ->
lastLineIndex = buffer.getLines().length - 1
lastLine = buffer.getLine(lastLineIndex)
expect(lastLine.length).toBeGreaterThan(0)
editor.setCursorPosition(row: lastLineIndex, column: 0)
editor.setCursorScreenPosition(row: lastLineIndex, column: 0)
editor.moveCursorDown()
editor.moveCursorUp()
expect(editor.getCursorPosition().column).toBe 0
expect(editor.getCursorScreenPosition().column).toBe 0
describe "horizontal movement", ->
describe "auto-scrolling", ->
@@ -253,57 +253,57 @@ describe "Editor", ->
editor.width(charWidth * 30)
# moving right
editor.setCursorPosition([2, 24])
editor.setCursorScreenPosition([2, 24])
expect(editor.scrollLeft()).toBe 0
editor.setCursorPosition([2, 25])
editor.setCursorScreenPosition([2, 25])
expect(editor.scrollLeft()).toBe charWidth
editor.setCursorPosition([2, 28])
editor.setCursorScreenPosition([2, 28])
expect(editor.scrollLeft()).toBe charWidth * 4
# moving left
editor.setCursorPosition([2, 9])
editor.setCursorScreenPosition([2, 9])
expect(editor.scrollLeft()).toBe charWidth * 4
editor.setCursorPosition([2, 8])
editor.setCursorScreenPosition([2, 8])
expect(editor.scrollLeft()).toBe charWidth * 3
editor.setCursorPosition([2, 5])
editor.setCursorScreenPosition([2, 5])
expect(editor.scrollLeft()).toBe 0
it "reduces scroll margins when there isn't enough width to maintain them and scroll smoothly", ->
editor.hScrollMargin = 6
editor.width(charWidth * 7)
editor.setCursorPosition([2, 3])
editor.setCursorScreenPosition([2, 3])
expect(editor.scrollLeft()).toBe(0)
editor.setCursorPosition([2, 4])
editor.setCursorScreenPosition([2, 4])
expect(editor.scrollLeft()).toBe(charWidth)
editor.setCursorPosition([2, 3])
editor.setCursorScreenPosition([2, 3])
expect(editor.scrollLeft()).toBe(0)
describe "when left is pressed on the first column", ->
describe "when there is a previous line", ->
it "wraps to the end of the previous line", ->
editor.setCursorPosition(row: 1, column: 0)
editor.setCursorScreenPosition(row: 1, column: 0)
editor.moveCursorLeft()
expect(editor.getCursorPosition()).toEqual(row: 0, column: buffer.getLine(0).length)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: buffer.getLine(0).length)
describe "when the cursor is on the first line", ->
it "remains in the same position (0,0)", ->
editor.setCursorPosition(row: 0, column: 0)
editor.setCursorScreenPosition(row: 0, column: 0)
editor.moveCursorLeft()
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
describe "when right is pressed on the last column", ->
describe "when there is a subsequent line", ->
it "wraps to the beginning of the next line", ->
editor.setCursorPosition(row: 0, column: buffer.getLine(0).length)
editor.setCursorScreenPosition(row: 0, column: buffer.getLine(0).length)
editor.moveCursorRight()
expect(editor.getCursorPosition()).toEqual(row: 1, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 1, column: 0)
describe "when the cursor is on the last line", ->
it "remains in the same position", ->
@@ -312,10 +312,10 @@ describe "Editor", ->
expect(lastLine.length).toBeGreaterThan(0)
lastPosition = { row: lastLineIndex, column: lastLine.length }
editor.setCursorPosition(lastPosition)
editor.setCursorScreenPosition(lastPosition)
editor.moveCursorRight()
expect(editor.getCursorPosition()).toEqual(lastPosition)
expect(editor.getCursorScreenPosition()).toEqual(lastPosition)
describe "when a mousedown event occurs in the editor", ->
beforeEach ->
@@ -329,24 +329,24 @@ describe "Editor", ->
describe "when it is a single click", ->
it "re-positions the cursor from the clicked screen position to the corresponding buffer position", ->
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
[pageX, pageY] = window.pixelPositionForPoint(editor, [4, 7])
editor.lines.trigger mousedownEvent({pageX, pageY})
expect(editor.getCursorPosition()).toEqual(row: 3, column: 58)
expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 58)
describe "when soft-wrap is disabled", ->
describe "when it is a single click", ->
it "re-positions the cursor to the clicked row / column", ->
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
[pageX, pageY] = window.pixelPositionForPoint(editor, [3, 10])
editor.lines.trigger mousedownEvent({pageX, pageY})
expect(editor.getCursorPosition()).toEqual(row: 3, column: 10)
expect(editor.getCursorScreenPosition()).toEqual(row: 3, column: 10)
describe "when it is a double click", ->
it "selects the word under the cursor", ->
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
[pageX, pageY] = window.pixelPositionForPoint(editor, [0, 8])
editor.lines.trigger mousedownEvent({pageX, pageY, originalEvent: {detail: 1}})
$(document).trigger 'mouseup'
@@ -355,7 +355,7 @@ describe "Editor", ->
describe "when it is clicked more then twice (tripple, quadruple, etc...)", ->
it "selects the line under the cursor", ->
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
# Triple click
[pageX, pageY] = window.pixelPositionForPoint(editor, [1, 8])
@@ -387,7 +387,7 @@ describe "Editor", ->
describe "when the arrow keys are pressed with the shift modifier", ->
it "expands the selection up to the cursor's new location", ->
editor.setCursorPosition(row: 1, column: 6)
editor.setCursorScreenPosition(row: 1, column: 6)
expect(selection.isEmpty()).toBeTruthy()
@@ -456,7 +456,7 @@ describe "Editor", ->
range = editor.selection.getRange()
expect(range.start).toEqual({row: 4, column: 10})
expect(range.end).toEqual({row: 5, column: 27})
expect(editor.getCursorPosition()).toEqual(row: 5, column: 27)
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
# mouse up may occur outside of editor, but still need to halt selection
$(document).trigger 'mouseup'
@@ -468,7 +468,7 @@ describe "Editor", ->
range = editor.selection.getRange()
expect(range.start).toEqual({row: 4, column: 10})
expect(range.end).toEqual({row: 5, column: 27})
expect(editor.getCursorPosition()).toEqual(row: 5, column: 27)
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
it "creates a selection from word underneath double click to mouse cursor's location ", ->
editor.attachToDom()
@@ -487,7 +487,7 @@ describe "Editor", ->
range = editor.selection.getRange()
expect(range.start).toEqual({row: 4, column: 4})
expect(range.end).toEqual({row: 5, column: 27})
expect(editor.getCursorPosition()).toEqual(row: 5, column: 27)
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
# mouse up may occur outside of editor, but still need to halt selection
$(document).trigger 'mouseup'
@@ -499,7 +499,7 @@ describe "Editor", ->
range = editor.selection.getRange()
expect(range.start).toEqual({row: 4, column: 4})
expect(range.end).toEqual({row: 5, column: 27})
expect(editor.getCursorPosition()).toEqual(row: 5, column: 27)
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
it "creates a selection from line underneath triple click to mouse cursor's location ", ->
@@ -521,7 +521,7 @@ describe "Editor", ->
range = editor.selection.getRange()
expect(range.start).toEqual({row: 4, column: 0})
expect(range.end).toEqual({row: 5, column: 27})
expect(editor.getCursorPosition()).toEqual(row: 5, column: 27)
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
# mouse up may occur outside of editor, but still need to halt selection
$(document).trigger 'mouseup'
@@ -533,20 +533,20 @@ describe "Editor", ->
range = editor.selection.getRange()
expect(range.start).toEqual({row: 4, column: 0})
expect(range.end).toEqual({row: 5, column: 27})
expect(editor.getCursorPosition()).toEqual(row: 5, column: 27)
expect(editor.getCursorScreenPosition()).toEqual(row: 5, column: 27)
describe "buffer manipulation", ->
describe "when text input events are triggered on the hidden input element", ->
describe "when there is no selection", ->
it "inserts the typed character at the cursor position, both in the buffer and the pre element", ->
editor.setCursorPosition(row: 1, column: 6)
editor.setCursorScreenPosition(row: 1, column: 6)
expect(editor.getCurrentLine().charAt(6)).not.toBe 'q'
editor.hiddenInput.textInput 'q'
expect(editor.getCurrentLine().charAt(6)).toBe 'q'
expect(editor.getCursorPosition()).toEqual(row: 1, column: 7)
expect(editor.getCursorScreenPosition()).toEqual(row: 1, column: 7)
expect(editor.lines.find('pre:eq(1)')).toHaveText editor.getCurrentLine()
describe "when there is a selection", ->
@@ -558,16 +558,16 @@ describe "Editor", ->
describe "when return is pressed", ->
describe "when the cursor is at the beginning of a line", ->
it "inserts an empty line before it", ->
editor.setCursorPosition(row: 1, column: 0)
editor.setCursorScreenPosition(row: 1, column: 0)
editor.trigger keydownEvent('enter')
expect(editor.lines.find('pre:eq(1)')).toHaveHtml ' '
expect(editor.getCursorPosition()).toEqual(row: 2, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
describe "when the cursor is in the middle of a line", ->
it "splits the current line to form a new line", ->
editor.setCursorPosition(row: 1, column: 6)
editor.setCursorScreenPosition(row: 1, column: 6)
originalLine = editor.lines.find('pre:eq(1)').text()
lineBelowOriginalLine = editor.lines.find('pre:eq(2)').text()
@@ -576,21 +576,21 @@ describe "Editor", ->
expect(editor.lines.find('pre:eq(1)')).toHaveText originalLine[0...6]
expect(editor.lines.find('pre:eq(2)')).toHaveText originalLine[6..]
expect(editor.lines.find('pre:eq(3)')).toHaveText lineBelowOriginalLine
expect(editor.getCursorPosition()).toEqual(row: 2, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
describe "when the cursor is on the end of a line", ->
it "inserts an empty line after it", ->
editor.setCursorPosition(row: 1, column: buffer.getLine(1).length)
editor.setCursorScreenPosition(row: 1, column: buffer.getLine(1).length)
editor.trigger keydownEvent('enter')
expect(editor.lines.find('pre:eq(2)')).toHaveHtml ' '
expect(editor.getCursorPosition()).toEqual(row: 2, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 2, column: 0)
describe "when backspace is pressed", ->
describe "when the cursor is on the middle of the line", ->
it "removes the character before the cursor", ->
editor.setCursorPosition(row: 1, column: 7)
editor.setCursorScreenPosition(row: 1, column: 7)
expect(buffer.getLine(1)).toBe " var sort = function(items) {"
editor.trigger keydownEvent('backspace')
@@ -598,7 +598,7 @@ describe "Editor", ->
line = buffer.getLine(1)
expect(line).toBe " var ort = function(items) {"
expect(editor.lines.find('pre:eq(1)')).toHaveText line
expect(editor.getCursorPosition()).toEqual {row: 1, column: 6}
expect(editor.getCursorScreenPosition()).toEqual {row: 1, column: 6}
describe "when the cursor is at the beginning of a line", ->
it "joins it with the line above", ->
@@ -606,7 +606,7 @@ describe "Editor", ->
expect(originalLine0).toBe "var quicksort = function () {"
expect(buffer.getLine(1)).toBe " var sort = function(items) {"
editor.setCursorPosition(row: 1, column: 0)
editor.setCursorScreenPosition(row: 1, column: 0)
editor.trigger keydownEvent('backspace')
line0 = buffer.getLine(0)
@@ -616,11 +616,11 @@ describe "Editor", ->
expect(editor.lines.find('pre:eq(0)')).toHaveText line0
expect(editor.lines.find('pre:eq(1)')).toHaveText line1
expect(editor.getCursorPosition()).toEqual {row: 0, column: originalLine0.length}
expect(editor.getCursorScreenPosition()).toEqual {row: 0, column: originalLine0.length}
describe "when the cursor is at the first column of the first line", ->
it "does nothing, but doesn't raise an error", ->
editor.setCursorPosition(row: 0, column: 0)
editor.setCursorScreenPosition(row: 0, column: 0)
editor.trigger keydownEvent('backspace')
describe "when there is a selection", ->
@@ -632,13 +632,13 @@ describe "Editor", ->
describe "when delete is pressed", ->
describe "when the cursor is on the middle of a line", ->
it "deletes the character following the cursor", ->
editor.setCursorPosition([1, 6])
editor.setCursorScreenPosition([1, 6])
editor.trigger keydownEvent('delete')
expect(buffer.getLine(1)).toBe ' var ort = function(items) {'
describe "when the cursor is on the end of a line", ->
it "joins the line with the following line", ->
editor.setCursorPosition([1, buffer.getLine(1).length])
editor.setCursorScreenPosition([1, buffer.getLine(1).length])
editor.trigger keydownEvent('delete')
expect(buffer.getLine(1)).toBe ' var sort = function(items) { if (items.length <= 1) return items;'
@@ -650,7 +650,7 @@ describe "Editor", ->
describe "when the cursor is on the last column of the last line", ->
it "does nothing, but doesn't raise an error", ->
editor.setCursorPosition([12, buffer.getLine(12).length])
editor.setCursorScreenPosition([12, buffer.getLine(12).length])
editor.trigger keydownEvent('delete')
expect(buffer.getLine(12)).toBe '};'
@@ -673,7 +673,7 @@ describe "Editor", ->
it "calculates line height and char width and updates the pixel position of the cursor", ->
expect(editor.lineHeight).toBeNull()
expect(editor.charWidth).toBeNull()
editor.setCursorPosition(row: 2, column: 2)
editor.setCursorScreenPosition(row: 2, column: 2)
editor.attachToDom()
@@ -695,7 +695,7 @@ describe "Editor", ->
describe ".setBuffer(buffer)", ->
it "sets the cursor to the beginning of the file", ->
expect(editor.getCursorPosition()).toEqual(row: 0, column: 0)
expect(editor.getCursorScreenPosition()).toEqual(row: 0, column: 0)
describe ".clipPosition(point)", ->
it "selects the nearest valid position to the given point", ->
@@ -724,7 +724,7 @@ describe "Editor", ->
describe "when a paste event is triggered", ->
it "pastes text into the buffer", ->
editor.setCursorPosition [0, 4]
editor.setCursorScreenPosition [0, 4]
editor.trigger "paste"
expect(editor.buffer.getLine(0)).toBe "var firstquicksort = function () {"
@@ -733,3 +733,10 @@ describe "Editor", ->
editor.trigger "paste"
expect(editor.buffer.getLine(1)).toBe " var first = function(items) {"
describe "folding", ->
describe "when a fold-selection event is triggered", ->
it "folds the selected text and renders a placeholder for it", ->
editor.selection.setRange(new Range([4, 29], [7, 4]))
editor.trigger 'fold-selection'
expect(editor.lines.find('.line:eq(4)').text()).toBe ' while(items.length > 0) {...}'

View File

@@ -11,8 +11,8 @@ describe "Highlighter", ->
describe "constructor", ->
it "tokenizes all the lines in the buffer", ->
expect(highlighter.screenLineForRow(0).tokens[0]).toEqual(type: 'keyword.definition', value: 'var')
expect(highlighter.screenLineForRow(11).tokens[1]).toEqual(type: 'keyword', value: 'return')
expect(highlighter.lineForScreenRow(0).tokens[0]).toEqual(type: 'keyword.definition', value: 'var')
expect(highlighter.lineForScreenRow(11).tokens[1]).toEqual(type: 'keyword', value: 'return')
describe "when the buffer changes", ->
changeHandler = null
@@ -26,11 +26,11 @@ describe "Highlighter", ->
range = new Range([0, 0], [2, 0])
buffer.change(range, "foo()\nbar()\n")
expect(highlighter.screenLineForRow(0).tokens[0]).toEqual(type: 'identifier', value: 'foo')
expect(highlighter.screenLineForRow(1).tokens[0]).toEqual(type: 'identifier', value: 'bar')
expect(highlighter.lineForScreenRow(0).tokens[0]).toEqual(type: 'identifier', value: 'foo')
expect(highlighter.lineForScreenRow(1).tokens[0]).toEqual(type: 'identifier', value: 'bar')
# line 2 is unchanged
expect(highlighter.screenLineForRow(2).tokens[1]).toEqual(type: 'keyword', value: 'if')
expect(highlighter.lineForScreenRow(2).tokens[1]).toEqual(type: 'keyword', value: 'if')
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -43,9 +43,9 @@ describe "Highlighter", ->
changeHandler.reset()
buffer.insert([2, 0], '/*')
expect(highlighter.screenLineForRow(3).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(4).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(5).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(3).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(4).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(5).tokens[0].type).toBe 'comment'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -57,7 +57,7 @@ describe "Highlighter", ->
buffer.insert([5, 0], '*/')
buffer.insert([1, 0], 'var ')
expect(highlighter.screenLineForRow(1).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(1).tokens[0].type).toBe 'comment'
describe "when lines are both updated and removed", ->
it "updates tokens to reflect the removed lines", ->
@@ -65,16 +65,16 @@ describe "Highlighter", ->
buffer.change(range, "foo()")
# previous line 0 remains
expect(highlighter.screenLineForRow(0).tokens[0]).toEqual(type: 'keyword.definition', value: 'var')
expect(highlighter.lineForScreenRow(0).tokens[0]).toEqual(type: 'keyword.definition', value: 'var')
# previous line 3 should be combined with input to form line 1
expect(highlighter.screenLineForRow(1).tokens[0]).toEqual(type: 'identifier', value: 'foo')
expect(highlighter.screenLineForRow(1).tokens[6]).toEqual(type: 'identifier', value: 'pivot')
expect(highlighter.lineForScreenRow(1).tokens[0]).toEqual(type: 'identifier', value: 'foo')
expect(highlighter.lineForScreenRow(1).tokens[6]).toEqual(type: 'identifier', value: 'pivot')
# lines below deleted regions should be shifted upward
expect(highlighter.screenLineForRow(2).tokens[1]).toEqual(type: 'keyword', value: 'while')
expect(highlighter.screenLineForRow(3).tokens[1]).toEqual(type: 'identifier', value: 'current')
expect(highlighter.screenLineForRow(4).tokens[3]).toEqual(type: 'keyword.operator', value: '<')
expect(highlighter.lineForScreenRow(2).tokens[1]).toEqual(type: 'keyword', value: 'while')
expect(highlighter.lineForScreenRow(3).tokens[1]).toEqual(type: 'identifier', value: 'current')
expect(highlighter.lineForScreenRow(4).tokens[3]).toEqual(type: 'keyword.operator', value: '<')
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -86,9 +86,9 @@ describe "Highlighter", ->
changeHandler.reset()
buffer.change(new Range([2, 0], [3, 0]), '/*')
expect(highlighter.screenLineForRow(2).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(3).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(4).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(2).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(3).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(4).tokens[0].type).toBe 'comment'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -101,19 +101,19 @@ describe "Highlighter", ->
buffer.change(range, "foo()\nbar()\nbaz()\nquux()")
# previous line 0 remains
expect(highlighter.screenLineForRow(0).tokens[0]).toEqual(type: 'keyword.definition', value: 'var')
expect(highlighter.lineForScreenRow(0).tokens[0]).toEqual(type: 'keyword.definition', value: 'var')
# 3 new lines inserted
expect(highlighter.screenLineForRow(1).tokens[0]).toEqual(type: 'identifier', value: 'foo')
expect(highlighter.screenLineForRow(2).tokens[0]).toEqual(type: 'identifier', value: 'bar')
expect(highlighter.screenLineForRow(3).tokens[0]).toEqual(type: 'identifier', value: 'baz')
expect(highlighter.lineForScreenRow(1).tokens[0]).toEqual(type: 'identifier', value: 'foo')
expect(highlighter.lineForScreenRow(2).tokens[0]).toEqual(type: 'identifier', value: 'bar')
expect(highlighter.lineForScreenRow(3).tokens[0]).toEqual(type: 'identifier', value: 'baz')
# previous line 2 is joined with quux() on line 4
expect(highlighter.screenLineForRow(4).tokens[0]).toEqual(type: 'identifier', value: 'quux')
expect(highlighter.screenLineForRow(4).tokens[4]).toEqual(type: 'keyword', value: 'if')
expect(highlighter.lineForScreenRow(4).tokens[0]).toEqual(type: 'identifier', value: 'quux')
expect(highlighter.lineForScreenRow(4).tokens[4]).toEqual(type: 'keyword', value: 'if')
# previous line 3 is pushed down to become line 5
expect(highlighter.screenLineForRow(5).tokens[3]).toEqual(type: 'identifier', value: 'pivot')
expect(highlighter.lineForScreenRow(5).tokens[3]).toEqual(type: 'identifier', value: 'pivot')
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -125,13 +125,13 @@ describe "Highlighter", ->
changeHandler.reset()
buffer.insert([2, 0], '/*\nabcde\nabcder')
expect(highlighter.screenLineForRow(2).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(3).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(4).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(5).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(6).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(7).tokens[0].type).toBe 'comment'
expect(highlighter.screenLineForRow(8).tokens[0].type).not.toBe 'comment'
expect(highlighter.lineForScreenRow(2).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(3).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(4).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(5).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(6).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(7).tokens[0].type).toBe 'comment'
expect(highlighter.lineForScreenRow(8).tokens[0].type).not.toBe 'comment'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]

View File

@@ -4,34 +4,92 @@ LineFolder = require 'line-folder'
Range = require 'range'
describe "LineFolder", ->
[buffer, folder] = []
[buffer, folder, changeHandler] = []
beforeEach ->
buffer = new Buffer(require.resolve 'fixtures/sample.js')
highlighter = new Higlighter(buffer)
folder = new LineFolder(highlighter)
changeHandler = jasmine.createSpy('changeHandler')
folder.on 'change', changeHandler
describe "when folds are created and removed", ->
it "emits 'fold' and 'unfold' events", ->
foldHandler = jasmine.createSpy 'foldHandler'
unfoldHandler = jasmine.createSpy 'unfoldHandler'
folder.on 'fold', foldHandler
folder.on 'unfold', unfoldHandler
foldRange = new Range([4, 29], [7, 4])
fold = folder.createFold(foldRange)
expect(foldHandler).toHaveBeenCalled()
[[range]] = foldHandler.argsForCall
expect(range).toEqual foldRange
fold.destroy()
expect(unfoldHandler).toHaveBeenCalled()
[[range]] = unfoldHandler.argsForCall
expect(range).toEqual foldRange
describe "screen line rendering", ->
describe "when there is a single fold spanning multiple lines", ->
it "renders a placeholder on the first line of a fold, and skips subsequent lines", ->
folder.fold(new Range([4, 29], [7, 4]))
[line4, line5] = fragments = folder.linesForScreenRows(4, 5)
it "replaces folded lines with a single line containing a placeholder and emits a change event", ->
[line4, line5] = folder.linesForScreenRows(4, 5)
previousLine4Text = line4.text
previousLine5Text = line5.text
fold = folder.createFold(new Range([4, 29], [7, 4]))
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe ' while(items.length > 0) {...}'
expect(line5.text).toBe ' return sort(left).concat(pivot).concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[4, 0], [7, 5]]
expect(event.newRange).toEqual [[4, 0], [4, 33]]
changeHandler.reset()
fold.destroy()
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe previousLine4Text
expect(line5.text).toBe previousLine5Text
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[4, 0], [4, 33]]
expect(event.newRange).toEqual [[4, 0], [7, 5]]
describe "when there is a single fold contained on a single line", ->
it "renders a placeholder for the folded region, but does not skip any lines", ->
folder.fold(new Range([2, 8], [2, 25]))
[line2, line3] = folder.linesForScreenRows(2, 3)
fold = folder.createFold(new Range([2, 8], [2, 25]))
[line2, line3] = folder.linesForScreenRows(2, 3)
expect(line2.text).toBe ' if (...) return items;'
expect(line3.text).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[2, 0], [2, 40]]
expect(event.newRange).toEqual [[2, 0], [2, 26]]
changeHandler.reset()
fold.destroy()
[line2, line3] = folder.linesForScreenRows(2, 3)
expect(line2.text).toBe ' if (items.length <= 1) return items;'
expect(line3.text).toBe ' var pivot = items.shift(), current, left = [], right = [];'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.newRange).toEqual [[2, 0], [2, 40]]
expect(event.oldRange).toEqual [[2, 0], [2, 26]]
changeHandler.reset()
describe "when there is a nested fold on the last line of another fold", ->
it "does not render a placeholder for the nested fold because it is inside of the other fold", ->
folder.fold(new Range([8, 5], [8, 10]))
folder.fold(new Range([4, 29], [8, 36]))
folder.createFold(new Range([8, 5], [8, 10]))
folder.createFold(new Range([4, 29], [8, 36]))
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe ' while(items.length > 0) {...concat(sort(right));'
@@ -40,40 +98,237 @@ describe "LineFolder", ->
describe "when another fold begins on the last line of a fold", ->
describe "when the second fold is created before the first fold", ->
it "renders a placeholder for both folds on the first line of the first fold", ->
folder.fold(new Range([7, 5], [8, 36]))
folder.fold(new Range([4, 29], [7, 4]))
[line4, line5] = folder.linesForScreenRows(4, 5)
fold1 = folder.createFold(new Range([7, 5], [8, 36]))
fold2 = folder.createFold(new Range([4, 29], [7, 4]))
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe ' while(items.length > 0) {...}...concat(sort(right));'
expect(line5.text).toBe ' };'
expect(changeHandler.callCount).toBe 2
[[event1], [event2]] = changeHandler.argsForCall
expect(event1.oldRange).toEqual [[7, 0], [8, 56]]
expect(event1.newRange).toEqual [[7, 0], [7, 28]]
expect(event2.oldRange).toEqual [[4, 0], [7, 28]]
expect(event2.newRange).toEqual [[4, 0], [4, 56]]
changeHandler.reset()
fold1.destroy()
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe ' while(items.length > 0) {...}'
expect(line5.text).toBe ' return sort(left).concat(pivot).concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[4, 0], [4, 56]]
expect(event.newRange).toEqual [[4, 0], [5, 56]]
changeHandler.reset()
fold2.destroy()
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe ' while(items.length > 0) {'
expect(line5.text).toBe ' current = items.shift();'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[4, 0], [4, 33]]
expect(event.newRange).toEqual [[4, 0], [7, 5]]
describe "when the second fold is created after the first fold", ->
it "renders a placeholder for both folds on the first line of the first fold", ->
folder.fold(new Range([4, 29], [7, 4]))
folder.fold(new Range([7, 5], [8, 36]))
fold1 = folder.createFold(new Range([4, 29], [7, 4]))
fold2 = folder.createFold(new Range([7, 5], [8, 36]))
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe ' while(items.length > 0) {...}...concat(sort(right));'
expect(line5.text).toBe ' };'
describe ".screenPositionForBufferPosition(bufferPosition)", ->
expect(changeHandler.callCount).toBe 2
[[event1], [event2]] = changeHandler.argsForCall
expect(event1.oldRange).toEqual [[4, 0], [7, 5]]
expect(event1.newRange).toEqual [[4, 0], [4, 33]]
expect(event2.oldRange).toEqual [[4, 0], [5, 56]]
expect(event2.newRange).toEqual [[4, 0], [4, 56]]
changeHandler.reset()
fold1.destroy()
[line4, line5] = folder.linesForScreenRows(4, 5)
[line7] = folder.linesForScreenRows(7, 7)
expect(line4.text).toBe ' while(items.length > 0) {'
expect(line5.text).toBe ' current = items.shift();'
expect(line7.text).toBe ' }...concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[4, 0], [4, 56]]
expect(event.newRange).toEqual [[4, 0], [7, 28]]
changeHandler.reset()
fold2.destroy()
[line4, line5] = folder.linesForScreenRows(4, 5)
expect(line4.text).toBe ' while(items.length > 0) {'
expect(line5.text).toBe ' current = items.shift();'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
expect(event.oldRange).toEqual [[7, 0], [7, 28]]
expect(event.newRange).toEqual [[7, 0], [8, 56]]
describe "when the buffer changes", ->
[fold1, fold2] = []
beforeEach ->
fold1 = folder.createFold(new Range([4, 29], [7, 4]))
fold2 = folder.createFold(new Range([7, 5], [8, 36]))
changeHandler.reset()
describe "when the old range precedes lines with a fold", ->
it "updates the buffer and re-positions subsequent folds", ->
buffer.change(new Range([1, 5], [2, 10]), 'abc')
expect(folder.lineForScreenRow(1).text).toBe ' varabcems.length <= 1) return items;'
expect(folder.lineForScreenRow(3).text).toBe ' while(items.length > 0) {...}...concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[[event]] = changeHandler.argsForCall
expect(event.oldRange).toEqual [[1, 0], [2, 40]]
expect(event.newRange).toEqual [[1, 0], [1, 38]]
changeHandler.reset()
fold1.destroy()
expect(folder.lineForScreenRow(3).text).toBe ' while(items.length > 0) {'
expect(folder.lineForScreenRow(6).text).toBe ' }...concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[[event]] = changeHandler.argsForCall
expect(event.oldRange).toEqual [[3, 0], [3, 56]]
expect(event.newRange).toEqual [[3, 0], [6, 28]]
describe "when the old range follows lines with a fold", ->
it "re-positions the screen ranges for the change event based on the preceding fold", ->
buffer.change(new Range([9, 3], [10, 0]), 'abc')
expect(folder.lineForScreenRow(5).text).toBe ' }abc'
expect(folder.lineForScreenRow(6).text).toBe ' return sort(Array.apply(this, arguments));'
expect(changeHandler).toHaveBeenCalled()
[[event]] = changeHandler.argsForCall
expect(event.oldRange).toEqual [[5, 0], [6, 0]]
expect(event.newRange).toEqual [[5, 0], [5, 6]]
describe "when the old range contains unfolded text on the first line of a fold, preceding the fold placeholder", ->
it "re-renders the line with the placeholder and re-positions the fold", ->
buffer.change(new Range([4, 4], [4, 9]), 'slongaz')
expect(folder.lineForScreenRow(4).text).toBe ' slongaz(items.length > 0) {...}...concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[[event]] = changeHandler.argsForCall
expect(event.oldRange).toEqual [[4, 0], [4, 56]]
expect(event.newRange).toEqual [[4, 0], [4, 58]]
fold1.destroy()
expect(folder.lineForScreenRow(4).text).toBe ' slongaz(items.length > 0) {'
describe "when the old range is contained to a single line in-between two fold placeholders", ->
it "re-renders the line with the placeholder and re-positions the second fold", ->
buffer.insert([7, 4], 'abc')
expect(folder.lineForScreenRow(4).text).toBe ' while(items.length > 0) {...abc}...concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[[event]] = changeHandler.argsForCall
expect(event.oldRange).toEqual [[4, 0], [4, 56]]
expect(event.newRange).toEqual [[4, 0], [4, 59]]
fold2.destroy()
expect(folder.lineForScreenRow(4).text).toBe ' while(items.length > 0) {...abc}'
describe "when the old range is inside a fold", ->
it "does not trigger a change event, but updates the fold and ensures the change is present when the fold is destroyed", ->
buffer.change(new Range([4, 29], [6, 0]), 'abc')
expect(folder.lineForScreenRow(4).text).toBe ' while(items.length > 0) {...}...concat(sort(right));'
expect(changeHandler).not.toHaveBeenCalled()
fold1.destroy()
expect(folder.lineForScreenRow(4).text).toBe ' while(items.length > 0) {abc current < pivot ? left.push(current) : right.push(current);'
expect(folder.lineForScreenRow(5).text).toBe ' }...concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[[event]] = changeHandler.argsForCall
expect(event.oldRange).toEqual [[4, 0], [4, 56]]
expect(event.newRange).toEqual [[4, 0], [5, 28]]
describe "when the old range surrounds a fold", ->
it "removes the fold and replaces the fold placeholder with the new text", ->
buffer.change(new Range([4, 29], [7, 4]), 'party()')
expect(folder.lineForScreenRow(4).text).toBe ' while(items.length > 0) {party()}...concat(sort(right));'
expect(changeHandler).toHaveBeenCalled()
[[event]] = changeHandler.argsForCall
expect(event.oldRange).toEqual [[4, 0], [4, 56]]
expect(event.newRange).toEqual [[4, 0], [4, 60]]
describe "when the old range straddles the start of a fold", ->
it "moves the start of the fold to the end of the new range", ->
describe "when the old region straddles the end of a fold", ->
it "moves the start of the fold to the beginning of the new range", ->
describe "position translation", ->
describe "when there is single fold spanning multiple lines", ->
it "translates positions to account for folded lines and characters and the placeholder", ->
folder.fold(new Range([4, 29], [7, 4]))
folder.createFold(new Range([4, 29], [7, 4]))
# preceding fold: identity
expect(folder.screenPositionForBufferPosition([3, 0])).toEqual [3, 0]
expect(folder.screenPositionForBufferPosition([4, 0])).toEqual [4, 0]
expect(folder.screenPositionForBufferPosition([4, 29])).toEqual [4, 29]
expect(folder.bufferPositionForScreenPosition([3, 0])).toEqual [3, 0]
expect(folder.bufferPositionForScreenPosition([4, 0])).toEqual [4, 0]
expect(folder.bufferPositionForScreenPosition([4, 29])).toEqual [4, 29]
# inside of fold: translate to the start of the fold
# expect(folder.screenPositionForBufferPosition([4, 30])).toEqual [4, 29]
# expect(folder.screenPositionForBufferPosition([5, 5])).toEqual [4, 29]
expect(folder.screenPositionForBufferPosition([4, 35])).toEqual [4, 29]
expect(folder.screenPositionForBufferPosition([5, 5])).toEqual [4, 29]
# following fold, on last line of fold
expect(folder.screenPositionForBufferPosition([7, 4])).toEqual [4, 32]
expect(folder.screenPositionForBufferPosition([7, 7])).toEqual [4, 35]
expect(folder.bufferPositionForScreenPosition([4, 32])).toEqual [7, 4]
# # following fold, subsequent line
expect(folder.screenPositionForBufferPosition([8, 0])).toEqual [5, 0]
expect(folder.screenPositionForBufferPosition([13, 13])).toEqual [10, 13]
expect(folder.screenPositionForBufferPosition([11, 13])).toEqual [8, 13]
expect(folder.bufferPositionForScreenPosition([5, 0])).toEqual [8, 0]
expect(folder.bufferPositionForScreenPosition([10, 13])).toEqual [13, 13]
describe "when there is a single fold spanning a single line", ->
it "translates positions to account for folded characters and the placeholder", ->
folder.createFold(new Range([4, 10], [4, 15]))
expect(folder.screenPositionForBufferPosition([4, 5])).toEqual [4, 5]
expect(folder.screenPositionForBufferPosition([4, 15])).toEqual [4, 13]
expect(folder.screenPositionForBufferPosition([4, 20])).toEqual [4, 18]
expect(folder.bufferPositionForScreenPosition([4, 5])).toEqual [4, 5]
expect(folder.bufferPositionForScreenPosition([4, 13])).toEqual [4, 15]
expect(folder.bufferPositionForScreenPosition([4, 18])).toEqual [4, 20]
describe ".clipScreenPosition(screenPosition)", ->
beforeEach ->
folder.createFold(new Range([4, 29], [7, 4]))
it "returns the nearest valid position based on the current screen lines", ->
expect(folder.clipScreenPosition([-1, -1])).toEqual [0, 0]
expect(folder.clipScreenPosition([0, -1])).toEqual [0, 0]
expect(folder.clipScreenPosition([1, 10000])).toEqual [1, 30]
expect(folder.clipScreenPosition([2, 15])).toEqual [2, 15]
expect(folder.clipScreenPosition([4, 32])).toEqual [4, 32]
expect(folder.clipScreenPosition([4, 1000])).toEqual [4, 33]
expect(folder.clipScreenPosition([1000, 1000])).toEqual [10, 2]
it "clips positions inside a placeholder to the beginning of the placeholder", ->
expect(folder.clipScreenPosition([4, 30])).toEqual [4, 29]
expect(folder.clipScreenPosition([4, 31])).toEqual [4, 29]

View File

@@ -2,7 +2,7 @@ LineMap = require 'line-map'
ScreenLineFragment = require 'screen-line-fragment'
Buffer = require 'buffer'
Highlighter = require 'highlighter'
Delta = require 'delta'
Point = require 'point'
describe "LineMap", ->
[highlighter, map] = []
@@ -12,9 +12,9 @@ describe "LineMap", ->
buffer = new Buffer(require.resolve 'fixtures/sample.js')
highlighter = new Highlighter(buffer)
map = new LineMap
[line0, line1, line2, line3, line4] = highlighter.lineFragmentsForRows(0, 4)
[line0, line1, line2, line3, line4] = highlighter.linesForScreenRows(0, 4)
describe ".insertAtBufferRow(row, lineFragment(s))", ->
describe ".insertAtBufferRow(row, screenLine(s))", ->
describe "when passed a single, line fragment", ->
it "inserts the line fragment before the specified buffer row", ->
map.insertAtBufferRow(0, line1)
@@ -22,10 +22,10 @@ describe "LineMap", ->
map.insertAtBufferRow(2, line3)
map.insertAtBufferRow(2, line2)
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line1]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line2]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line3]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line1
expect(map.lineForScreenRow(2)).toEqual line2
expect(map.lineForScreenRow(3)).toEqual line3
describe "when passed an array of line fragments", ->
it "inserts the given line fragments before the specified buffer row", ->
@@ -33,23 +33,23 @@ describe "LineMap", ->
map.insertAtBufferRow(0, [line0, line1])
map.insertAtBufferRow(4, [line4])
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line1]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line2]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line3]
expect(map.lineFragmentsForScreenRow(4)).toEqual [line4]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line1
expect(map.lineForScreenRow(2)).toEqual line2
expect(map.lineForScreenRow(3)).toEqual line3
expect(map.lineForScreenRow(4)).toEqual line4
describe ".spliceAtBufferRow(bufferRow, rowCount, lineFragments)", ->
describe ".spliceAtBufferRow(bufferRow, rowCount, screenLines)", ->
describe "when called with a row count of 0", ->
it "inserts the given line fragments before the specified buffer row", ->
map.insertAtBufferRow(0, [line0, line1, line2])
map.spliceAtBufferRow(1, 0, [line3, line4])
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line1]
expect(map.lineFragmentsForScreenRow(4)).toEqual [line2]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3
expect(map.lineForScreenRow(2)).toEqual line4
expect(map.lineForScreenRow(3)).toEqual line1
expect(map.lineForScreenRow(4)).toEqual line2
describe "when called with a row count of 1", ->
describe "when the specified buffer row is spanned by a single line fragment", ->
@@ -58,10 +58,10 @@ describe "LineMap", ->
map.spliceAtBufferRow(1, 1, [line3, line4])
expect(map.bufferLineCount()).toBe 4
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line2]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3
expect(map.lineForScreenRow(2)).toEqual line4
expect(map.lineForScreenRow(3)).toEqual line2
describe "when the specified buffer row is spanned by multiple line fragments", ->
it "replaces all spanning line fragments with the given line fragments", ->
@@ -72,10 +72,10 @@ describe "LineMap", ->
map.spliceAtBufferRow(1, 1, [line3a, line3b, line4])
expect(map.bufferLineCount()).toBe 4
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3a, line3b]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line2]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3a.concat(line3b)
expect(map.lineForScreenRow(2)).toEqual line4
expect(map.lineForScreenRow(3)).toEqual line2
describe "when called with a row count greater than 1", ->
it "replaces all line fragments spanning the multiple buffer rows with the given line fragments", ->
@@ -86,9 +86,9 @@ describe "LineMap", ->
map.spliceAtBufferRow(1, 2, [line3a, line3b, line4])
expect(map.bufferLineCount()).toBe 3
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3a, line3b]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3a.concat(line3b)
expect(map.lineForScreenRow(2)).toEqual line4
describe ".spliceAtScreenRow(startRow, rowCount, lineFragemnts)", ->
describe "when called with a row count of 0", ->
@@ -96,11 +96,11 @@ describe "LineMap", ->
map.insertAtBufferRow(0, [line0, line1, line2])
map.spliceAtScreenRow(1, 0, [line3, line4])
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line1]
expect(map.lineFragmentsForScreenRow(4)).toEqual [line2]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3
expect(map.lineForScreenRow(2)).toEqual line4
expect(map.lineForScreenRow(3)).toEqual line1
expect(map.lineForScreenRow(4)).toEqual line2
describe "when called with a row count of 1", ->
describe "when the specified screen row is spanned by a single line fragment", ->
@@ -109,10 +109,10 @@ describe "LineMap", ->
map.spliceAtScreenRow(1, 1, [line3, line4])
expect(map.bufferLineCount()).toBe 4
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line2]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3
expect(map.lineForScreenRow(2)).toEqual line4
expect(map.lineForScreenRow(3)).toEqual line2
describe "when the specified screen row is spanned by multiple line fragments", ->
it "replaces all spanning line fragments with the given line fragments", ->
@@ -123,10 +123,10 @@ describe "LineMap", ->
map.spliceAtScreenRow(1, 1, [line3a, line3b, line4])
expect(map.bufferLineCount()).toBe 4
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3a, line3b]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineFragmentsForScreenRow(3)).toEqual [line2]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3a.concat(line3b)
expect(map.lineForScreenRow(2)).toEqual line4
expect(map.lineForScreenRow(3)).toEqual line2
describe "when called with a row count greater than 1", ->
it "replaces all line fragments spanning the multiple buffer rows with the given line fragments", ->
@@ -137,13 +137,12 @@ describe "LineMap", ->
map.spliceAtScreenRow(1, 2, [line3a, line3b, line4])
expect(map.bufferLineCount()).toBe 3
expect(map.lineFragmentsForScreenRow(0)).toEqual [line0]
expect(map.lineFragmentsForScreenRow(1)).toEqual [line3a, line3b]
expect(map.lineFragmentsForScreenRow(2)).toEqual [line4]
expect(map.lineForScreenRow(0)).toEqual line0
expect(map.lineForScreenRow(1)).toEqual line3a.concat(line3b)
expect(map.lineForScreenRow(2)).toEqual line4
describe ".linesForScreenRows(startRow, endRow)", ->
it "returns lines for the given row range, concatenating fragments that belong on a single screen line", ->
line1Text = line1.text
[line1a, line1b] = line1.splitAt(11)
[line3a, line3b] = line3.splitAt(16)
map.insertAtBufferRow(0, [line0, line1a, line1b, line2, line3a, line3b, line4])
@@ -151,17 +150,28 @@ describe "LineMap", ->
# repeating assertion to cover a regression where this method mutated lines
expect(map.linesForScreenRows(1, 3)).toEqual [line1, line2, line3]
describe ".screenPositionForBufferPosition(bufferPosition, allowEOL=true)", ->
describe ".lineForBufferRow(bufferRow)", ->
it "returns the concatenated screen line fragments that comprise the given buffer row", ->
line1Text = line1.text
[line1a, line1b] = line1.splitAt(11)
line1a.screenDelta = new Point(1, 0)
map.insertAtBufferRow(0, [line0, line1a, line1b, line2])
expect(map.lineForBufferRow(0).text).toBe line0.text
expect(map.lineForBufferRow(1).text).toBe line1Text
describe ".screenPositionForBufferPosition(bufferPosition, eagerWrap=true)", ->
beforeEach ->
# line1a-line3b describes a fold
[line1a, line1b] = line1.splitAt(10)
[line3a, line3b] = line3.splitAt(20)
line1a.bufferDelta.rows = 2
line1a.bufferDelta.columns = 20
line1a.bufferDelta.row = 2
line1a.bufferDelta.column = 20
# line4a-line4b describes a wrapped line
[line4a, line4b] = line4.splitAt(20)
line4a.screenDelta = new Delta(1, 0)
line4a.screenDelta = new Point(1, 0)
map.insertAtBufferRow(0, [line0, line1a, line3b, line4a, line4b])
@@ -176,6 +186,7 @@ describe "LineMap", ->
describe "when eagerWrap is false", ->
it "does not wrap buffer positions at the end of a screen line to the beginning of the next screen line", ->
expect(map.screenPositionForBufferPosition([4, 20], false)).toEqual [2, 20]
expect(map.screenPositionForBufferPosition([4, 29], false)).toEqual [3, 9]
describe "when eagerWrap is true", ->
it "wraps buffer positions at the end of a screen line to the end end of the next screen line", ->
@@ -185,8 +196,8 @@ describe "LineMap", ->
it "returns the total of all inserted screen row deltas", ->
[line1a, line1b] = line1.splitAt(10)
[line3a, line3b] = line3.splitAt(10)
line1a.screenDelta = new Delta(1, 0)
line3a.screenDelta = new Delta(1, 0)
line1a.screenDelta = new Point(1, 0)
line3a.screenDelta = new Point(1, 0)
map.insertAtBufferRow(0, [line0, line1a, line1b, line2])

View File

@@ -1,37 +1,40 @@
Buffer = require 'buffer'
LineWrapper = require 'line-wrapper'
Buffer = require 'buffer'
Highlighter = require 'highlighter'
LineFolder = require 'line-folder'
Range = require 'range'
ScreenLineFragment = require 'screen-line-fragment'
_ = require 'underscore'
describe "LineWrapper", ->
[wrapper, buffer, changeHandler] = []
[wrapper, folder, buffer, changeHandler] = []
beforeEach ->
buffer = new Buffer(require.resolve('fixtures/sample.js'))
wrapper = new LineWrapper(50, new Highlighter(buffer))
highlighter = new Highlighter(buffer)
folder = new LineFolder(highlighter)
wrapper = new LineWrapper(50, folder)
changeHandler = jasmine.createSpy('changeHandler')
wrapper.on 'change', changeHandler
describe ".tokensForScreenRow(row)", ->
describe ".lineForScreenRow(row)", ->
it "returns tokens for the line fragment corresponding to the given screen row", ->
expect(tokensText wrapper.screenLineForRow(3).tokens).toEqual(' var pivot = items.shift(), current, left = [], ')
expect(tokensText wrapper.screenLineForRow(4).tokens).toEqual('right = [];')
expect(tokensText wrapper.screenLineForRow(5).tokens).toEqual(' while(items.length > 0) {')
expect(tokensText wrapper.lineForScreenRow(3).tokens).toEqual(' var pivot = items.shift(), current, left = [], ')
expect(tokensText wrapper.lineForScreenRow(4).tokens).toEqual('right = [];')
expect(tokensText wrapper.lineForScreenRow(5).tokens).toEqual(' while(items.length > 0) {')
describe ".screenLineCount()", ->
describe ".lineCount()", ->
it "returns the total number of screen lines", ->
expect(wrapper.screenLineCount()).toBe 16
expect(wrapper.lineCount()).toBe 16
describe "when the buffer changes", ->
describe "when a buffer line is updated", ->
describe "when the number of screen lines remains the same for the changed buffer line", ->
it "re-wraps the existing lines and emits a change event for all its screen lines", ->
buffer.insert([6, 28], '1234567')
expect(wrapper.screenLineForRow(7).text).toBe ' current < pivot ? left1234567.push(current) '
expect(wrapper.screenLineForRow(8).text).toBe ': right.push(current);'
expect(wrapper.screenLineForRow(9).text).toBe ' }'
expect(wrapper.lineForScreenRow(7).text).toBe ' current < pivot ? left1234567.push(current) '
expect(wrapper.lineForScreenRow(8).text).toBe ': right.push(current);'
expect(wrapper.lineForScreenRow(9).text).toBe ' }'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -41,10 +44,10 @@ describe "LineWrapper", ->
describe "when the number of screen lines increases for the changed buffer line", ->
it "re-wraps and adds an additional screen line and emits a change event for all screen lines", ->
buffer.insert([6, 28], '1234567890')
expect(wrapper.screenLineForRow(7).text).toBe ' current < pivot ? '
expect(wrapper.screenLineForRow(8).text).toBe 'left1234567890.push(current) : '
expect(wrapper.screenLineForRow(9).text).toBe 'right.push(current);'
expect(wrapper.screenLineForRow(10).text).toBe ' }'
expect(wrapper.lineForScreenRow(7).text).toBe ' current < pivot ? '
expect(wrapper.lineForScreenRow(8).text).toBe 'left1234567890.push(current) : '
expect(wrapper.lineForScreenRow(9).text).toBe 'right.push(current);'
expect(wrapper.lineForScreenRow(10).text).toBe ' }'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -54,8 +57,8 @@ describe "LineWrapper", ->
describe "when the number of screen lines decreases for the changed buffer line", ->
it "re-wraps and removes a screen line and emits a change event for all screen lines", ->
buffer.change(new Range([6, 24], [6, 42]), '')
expect(wrapper.screenLineForRow(7).text).toBe ' current < pivot ? : right.push(current);'
expect(wrapper.screenLineForRow(8).text).toBe ' }'
expect(wrapper.lineForScreenRow(7).text).toBe ' current < pivot ? : right.push(current);'
expect(wrapper.lineForScreenRow(8).text).toBe ' }'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -65,10 +68,10 @@ describe "LineWrapper", ->
describe "when buffer lines are inserted", ->
it "re-wraps existing and new screen lines and emits a change event", ->
buffer.insert([6, 21], '1234567890 abcdefghij 1234567890\nabcdefghij')
expect(wrapper.screenLineForRow(7).text).toBe ' current < pivot1234567890 abcdefghij '
expect(wrapper.screenLineForRow(8).text).toBe '1234567890'
expect(wrapper.screenLineForRow(9).text).toBe 'abcdefghij ? left.push(current) : '
expect(wrapper.screenLineForRow(10).text).toBe 'right.push(current);'
expect(wrapper.lineForScreenRow(7).text).toBe ' current < pivot1234567890 abcdefghij '
expect(wrapper.lineForScreenRow(8).text).toBe '1234567890'
expect(wrapper.lineForScreenRow(9).text).toBe 'abcdefghij ? left.push(current) : '
expect(wrapper.lineForScreenRow(10).text).toBe 'right.push(current);'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -78,10 +81,10 @@ describe "LineWrapper", ->
describe "when buffer lines are removed", ->
it "removes screen lines and emits a change event", ->
buffer.change(new Range([3, 21], [7, 5]), ';')
expect(wrapper.screenLineForRow(3).text).toBe ' var pivot = items;'
expect(wrapper.screenLineForRow(4).text).toBe ' return '
expect(wrapper.screenLineForRow(5).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
expect(wrapper.screenLineForRow(6).text).toBe ' };'
expect(wrapper.lineForScreenRow(3).text).toBe ' var pivot = items;'
expect(wrapper.lineForScreenRow(4).text).toBe ' return '
expect(wrapper.lineForScreenRow(5).text).toBe 'sort(left).concat(pivot).concat(sort(right));'
expect(wrapper.lineForScreenRow(6).text).toBe ' };'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -91,9 +94,9 @@ describe "LineWrapper", ->
describe ".setMaxLength(length)", ->
it "changes the length at which lines are wrapped and emits a change event for all screen lines", ->
wrapper.setMaxLength(40)
expect(tokensText wrapper.screenLineForRow(4).tokens).toBe 'left = [], right = [];'
expect(tokensText wrapper.screenLineForRow(5).tokens).toBe ' while(items.length > 0) {'
expect(tokensText wrapper.screenLineForRow(12).tokens).toBe 'sort(left).concat(pivot).concat(sort(rig'
expect(tokensText wrapper.lineForScreenRow(4).tokens).toBe 'left = [], right = [];'
expect(tokensText wrapper.lineForScreenRow(5).tokens).toBe ' while(items.length > 0) {'
expect(tokensText wrapper.lineForScreenRow(12).tokens).toBe 'sort(left).concat(pivot).concat(sort(rig'
expect(changeHandler).toHaveBeenCalled()
[event] = changeHandler.argsForCall[0]
@@ -122,6 +125,11 @@ describe "LineWrapper", ->
it "translates a position at the end of a wrapped screen line to the begining of the next screen line", ->
expect(wrapper.screenPositionForBufferPosition([3, 51], true)).toEqual([4, 0])
describe "when the position follows a fold", ->
it "adjusts the position to account for the fold", ->
fold = folder.createFold(new Range([4, 29], [7, 4]))
expect(wrapper.screenPositionForBufferPosition([7, 4])).toEqual [5, 32]
describe ".bufferPositionForScreenPosition(point)", ->
it "translates the given screen position to a buffer position, account for wrapped lines", ->
# before any wrapped lines
@@ -135,6 +143,11 @@ describe "LineWrapper", ->
# following a wrapped line
expect(wrapper.bufferPositionForScreenPosition([5, 5])).toEqual([4, 5])
describe "when the position follows a fold placeholder", ->
it "adjusts the position to account for the fold", ->
fold = folder.createFold(new Range([4, 29], [7, 4]))
expect(wrapper.bufferPositionForScreenPosition([5, 32])).toEqual [7, 4]
describe ".wrapScreenLine(screenLine)", ->
makeTokens = (tokenValues...) ->
tokenValues.map (value) -> { value, type: 'foo' }

View File

@@ -3,16 +3,16 @@ Buffer = require 'buffer'
Highlighter = require 'highlighter'
describe "screenLineFragment", ->
[lineFragment, highlighter] = []
[screenLine, highlighter] = []
beforeEach ->
buffer = new Buffer(require.resolve 'fixtures/sample.js')
highlighter = new Highlighter(buffer)
lineFragment = highlighter.lineFragmentForRow(3)
screenLine = highlighter.lineForScreenRow(3)
describe ".splitAt(column)", ->
it "breaks the line fragment into two fragments", ->
[left, right] = lineFragment.splitAt(31)
[left, right] = screenLine.splitAt(31)
expect(left.text).toBe ' var pivot = items.shift(), '
expect(tokensText left.tokens).toBe left.text
@@ -20,7 +20,7 @@ describe "screenLineFragment", ->
expect(tokensText right.tokens).toBe right.text
it "splits tokens if they straddle the split boundary", ->
[left, right] = lineFragment.splitAt(34)
[left, right] = screenLine.splitAt(34)
expect(left.text).toBe ' var pivot = items.shift(), cur'
expect(tokensText left.tokens).toBe left.text
@@ -30,7 +30,7 @@ describe "screenLineFragment", ->
expect(_.last(left.tokens).type).toBe right.tokens[0].type
it "ensures the returned fragments cover the span of the original line", ->
[left, right] = lineFragment.splitAt(15)
[left, right] = screenLine.splitAt(15)
expect(left.bufferDelta).toEqual [0, 15]
expect(left.screenDelta).toEqual [0, 15]
@@ -46,15 +46,15 @@ describe "screenLineFragment", ->
describe "if splitting at 0", ->
it "returns undefined for the left half", ->
expect(lineFragment.splitAt(0)).toEqual [undefined, lineFragment]
expect(screenLine.splitAt(0)).toEqual [undefined, screenLine]
describe "if splitting at a column equal to the line length", ->
it "returns an empty line fragment that spans a row for the right half", ->
[left, right] = lineFragment.splitAt(lineFragment.text.length)
[left, right] = screenLine.splitAt(screenLine.text.length)
expect(left.text).toBe lineFragment.text
expect(left.screenDelta).toEqual [0, lineFragment.text.length]
expect(left.bufferDelta).toEqual [0, lineFragment.text.length]
expect(left.text).toBe screenLine.text
expect(left.screenDelta).toEqual [0, screenLine.text.length]
expect(left.bufferDelta).toEqual [0, screenLine.text.length]
expect(right.text).toBe ''
expect(right.screenDelta).toEqual [1, 0]
@@ -62,10 +62,10 @@ describe "screenLineFragment", ->
describe ".concat(otherFragment)", ->
it "returns the concatenation of the receiver and the given fragment", ->
[left, right] = lineFragment.splitAt(14)
expect(left.concat(right)).toEqual lineFragment
[left, right] = screenLine.splitAt(14)
expect(left.concat(right)).toEqual screenLine
concatenated = lineFragment.concat(highlighter.lineFragmentForRow(4))
concatenated = screenLine.concat(highlighter.lineForScreenRow(4))
expect(concatenated.text).toBe ' var pivot = items.shift(), current, left = [], right = []; while(items.length > 0) {'
expect(tokensText concatenated.tokens).toBe concatenated.text
expect(concatenated.screenDelta).toEqual [2, 0]

View File

@@ -1,50 +0,0 @@
_ = require 'underscore'
Buffer = require 'buffer'
Highlighter = require 'highlighter'
describe "ScreenLine", ->
[screenLine, highlighter] = []
beforeEach ->
buffer = new Buffer(require.resolve 'fixtures/sample.js')
highlighter = new Highlighter(buffer)
screenLine = highlighter.screenLineForRow(3)
describe ".pushToken(token)", ->
it "appends the given token to the screen line", ->
screenLine.pushToken(value: "foo", type: "bar")
expect(_.last(screenLine.tokens)).toEqual(value: "foo", type: "bar")
expect(screenLine.text).toBe ' var pivot = items.shift(), current, left = [], right = [];foo'
describe ".concat(screenLine)", ->
it "returns a new screen line combining the contents of the receiver and the given screen line", ->
otherLine = highlighter.screenLineForRow(4)
concatenated = screenLine.concat(otherLine)
expect(concatenated.text).toBe screenLine.text + otherLine.text
expect(concatenated.tokens).toEqual screenLine.tokens.concat(otherLine.tokens)
describe ".splitAt(splitColumn)", ->
describe "when the split column is less than the line length", ->
describe "when the split column is at the start of a token", ->
it "returns two screen lines", ->
[left, right] = screenLine.splitAt(31)
expect(left.text).toBe ' var pivot = items.shift(), '
expect(tokensText left.tokens).toBe left.text
expect(right.text).toBe 'current, left = [], right = [];'
expect(tokensText right.tokens).toBe right.text
describe "when the split column is in the middle of a token", ->
it "it returns two screen lines, with the token split in half", ->
[left, right] = screenLine.splitAt(34)
expect(left.text).toBe ' var pivot = items.shift(), cur'
expect(tokensText left.tokens).toBe left.text
expect(right.text).toBe 'rent, left = [], right = [];'
expect(tokensText right.tokens).toBe right.text
describe "when the split column is 0 or equals the line length", ->
it "returns a singleton array of the screen line (doesn't split it)", ->
expect(screenLine.splitAt(0)).toEqual([screenLine])
expect(screenLine.splitAt(screenLine.text.length)).toEqual([screenLine])

View File

@@ -16,8 +16,8 @@ describe "Selection", ->
it "places the anchor at the start of the range and the cursor at the end", ->
range = new Range({row: 2, column: 7}, {row: 3, column: 18})
selection.setRange(range)
expect(selection.anchor.getPosition()).toEqual range.start
expect(selection.cursor.getPosition()).toEqual range.end
expect(selection.anchor.getScreenPosition()).toEqual range.start
expect(selection.cursor.getScreenPosition()).toEqual range.end
describe ".delete()", ->
describe "when nothing is selected", ->
@@ -162,30 +162,30 @@ describe "Selection", ->
describe ".selectWord()", ->
describe "when the cursor is inside a word", ->
it "selects the entire word", ->
editor.setCursorPosition [0,8]
editor.setCursorScreenPosition [0,8]
selection.selectWord()
expect(selection.getText()).toBe 'quicksort'
describe "when the cursor is on beginning of a word", ->
it "selects the entire word", ->
editor.setCursorPosition [0,4]
editor.setCursorScreenPosition [0,4]
selection.selectWord()
expect(selection.getText()).toBe 'quicksort'
describe "when the cursor is at the end of a word", ->
it "selects the entire word", ->
editor.setCursorPosition [0,13]
editor.setCursorScreenPosition [0,13]
selection.selectWord()
expect(selection.getText()).toBe 'quicksort'
describe "when the cursor is not on a word", ->
it "selects nothing", ->
editor.setCursorPosition [5,2]
editor.setCursorScreenPosition [5,2]
selection.selectWord()
expect(selection.getText()).toBe ''
describe ".selectLine(row)", ->
it "selects the entire line at given row", ->
editor.setCursorPosition [0,2]
editor.setCursorScreenPosition [0,2]
selection.selectLine(1)
expect(selection.getText()).toBe " var sort = function(items) {"

View File

@@ -1,100 +0,0 @@
SpanIndex = require 'span-index'
describe "SpanIndex", ->
index = null
beforeEach ->
index = new SpanIndex
describe ".insert(index, span(s), values)", ->
describe "when called with an array of spans", ->
it "assigns each span in the array to the corresponding entry", ->
index.insert(0, [2, 1], ['a', 'b'])
expect(index.indexForSpan(1).index).toBe 0
expect(index.indexForSpan(2).index).toBe 1
describe "when called with a single number as the span", ->
it "assigns that span to all entries", ->
index.insert(0, 1, ['a', 'b'])
expect(index.indexForSpan(0).index).toBe 0
expect(index.indexForSpan(1).index).toBe 1
describe ".updateSpans(start, end, spans)", ->
it "updates the spans of a range of entries indicated by the given index to the given value", ->
index.insert(0, [3, 2, 3, 1, 2], ['a', 'b', 'c', 'd', 'e'])
index.updateSpans(1, 3, 1)
expect(index.spanForIndex(0)).toBe 3
expect(index.spanForIndex(1)).toBe 4
expect(index.spanForIndex(2)).toBe 5
expect(index.spanForIndex(3)).toBe 6
expect(index.spanForIndex(4)).toBe 8
describe ".sliceBySpan(start, end)", ->
describe "when the index contains values that start and end evenly on the given start/end span indices", ->
it "returns the spanning values with a start and end offset of 0", ->
index.insert(0, [1, 2, 3, 1, 2], ['a', 'b', 'c', 'd', 'e'])
{ values, startOffset, endOffset } = index.sliceBySpan(1, 6)
expect(values).toEqual ['b', 'c', 'd']
expect(startOffset).toBe 0
expect(endOffset).toBe 0
describe "when the index contains values that overlap the given start/end span indices", ->
it "includes the overlapping values, assigning the start and end offsets to indicate where they overlap the desired span indices", ->
index.insert(0, [3, 1, 1, 3, 1], ['a', 'b', 'c', 'd', 'e'])
{ values, startOffset, endOffset } = index.sliceBySpan(1, 7)
expect(values).toEqual ['a', 'b', 'c', 'd']
expect(startOffset).toBe 1
expect(endOffset).toBe 2
index.clear()
index.insert(0, [1, 4, 1], ['a', 'b', 'c'])
{ values, startOffset, endOffset } = index.sliceBySpan(3, 4)
expect(values).toEqual ['b']
expect(startOffset).toBe 2
expect(endOffset).toBe 3
describe "when the index contains values with a span of 0", ->
it "treats 0-spanning values as having no width", ->
index.insert(0, [0, 0, 3, 2, 3, 1], ['a', 'b', 'c', 'd', 'e', 'f'])
{ values, startOffset, endOffset } = index.sliceBySpan(1, 7)
expect(values).toEqual ['c', 'd', 'e']
expect(startOffset).toBe 1
expect(endOffset).toBe 2
it "does not include 0-spanning values in the returned slice", ->
index.insert(0, [3, 0, 2, 0, 3, 1], ['a', 'b', 'c', 'd', 'e', 'f'])
{ values, startOffset, endOffset } = index.sliceBySpan(1, 7)
expect(values).toEqual ['a', 'c', 'e']
expect(startOffset).toBe 1
expect(endOffset).toBe 2
describe ".lengthBySpan()", ->
it "returns the sum the spans of all entries in the index", ->
index.insert(0, [3, 0, 2, 0, 3, 1], ['a', 'b', 'c', 'd', 'e', 'f'])
expect(index.lengthBySpan()).toBe 9
describe ".indexForSpan(span)", ->
it "returns the index of the entry whose aggregated span meets or exceeds the given span, plus an offset", ->
index.insert(0, [3, 0, 2, 1], ['a', 'b', 'c', 'd'])
expect(index.indexForSpan(0)).toEqual(index: 0, offset: 0)
expect(index.indexForSpan(2)).toEqual(index: 0, offset: 2)
expect(index.indexForSpan(3)).toEqual(index: 2, offset: 0)
expect(index.indexForSpan(4)).toEqual(index: 2, offset: 1)
expect(index.indexForSpan(5)).toEqual(index: 3, offset: 0)
describe ".spanForIndex(index)", ->
it "returns the aggregate of spans for all elements up to and including the given index", ->
index.insert(0, [3, 0, 2, 0, 3, 1], ['a', 'b', 'c', 'd', 'e', 'f'])
expect(index.spanForIndex(0)).toBe 3
expect(index.spanForIndex(1)).toBe 3
expect(index.spanForIndex(2)).toBe 5
expect(index.spanForIndex(3)).toBe 5
expect(index.spanForIndex(4)).toBe 8

View File

@@ -27,14 +27,14 @@ describe "VimMode", ->
it "does not allow the cursor to be placed on the \n character, unless the line is empty", ->
editor.buffer.setText("012345\n\nabcdef")
editor.setCursorPosition([0, 5])
expect(editor.getCursorPosition()).toEqual [0,5]
editor.setCursorScreenPosition([0, 5])
expect(editor.getCursorScreenPosition()).toEqual [0,5]
editor.setCursorPosition([0, 6])
expect(editor.getCursorPosition()).toEqual [0,5]
editor.setCursorScreenPosition([0, 6])
expect(editor.getCursorScreenPosition()).toEqual [0,5]
editor.setCursorPosition([1, 0])
expect(editor.getCursorPosition()).toEqual [1,0]
editor.setCursorScreenPosition([1, 0])
expect(editor.getCursorScreenPosition()).toEqual [1,0]
it "clears the operator stack when commands can't be composed", ->
editor.trigger keydownEvent('d')
@@ -67,23 +67,23 @@ describe "VimMode", ->
describe "the x keybinding", ->
it "deletes a charachter", ->
editor.buffer.setText("012345")
editor.setCursorPosition([0, 4])
editor.setCursorScreenPosition([0, 4])
editor.trigger keydownEvent('x')
expect(editor.buffer.getText()).toBe '01235'
expect(editor.getCursorPosition()).toEqual([0, 4])
expect(editor.getCursorScreenPosition()).toEqual([0, 4])
editor.trigger keydownEvent('x')
expect(editor.buffer.getText()).toBe '0123'
expect(editor.getCursorPosition()).toEqual([0, 3])
expect(editor.getCursorScreenPosition()).toEqual([0, 3])
editor.trigger keydownEvent('x')
expect(editor.buffer.getText()).toBe '012'
expect(editor.getCursorPosition()).toEqual([0, 2])
expect(editor.getCursorScreenPosition()).toEqual([0, 2])
it "deletes nothing when cursor is on empty line", ->
editor.buffer.setText "012345\n\nabcdef"
editor.setCursorPosition [1, 0]
editor.setCursorScreenPosition [1, 0]
editor.trigger keydownEvent 'x'
expect(editor.buffer.getText()).toBe "012345\n\nabcdef"
@@ -92,202 +92,202 @@ describe "VimMode", ->
describe "when followed by a d", ->
it "deletes the current line", ->
editor.buffer.setText("12345\nabcde\nABCDE")
editor.setCursorPosition([1,1])
editor.setCursorScreenPosition([1,1])
editor.trigger keydownEvent('d')
editor.trigger keydownEvent('d')
expect(editor.buffer.getText()).toBe "12345\nABCDE"
expect(editor.getCursorPosition()).toEqual([1,0])
expect(editor.getCursorScreenPosition()).toEqual([1,0])
it "deletes the last line", ->
editor.buffer.setText("12345\nabcde\nABCDE")
editor.setCursorPosition([2,1])
editor.setCursorScreenPosition([2,1])
editor.trigger keydownEvent('d')
editor.trigger keydownEvent('d')
expect(editor.buffer.getText()).toBe "12345\nabcde"
expect(editor.getCursorPosition()).toEqual([1,0])
expect(editor.getCursorScreenPosition()).toEqual([1,0])
xdescribe "when the second d is prefixed by a count", ->
it "deletes n lines, starting from the current", ->
editor.buffer.setText("12345\nabcde\nABCDE\nQWERT")
editor.setCursorPosition([1,1])
editor.setCursorScreenPosition([1,1])
editor.trigger keydownEvent('d')
editor.trigger keydownEvent('2')
editor.trigger keydownEvent('d')
expect(editor.buffer.getText()).toBe "12345\nQWERT"
expect(editor.getCursorPosition()).toEqual([1,0])
expect(editor.getCursorScreenPosition()).toEqual([1,0])
describe "when followed by an h", ->
it "deletes the previous letter on the current line", ->
editor.buffer.setText("abcd\n01234")
editor.setCursorPosition([1,1])
editor.setCursorScreenPosition([1,1])
editor.trigger keydownEvent 'd'
editor.trigger keydownEvent 'h'
expect(editor.buffer.getText()).toBe "abcd\n1234"
expect(editor.getCursorPosition()).toEqual([1,0])
expect(editor.getCursorScreenPosition()).toEqual([1,0])
editor.trigger keydownEvent 'd'
editor.trigger keydownEvent 'h'
expect(editor.buffer.getText()).toBe "abcd\n1234"
expect(editor.getCursorPosition()).toEqual([1,0])
expect(editor.getCursorScreenPosition()).toEqual([1,0])
describe "when followed by a w", ->
it "deletes to the beginning of the next word", ->
editor.buffer.setText("abcd efg")
editor.setCursorPosition([0,2])
editor.setCursorScreenPosition([0,2])
editor.trigger keydownEvent('d')
editor.trigger keydownEvent('w')
expect(editor.buffer.getText()).toBe "abefg"
expect(editor.getCursorPosition()).toEqual([0,2])
expect(editor.getCursorScreenPosition()).toEqual([0,2])
editor.buffer.setText("one two three four")
editor.setCursorPosition([0,0])
editor.setCursorScreenPosition([0,0])
editor.trigger keydownEvent('d')
editor.trigger keydownEvent('3')
editor.trigger keydownEvent('w')
expect(editor.buffer.getText()).toBe "four"
expect(editor.getCursorPosition()).toEqual([0,0])
expect(editor.getCursorScreenPosition()).toEqual([0,0])
describe "when followed by a b", ->
it "deletes to the beginning of the previous word", ->
editor.buffer.setText("abcd efg")
editor.setCursorPosition([0,2])
editor.setCursorScreenPosition([0,2])
editor.trigger keydownEvent('d')
editor.trigger keydownEvent('b')
expect(editor.buffer.getText()).toBe "cd efg"
expect(editor.getCursorPosition()).toEqual([0,0])
expect(editor.getCursorScreenPosition()).toEqual([0,0])
editor.buffer.setText("one two three four")
editor.setCursorPosition([0,11])
editor.setCursorScreenPosition([0,11])
editor.trigger keydownEvent('d')
editor.trigger keydownEvent('3')
editor.trigger keydownEvent('b')
expect(editor.buffer.getText()).toBe "ee four"
expect(editor.getCursorPosition()).toEqual([0,0])
expect(editor.getCursorScreenPosition()).toEqual([0,0])
describe "basic motion bindings", ->
beforeEach ->
editor.buffer.setText("12345\nabcde\nABCDE")
editor.setCursorPosition([1,1])
editor.setCursorScreenPosition([1,1])
describe "the h keybinding", ->
it "moves the cursor left, but not to the previous line", ->
editor.trigger keydownEvent('h')
expect(editor.getCursorPosition()).toEqual([1,0])
expect(editor.getCursorScreenPosition()).toEqual([1,0])
editor.trigger keydownEvent('h')
expect(editor.getCursorPosition()).toEqual([1,0])
expect(editor.getCursorScreenPosition()).toEqual([1,0])
describe "the j keybinding", ->
it "moves the cursor down, but not to the end of the last line", ->
editor.trigger keydownEvent 'j'
expect(editor.getCursorPosition()).toEqual([2,1])
expect(editor.getCursorScreenPosition()).toEqual([2,1])
editor.trigger keydownEvent 'j'
expect(editor.getCursorPosition()).toEqual([2,1])
expect(editor.getCursorScreenPosition()).toEqual([2,1])
describe "the k keybinding", ->
it "moves the cursor up, but not to the beginning of the first line", ->
editor.trigger keydownEvent('k')
expect(editor.getCursorPosition()).toEqual([0,1])
expect(editor.getCursorScreenPosition()).toEqual([0,1])
editor.trigger keydownEvent('k')
expect(editor.getCursorPosition()).toEqual([0,1])
expect(editor.getCursorScreenPosition()).toEqual([0,1])
describe "the l keybinding", ->
it "moves the cursor right, but not to the next line", ->
editor.setCursorPosition([1,3])
editor.setCursorScreenPosition([1,3])
editor.trigger keydownEvent('l')
expect(editor.getCursorPosition()).toEqual([1,4])
expect(editor.getCursorScreenPosition()).toEqual([1,4])
editor.trigger keydownEvent('l')
expect(editor.getCursorPosition()).toEqual([1,4])
expect(editor.getCursorScreenPosition()).toEqual([1,4])
describe "the w keybinding", ->
it "moves the cursor to the beginning of the next word", ->
editor.buffer.setText("ab cde1+- \n xyz\n\nzip")
editor.setCursorPosition([0,0])
editor.setCursorScreenPosition([0,0])
editor.trigger keydownEvent('w')
expect(editor.getCursorPosition()).toEqual([0,3])
expect(editor.getCursorScreenPosition()).toEqual([0,3])
editor.trigger keydownEvent('w')
expect(editor.getCursorPosition()).toEqual([0,7])
expect(editor.getCursorScreenPosition()).toEqual([0,7])
editor.trigger keydownEvent('w')
expect(editor.getCursorPosition()).toEqual([1,1])
expect(editor.getCursorScreenPosition()).toEqual([1,1])
editor.trigger keydownEvent('w')
expect(editor.getCursorPosition()).toEqual([2,0])
expect(editor.getCursorScreenPosition()).toEqual([2,0])
editor.trigger keydownEvent('w')
expect(editor.getCursorPosition()).toEqual([3,0])
expect(editor.getCursorScreenPosition()).toEqual([3,0])
editor.trigger keydownEvent('w')
expect(editor.getCursorPosition()).toEqual([3,2])
expect(editor.getCursorScreenPosition()).toEqual([3,2])
describe "the { keybinding", ->
it "moves the cursor to the beginning of the paragraph", ->
editor.buffer.setText("abcde\n\nfghij\nhijk\n xyz \n\nzip\n\n \nthe end")
editor.setCursorPosition([0,0])
editor.setCursorScreenPosition([0,0])
editor.trigger keydownEvent('}')
expect(editor.getCursorPosition()).toEqual [1,0]
expect(editor.getCursorScreenPosition()).toEqual [1,0]
editor.trigger keydownEvent('}')
expect(editor.getCursorPosition()).toEqual [5,0]
expect(editor.getCursorScreenPosition()).toEqual [5,0]
editor.trigger keydownEvent('}')
expect(editor.getCursorPosition()).toEqual [7,0]
expect(editor.getCursorScreenPosition()).toEqual [7,0]
editor.trigger keydownEvent('}')
expect(editor.getCursorPosition()).toEqual [9,6]
expect(editor.getCursorScreenPosition()).toEqual [9,6]
describe "the b keybinding", ->
it "moves the cursor to the beginning of the previous word", ->
editor.buffer.setText(" ab cde1+- \n xyz\n\nzip }\n last")
editor.setCursorPosition [4,1]
editor.setCursorScreenPosition [4,1]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [3,4]
expect(editor.getCursorScreenPosition()).toEqual [3,4]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [3,0]
expect(editor.getCursorScreenPosition()).toEqual [3,0]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [2,0]
expect(editor.getCursorScreenPosition()).toEqual [2,0]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [1,1]
expect(editor.getCursorScreenPosition()).toEqual [1,1]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [0,8]
expect(editor.getCursorScreenPosition()).toEqual [0,8]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [0,4]
expect(editor.getCursorScreenPosition()).toEqual [0,4]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [0,1]
expect(editor.getCursorScreenPosition()).toEqual [0,1]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [0,0]
expect(editor.getCursorScreenPosition()).toEqual [0,0]
editor.trigger keydownEvent('b')
expect(editor.getCursorPosition()).toEqual [0,0]
expect(editor.getCursorScreenPosition()).toEqual [0,0]
describe "numeric prefix bindings", ->
it "repeats the following operation N times", ->
editor.buffer.setText("12345")
editor.setCursorPosition([0,1])
editor.setCursorScreenPosition([0,1])
editor.trigger keydownEvent('3')
editor.trigger keydownEvent('x')
@@ -295,7 +295,7 @@ describe "VimMode", ->
expect(editor.buffer.getText()).toBe '15'
editor.buffer.setText("123456789abc")
editor.setCursorPosition([0,0])
editor.setCursorScreenPosition([0,0])
editor.trigger keydownEvent('1')
editor.trigger keydownEvent('0')
editor.trigger keydownEvent('x')
@@ -308,11 +308,11 @@ describe "VimMode", ->
it "allows the cursor to reach the end of the line", ->
editor.buffer.setText("012345\n\nabcdef")
editor.setCursorPosition([0, 5])
expect(editor.getCursorPosition()).toEqual [0,5]
editor.setCursorScreenPosition([0, 5])
expect(editor.getCursorScreenPosition()).toEqual [0,5]
editor.setCursorPosition([0, 6])
expect(editor.getCursorPosition()).toEqual [0,6]
editor.setCursorScreenPosition([0, 6])
expect(editor.getCursorScreenPosition()).toEqual [0,6]
it "puts the editor into command mode when <escape> is pressed", ->
expect(editor).not.toHaveClass 'command-mode'

View File

@@ -13,11 +13,11 @@ class Cursor extends View
@one 'attach', => @updateAppearance()
bufferChanged: (e) ->
@setPosition(e.newRange.end)
@setScreenPosition(e.newRange.end)
setPosition: (point) ->
setScreenPosition: (point) ->
point = Point.fromObject(point)
@point = @editor.clipPosition(point)
@$position = @editor.clipPosition(point)
@goalColumn = null
@updateAppearance()
@trigger 'cursor:position-changed'
@@ -26,67 +26,67 @@ class Cursor extends View
window.clearTimeout(@idleTimeout) if @idleTimeout
@idleTimeout = window.setTimeout (=> @addClass 'idle'), 200
getPosition: -> _.clone(@point)
getScreenPosition: -> _.clone(@$position)
getColumn: ->
@getPosition().column
@getScreenPosition().column
setColumn: (column) ->
{ row } = @getPosition()
@setPosition {row, column}
{ row } = @getScreenPosition()
@setScreenPosition {row, column}
getRow: ->
@getPosition().row
@getScreenPosition().row
isOnEOL: ->
@getColumn() == @editor.getCurrentLine().length
moveUp: ->
{ row, column } = @getPosition()
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
if row > 0
@setPosition({row: row - 1, column: column})
@setScreenPosition({row: row - 1, column: column})
else
@moveToLineStart()
@goalColumn = column
moveDown: ->
{ row, column } = @getPosition()
{ row, column } = @getScreenPosition()
column = @goalColumn if @goalColumn?
if row < @editor.buffer.numLines() - 1
@setPosition({row: row + 1, column: column})
@setScreenPosition({row: row + 1, column: column})
else
@moveToLineEnd()
@goalColumn = column
moveToLineEnd: ->
{ row } = @getPosition()
@setPosition({ row, column: @editor.buffer.getLine(row).length })
{ row } = @getScreenPosition()
@setScreenPosition({ row, column: @editor.buffer.getLine(row).length })
moveToLineStart: ->
{ row } = @getPosition()
@setPosition({ row, column: 0 })
{ row } = @getScreenPosition()
@setScreenPosition({ row, column: 0 })
moveRight: ->
{ row, column } = @getPosition()
{ row, column } = @getScreenPosition()
if column < @editor.buffer.getLine(row).length
column++
else if row < @editor.buffer.numLines() - 1
row++
column = 0
@setPosition({row, column})
@setScreenPosition({row, column})
moveLeft: ->
{ row, column } = @getPosition()
{ row, column } = @getScreenPosition()
if column > 0
column--
else if row > 0
row--
column = @editor.buffer.getLine(row).length
@setPosition({row, column})
@setScreenPosition({row, column})
moveLeftUntilMatch: (regex) ->
row = @getRow()
@@ -108,10 +108,10 @@ class Cursor extends View
offset = match and -match[0].length or 0
@setPosition [row, column + offset]
@setScreenPosition [row, column + offset]
updateAppearance: ->
position = @editor.pixelPositionFromPoint(@point)
position = @editor.pixelPositionFromPoint(@getScreenPosition())
@css(position)
@autoScrollVertically(position)
@autoScrollHorizontally(position)

View File

@@ -1,39 +0,0 @@
Point = require 'point'
module.exports =
class Delta
@fromObject: (object) ->
if object instanceof Delta
object
else
new Delta(object[0], object[1])
constructor: (@rows=0, @columns=0) ->
add: (other) ->
debugger unless other
rows = @rows + other.rows
if other.rows == 0
columns = @columns + other.columns
else
columns = other.columns
new Delta(rows, columns)
splitAt: (column) ->
if @rows == 0
rightColumns = @columns - column
else
rightColumns = @columns
[new Delta(0, column), new Delta(@rows, rightColumns)]
inspect: ->
"(#{@rows}, #{@columns})"
isEqual: (other) ->
other = Delta.fromObject(other)
@rows == other.rows and @columns == other.columns
toPoint: ->
new Point(@rows, @columns)

View File

@@ -4,6 +4,7 @@ Point = require 'point'
Cursor = require 'cursor'
Selection = require 'selection'
Highlighter = require 'highlighter'
LineFolder = require 'line-folder'
LineWrapper = require 'line-wrapper'
UndoManager = require 'undo-manager'
Range = require 'range'
@@ -57,6 +58,7 @@ class Editor extends View
'meta-z': 'undo'
'meta-Z': 'redo'
'alt-meta-w': 'toggle-soft-wrap'
'alt-meta-f': 'fold-selection'
@on 'move-right', => @moveCursorRight()
@on 'move-left', => @moveCursorLeft()
@@ -75,6 +77,7 @@ class Editor extends View
@on 'undo', => @undo()
@on 'redo', => @redo()
@on 'toggle-soft-wrap', => @toggleSoftWrap()
@on 'fold-selection', => @foldSelection()
buildCursorAndSelection: ->
@cursor = new Cursor(this)
@@ -92,7 +95,7 @@ class Editor extends View
clickCount = e.originalEvent.detail
if clickCount == 1
@setCursorPosition @pointFromMouseEvent(e)
@setCursorScreenPosition @pointFromMouseEvent(e)
else if clickCount == 2
@selection.selectWord()
else if clickCount >= 3
@@ -104,7 +107,7 @@ class Editor extends View
@insertText(e.originalEvent.data)
@on 'cursor:position-changed', =>
@hiddenInput.css(@pixelPositionFromPoint(@cursor.getPosition()))
@hiddenInput.css(@pixelPositionFromPoint(@cursor.getScreenPosition()))
@one 'attach', =>
@calculateDimensions()
@@ -129,22 +132,23 @@ class Editor extends View
renderLines: ->
@lines.empty()
for screenLine in @lineWrapper.screenLines()
for screenLine in @lineWrapper.getLines()
@lines.append @buildLineElement(screenLine)
setBuffer: (@buffer) ->
@highlighter = new Highlighter(@buffer)
@lineWrapper = new LineWrapper(Infinity, @highlighter)
@lineFolder = new LineFolder(@highlighter)
@lineWrapper = new LineWrapper(Infinity, @lineFolder)
@undoManager = new UndoManager(@buffer)
@renderLines()
@setCursorPosition(row: 0, column: 0)
@setCursorScreenPosition(row: 0, column: 0)
@buffer.on 'change', (e) =>
@cursor.bufferChanged(e)
@lineWrapper.on 'change', (e) =>
{ oldRange, newRange } = e
screenLines = @lineWrapper.screenLinesForRows(newRange.start.row, newRange.end.row)
screenLines = @lineWrapper.linesForScreenRows(newRange.start.row, newRange.end.row)
if newRange.end.row > oldRange.end.row
# update, then insert elements
for row in [newRange.start.row..newRange.end.row]
@@ -250,8 +254,8 @@ class Editor extends View
moveCursorDown: -> @cursor.moveDown()
moveCursorRight: -> @cursor.moveRight()
moveCursorLeft: -> @cursor.moveLeft()
setCursorPosition: (point) -> @cursor.setPosition(point)
getCursorPosition: -> @cursor.getPosition()
setCursorScreenPosition: (point) -> @cursor.setScreenPosition(point)
getCursorScreenPosition: -> @cursor.getScreenPosition()
setCursorRow: (row) -> @cursor.setRow(row)
getCursorRow: -> @cursor.getRow()
setCursorColumn: (column) -> @cursor.setColumn(column)
@@ -271,6 +275,8 @@ class Editor extends View
copySelection: -> @selection.copy()
paste: -> @selection.insertText(atom.native.readFromPasteboard())
foldSelection: -> @selection.fold()
backspace: ->
@selectLeft() if @selection.isEmpty()
@selection.delete()

View File

@@ -1,6 +1,5 @@
_ = require 'underscore'
ScreenLineFragment = require 'screen-line-fragment'
ScreenLine = require 'screen-line'
EventEmitter = require 'event-emitter'
module.exports =
@@ -11,7 +10,7 @@ class Highlighter
constructor: (@buffer) ->
@buildTokenizer()
@screenLines = @buildScreenLinesForRows('start', 0, @buffer.lastRow())
@screenLines = @buildLinesForScreenRows('start', 0, @buffer.lastRow())
@buffer.on 'change', (e) => @handleBufferChange(e)
buildTokenizer: ->
@@ -25,7 +24,7 @@ class Highlighter
startState = @screenLines[newRange.start.row - 1]?.state or 'start'
@screenLines[oldRange.start.row..oldRange.end.row] =
@buildScreenLinesForRows(startState, newRange.start.row, newRange.end.row)
@buildLinesForScreenRows(startState, newRange.start.row, newRange.end.row)
# spill detection
# compare scanner state of last re-highlighted line with its previous state.
@@ -36,7 +35,7 @@ class Highlighter
break if @screenLines[row].state == previousState
nextRow = row + 1
previousState = @screenLines[nextRow].state
@screenLines[nextRow] = @buildScreenLineForRow(@screenLines[row].state, nextRow)
@screenLines[nextRow] = @buildLineForScreenRow(@screenLines[row].state, nextRow)
# if highlighting spilled beyond the bounds of the textual change, update
# the pre and post range to reflect area of highlight changes
@@ -49,30 +48,25 @@ class Highlighter
@trigger("change", {oldRange, newRange})
buildScreenLinesForRows: (startState, startRow, endRow) ->
buildLinesForScreenRows: (startState, startRow, endRow) ->
state = startState
for row in [startRow..endRow]
screenLine = @buildScreenLineForRow(state, row)
screenLine = @buildLineForScreenRow(state, row)
state = screenLine.state
screenLine
buildScreenLineForRow: (state, row) ->
buildLineForScreenRow: (state, row) ->
line = @buffer.getLine(row)
{tokens, state} = @tokenizer.getLineTokens(line, state)
new ScreenLine(tokens, line, state)
new ScreenLineFragment(tokens, line, [1, 0], [1, 0], { state })
screenLineForRow: (row) ->
lineForScreenRow: (row) ->
@screenLines[row]
lineFragments: ->
@lineFragmentsForRows(0, @buffer.lastRow())
linesForScreenRows: (startRow, endRow) ->
@screenLines[startRow..endRow]
lineFragmentsForRows: (startRow, endRow) ->
for row in [startRow..endRow]
@lineFragmentForRow(row)
lineFragmentForRow: (row) ->
{ tokens, text } = @screenLines[row]
new ScreenLineFragment(tokens, text, [1, 0], [1, 0])
lastRow: ->
@screenLines.length - 1
_.extend(Highlighter.prototype, EventEmitter)

View File

@@ -1,50 +1,110 @@
_ = require 'underscore'
Point = require 'point'
Range = require 'range'
LineMap = require 'line-map'
ScreenLineFragment = require 'screen-line-fragment'
_ = require 'underscore'
EventEmitter = require 'event-emitter'
module.exports =
class LineFolder
lineMap: null
lastHighlighterChangeEvent: null
constructor: (@highlighter) ->
@activeFolds = {}
@buildLineMap()
@highlighter.buffer.on 'change', (e) => @handleBufferChange(e)
@highlighter.on 'change', (e) => @lastHighlighterChangeEvent = e
buildLineMap: ->
@lineMap = new LineMap
@lineMap.insertAtBufferRow(0, @highlighter.lineFragments())
@lineMap.insertAtBufferRow(0, @highlighter.screenLines)
fold: (bufferRange) ->
@activeFolds[bufferRange.start.row] ?= []
@activeFolds[bufferRange.start.row].push(new Fold(this, bufferRange))
screenRange = @screenRangeForBufferRange(bufferRange)
@lineMap.replaceScreenRows(screenRange.start.row, screenRange.end.row, @renderScreenLine(screenRange.start.row))
logLines: (start=0, end=@lastRow())->
for row in [start..end]
line = @lineForScreenRow(row).text
console.log row, line, line.length
renderScreenLine: (screenRow) ->
@renderScreenLineForBufferRow(@bufferRowForScreenRow(screenRow))
createFold: (bufferRange) ->
fold = new Fold(this, bufferRange)
@registerFold(bufferRange.start.row, fold)
oldScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange))
renderScreenLineForBufferRow: (bufferRow, startColumn=0) ->
screenLine = @highlighter.lineFragmentForRow(bufferRow).splitAt(startColumn)[1]
lineWithFold = @buildLine(oldScreenRange.start.row)
@lineMap.replaceScreenRows(oldScreenRange.start.row, oldScreenRange.end.row, lineWithFold)
newScreenRange = oldScreenRange.copy()
newScreenRange.end = _.clone(newScreenRange.start)
for fragment in lineWithFold
newScreenRange.end.column += fragment.text.length
@trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange
@trigger 'fold', bufferRange
fold
destroyFold: (fold) ->
bufferRange = fold.getRange()
@unregisterFold(bufferRange.start.row, fold)
startScreenRow = @screenRowForBufferRow(bufferRange.start.row)
oldScreenRange = new Range()
oldScreenRange.start.row = startScreenRow
oldScreenRange.end.row = startScreenRow
oldScreenRange.end.column = @lineMap.lineForScreenRow(startScreenRow).text.length
@lineMap.replaceScreenRow(startScreenRow, @buildLinesForBufferRows(bufferRange.start.row, bufferRange.end.row))
newScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange))
@trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange
@trigger 'unfold', fold.getRange()
registerFold: (bufferRow, fold) ->
@activeFolds[bufferRow] ?= []
@activeFolds[bufferRow].push(fold)
unregisterFold: (bufferRow, fold) ->
folds = @activeFolds[bufferRow]
folds.splice(folds.indexOf(fold), 1)
handleBufferChange: (e) ->
for row, folds of @activeFolds
fold.handleBufferChange(e) for fold in folds
@handleHighlighterChange(@lastHighlighterChangeEvent)
handleHighlighterChange: (e) ->
oldScreenRange = @screenRangeForBufferRange(e.oldRange)
expandedOldScreenRange = @expandScreenRangeToLineEnds(oldScreenRange)
lines = @buildLinesForBufferRows(e.newRange.start.row, e.newRange.end.row)
@lineMap.replaceScreenRows(oldScreenRange.start.row, oldScreenRange.end.row, lines)
newScreenRange = @screenRangeForBufferRange(e.newRange)
expandedNewScreenRange = @expandScreenRangeToLineEnds(newScreenRange)
unless oldScreenRange.isEmpty() and newScreenRange.isEmpty()
@trigger 'change', oldRange: expandedOldScreenRange, newRange: expandedNewScreenRange
buildLinesForBufferRows: (start, end) ->
lines = [@buildLine(@screenRowForBufferRow(start))]
if end > start
for row in [start + 1..end]
lines.push @buildLineForBufferRow(row)
_.flatten(lines)
buildLine: (screenRow) ->
@buildLineForBufferRow(@bufferRowForScreenRow(screenRow))
buildLineForBufferRow: (bufferRow, startColumn=0) ->
screenLine = @highlighter.lineForScreenRow(bufferRow).splitAt(startColumn)[1]
for fold in @foldsForBufferRow(bufferRow)
{ start, end } = fold.range
{ start, end } = fold.getRange()
if start.column > startColumn
prefix = screenLine.splitAt(start.column - startColumn)[0]
suffix = @buildScreenLineForBufferRow(end.row, end.column)
suffix = @buildLineForBufferRow(end.row, end.column)
return _.flatten([prefix, @buildFoldPlaceholder(fold), suffix])
screenLine
buildScreenLineForBufferRow: (bufferRow, startColumn=0) ->
screenLine = @highlighter.lineFragmentForRow(bufferRow).splitAt(startColumn)[1]
for fold in @foldsForBufferRow(bufferRow)
{ start, end } = fold.range
if start.column > startColumn
prefix = screenLine.splitAt(start.column - startColumn)[0]
suffix = @buildScreenLineForBufferRow(end.row, end.column)
screenLine = _.flatten([prefix, @buildFoldPlaceholder(fold), suffix])
return screenLine
screenLine
buildFoldPlaceholder: (fold) ->
new ScreenLineFragment([{value: '...', type: 'fold-placeholder'}], '...', [0, 3], fold.range.toDelta())
new ScreenLineFragment([{value: '...', type: 'fold-placeholder'}], '...', [0, 3], fold.getRange().toDelta(), isAtomic: true)
foldsForBufferRow: (bufferRow) ->
@activeFolds[bufferRow] or []
@@ -52,6 +112,18 @@ class LineFolder
linesForScreenRows: (startRow, endRow) ->
@lineMap.linesForScreenRows(startRow, endRow)
lineForScreenRow: (screenRow) ->
@lineMap.lineForScreenRow(screenRow)
getLines: ->
@lineMap.getScreenLines()
lineCount: ->
@lineMap.screenLineCount()
lastRow: ->
@lineCount() - 1
screenRowForBufferRow: (bufferRow) ->
@screenPositionForBufferPosition([bufferRow, 0]).row
@@ -64,8 +136,48 @@ class LineFolder
bufferPositionForScreenPosition: (screenPosition) ->
@lineMap.bufferPositionForScreenPosition(screenPosition)
clipScreenPosition: (screenPosition) ->
@lineMap.clipScreenPosition(screenPosition)
screenRangeForBufferRange: (bufferRange) ->
@lineMap.screenRangeForBufferRange(bufferRange)
expandScreenRangeToLineEnds: (screenRange) ->
{ start, end } = screenRange
new Range([start.row, 0], [end.row, @lineMap.lineForScreenRow(end.row).text.length])
_.extend LineFolder.prototype, EventEmitter
class Fold
constructor: (@lineFolder, @range) ->
constructor: (@lineFolder, {@start, @end}) ->
destroy: ->
@lineFolder.destroyFold(this)
getRange: ->
new Range(@start, @end)
handleBufferChange: (event) ->
oldStartRow = @start.row
{ oldRange } = event
if oldRange.start.isLessThanOrEqual(@start) and oldRange.end.isGreaterThanOrEqual(@end)
@lineFolder.unregisterFold(oldStartRow, this)
return
@start = @updateAnchorPoint(@start, event)
@end = @updateAnchorPoint(@end, event, false)
if @start.row != oldStartRow
@lineFolder.unregisterFold(oldStartRow, this)
@lineFolder.registerFold(@start.row, this)
updateAnchorPoint: (point, event, inclusive=true) ->
{ newRange, oldRange } = event
if inclusive
return point if oldRange.end.isGreaterThan(point)
else
return point if oldRange.end.isGreaterThanOrEqual(point)
newRange.end.add(point.subtract(oldRange.end))

View File

@@ -1,129 +1,152 @@
_ = require 'underscore'
Delta = require 'delta'
Point = require 'point'
Range = require 'range'
module.exports =
class LineMap
constructor: ->
@lineFragments = []
@screenLines = []
insertAtBufferRow: (bufferRow, lineFragments) ->
lineFragments = [lineFragments] unless _.isArray(lineFragments)
delta = new Delta
insertAtBufferRow: (bufferRow, screenLines) ->
screenLines = [screenLines] unless _.isArray(screenLines)
delta = new Point
insertIndex = 0
for lineFragment in @lineFragments
nextDelta = delta.add(lineFragment.bufferDelta)
break if nextDelta.rows > bufferRow
for screenLine in @screenLines
nextDelta = delta.add(screenLine.bufferDelta)
break if nextDelta.row > bufferRow
delta = nextDelta
insertIndex++
@lineFragments[insertIndex...insertIndex] = lineFragments
@screenLines[insertIndex...insertIndex] = screenLines
spliceAtBufferRow: (startRow, rowCount, lineFragments) ->
@spliceByDelta('bufferDelta', startRow, rowCount, lineFragments)
spliceAtBufferRow: (startRow, rowCount, screenLines) ->
@spliceByDelta('bufferDelta', startRow, rowCount, screenLines)
spliceAtScreenRow: (startRow, rowCount, lineFragments) ->
@spliceByDelta('screenDelta', startRow, rowCount, lineFragments)
spliceAtScreenRow: (startRow, rowCount, screenLines) ->
@spliceByDelta('screenDelta', startRow, rowCount, screenLines)
spliceByDelta: (deltaType, startRow, rowCount, lineFragments) ->
spliceByDelta: (deltaType, startRow, rowCount, screenLines) ->
stopRow = startRow + rowCount
startIndex = undefined
stopIndex = 0
delta = new Delta
delta = new Point
for lineFragment, i in @lineFragments
startIndex = i if delta.rows == startRow and not startIndex
nextDelta = delta.add(lineFragment[deltaType])
break if nextDelta.rows > stopRow
for screenLine, i in @screenLines
startIndex = i if delta.row == startRow and not startIndex
nextDelta = delta.add(screenLine[deltaType])
break if nextDelta.row > stopRow
delta = nextDelta
stopIndex++
@lineFragments[startIndex...stopIndex] = lineFragments
@screenLines[startIndex...stopIndex] = screenLines
replaceBufferRows: (start, end, lineFragments) ->
@spliceAtBufferRow(start, end - start + 1, lineFragments)
replaceBufferRows: (start, end, screenLines) ->
@spliceAtBufferRow(start, end - start + 1, screenLines)
replaceScreenRows: (start, end, lineFragments) ->
@spliceAtScreenRow(start, end - start + 1, lineFragments)
replaceScreenRow: (row, screenLines) ->
@replaceScreenRows(row, row, screenLines)
lineFragmentsForScreenRow: (screenRow) ->
@lineFragmentsForScreenRows(screenRow, screenRow)
replaceScreenRows: (start, end, screenLines) ->
@spliceAtScreenRow(start, end - start + 1, screenLines)
lineFragmentsForScreenRows: (startRow, endRow) ->
lineFragments = []
delta = new Delta
getScreenLines: ->
return @screenLines
for lineFragment in @lineFragments
break if delta.rows > endRow
lineFragments.push(lineFragment) if delta.rows >= startRow
delta = delta.add(lineFragment.screenDelta)
lineFragments
lineForScreenRow: (row) ->
@linesForScreenRows(row, row)[0]
linesForScreenRows: (startRow, endRow) ->
lastLine = null
lines = []
delta = new Delta
delta = new Point
for fragment in @lineFragments
break if delta.rows > endRow
if delta.rows >= startRow
for fragment in @screenLines
break if delta.row > endRow
if delta.row >= startRow
if pendingFragment
pendingFragment = pendingFragment.concat(fragment)
else
pendingFragment = fragment
if pendingFragment.screenDelta.rows > 0
if pendingFragment.screenDelta.row > 0
lines.push pendingFragment
pendingFragment = null
delta = delta.add(fragment.screenDelta)
lines
lineForBufferRow: (row) ->
line = null
delta = new Point
for fragment in @screenLines
break if delta.row > row
if delta.row == row
if line
line = line.concat(fragment)
else
line = fragment
delta = delta.add(fragment.bufferDelta)
line
bufferLineCount: ->
delta = new Delta
for lineFragment in @lineFragments
delta = delta.add(lineFragment.bufferDelta)
delta.rows
delta = new Point
for screenLine in @screenLines
delta = delta.add(screenLine.bufferDelta)
delta.row
screenLineCount: ->
delta = new Delta
for lineFragment in @lineFragments
delta = delta.add(lineFragment.screenDelta)
delta.rows
delta = new Point
for screenLine in @screenLines
delta = delta.add(screenLine.screenDelta)
delta.row
screenPositionForBufferPosition: (bufferPosition, eagerWrap=true) ->
bufferPosition = Point.fromObject(bufferPosition)
bufferDelta = new Delta
screenDelta = new Delta
for lineFragment in @lineFragments
nextDelta = bufferDelta.add(lineFragment.bufferDelta)
break if nextDelta.toPoint().greaterThan(bufferPosition)
break if nextDelta.toPoint().isEqual(bufferPosition) and not eagerWrap
bufferDelta = new Point
screenDelta = new Point
for screenLine in @screenLines
nextDelta = bufferDelta.add(screenLine.bufferDelta)
break if nextDelta.isGreaterThan(bufferPosition)
break if nextDelta.isEqual(bufferPosition) and not eagerWrap
bufferDelta = nextDelta
screenDelta = screenDelta.add(lineFragment.screenDelta)
screenDelta = screenDelta.add(screenLine.screenDelta)
columns = screenDelta.columns + (bufferPosition.column - bufferDelta.columns)
new Point(screenDelta.rows, columns)
remainingBufferColumn = bufferPosition.column - bufferDelta.column
additionalScreenColumn = Math.max(0, Math.min(remainingBufferColumn, screenLine.lengthForClipping()))
new Point(screenDelta.row, screenDelta.column + additionalScreenColumn)
bufferPositionForScreenPosition: (screenPosition) ->
screenPosition = Point.fromObject(screenPosition)
bufferDelta = new Delta
screenDelta = new Delta
bufferDelta = new Point
screenDelta = new Point
for lineFragment in @lineFragments
nextDelta = screenDelta.add(lineFragment.screenDelta)
break if nextDelta.toPoint().greaterThan(screenPosition)
for screenLine in @screenLines
nextDelta = screenDelta.add(screenLine.screenDelta)
break if nextDelta.isGreaterThan(screenPosition)
screenDelta = nextDelta
bufferDelta = bufferDelta.add(lineFragment.bufferDelta)
bufferDelta = bufferDelta.add(screenLine.bufferDelta)
columns = bufferDelta.columns + (screenPosition.column - screenDelta.columns)
new Point(bufferDelta.rows, columns)
column = bufferDelta.column + (screenPosition.column - screenDelta.column)
new Point(bufferDelta.row, column)
screenRangeForBufferRange: (bufferRange) ->
start = @screenPositionForBufferPosition(bufferRange.start)
end = @screenPositionForBufferPosition(bufferRange.end)
new Range(start, end)
clipScreenPosition: (screenPosition) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = new Point(Math.max(0, screenPosition.row), Math.max(0, screenPosition.column))
screenDelta = new Point
for screenLine in @screenLines
nextDelta = screenDelta.add(screenLine.screenDelta)
break if nextDelta.isGreaterThan(screenPosition)
screenDelta = nextDelta
maxColumn = screenDelta.column + screenLine.lengthForClipping()
screenDelta.column = Math.min(maxColumn, screenPosition.column)
screenDelta

View File

@@ -1,73 +1,49 @@
_ = require 'underscore'
EventEmitter = require 'event-emitter'
SpanIndex = require 'span-index'
LineMap = require 'line-map'
Point = require 'point'
Range = require 'range'
Delta = require 'delta'
module.exports =
class LineWrapper
constructor: (@maxLength, @highlighter) ->
@buffer = @highlighter.buffer
@buildWrappedLines()
@highlighter.on 'change', (e) => @handleChange(e)
constructor: (@maxLength, @lineFolder) ->
@buildLineMap()
@lineFolder.on 'change', (e) => @handleChange(e)
setMaxLength: (@maxLength) ->
oldRange = new Range
oldRange.end.row = @screenLineCount() - 1
oldRange.end.column = _.last(@index.last().screenLines).text.length
@buildWrappedLines()
newRange = new Range
newRange.end.row = @screenLineCount() - 1
newRange.end.column = _.last(@index.last().screenLines).text.length
oldRange = @rangeForAllLines()
@buildLineMap()
newRange = @rangeForAllLines()
@trigger 'change', { oldRange, newRange }
getSpans: (wrappedLines) ->
wrappedLines.map (line) -> line.screenLines.length
unpackWrappedLines: (wrappedLines) ->
_.flatten(_.pluck(wrappedLines, 'screenLines'))
buildWrappedLines: ->
@index = new SpanIndex
buildLineMap: ->
@lineMap = new LineMap
wrappedLines = @buildWrappedLinesForBufferRows(0, @buffer.lastRow())
@index.insert 0, @getSpans(wrappedLines), wrappedLines
@lineMap.insertAtBufferRow 0, @unpackWrappedLines(wrappedLines)
@lineMap.insertAtBufferRow 0, @buildScreenLinesForBufferRows(0, @lineFolder.lastRow())
handleChange: (e) ->
oldRange = new Range
oldBufferRange = e.oldRange
newBufferRange = e.newRange
bufferRow = e.oldRange.start.row
oldRange.start.row = @firstScreenRowForBufferRow(e.oldRange.start.row)
oldRange.end.row = @lastScreenRowForBufferRow(e.oldRange.end.row)
oldRange.end.column = _.last(@index.at(e.oldRange.end.row).screenLines).text.length
oldScreenRange = @lineMap.screenRangeForBufferRange(@expandBufferRangeToLineEnds(oldBufferRange))
newScreenLines = @buildScreenLinesForBufferRows(newBufferRange.start.row, newBufferRange.end.row)
@lineMap.replaceBufferRows oldBufferRange.start.row, oldBufferRange.end.row, newScreenLines
newScreenRange = @lineMap.screenRangeForBufferRange(@expandBufferRangeToLineEnds(newBufferRange))
{ start, end } = e.oldRange
wrappedLines = @buildWrappedLinesForBufferRows(e.newRange.start.row, e.newRange.end.row)
@index.splice start.row, end.row, @getSpans(wrappedLines), wrappedLines
@lineMap.replaceBufferRows start.row, end.row, @unpackWrappedLines(wrappedLines)
@trigger 'change', { oldRange: oldScreenRange, newRange: newScreenRange }
newRange = oldRange.copy()
newRange.end.row = @lastScreenRowForBufferRow(e.newRange.end.row)
newRange.end.column = _.last(@index.at(e.newRange.end.row).screenLines).text.length
expandBufferRangeToLineEnds: (bufferRange) ->
{ start, end } = bufferRange
new Range([start.row, 0], [end.row, @lineMap.lineForBufferRow(end.row).text.length])
@trigger 'change', { oldRange, newRange }
rangeForAllLines: ->
endRow = @lineCount() - 1
endColumn = @lineMap.lineForScreenRow(endRow).text.length
new Range([0, 0], [endRow, endColumn])
firstScreenRowForBufferRow: (bufferRow) ->
@screenPositionForBufferPosition([bufferRow, 0]).row
lastScreenRowForBufferRow: (bufferRow) ->
startRow = @screenPositionForBufferPosition([bufferRow, 0]).row
startRow + (@index.at(bufferRow).screenLines.length - 1)
buildWrappedLinesForBufferRows: (start, end) ->
for row in [start..end]
@buildWrappedLineForBufferRow(row)
buildWrappedLineForBufferRow: (bufferRow) ->
{ screenLines: @wrapScreenLine(@highlighter.lineFragmentForRow(bufferRow)) }
buildScreenLinesForBufferRows: (start, end) ->
_(@lineFolder
.linesForScreenRows(start, end)
.map((screenLine) => @wrapScreenLine(screenLine))).flatten()
wrapScreenLine: (screenLine, startColumn=0) ->
screenLines = []
@@ -78,7 +54,7 @@ class LineWrapper
endColumn = startColumn + screenLine.text.length
else
[leftHalf, rightHalf] = screenLine.splitAt(splitColumn)
leftHalf.screenDelta = new Delta(1, 0)
leftHalf.screenDelta = new Point(1, 0)
screenLines.push leftHalf
endColumn = startColumn + leftHalf.text.length
screenLines.push @wrapScreenLine(rightHalf, endColumn)...
@@ -100,35 +76,28 @@ class LineWrapper
return column + 1 if /\s/.test(line[column])
return @maxLength
screenRangeFromBufferRange: (bufferRange) ->
start = @screenPositionForBufferPosition(bufferRange.start, false)
end = @screenPositionForBufferPosition(bufferRange.end, false)
new Range(start,end)
screenRangeForBufferRange: (bufferRange) ->
@lineMap.screenRangeForBufferRange(bufferRange)
screenPositionForBufferPosition: (bufferPosition, eagerWrap=true) ->
@lineMap.screenPositionForBufferPosition(bufferPosition, eagerWrap)
@lineMap.screenPositionForBufferPosition(
@lineFolder.screenPositionForBufferPosition(bufferPosition),
eagerWrap)
bufferPositionForScreenPosition: (screenPosition) ->
@lineMap.bufferPositionForScreenPosition(screenPosition)
@lineFolder.bufferPositionForScreenPosition(
@lineMap.bufferPositionForScreenPosition(screenPosition))
screenLineForRow: (screenRow) ->
@screenLinesForRows(screenRow, screenRow)[0]
lineForScreenRow: (screenRow) ->
@linesForScreenRows(screenRow, screenRow)[0]
screenLinesForRows: (startRow, endRow) ->
screenLines = []
linesForScreenRows: (startRow, endRow) ->
@lineMap.linesForScreenRows(startRow, endRow)
{ values, startOffset, endOffset } = @index.sliceBySpan(startRow, endRow)
getLines: ->
@linesForScreenRows(0, @lineCount() - 1)
screenLines.push(values[0].screenLines[startOffset..-1]...)
for wrappedLine in values[1...-1]
screenLines.push(wrappedLine.screenLines...)
screenLines.push(_.last(values).screenLines[0..endOffset]...)
screenLines
screenLines: ->
@screenLinesForRows(0, @screenLineCount() - 1)
screenLineCount: ->
lineCount: ->
@lineMap.screenLineCount()
_.extend(LineWrapper.prototype, EventEmitter)

View File

@@ -11,16 +11,33 @@ class Point
new Point(row, column)
constructor: (@row, @column) ->
constructor: (@row=0, @column=0) ->
isEqual: (other) ->
if other instanceof Array
@row == other[0] and @column == other[1]
add: (other) ->
row = @row + other.row
if other.row == 0
column = @column + other.column
else
@row == other.row and @column == other.column
column = other.column
inspect: ->
"(#{@row}, #{@column})"
new Point(row, column)
subtract: (other) ->
row = @row - other.row
if @row == other.row
column = @column - other.column
else
column = @column
new Point(row, column)
splitAt: (column) ->
if @row == 0
rightColumn = @column - column
else
rightColumn = @column
[new Point(0, column), new Point(@row, rightColumn)]
compare: (other) ->
if @row > other.row
@@ -35,5 +52,22 @@ class Point
else
0
greaterThan: (other) ->
isEqual: (other) ->
other = Point.fromObject(other)
@compare(other) == 0
isLessThan: (other) ->
@compare(other) < 0
isLessThanOrEqual: (other) ->
@compare(other) <= 0
isGreaterThan: (other) ->
@compare(other) > 0
isGreaterThanOrEqual: (other) ->
@compare(other) >= 0
inspect: ->
"(#{@row}, #{@column})"

View File

@@ -1,5 +1,4 @@
Point = require 'point'
Delta = require 'delta'
_ = require 'underscore'
@@ -37,5 +36,5 @@ class Range
columns = @end.column - @start.column
else
columns = @end.column
new Delta(rows, columns)
new Point(rows, columns)

View File

@@ -1,11 +1,14 @@
_ = require 'underscore'
Delta = require 'delta'
Point = require 'point'
module.exports =
class ScreenLineFragment
constructor: (@tokens, @text, screenDelta, bufferDelta) ->
@screenDelta = Delta.fromObject(screenDelta)
@bufferDelta = Delta.fromObject(bufferDelta)
isAtomic: false
constructor: (@tokens, @text, screenDelta, bufferDelta, extraFields) ->
@screenDelta = Point.fromObject(screenDelta)
@bufferDelta = Point.fromObject(bufferDelta)
_.extend(this, extraFields)
splitAt: (column) ->
return [undefined, this] if column == 0
@@ -43,5 +46,11 @@ class ScreenLineFragment
bufferDelta = @bufferDelta.add(other.bufferDelta)
new ScreenLineFragment(tokens, text, screenDelta, bufferDelta)
lengthForClipping: ->
if @isAtomic
0
else
@text.length
isEqual: (other) ->
_.isEqual(@tokens, other.tokens) and @screenDelta.isEqual(other.screenDelta) and @bufferDelta.isEqual(other.bufferDelta)

View File

@@ -1,39 +0,0 @@
_ = require 'underscore'
module.exports =
class ScreenLine
tokens: null
text: null
state: null
constructor: (@tokens, @text, @state) ->
pushToken: (token) ->
@tokens.push(token)
@text += token.value
concat: (otherLine) ->
new ScreenLine(@tokens.concat(otherLine.tokens), @text + otherLine.text)
splitAt: (column) ->
return [this] if column == 0 or column >= @text.length
rightTokens = _.clone(@tokens)
leftTokens = []
leftTextLength = 0
while leftTextLength < column
if leftTextLength + rightTokens[0].value.length > column
rightTokens[0..0] = @splitTokenAt(rightTokens[0], column - leftTextLength)
nextToken = rightTokens.shift()
leftTextLength += nextToken.value.length
leftTokens.push nextToken
leftLine = new ScreenLine(leftTokens, @text.substring(0, column))
rightLine = new ScreenLine(rightTokens, @text.substring(column))
[leftLine, rightLine]
splitTokenAt: (token, splitIndex) ->
{ type, value } = token
value1 = value.substring(0, splitIndex)
value2 = value.substring(splitIndex)
[{value: value1, type }, {value: value2, type}]

View File

@@ -1,4 +1,5 @@
Cursor = require 'cursor'
Range = require 'range'
{View, $$} = require 'space-pen'
@@ -61,17 +62,17 @@ class Selection extends View
getRange: ->
if @anchor
new Range(@anchor.getPosition(), @cursor.getPosition())
new Range(@anchor.getScreenPosition(), @cursor.getScreenPosition())
else
new Range(@cursor.getPosition(), @cursor.getPosition())
new Range(@cursor.getScreenPosition(), @cursor.getScreenPosition())
setRange: (range) ->
@cursor.setPosition(range.start)
@cursor.setScreenPosition(range.start)
@modifySelection =>
@cursor.setPosition(range.end)
@cursor.setScreenPosition(range.end)
getScreenRange: ->
@editor.lineWrapper.screenRangeFromBufferRange(@getRange())
@editor.lineWrapper.screenRangeForBufferRange(@getRange())
getText: ->
@editor.buffer.getTextInRange @getRange()
@@ -97,8 +98,8 @@ class Selection extends View
placeAnchor: ->
return if @anchor
cursorPosition = @cursor.getPosition()
@anchor = { getPosition: -> cursorPosition }
cursorPosition = @cursor.getScreenPosition()
@anchor = { getScreenPosition: -> cursorPosition }
selectWord: ->
row = @cursor.getRow()
@@ -141,7 +142,7 @@ class Selection extends View
selectToPosition: (position) ->
@modifySelection =>
@cursor.setPosition(position)
@cursor.setScreenPosition(position)
moveCursorToLineEnd: ->
@cursor.moveToLineEnd()
@@ -157,3 +158,6 @@ class Selection extends View
return if @isEmpty()
text = @editor.buffer.getTextInRange @getRange()
atom.native.writeToPasteboard text
fold: ->
@editor.lineFolder.createFold(@getRange())

View File

@@ -1,81 +0,0 @@
_ = require 'underscore'
module.exports =
class SpanIndex
constructor: ->
@entries = []
insert: (index, spans, values) ->
@entries[index..index] = @buildIndexEntries(spans, values)
replace: (index, span, value) ->
@splice(index, index, span, [value])
splice: (start, end, spans, values) ->
@entries[start..end] = @buildIndexEntries(spans, values)
updateSpans: (start, end, span) ->
for i in [start..end]
@entries[i].span = span
at: (index) ->
@entries[index].value
last: ->
_.last(@entries).value
clear: ->
@entries = []
lengthBySpan: ->
length = 0
for entry in @entries
length += entry.span
length
sliceBySpan: (start, end) ->
currentSpan = 0
values = []
for entry in @entries
continue if entry.span is 0
nextSpan = currentSpan + entry.span
if nextSpan > start
startOffset = start - currentSpan if currentSpan <= start
if currentSpan <= end
values.push entry.value
endOffset = end - currentSpan if nextSpan >= end
else
break
currentSpan = nextSpan
{ values, startOffset, endOffset }
indexForSpan: (targetSpan) ->
currentSpan = 0
index = 0
offset = 0
for entry in @entries
nextSpan = currentSpan + entry.span
if nextSpan > targetSpan
offset = targetSpan - currentSpan
return { index, offset}
currentSpan = nextSpan
index++
spanForIndex: (index) ->
span = 0
for i in [0..index]
span += @entries[i].span
span
buildIndexEntries: (spans, values) ->
if _.isArray(spans)
_.zip(spans, values).map ([span, value]) -> new SpanIndexEntry(span, value)
else
values.map (value) -> new SpanIndexEntry(spans, value)
class SpanIndexEntry
constructor: (@span, @value) ->

View File

@@ -7,27 +7,27 @@ class Motion
class MoveLeft extends Motion
execute: ->
{column, row} = @editor.getCursorPosition()
{column, row} = @editor.getCursorScreenPosition()
@editor.moveCursorLeft() if column > 0
select: ->
position = @editor.getCursorPosition()
position = @editor.getCursorScreenPosition()
position.column-- if position.column > 0
@editor.selectToPosition position
class MoveRight extends Motion
execute: ->
{column, row} = @editor.getCursorPosition()
{column, row} = @editor.getCursorScreenPosition()
@editor.moveCursorRight()
class MoveUp extends Motion
execute: ->
{column, row} = @editor.getCursorPosition()
{column, row} = @editor.getCursorScreenPosition()
@editor.moveCursorUp() if row > 0
class MoveDown extends Motion
execute: ->
{column, row} = @editor.getCursorPosition()
{column, row} = @editor.getCursorScreenPosition()
@editor.moveCursorDown() if row < (@editor.buffer.numLines() - 1)
class MoveToPreviousWord extends Motion
@@ -39,14 +39,14 @@ class MoveToPreviousWord extends Motion
class MoveToNextWord extends Motion
execute: ->
@editor.setCursorPosition(@nextWordPosition())
@editor.setCursorScreenPosition(@nextWordPosition())
select: ->
@editor.selectToPosition(@nextWordPosition())
nextWordPosition: ->
regex = getWordRegex()
{ row, column } = @editor.getCursorPosition()
{ row, column } = @editor.getCursorScreenPosition()
rightOfCursor = @editor.buffer.getLine(row).substring(column)
match = regex.exec(rightOfCursor)
@@ -64,7 +64,7 @@ class MoveToNextWord extends Motion
class MoveToNextParagraph extends Motion
execute: ->
@editor.setCursorPosition(@nextPosition())
@editor.setCursorScreenPosition(@nextPosition())
select: ->
@editor.selectToPosition(@nextPosition())

View File

@@ -44,7 +44,7 @@ class Delete
@editor.getSelection().delete()
else
@editor.buffer.deleteRow(@editor.getCursorRow())
@editor.setCursorPosition([@editor.getCursorRow(), 0])
@editor.setCursorScreenPosition([@editor.getCursorRow(), 0])
compose: (motion) ->
if not motion.select