diff --git a/spec/atom/cursor-spec.coffee b/spec/atom/cursor-spec.coffee index f1bdc14cd..4dc5e8348 100644 --- a/spec/atom/cursor-spec.coffee +++ b/spec/atom/cursor-spec.coffee @@ -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() diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index cb2d9bbde..2009ae721 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -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) {...}' + diff --git a/spec/atom/highlighter-spec.coffee b/spec/atom/highlighter-spec.coffee index ffe369372..2bc783a26 100644 --- a/spec/atom/highlighter-spec.coffee +++ b/spec/atom/highlighter-spec.coffee @@ -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] diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 008139694..7c9d26f90 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -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] + + + diff --git a/spec/atom/line-map-spec.coffee b/spec/atom/line-map-spec.coffee index a25597176..517e16628 100644 --- a/spec/atom/line-map-spec.coffee +++ b/spec/atom/line-map-spec.coffee @@ -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]) diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index c456d4d8d..49e86284c 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -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' } diff --git a/spec/atom/screen-line-fragment-spec.coffee b/spec/atom/screen-line-fragment-spec.coffee index 183203147..c39f33ad8 100644 --- a/spec/atom/screen-line-fragment-spec.coffee +++ b/spec/atom/screen-line-fragment-spec.coffee @@ -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] diff --git a/spec/atom/screen-line-spec.coffee b/spec/atom/screen-line-spec.coffee deleted file mode 100644 index 7f23e8cb8..000000000 --- a/spec/atom/screen-line-spec.coffee +++ /dev/null @@ -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]) - diff --git a/spec/atom/selection-spec.coffee b/spec/atom/selection-spec.coffee index b15c0e361..ee060b3f7 100644 --- a/spec/atom/selection-spec.coffee +++ b/spec/atom/selection-spec.coffee @@ -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) {" diff --git a/spec/atom/span-index-spec.coffee b/spec/atom/span-index-spec.coffee deleted file mode 100644 index 971dfdde6..000000000 --- a/spec/atom/span-index-spec.coffee +++ /dev/null @@ -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 - - - - - - diff --git a/spec/atom/vim-mode-spec.coffee b/spec/atom/vim-mode-spec.coffee index 8c9bdfcdb..8d8a46379 100644 --- a/spec/atom/vim-mode-spec.coffee +++ b/spec/atom/vim-mode-spec.coffee @@ -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 is pressed", -> expect(editor).not.toHaveClass 'command-mode' diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index 2163f1fbd..ee4e8f6d5 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -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) diff --git a/src/atom/delta.coffee b/src/atom/delta.coffee deleted file mode 100644 index 867614bbb..000000000 --- a/src/atom/delta.coffee +++ /dev/null @@ -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) diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 7a40b34bb..cbcb6e551 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -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() diff --git a/src/atom/highlighter.coffee b/src/atom/highlighter.coffee index ae3a6bc27..494d3391f 100644 --- a/src/atom/highlighter.coffee +++ b/src/atom/highlighter.coffee @@ -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) diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index fc912535d..d098df11a 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -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)) + diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 438a8a6a6..b63b14b7d 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -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 + diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 79353eb70..2faa8b730 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -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) diff --git a/src/atom/point.coffee b/src/atom/point.coffee index 85521baff..c29883293 100644 --- a/src/atom/point.coffee +++ b/src/atom/point.coffee @@ -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})" + diff --git a/src/atom/range.coffee b/src/atom/range.coffee index 0faa9992b..2a8254f9a 100644 --- a/src/atom/range.coffee +++ b/src/atom/range.coffee @@ -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) diff --git a/src/atom/screen-line-fragment.coffee b/src/atom/screen-line-fragment.coffee index 4593b65e9..7ac5d9987 100644 --- a/src/atom/screen-line-fragment.coffee +++ b/src/atom/screen-line-fragment.coffee @@ -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) diff --git a/src/atom/screen-line.coffee b/src/atom/screen-line.coffee deleted file mode 100644 index d772f3656..000000000 --- a/src/atom/screen-line.coffee +++ /dev/null @@ -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}] diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index b4eaa02be..e2c51c0f1 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -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()) diff --git a/src/atom/span-index.coffee b/src/atom/span-index.coffee deleted file mode 100644 index 44b56f529..000000000 --- a/src/atom/span-index.coffee +++ /dev/null @@ -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) -> - diff --git a/src/atom/vim-mode/motions.coffee b/src/atom/vim-mode/motions.coffee index 7535e28ab..70c33ae1b 100644 --- a/src/atom/vim-mode/motions.coffee +++ b/src/atom/vim-mode/motions.coffee @@ -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()) diff --git a/src/atom/vim-mode/operators.coffee b/src/atom/vim-mode/operators.coffee index c5094ad69..e0838ae47 100644 --- a/src/atom/vim-mode/operators.coffee +++ b/src/atom/vim-mode/operators.coffee @@ -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