From 29543c73b7ed770d132bad161a7c0c6437c37c01 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 21 Feb 2012 23:33:55 -0700 Subject: [PATCH 01/32] Add LineMap.lineForBufferRow to support LineWrapper LineWrapper uses lineForBufferRow to expand ranges to encompass the entire span of the lines that contain them when emitting change events. --- spec/atom/line-map-spec.coffee | 15 +++++++++++++-- src/atom/line-map.coffee | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/spec/atom/line-map-spec.coffee b/spec/atom/line-map-spec.coffee index a25597176..4325a7cba 100644 --- a/spec/atom/line-map-spec.coffee +++ b/spec/atom/line-map-spec.coffee @@ -143,7 +143,6 @@ describe "LineMap", -> 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,7 +150,18 @@ 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 Delta(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) @@ -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", -> diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 438a8a6a6..475df147c 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -62,6 +62,9 @@ class LineMap lineFragments + lineForScreenRow: (row) -> + @linesForScreenRows(row, row)[0] + linesForScreenRows: (startRow, endRow) -> lastLine = null lines = [] @@ -80,6 +83,19 @@ class LineMap delta = delta.add(fragment.screenDelta) lines + lineForBufferRow: (row) -> + line = null + delta = new Delta + for fragment in @lineFragments + break if delta.rows > row + if delta.rows == row + if line + line = line.concat(fragment) + else + line = fragment + delta = delta.add(fragment.bufferDelta) + line + bufferLineCount: -> delta = new Delta for lineFragment in @lineFragments From 14fc741b4282e60f48da62758ad2a2204a50b374 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 21 Feb 2012 23:36:11 -0700 Subject: [PATCH 02/32] Eliminate @index from LineWrapper plus :lipstick: LineWrapper only uses LineMap now --- src/atom/line-wrapper.coffee | 45 +++++++++--------------------------- src/atom/selection.coffee | 2 +- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 79353eb70..d434d44d6 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -16,11 +16,11 @@ class LineWrapper setMaxLength: (@maxLength) -> oldRange = new Range oldRange.end.row = @screenLineCount() - 1 - oldRange.end.column = _.last(@index.last().screenLines).text.length + oldRange.end.column = @lineMap.lineForScreenRow(oldRange.end.row).text.length @buildWrappedLines() newRange = new Range newRange.end.row = @screenLineCount() - 1 - newRange.end.column = _.last(@index.last().screenLines).text.length + newRange.end.column = @lineMap.lineForScreenRow(newRange.end.row).text.length @trigger 'change', { oldRange, newRange } getSpans: (wrappedLines) -> @@ -30,37 +30,24 @@ class LineWrapper _.flatten(_.pluck(wrappedLines, 'screenLines')) buildWrappedLines: -> - @index = new SpanIndex @lineMap = new LineMap wrappedLines = @buildWrappedLinesForBufferRows(0, @buffer.lastRow()) - @index.insert 0, @getSpans(wrappedLines), wrappedLines @lineMap.insertAtBufferRow 0, @unpackWrappedLines(wrappedLines) handleChange: (e) -> - oldRange = new Range - - 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(@expandRangeToLineEnds(e.oldRange)) { 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) - newRange = oldRange.copy() - newRange.end.row = @lastScreenRowForBufferRow(e.newRange.end.row) - newRange.end.column = _.last(@index.at(e.newRange.end.row).screenLines).text.length + newScreenRange = @lineMap.screenRangeForBufferRange(@expandRangeToLineEnds(e.newRange)) - @trigger 'change', { oldRange, newRange } + @trigger 'change', { oldRange: oldScreenRange, newRange: newScreenRange } - firstScreenRowForBufferRow: (bufferRow) -> - @screenPositionForBufferPosition([bufferRow, 0]).row - - lastScreenRowForBufferRow: (bufferRow) -> - startRow = @screenPositionForBufferPosition([bufferRow, 0]).row - startRow + (@index.at(bufferRow).screenLines.length - 1) + expandRangeToLineEnds: (bufferRange) -> + { start, end } = bufferRange + new Range([start.row, 0], [end.row, @lineMap.lineForBufferRow(end.row).text.length]) buildWrappedLinesForBufferRows: (start, end) -> for row in [start..end] @@ -100,10 +87,8 @@ 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) @@ -115,15 +100,7 @@ class LineWrapper @screenLinesForRows(screenRow, screenRow)[0] screenLinesForRows: (startRow, endRow) -> - screenLines = [] - - { values, startOffset, endOffset } = @index.sliceBySpan(startRow, endRow) - - 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 + @lineMap.linesForScreenRows(startRow, endRow) screenLines: -> @screenLinesForRows(0, @screenLineCount() - 1) diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index b4eaa02be..0efa856be 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -71,7 +71,7 @@ class Selection extends View @cursor.setPosition(range.end) getScreenRange: -> - @editor.lineWrapper.screenRangeFromBufferRange(@getRange()) + @editor.lineWrapper.screenRangeForBufferRange(@getRange()) getText: -> @editor.buffer.getTextInRange @getRange() From 52e1400dc4c98203f4fab6ba54a355b25dd04b2a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 21 Feb 2012 23:54:17 -0700 Subject: [PATCH 03/32] :lipstick: for LineWrapper --- src/atom/line-wrapper.coffee | 49 +++++++++++++++--------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index d434d44d6..ba7483466 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -1,6 +1,5 @@ _ = require 'underscore' EventEmitter = require 'event-emitter' -SpanIndex = require 'span-index' LineMap = require 'line-map' Point = require 'point' Range = require 'range' @@ -10,38 +9,27 @@ module.exports = class LineWrapper constructor: (@maxLength, @highlighter) -> @buffer = @highlighter.buffer - @buildWrappedLines() + @buildLineMap() @highlighter.on 'change', (e) => @handleChange(e) setMaxLength: (@maxLength) -> - oldRange = new Range - oldRange.end.row = @screenLineCount() - 1 - oldRange.end.column = @lineMap.lineForScreenRow(oldRange.end.row).text.length - @buildWrappedLines() - newRange = new Range - newRange.end.row = @screenLineCount() - 1 - newRange.end.column = @lineMap.lineForScreenRow(newRange.end.row).text.length + oldRange = @rangeForAllScreenLines() + @buildLineMap() + newRange = @rangeForAllScreenLines() @trigger 'change', { oldRange, newRange } - getSpans: (wrappedLines) -> - wrappedLines.map (line) -> line.screenLines.length - - unpackWrappedLines: (wrappedLines) -> - _.flatten(_.pluck(wrappedLines, 'screenLines')) - - buildWrappedLines: -> + buildLineMap: -> @lineMap = new LineMap - wrappedLines = @buildWrappedLinesForBufferRows(0, @buffer.lastRow()) - @lineMap.insertAtBufferRow 0, @unpackWrappedLines(wrappedLines) + @lineMap.insertAtBufferRow 0, @buildScreenLinesForBufferRows(0, @buffer.lastRow()) handleChange: (e) -> - oldScreenRange = @lineMap.screenRangeForBufferRange(@expandRangeToLineEnds(e.oldRange)) + oldBufferRange = e.oldRange + newBufferRange = e.newRange - { start, end } = e.oldRange - wrappedLines = @buildWrappedLinesForBufferRows(e.newRange.start.row, e.newRange.end.row) - @lineMap.replaceBufferRows start.row, end.row, @unpackWrappedLines(wrappedLines) - - newScreenRange = @lineMap.screenRangeForBufferRange(@expandRangeToLineEnds(e.newRange)) + oldScreenRange = @lineMap.screenRangeForBufferRange(@expandRangeToLineEnds(oldBufferRange)) + newScreenLines = @buildScreenLinesForBufferRows(newBufferRange.start.row, newBufferRange.end.row) + @lineMap.replaceBufferRows oldBufferRange.start.row, oldBufferRange.end.row, newScreenLines + newScreenRange = @lineMap.screenRangeForBufferRange(@expandRangeToLineEnds(newBufferRange)) @trigger 'change', { oldRange: oldScreenRange, newRange: newScreenRange } @@ -49,12 +37,15 @@ class LineWrapper { start, end } = bufferRange new Range([start.row, 0], [end.row, @lineMap.lineForBufferRow(end.row).text.length]) - buildWrappedLinesForBufferRows: (start, end) -> - for row in [start..end] - @buildWrappedLineForBufferRow(row) + rangeForAllScreenLines: -> + endRow = @screenLineCount() - 1 + endColumn = @lineMap.lineForScreenRow(endRow).text.length + new Range([0, 0], [endRow, endColumn]) - buildWrappedLineForBufferRow: (bufferRow) -> - { screenLines: @wrapScreenLine(@highlighter.lineFragmentForRow(bufferRow)) } + buildScreenLinesForBufferRows: (start, end) -> + _(@highlighter + .lineFragmentsForRows(start, end) + .map((screenLine) => @wrapScreenLine(screenLine))).flatten() wrapScreenLine: (screenLine, startColumn=0) -> screenLines = [] From ab9a93b39036d340ba8d1c72286df8fe81f27fc0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 10:27:49 -0700 Subject: [PATCH 04/32] Eliminate old ScreenLine --- spec/atom/line-map-spec.coffee | 88 +++++++++++----------- spec/atom/screen-line-fragment-spec.coffee | 26 +++---- spec/atom/screen-line-spec.coffee | 50 ------------ src/atom/highlighter.coffee | 16 ++-- src/atom/line-folder.coffee | 17 +---- src/atom/line-map.coffee | 76 +++++++++---------- src/atom/line-wrapper.coffee | 5 +- src/atom/screen-line-fragment.coffee | 3 +- src/atom/screen-line.coffee | 39 ---------- 9 files changed, 107 insertions(+), 213 deletions(-) delete mode 100644 spec/atom/screen-line-spec.coffee delete mode 100644 src/atom/screen-line.coffee diff --git a/spec/atom/line-map-spec.coffee b/spec/atom/line-map-spec.coffee index 4325a7cba..63fd0c37e 100644 --- a/spec/atom/line-map-spec.coffee +++ b/spec/atom/line-map-spec.coffee @@ -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.screenLinesForRows(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line1] + expect(map.screenLinesForScreenRow(2)).toEqual [line2] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line1] + expect(map.screenLinesForScreenRow(2)).toEqual [line2] + expect(map.screenLinesForScreenRow(3)).toEqual [line3] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3] + expect(map.screenLinesForScreenRow(2)).toEqual [line4] + expect(map.screenLinesForScreenRow(3)).toEqual [line1] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3] + expect(map.screenLinesForScreenRow(2)).toEqual [line4] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] + expect(map.screenLinesForScreenRow(2)).toEqual [line4] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3] + expect(map.screenLinesForScreenRow(2)).toEqual [line4] + expect(map.screenLinesForScreenRow(3)).toEqual [line1] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3] + expect(map.screenLinesForScreenRow(2)).toEqual [line4] + expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] + expect(map.screenLinesForScreenRow(2)).toEqual [line4] + expect(map.screenLinesForScreenRow(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,9 +137,9 @@ 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.screenLinesForScreenRow(0)).toEqual [line0] + expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] + expect(map.screenLinesForScreenRow(2)).toEqual [line4] describe ".linesForScreenRows(startRow, endRow)", -> it "returns lines for the given row range, concatenating fragments that belong on a single screen line", -> diff --git a/spec/atom/screen-line-fragment-spec.coffee b/spec/atom/screen-line-fragment-spec.coffee index 183203147..a0590e8d6 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.screenLineForRow(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.screenLineForRow(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/src/atom/highlighter.coffee b/src/atom/highlighter.coffee index ae3a6bc27..f139680c7 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 = @@ -59,20 +58,15 @@ class Highlighter buildScreenLineForRow: (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) -> @screenLines[row] - lineFragments: -> - @lineFragmentsForRows(0, @buffer.lastRow()) + screenLinesForRows: (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..411133b0e 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -11,7 +11,7 @@ class LineFolder buildLineMap: -> @lineMap = new LineMap - @lineMap.insertAtBufferRow(0, @highlighter.lineFragments()) + @lineMap.insertAtBufferRow(0, @highlighter.screenLines) fold: (bufferRange) -> @activeFolds[bufferRange.start.row] ?= [] @@ -23,26 +23,15 @@ class LineFolder @renderScreenLineForBufferRow(@bufferRowForScreenRow(screenRow)) renderScreenLineForBufferRow: (bufferRow, startColumn=0) -> - screenLine = @highlighter.lineFragmentForRow(bufferRow).splitAt(startColumn)[1] + screenLine = @highlighter.screenLineForRow(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) + suffix = @renderScreenLineForBufferRow(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()) diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 475df147c..b0bfc7f5b 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -6,61 +6,61 @@ Range = require 'range' module.exports = class LineMap constructor: -> - @lineFragments = [] + @screenLines = [] - insertAtBufferRow: (bufferRow, lineFragments) -> - lineFragments = [lineFragments] unless _.isArray(lineFragments) + insertAtBufferRow: (bufferRow, screenLines) -> + screenLines = [screenLines] unless _.isArray(screenLines) delta = new Delta insertIndex = 0 - for lineFragment in @lineFragments - nextDelta = delta.add(lineFragment.bufferDelta) + for screenLine in @screenLines + nextDelta = delta.add(screenLine.bufferDelta) break if nextDelta.rows > 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 - for lineFragment, i in @lineFragments + for screenLine, i in @screenLines startIndex = i if delta.rows == startRow and not startIndex - nextDelta = delta.add(lineFragment[deltaType]) + nextDelta = delta.add(screenLine[deltaType]) break if nextDelta.rows > 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) + replaceScreenRows: (start, end, screenLines) -> + @spliceAtScreenRow(start, end - start + 1, screenLines) - lineFragmentsForScreenRow: (screenRow) -> - @lineFragmentsForScreenRows(screenRow, screenRow) + screenLinesForScreenRow: (screenRow) -> + @screenLinesForScreenRows(screenRow, screenRow) - lineFragmentsForScreenRows: (startRow, endRow) -> - lineFragments = [] + screenLinesForScreenRows: (startRow, endRow) -> + screenLines = [] delta = new Delta - for lineFragment in @lineFragments + for screenLine in @screenLines break if delta.rows > endRow - lineFragments.push(lineFragment) if delta.rows >= startRow - delta = delta.add(lineFragment.screenDelta) + screenLines.push(screenLine) if delta.rows >= startRow + delta = delta.add(screenLine.screenDelta) - lineFragments + screenLines lineForScreenRow: (row) -> @linesForScreenRows(row, row)[0] @@ -70,7 +70,7 @@ class LineMap lines = [] delta = new Delta - for fragment in @lineFragments + for fragment in @screenLines break if delta.rows > endRow if delta.rows >= startRow if pendingFragment @@ -86,7 +86,7 @@ class LineMap lineForBufferRow: (row) -> line = null delta = new Delta - for fragment in @lineFragments + for fragment in @screenLines break if delta.rows > row if delta.rows == row if line @@ -98,14 +98,14 @@ class LineMap bufferLineCount: -> delta = new Delta - for lineFragment in @lineFragments - delta = delta.add(lineFragment.bufferDelta) + for screenLine in @screenLines + delta = delta.add(screenLine.bufferDelta) delta.rows screenLineCount: -> delta = new Delta - for lineFragment in @lineFragments - delta = delta.add(lineFragment.screenDelta) + for screenLine in @screenLines + delta = delta.add(screenLine.screenDelta) delta.rows screenPositionForBufferPosition: (bufferPosition, eagerWrap=true) -> @@ -113,13 +113,13 @@ class LineMap bufferDelta = new Delta screenDelta = new Delta - for lineFragment in @lineFragments - nextDelta = bufferDelta.add(lineFragment.bufferDelta) + for screenLine in @screenLines + nextDelta = bufferDelta.add(screenLine.bufferDelta) break if nextDelta.toPoint().greaterThan(bufferPosition) break if nextDelta.toPoint().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) @@ -129,11 +129,11 @@ class LineMap bufferDelta = new Delta screenDelta = new Delta - for lineFragment in @lineFragments - nextDelta = screenDelta.add(lineFragment.screenDelta) + for screenLine in @screenLines + nextDelta = screenDelta.add(screenLine.screenDelta) break if nextDelta.toPoint().greaterThan(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) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index ba7483466..5ca5ca08d 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -8,7 +8,6 @@ Delta = require 'delta' module.exports = class LineWrapper constructor: (@maxLength, @highlighter) -> - @buffer = @highlighter.buffer @buildLineMap() @highlighter.on 'change', (e) => @handleChange(e) @@ -20,7 +19,7 @@ class LineWrapper buildLineMap: -> @lineMap = new LineMap - @lineMap.insertAtBufferRow 0, @buildScreenLinesForBufferRows(0, @buffer.lastRow()) + @lineMap.insertAtBufferRow 0, @buildScreenLinesForBufferRows(0, @highlighter.lastRow()) handleChange: (e) -> oldBufferRange = e.oldRange @@ -44,7 +43,7 @@ class LineWrapper buildScreenLinesForBufferRows: (start, end) -> _(@highlighter - .lineFragmentsForRows(start, end) + .screenLinesForRows(start, end) .map((screenLine) => @wrapScreenLine(screenLine))).flatten() wrapScreenLine: (screenLine, startColumn=0) -> diff --git a/src/atom/screen-line-fragment.coffee b/src/atom/screen-line-fragment.coffee index 4593b65e9..2bc12be31 100644 --- a/src/atom/screen-line-fragment.coffee +++ b/src/atom/screen-line-fragment.coffee @@ -3,9 +3,10 @@ Delta = require 'delta' module.exports = class ScreenLineFragment - constructor: (@tokens, @text, screenDelta, bufferDelta) -> + constructor: (@tokens, @text, screenDelta, bufferDelta, extraFields) -> @screenDelta = Delta.fromObject(screenDelta) @bufferDelta = Delta.fromObject(bufferDelta) + _.extend(this, extraFields) splitAt: (column) -> return [undefined, this] if column == 0 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}] From 526f15bcd266d12352bcbde7c649f664759c6db9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 10:29:07 -0700 Subject: [PATCH 05/32] Remove SpanIndex. Fully replaced by LineMap --- spec/atom/span-index-spec.coffee | 100 ------------------------------- src/atom/span-index.coffee | 81 ------------------------- 2 files changed, 181 deletions(-) delete mode 100644 spec/atom/span-index-spec.coffee delete mode 100644 src/atom/span-index.coffee 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/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) -> - From 8ef270f7972b9153dbfff991ebe387b0642d0f45 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 10:48:35 -0700 Subject: [PATCH 06/32] Add more position translation specs to LineFolder spec --- spec/atom/line-folder-spec.coffee | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 008139694..ded1dc5b6 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -55,7 +55,7 @@ describe "LineFolder", -> expect(line4.text).toBe ' while(items.length > 0) {...}...concat(sort(right));' expect(line5.text).toBe ' };' - describe ".screenPositionForBufferPosition(bufferPosition)", -> + fdescribe "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])) @@ -65,6 +65,10 @@ describe "LineFolder", -> 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] @@ -73,7 +77,24 @@ describe "LineFolder", -> expect(folder.screenPositionForBufferPosition([7, 4])).toEqual [4, 32] expect(folder.screenPositionForBufferPosition([7, 7])).toEqual [4, 35] + expect(folder.bufferPositionForScreenPosition([4, 32])).toEqual [7, 4] + expect(folder.bufferPositionForScreenPosition([4, 35])).toEqual [7, 7] + # # following fold, subsequent line expect(folder.screenPositionForBufferPosition([8, 0])).toEqual [5, 0] expect(folder.screenPositionForBufferPosition([13, 13])).toEqual [10, 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.fold(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] From 2132b5f8f4108521a37b60807e4b705245dddf62 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 11:15:15 -0700 Subject: [PATCH 07/32] Buffer positions inside folds translate to screen positions preceding fold placeholder --- spec/atom/line-folder-spec.coffee | 33 ++++++++++++++-------------- src/atom/line-folder.coffee | 2 +- src/atom/line-map.coffee | 6 ++--- src/atom/screen-line-fragment.coffee | 8 +++++++ 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index ded1dc5b6..023684a89 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -55,37 +55,38 @@ describe "LineFolder", -> expect(line4.text).toBe ' while(items.length > 0) {...}...concat(sort(right));' expect(line5.text).toBe ' };' - fdescribe "position translation", -> + 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])) # 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.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] + # 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] + console.log "!!!!!!!!!" + 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.screenPositionForBufferPosition([7, 4])).toEqual [4, 32] + # expect(folder.screenPositionForBufferPosition([7, 7])).toEqual [4, 35] - expect(folder.bufferPositionForScreenPosition([4, 32])).toEqual [7, 4] - expect(folder.bufferPositionForScreenPosition([4, 35])).toEqual [7, 7] + # expect(folder.bufferPositionForScreenPosition([4, 32])).toEqual [7, 4] + # expect(folder.bufferPositionForScreenPosition([4, 35])).toEqual [7, 7] # # following fold, subsequent line - expect(folder.screenPositionForBufferPosition([8, 0])).toEqual [5, 0] - expect(folder.screenPositionForBufferPosition([13, 13])).toEqual [10, 13] + # expect(folder.screenPositionForBufferPosition([8, 0])).toEqual [5, 0] + # expect(folder.screenPositionForBufferPosition([13, 13])).toEqual [10, 13] - expect(folder.bufferPositionForScreenPosition([5, 0])).toEqual [8, 0] - expect(folder.bufferPositionForScreenPosition([10, 13])).toEqual [13, 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", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 411133b0e..53cc9c2b2 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -33,7 +33,7 @@ class LineFolder screenLine buildFoldPlaceholder: (fold) -> - new ScreenLineFragment([{value: '...', type: 'fold-placeholder'}], '...', [0, 3], fold.range.toDelta()) + new ScreenLineFragment([{value: '...', type: 'fold-placeholder'}], '...', [0, 3], fold.range.toDelta(), isAtomic: true) foldsForBufferRow: (bufferRow) -> @activeFolds[bufferRow] or [] diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index b0bfc7f5b..81fe224dc 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -117,12 +117,12 @@ class LineMap nextDelta = bufferDelta.add(screenLine.bufferDelta) break if nextDelta.toPoint().greaterThan(bufferPosition) break if nextDelta.toPoint().isEqual(bufferPosition) and not eagerWrap - bufferDelta = nextDelta screenDelta = screenDelta.add(screenLine.screenDelta) - columns = screenDelta.columns + (bufferPosition.column - bufferDelta.columns) - new Point(screenDelta.rows, columns) + remainingBufferColumns = bufferPosition.column - bufferDelta.columns + additionalScreenColumns = Math.min(remainingBufferColumns, screenLine.lengthForClipping()) + new Point(screenDelta.rows, screenDelta.columns + additionalScreenColumns) bufferPositionForScreenPosition: (screenPosition) -> screenPosition = Point.fromObject(screenPosition) diff --git a/src/atom/screen-line-fragment.coffee b/src/atom/screen-line-fragment.coffee index 2bc12be31..994ff3c7b 100644 --- a/src/atom/screen-line-fragment.coffee +++ b/src/atom/screen-line-fragment.coffee @@ -3,6 +3,8 @@ Delta = require 'delta' module.exports = class ScreenLineFragment + isAtomic: false + constructor: (@tokens, @text, screenDelta, bufferDelta, extraFields) -> @screenDelta = Delta.fromObject(screenDelta) @bufferDelta = Delta.fromObject(bufferDelta) @@ -44,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) From e42a8878e977f382136c5c1497f9d6f62c7c12e6 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 12:17:08 -0700 Subject: [PATCH 08/32] Fix position translation --- spec/atom/line-folder-spec.coffee | 32 ++++++++++++++----------------- src/atom/line-map.coffee | 3 ++- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 023684a89..f4a5f3858 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -57,36 +57,32 @@ describe "LineFolder", -> 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", -> + fit "translates positions to account for folded lines and characters and the placeholder", -> folder.fold(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.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] + 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 - console.log "!!!!!!!!!" expect(folder.screenPositionForBufferPosition([4, 35])).toEqual [4, 29] - # expect(folder.screenPositionForBufferPosition([5, 5])).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] - # expect(folder.bufferPositionForScreenPosition([4, 35])).toEqual [7, 7] + expect(folder.screenPositionForBufferPosition([7, 4])).toEqual [4, 32] + 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([8, 0])).toEqual [5, 0] + expect(folder.screenPositionForBufferPosition([11, 13])).toEqual [8, 13] - # expect(folder.bufferPositionForScreenPosition([5, 0])).toEqual [8, 0] - # expect(folder.bufferPositionForScreenPosition([10, 13])).toEqual [13, 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", -> diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 81fe224dc..31a9ab26b 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -121,7 +121,8 @@ class LineMap screenDelta = screenDelta.add(screenLine.screenDelta) remainingBufferColumns = bufferPosition.column - bufferDelta.columns - additionalScreenColumns = Math.min(remainingBufferColumns, screenLine.lengthForClipping()) + additionalScreenColumns = Math.max(0, Math.min(remainingBufferColumns, screenLine.lengthForClipping())) + new Point(screenDelta.rows, screenDelta.columns + additionalScreenColumns) bufferPositionForScreenPosition: (screenPosition) -> From 3ba17d28e8a03d71f8b09eeb38299d17c286d042 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 12:20:50 -0700 Subject: [PATCH 09/32] Un F --- spec/atom/line-folder-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index f4a5f3858..e64f50c9a 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -57,7 +57,7 @@ describe "LineFolder", -> describe "position translation", -> describe "when there is single fold spanning multiple lines", -> - fit "translates positions to account for folded lines and characters and the placeholder", -> + it "translates positions to account for folded lines and characters and the placeholder", -> folder.fold(new Range([4, 29], [7, 4])) # preceding fold: identity From cf00753c9cd18588c8f7953285cd802286d2100a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 14:02:51 -0700 Subject: [PATCH 10/32] WIP: can destroy folds. emits change events. --- spec/atom/line-folder-spec.coffee | 64 ++++++++++++++++++++++++++++--- src/atom/line-folder.coffee | 54 ++++++++++++++++++++++++-- src/atom/line-map.coffee | 4 ++ src/atom/line-wrapper.coffee | 6 +-- 4 files changed, 116 insertions(+), 12 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index e64f50c9a..cde89400f 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -4,22 +4,44 @@ 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 "screen line rendering", -> + describe "when folds are created and removed", -> 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.fold(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])) @@ -28,6 +50,7 @@ describe "LineFolder", -> expect(line2.text).toBe ' if (...) return items;' expect(line3.text).toBe ' var pivot = items.shift(), current, left = [], right = [];' + describe "screen line rendering", -> 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])) @@ -55,6 +78,37 @@ describe "LineFolder", -> expect(line4.text).toBe ' while(items.length > 0) {...}...concat(sort(right));' expect(line5.text).toBe ' };' + describe "change events", -> + changeHandler = null + + beforeEach -> + + describe "when folds are created or destroyed", -> + it "emits a change event for the lines that are folded / unfolded", -> + fold1 = folder.fold(new Range([4, 29], [7, 4])) + changeHandler.reset() + + fold2 = folder.fold(new Range([7, 5], [8, 34])) + expect(changeHandler).toHaveBeenCalled() + [event] = changeHandler.argsForCall[0] + expect(event.oldRange).toEqual [[4, 0], [5, 56]] + expect(event.newRange).toEqual [[4, 0], [4, 58]] + changeHandler.reset() + + # fold1.destroy() + # expect(changeHandler).toHaveBeenCalled() + # [event] = changeHandler.argsForCall[0] + # expect(event.oldRange).toEqual [[4, 0], [4, 33]] + # expect(event.newRange).toEqual [[4, 0], [7, 5]] + # changeHandler.reset() + + describe "when the buffer changes", -> + describe "when the change precedes any folds", -> + + describe "when the change follows a fold", -> + + describe "when the change is inside of a fold", -> + 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", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 53cc9c2b2..9fad493c8 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -1,7 +1,9 @@ +_ = 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 @@ -14,10 +16,44 @@ class LineFolder @lineMap.insertAtBufferRow(0, @highlighter.screenLines) fold: (bufferRange) -> + fold = new Fold(this, 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)) + @activeFolds[bufferRange.start.row].push(fold) + oldScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange)) + + lineWithFold = @renderScreenLine(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 + fold + + destroyFold: (fold) -> + bufferRange = fold.range + folds = @activeFolds[bufferRange.start.row] + foldIndex = folds.indexOf(fold) + folds[foldIndex..foldIndex] = [] + + 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, @renderScreenLinesForBufferRows(bufferRange.start.row, bufferRange.end.row)) + + newScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange)) + + @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange + + renderScreenLinesForBufferRows: (start, end) -> + for row in [start..end] + @renderScreenLineForBufferRow(row) renderScreenLine: (screenRow) -> @renderScreenLineForBufferRow(@bufferRowForScreenRow(screenRow)) @@ -56,5 +92,15 @@ class LineFolder 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) -> + + destroy: -> + @lineFolder.destroyFold(this) + diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 31a9ab26b..8e66fd476 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -45,6 +45,10 @@ class LineMap replaceBufferRows: (start, end, screenLines) -> @spliceAtBufferRow(start, end - start + 1, screenLines) + + replaceScreenRow: (row, screenLines) -> + @replaceScreenRows(row, row, screenLines) + replaceScreenRows: (start, end, screenLines) -> @spliceAtScreenRow(start, end - start + 1, screenLines) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 5ca5ca08d..7e16f4958 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -25,14 +25,14 @@ class LineWrapper oldBufferRange = e.oldRange newBufferRange = e.newRange - oldScreenRange = @lineMap.screenRangeForBufferRange(@expandRangeToLineEnds(oldBufferRange)) + 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(@expandRangeToLineEnds(newBufferRange)) + newScreenRange = @lineMap.screenRangeForBufferRange(@expandBufferRangeToLineEnds(newBufferRange)) @trigger 'change', { oldRange: oldScreenRange, newRange: newScreenRange } - expandRangeToLineEnds: (bufferRange) -> + expandBufferRangeToLineEnds: (bufferRange) -> { start, end } = bufferRange new Range([start.row, 0], [end.row, @lineMap.lineForBufferRow(end.row).text.length]) From 7bf12430cd4e5ad445d53165f7c095f2c5b9ade0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 16:52:18 -0700 Subject: [PATCH 11/32] Folds can be created and destroyed; Folder emits change events Reorganized the line folder spec around creating and destroying folds --- spec/atom/line-folder-spec.coffee | 114 +++++++++++++++++++++--------- src/atom/line-folder.coffee | 7 +- 2 files changed, 85 insertions(+), 36 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index cde89400f..8e00bf3b5 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -44,13 +44,30 @@ describe "LineFolder", -> 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.fold(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 = [];' - describe "screen line rendering", -> + 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])) @@ -63,51 +80,80 @@ 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.fold(new Range([7, 5], [8, 36])) + fold2 = folder.fold(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.fold(new Range([4, 29], [7, 4])) + fold2 = folder.fold(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 "change events", -> - changeHandler = null + 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() - beforeEach -> + 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));' - describe "when folds are created or destroyed", -> - it "emits a change event for the lines that are folded / unfolded", -> - fold1 = folder.fold(new Range([4, 29], [7, 4])) - changeHandler.reset() + 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 = folder.fold(new Range([7, 5], [8, 34])) - expect(changeHandler).toHaveBeenCalled() - [event] = changeHandler.argsForCall[0] - expect(event.oldRange).toEqual [[4, 0], [5, 56]] - expect(event.newRange).toEqual [[4, 0], [4, 58]] - 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();' - # fold1.destroy() - # expect(changeHandler).toHaveBeenCalled() - # [event] = changeHandler.argsForCall[0] - # expect(event.oldRange).toEqual [[4, 0], [4, 33]] - # expect(event.newRange).toEqual [[4, 0], [7, 5]] - # changeHandler.reset() - - describe "when the buffer changes", -> - describe "when the change precedes any folds", -> - - describe "when the change follows a fold", -> - - describe "when the change is inside of a fold", -> + expect(changeHandler).toHaveBeenCalled() + [event] = changeHandler.argsForCall[0] + expect(event.oldRange).toEqual [[7, 0], [7, 28]] + expect(event.newRange).toEqual [[7, 0], [8, 56]] describe "position translation", -> describe "when there is single fold spanning multiple lines", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 9fad493c8..2a885ca90 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -52,8 +52,11 @@ class LineFolder @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange renderScreenLinesForBufferRows: (start, end) -> - for row in [start..end] - @renderScreenLineForBufferRow(row) + lines = [@renderScreenLine(@screenRowForBufferRow(start))] + if end > start + for row in [start + 1..end] + lines.push @renderScreenLineForBufferRow(row) + _.flatten(lines) renderScreenLine: (screenRow) -> @renderScreenLineForBufferRow(@bufferRowForScreenRow(screenRow)) From 852d066378ec057808b2eb66f966f86875a926ef Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 16:53:08 -0700 Subject: [PATCH 12/32] :lipstick: --- spec/atom/line-folder-spec.coffee | 20 ++++++++++---------- src/atom/line-folder.coffee | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 8e00bf3b5..1e064d644 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -20,7 +20,7 @@ describe "LineFolder", -> previousLine4Text = line4.text previousLine5Text = line5.text - fold = folder.fold(new Range([4, 29], [7, 4])) + fold = folder.createFold(new Range([4, 29], [7, 4])) [line4, line5] = folder.linesForScreenRows(4, 5) expect(line4.text).toBe ' while(items.length > 0) {...}' @@ -44,7 +44,7 @@ describe "LineFolder", -> 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", -> - fold = folder.fold(new Range([2, 8], [2, 25])) + fold = folder.createFold(new Range([2, 8], [2, 25])) [line2, line3] = folder.linesForScreenRows(2, 3) expect(line2.text).toBe ' if (...) return items;' @@ -70,8 +70,8 @@ describe "LineFolder", -> 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));' @@ -80,8 +80,8 @@ 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", -> - fold1 = folder.fold(new Range([7, 5], [8, 36])) - fold2 = folder.fold(new Range([4, 29], [7, 4])) + 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));' @@ -118,8 +118,8 @@ describe "LineFolder", -> 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", -> - fold1 = folder.fold(new Range([4, 29], [7, 4])) - fold2 = 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 ' };' @@ -158,7 +158,7 @@ describe "LineFolder", -> 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] @@ -186,7 +186,7 @@ describe "LineFolder", -> describe "when there is a single fold spanning a single line", -> it "translates positions to account for folded characters and the placeholder", -> - folder.fold(new Range([4, 10], [4, 15])) + folder.createFold(new Range([4, 10], [4, 15])) expect(folder.screenPositionForBufferPosition([4, 5])).toEqual [4, 5] expect(folder.screenPositionForBufferPosition([4, 15])).toEqual [4, 13] diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 2a885ca90..34b7544a1 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -15,7 +15,7 @@ class LineFolder @lineMap = new LineMap @lineMap.insertAtBufferRow(0, @highlighter.screenLines) - fold: (bufferRange) -> + createFold: (bufferRange) -> fold = new Fold(this, bufferRange) @activeFolds[bufferRange.start.row] ?= [] @activeFolds[bufferRange.start.row].push(fold) From 237c03be7b95cf47a074a4d86c2df5017734cc3a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 17:24:27 -0700 Subject: [PATCH 13/32] :lipstick: More consistent method names among Highlighter, LineWrapper, LineFolder, and LineMap --- spec/atom/highlighter-spec.coffee | 64 +++++++++++----------- spec/atom/line-map-spec.coffee | 6 +- spec/atom/line-wrapper-spec.coffee | 50 ++++++++--------- spec/atom/screen-line-fragment-spec.coffee | 4 +- src/atom/editor.coffee | 4 +- src/atom/highlighter.coffee | 16 +++--- src/atom/line-folder.coffee | 2 +- src/atom/line-map.coffee | 2 +- src/atom/line-wrapper.coffee | 24 ++++---- 9 files changed, 86 insertions(+), 86 deletions(-) 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-map-spec.coffee b/spec/atom/line-map-spec.coffee index 63fd0c37e..54b341829 100644 --- a/spec/atom/line-map-spec.coffee +++ b/spec/atom/line-map-spec.coffee @@ -12,7 +12,7 @@ describe "LineMap", -> buffer = new Buffer(require.resolve 'fixtures/sample.js') highlighter = new Highlighter(buffer) map = new LineMap - [line0, line1, line2, line3, line4] = highlighter.screenLinesForRows(0, 4) + [line0, line1, line2, line3, line4] = highlighter.linesForScreenRows(0, 4) describe ".insertAtBufferRow(row, screenLine(s))", -> describe "when passed a single, line fragment", -> @@ -192,7 +192,7 @@ describe "LineMap", -> it "wraps buffer positions at the end of a screen line to the end end of the next screen line", -> expect(map.screenPositionForBufferPosition([4, 20], true)).toEqual [3, 0] - describe ".screenLineCount()", -> + describe ".lineCount()", -> it "returns the total of all inserted screen row deltas", -> [line1a, line1b] = line1.splitAt(10) [line3a, line3b] = line3.splitAt(10) @@ -201,6 +201,6 @@ describe "LineMap", -> map.insertAtBufferRow(0, [line0, line1a, line1b, line2]) - expect(map.screenLineCount()).toBe 4 + expect(map.lineCount()).toBe 4 diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index c456d4d8d..1bd92069f 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -16,22 +16,22 @@ describe "LineWrapper", -> describe ".tokensForScreenRow(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 +41,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 +54,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 +65,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 +78,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 +91,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] diff --git a/spec/atom/screen-line-fragment-spec.coffee b/spec/atom/screen-line-fragment-spec.coffee index a0590e8d6..c39f33ad8 100644 --- a/spec/atom/screen-line-fragment-spec.coffee +++ b/spec/atom/screen-line-fragment-spec.coffee @@ -8,7 +8,7 @@ describe "screenLineFragment", -> beforeEach -> buffer = new Buffer(require.resolve 'fixtures/sample.js') highlighter = new Highlighter(buffer) - screenLine = highlighter.screenLineForRow(3) + screenLine = highlighter.lineForScreenRow(3) describe ".splitAt(column)", -> it "breaks the line fragment into two fragments", -> @@ -65,7 +65,7 @@ describe "screenLineFragment", -> [left, right] = screenLine.splitAt(14) expect(left.concat(right)).toEqual screenLine - concatenated = screenLine.concat(highlighter.screenLineForRow(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/src/atom/editor.coffee b/src/atom/editor.coffee index 7a40b34bb..4fd1fd79d 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -129,7 +129,7 @@ class Editor extends View renderLines: -> @lines.empty() - for screenLine in @lineWrapper.screenLines() + for screenLine in @lineWrapper.lines() @lines.append @buildLineElement(screenLine) setBuffer: (@buffer) -> @@ -144,7 +144,7 @@ class Editor extends View @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] diff --git a/src/atom/highlighter.coffee b/src/atom/highlighter.coffee index f139680c7..494d3391f 100644 --- a/src/atom/highlighter.coffee +++ b/src/atom/highlighter.coffee @@ -10,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: -> @@ -24,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. @@ -35,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 @@ -48,22 +48,22 @@ 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 ScreenLineFragment(tokens, line, [1, 0], [1, 0], { state }) - screenLineForRow: (row) -> + lineForScreenRow: (row) -> @screenLines[row] - screenLinesForRows: (startRow, endRow) -> + linesForScreenRows: (startRow, endRow) -> @screenLines[startRow..endRow] lastRow: -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 34b7544a1..7a489c3ed 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -62,7 +62,7 @@ class LineFolder @renderScreenLineForBufferRow(@bufferRowForScreenRow(screenRow)) renderScreenLineForBufferRow: (bufferRow, startColumn=0) -> - screenLine = @highlighter.screenLineForRow(bufferRow).splitAt(startColumn)[1] + screenLine = @highlighter.lineForScreenRow(bufferRow).splitAt(startColumn)[1] for fold in @foldsForBufferRow(bufferRow) { start, end } = fold.range if start.column > startColumn diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 8e66fd476..8dc432c05 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -106,7 +106,7 @@ class LineMap delta = delta.add(screenLine.bufferDelta) delta.rows - screenLineCount: -> + lineCount: -> delta = new Delta for screenLine in @screenLines delta = delta.add(screenLine.screenDelta) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 7e16f4958..5c8909caf 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -12,9 +12,9 @@ class LineWrapper @highlighter.on 'change', (e) => @handleChange(e) setMaxLength: (@maxLength) -> - oldRange = @rangeForAllScreenLines() + oldRange = @rangeForAllLines() @buildLineMap() - newRange = @rangeForAllScreenLines() + newRange = @rangeForAllLines() @trigger 'change', { oldRange, newRange } buildLineMap: -> @@ -36,14 +36,14 @@ class LineWrapper { start, end } = bufferRange new Range([start.row, 0], [end.row, @lineMap.lineForBufferRow(end.row).text.length]) - rangeForAllScreenLines: -> - endRow = @screenLineCount() - 1 + rangeForAllLines: -> + endRow = @lineCount() - 1 endColumn = @lineMap.lineForScreenRow(endRow).text.length new Range([0, 0], [endRow, endColumn]) buildScreenLinesForBufferRows: (start, end) -> _(@highlighter - .screenLinesForRows(start, end) + .linesForScreenRows(start, end) .map((screenLine) => @wrapScreenLine(screenLine))).flatten() wrapScreenLine: (screenLine, startColumn=0) -> @@ -86,16 +86,16 @@ class LineWrapper bufferPositionForScreenPosition: (screenPosition) -> @lineMap.bufferPositionForScreenPosition(screenPosition) - screenLineForRow: (screenRow) -> - @screenLinesForRows(screenRow, screenRow)[0] + lineForScreenRow: (screenRow) -> + @linesForScreenRows(screenRow, screenRow)[0] - screenLinesForRows: (startRow, endRow) -> + linesForScreenRows: (startRow, endRow) -> @lineMap.linesForScreenRows(startRow, endRow) - screenLines: -> - @screenLinesForRows(0, @screenLineCount() - 1) + lines: -> + @linesForScreenRows(0, @lineCount() - 1) - screenLineCount: -> - @lineMap.screenLineCount() + lineCount: -> + @lineMap.lineCount() _.extend(LineWrapper.prototype, EventEmitter) From 5bb539df27aafb45c77cff6d4f74a469ed9ddfac Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 17:36:38 -0700 Subject: [PATCH 14/32] :lipstick: --- spec/atom/line-map-spec.coffee | 82 +++++++++++++++++----------------- src/atom/editor.coffee | 2 +- src/atom/line-folder.coffee | 6 +++ src/atom/line-map.coffee | 16 +------ src/atom/line-wrapper.coffee | 2 +- 5 files changed, 51 insertions(+), 57 deletions(-) diff --git a/spec/atom/line-map-spec.coffee b/spec/atom/line-map-spec.coffee index 54b341829..13b8143ce 100644 --- a/spec/atom/line-map-spec.coffee +++ b/spec/atom/line-map-spec.coffee @@ -22,10 +22,10 @@ describe "LineMap", -> map.insertAtBufferRow(2, line3) map.insertAtBufferRow(2, line2) - expect(map.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line1] - expect(map.screenLinesForScreenRow(2)).toEqual [line2] - expect(map.screenLinesForScreenRow(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,11 +33,11 @@ describe "LineMap", -> map.insertAtBufferRow(0, [line0, line1]) map.insertAtBufferRow(4, [line4]) - expect(map.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line1] - expect(map.screenLinesForScreenRow(2)).toEqual [line2] - expect(map.screenLinesForScreenRow(3)).toEqual [line3] - expect(map.screenLinesForScreenRow(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, screenLines)", -> describe "when called with a row count of 0", -> @@ -45,11 +45,11 @@ describe "LineMap", -> map.insertAtBufferRow(0, [line0, line1, line2]) map.spliceAtBufferRow(1, 0, [line3, line4]) - expect(map.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3] - expect(map.screenLinesForScreenRow(2)).toEqual [line4] - expect(map.screenLinesForScreenRow(3)).toEqual [line1] - expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3] - expect(map.screenLinesForScreenRow(2)).toEqual [line4] - expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] - expect(map.screenLinesForScreenRow(2)).toEqual [line4] - expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] - expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3] - expect(map.screenLinesForScreenRow(2)).toEqual [line4] - expect(map.screenLinesForScreenRow(3)).toEqual [line1] - expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3] - expect(map.screenLinesForScreenRow(2)).toEqual [line4] - expect(map.screenLinesForScreenRow(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.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] - expect(map.screenLinesForScreenRow(2)).toEqual [line4] - expect(map.screenLinesForScreenRow(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,9 +137,9 @@ describe "LineMap", -> map.spliceAtScreenRow(1, 2, [line3a, line3b, line4]) expect(map.bufferLineCount()).toBe 3 - expect(map.screenLinesForScreenRow(0)).toEqual [line0] - expect(map.screenLinesForScreenRow(1)).toEqual [line3a, line3b] - expect(map.screenLinesForScreenRow(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", -> diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 4fd1fd79d..3c4572ef7 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -129,7 +129,7 @@ class Editor extends View renderLines: -> @lines.empty() - for screenLine in @lineWrapper.lines() + for screenLine in @lineWrapper.getLines() @lines.append @buildLineElement(screenLine) setBuffer: (@buffer) -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 7a489c3ed..e54fd5fcb 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -80,6 +80,12 @@ class LineFolder linesForScreenRows: (startRow, endRow) -> @lineMap.linesForScreenRows(startRow, endRow) + getLines: -> + @lineMap.getScreenLines() + + lineCount: -> + @lineMap.screenLineCount() + screenRowForBufferRow: (bufferRow) -> @screenPositionForBufferPosition([bufferRow, 0]).row diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 8dc432c05..51984def0 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -45,26 +45,14 @@ class LineMap replaceBufferRows: (start, end, screenLines) -> @spliceAtBufferRow(start, end - start + 1, screenLines) - replaceScreenRow: (row, screenLines) -> @replaceScreenRows(row, row, screenLines) replaceScreenRows: (start, end, screenLines) -> @spliceAtScreenRow(start, end - start + 1, screenLines) - screenLinesForScreenRow: (screenRow) -> - @screenLinesForScreenRows(screenRow, screenRow) - - screenLinesForScreenRows: (startRow, endRow) -> - screenLines = [] - delta = new Delta - - for screenLine in @screenLines - break if delta.rows > endRow - screenLines.push(screenLine) if delta.rows >= startRow - delta = delta.add(screenLine.screenDelta) - - screenLines + getScreenLines: -> + return @screenLines lineForScreenRow: (row) -> @linesForScreenRows(row, row)[0] diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 5c8909caf..9899d1d1a 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -92,7 +92,7 @@ class LineWrapper linesForScreenRows: (startRow, endRow) -> @lineMap.linesForScreenRows(startRow, endRow) - lines: -> + getLines: -> @linesForScreenRows(0, @lineCount() - 1) lineCount: -> From 1fc200c018d94b167b80cf9f66d4aa59298e32cd Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 22:34:21 -0700 Subject: [PATCH 15/32] :lipstick: --- src/atom/line-folder.coffee | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index e54fd5fcb..ce529c3fc 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -21,7 +21,7 @@ class LineFolder @activeFolds[bufferRange.start.row].push(fold) oldScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange)) - lineWithFold = @renderScreenLine(oldScreenRange.start.row) + lineWithFold = @buildLine(oldScreenRange.start.row) @lineMap.replaceScreenRows(oldScreenRange.start.row, oldScreenRange.end.row, lineWithFold) newScreenRange = oldScreenRange.copy() @@ -45,29 +45,29 @@ class LineFolder oldScreenRange.end.row = startScreenRow oldScreenRange.end.column = @lineMap.lineForScreenRow(startScreenRow).text.length - @lineMap.replaceScreenRow(startScreenRow, @renderScreenLinesForBufferRows(bufferRange.start.row, bufferRange.end.row)) + @lineMap.replaceScreenRow(startScreenRow, @buildLinesForBufferRows(bufferRange.start.row, bufferRange.end.row)) newScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange)) @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange - renderScreenLinesForBufferRows: (start, end) -> - lines = [@renderScreenLine(@screenRowForBufferRow(start))] + buildLinesForBufferRows: (start, end) -> + lines = [@buildLine(@screenRowForBufferRow(start))] if end > start for row in [start + 1..end] - lines.push @renderScreenLineForBufferRow(row) + lines.push @buildLineForBufferRow(row) _.flatten(lines) - renderScreenLine: (screenRow) -> - @renderScreenLineForBufferRow(@bufferRowForScreenRow(screenRow)) + buildLine: (screenRow) -> + @buildLineForBufferRow(@bufferRowForScreenRow(screenRow)) - renderScreenLineForBufferRow: (bufferRow, startColumn=0) -> + buildLineForBufferRow: (bufferRow, startColumn=0) -> screenLine = @highlighter.lineForScreenRow(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 = @renderScreenLineForBufferRow(end.row, end.column) + suffix = @buildLineForBufferRow(end.row, end.column) return _.flatten([prefix, @buildFoldPlaceholder(fold), suffix]) screenLine From c658423beaaabb0e918e8053045921ee969c9a1f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 22:40:03 -0700 Subject: [PATCH 16/32] Rename LineMap.lineCount to .screenLineCount --- src/atom/line-map.coffee | 2 +- src/atom/line-wrapper.coffee | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 51984def0..2d0c340f8 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -94,7 +94,7 @@ class LineMap delta = delta.add(screenLine.bufferDelta) delta.rows - lineCount: -> + screenLineCount: -> delta = new Delta for screenLine in @screenLines delta = delta.add(screenLine.screenDelta) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 9899d1d1a..2ba7743f3 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -97,5 +97,6 @@ class LineWrapper lineCount: -> @lineMap.lineCount() + @lineMap.screenLineCount() _.extend(LineWrapper.prototype, EventEmitter) From f7bf36eb1b0723be431b9bea482dec5d0fefd32d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 22:40:14 -0700 Subject: [PATCH 17/32] Add LineFolder.lastRow --- src/atom/line-folder.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index ce529c3fc..876aea6b5 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -86,6 +86,9 @@ class LineFolder lineCount: -> @lineMap.screenLineCount() + lastRow: -> + @lineCount() - 1 + screenRowForBufferRow: (bufferRow) -> @screenPositionForBufferPosition([bufferRow, 0]).row From e8ba72c3ec6aeab5bc52cebea2ed5644ec4752b2 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 22:42:17 -0700 Subject: [PATCH 18/32] Fixes for screenLineCount rename --- spec/atom/line-map-spec.coffee | 4 ++-- src/atom/line-wrapper.coffee | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/atom/line-map-spec.coffee b/spec/atom/line-map-spec.coffee index 13b8143ce..5868510ae 100644 --- a/spec/atom/line-map-spec.coffee +++ b/spec/atom/line-map-spec.coffee @@ -192,7 +192,7 @@ describe "LineMap", -> it "wraps buffer positions at the end of a screen line to the end end of the next screen line", -> expect(map.screenPositionForBufferPosition([4, 20], true)).toEqual [3, 0] - describe ".lineCount()", -> + describe ".screenLineCount()", -> it "returns the total of all inserted screen row deltas", -> [line1a, line1b] = line1.splitAt(10) [line3a, line3b] = line3.splitAt(10) @@ -201,6 +201,6 @@ describe "LineMap", -> map.insertAtBufferRow(0, [line0, line1a, line1b, line2]) - expect(map.lineCount()).toBe 4 + expect(map.screenLineCount()).toBe 4 diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 2ba7743f3..0845fa319 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -96,7 +96,6 @@ class LineWrapper @linesForScreenRows(0, @lineCount() - 1) lineCount: -> - @lineMap.lineCount() @lineMap.screenLineCount() _.extend(LineWrapper.prototype, EventEmitter) From 43c66a02a4f848cb7e5fb15d303c19377fbaf15c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 15:38:03 -0700 Subject: [PATCH 19/32] Merge Delta into Point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Point and delta were really pretty much the same thing. Point might need to be renamed… I'm thinking now it should be called offset, and have rows and columns instead of a row and column. Then you could interpret it as an offset from the beginning of the buffer, or an offset from any other location. --- spec/atom/line-map-spec.coffee | 14 +++---- src/atom/delta.coffee | 39 ------------------- src/atom/line-map.coffee | 57 ++++++++++++++-------------- src/atom/line-wrapper.coffee | 3 +- src/atom/point.coffee | 33 ++++++++++++---- src/atom/range.coffee | 3 +- src/atom/screen-line-fragment.coffee | 6 +-- 7 files changed, 65 insertions(+), 90 deletions(-) delete mode 100644 src/atom/delta.coffee diff --git a/spec/atom/line-map-spec.coffee b/spec/atom/line-map-spec.coffee index 5868510ae..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] = [] @@ -154,7 +154,7 @@ describe "LineMap", -> it "returns the concatenated screen line fragments that comprise the given buffer row", -> line1Text = line1.text [line1a, line1b] = line1.splitAt(11) - line1a.screenDelta = new Delta(1, 0) + line1a.screenDelta = new Point(1, 0) map.insertAtBufferRow(0, [line0, line1a, line1b, line2]) @@ -166,12 +166,12 @@ describe "LineMap", -> # 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]) @@ -196,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/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/line-map.coffee b/src/atom/line-map.coffee index 2d0c340f8..4fa2dea28 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -1,5 +1,4 @@ _ = require 'underscore' -Delta = require 'delta' Point = require 'point' Range = require 'range' @@ -10,12 +9,12 @@ class LineMap insertAtBufferRow: (bufferRow, screenLines) -> screenLines = [screenLines] unless _.isArray(screenLines) - delta = new Delta + delta = new Point insertIndex = 0 for screenLine in @screenLines nextDelta = delta.add(screenLine.bufferDelta) - break if nextDelta.rows > bufferRow + break if nextDelta.row > bufferRow delta = nextDelta insertIndex++ @@ -31,12 +30,12 @@ class LineMap stopRow = startRow + rowCount startIndex = undefined stopIndex = 0 - delta = new Delta + delta = new Point for screenLine, i in @screenLines - startIndex = i if delta.rows == startRow and not startIndex + startIndex = i if delta.row == startRow and not startIndex nextDelta = delta.add(screenLine[deltaType]) - break if nextDelta.rows > stopRow + break if nextDelta.row > stopRow delta = nextDelta stopIndex++ @@ -60,16 +59,16 @@ class LineMap linesForScreenRows: (startRow, endRow) -> lastLine = null lines = [] - delta = new Delta + delta = new Point for fragment in @screenLines - break if delta.rows > endRow - if delta.rows >= startRow + 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) @@ -77,10 +76,10 @@ class LineMap lineForBufferRow: (row) -> line = null - delta = new Delta + delta = new Point for fragment in @screenLines - break if delta.rows > row - if delta.rows == row + break if delta.row > row + if delta.row == row if line line = line.concat(fragment) else @@ -89,47 +88,47 @@ class LineMap line bufferLineCount: -> - delta = new Delta + delta = new Point for screenLine in @screenLines delta = delta.add(screenLine.bufferDelta) - delta.rows + delta.row screenLineCount: -> - delta = new Delta + delta = new Point for screenLine in @screenLines delta = delta.add(screenLine.screenDelta) - delta.rows + delta.row screenPositionForBufferPosition: (bufferPosition, eagerWrap=true) -> bufferPosition = Point.fromObject(bufferPosition) - bufferDelta = new Delta - screenDelta = new Delta + bufferDelta = new Point + screenDelta = new Point for screenLine in @screenLines nextDelta = bufferDelta.add(screenLine.bufferDelta) - break if nextDelta.toPoint().greaterThan(bufferPosition) - break if nextDelta.toPoint().isEqual(bufferPosition) and not eagerWrap + break if nextDelta.isGreaterThan(bufferPosition) + break if nextDelta.isEqual(bufferPosition) and not eagerWrap bufferDelta = nextDelta screenDelta = screenDelta.add(screenLine.screenDelta) - remainingBufferColumns = bufferPosition.column - bufferDelta.columns - additionalScreenColumns = Math.max(0, Math.min(remainingBufferColumns, screenLine.lengthForClipping())) + remainingBufferColumn = bufferPosition.column - bufferDelta.column + additionalScreenColumn = Math.max(0, Math.min(remainingBufferColumn, screenLine.lengthForClipping())) - new Point(screenDelta.rows, screenDelta.columns + additionalScreenColumns) + 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 screenLine in @screenLines nextDelta = screenDelta.add(screenLine.screenDelta) - break if nextDelta.toPoint().greaterThan(screenPosition) + break if nextDelta.isGreaterThan(screenPosition) screenDelta = nextDelta 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) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 0845fa319..16ee097a4 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -3,7 +3,6 @@ EventEmitter = require 'event-emitter' LineMap = require 'line-map' Point = require 'point' Range = require 'range' -Delta = require 'delta' module.exports = class LineWrapper @@ -55,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)... diff --git a/src/atom/point.coffee b/src/atom/point.coffee index 85521baff..d18d3affe 100644 --- a/src/atom/point.coffee +++ b/src/atom/point.coffee @@ -11,16 +11,25 @@ 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) -> + debugger unless 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) + + 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 +44,13 @@ class Point else 0 - greaterThan: (other) -> + isEqual: (other) -> + other = Point.fromObject(other) + @compare(other) == 0 + + isGreaterThan: (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 994ff3c7b..7ac5d9987 100644 --- a/src/atom/screen-line-fragment.coffee +++ b/src/atom/screen-line-fragment.coffee @@ -1,13 +1,13 @@ _ = require 'underscore' -Delta = require 'delta' +Point = require 'point' module.exports = class ScreenLineFragment isAtomic: false constructor: (@tokens, @text, screenDelta, bufferDelta, extraFields) -> - @screenDelta = Delta.fromObject(screenDelta) - @bufferDelta = Delta.fromObject(bufferDelta) + @screenDelta = Point.fromObject(screenDelta) + @bufferDelta = Point.fromObject(bufferDelta) _.extend(this, extraFields) splitAt: (column) -> From 16a2fd0bb3856300d2cf158dca5e29ef6e31d9c3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 16:12:22 -0700 Subject: [PATCH 20/32] WIP: Start handling buffer updates w/ LineFolder Still a ways to go here, but folds are moved correctly when there are buffer updates. Many unfinished specs. --- spec/atom/line-folder-spec.coffee | 49 +++++++++++++++++++++ src/atom/line-folder.coffee | 66 +++++++++++++++++++++++----- src/atom/point.coffee | 10 ++++- src/atom/screen-line-fragment.coffee | 1 + 4 files changed, 115 insertions(+), 11 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 1e064d644..07ab31345 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -155,6 +155,55 @@ describe "LineFolder", -> 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 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 a fold", -> + it "re-positions the change based on the preceding fold", -> + + describe "when the old range is contained to a single line in-between two fold placeholders", -> + describe "when the line is updated", -> + + describe "when lines are inserted", -> + + describe "when the old range is inside a fold", -> + it "does not trigger a change event, but ensures the change is present when the fold is destroyed", -> + + describe "when the old range surrounds a fold", -> + it "removes the fold and replaces the placeholder with the new text", -> + + 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", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 876aea6b5..6d512aa0b 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -10,15 +10,20 @@ class LineFolder constructor: (@highlighter) -> @activeFolds = {} @buildLineMap() + @highlighter.buffer.on 'change', (e) => @handleBufferChange(e) + @highlighter.on 'change', (e) => @handleHighlighterChange(e) buildLineMap: -> @lineMap = new LineMap @lineMap.insertAtBufferRow(0, @highlighter.screenLines) + logLines: (start=0, end=@lastRow())-> + for row in [start..end] + console.log row, @lineForScreenRow(row).text + createFold: (bufferRange) -> fold = new Fold(this, bufferRange) - @activeFolds[bufferRange.start.row] ?= [] - @activeFolds[bufferRange.start.row].push(fold) + @registerFold(bufferRange.start.row, fold) oldScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange)) lineWithFold = @buildLine(oldScreenRange.start.row) @@ -33,11 +38,8 @@ class LineFolder fold destroyFold: (fold) -> - bufferRange = fold.range - folds = @activeFolds[bufferRange.start.row] - foldIndex = folds.indexOf(fold) - folds[foldIndex..foldIndex] = [] - + bufferRange = fold.getRange() + @unregisterFold(bufferRange.start.row, fold) startScreenRow = @screenRowForBufferRow(bufferRange.start.row) oldScreenRange = new Range() @@ -51,6 +53,26 @@ class LineFolder @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange + 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: (e) -> + oldScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(e.oldRange)) + lines = @buildLinesForBufferRows(e.newRange.start.row, e.newRange.end.row) + @lineMap.replaceScreenRows(e.oldRange.start.row, e.oldRange.end.row, lines) + newScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(e.newRange)) + + @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange + buildLinesForBufferRows: (start, end) -> lines = [@buildLine(@screenRowForBufferRow(start))] if end > start @@ -64,7 +86,7 @@ class LineFolder 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 = @buildLineForBufferRow(end.row, end.column) @@ -72,7 +94,7 @@ class LineFolder screenLine buildFoldPlaceholder: (fold) -> - new ScreenLineFragment([{value: '...', type: 'fold-placeholder'}], '...', [0, 3], fold.range.toDelta(), isAtomic: true) + new ScreenLineFragment([{value: '...', type: 'fold-placeholder'}], '...', [0, 3], fold.getRange().toDelta(), isAtomic: true) foldsForBufferRow: (bufferRow) -> @activeFolds[bufferRow] or [] @@ -80,6 +102,9 @@ class LineFolder linesForScreenRows: (startRow, endRow) -> @lineMap.linesForScreenRows(startRow, endRow) + lineForScreenRow: (screenRow) -> + @lineMap.lineForScreenRow(screenRow) + getLines: -> @lineMap.getScreenLines() @@ -111,8 +136,29 @@ class LineFolder _.extend LineFolder.prototype, EventEmitter class Fold - constructor: (@lineFolder, @range) -> + constructor: (@lineFolder, {start, end}) -> + @start = new Anchor(start) + @end = new Anchor(end) destroy: -> @lineFolder.destroyFold(this) + getRange: -> + new Range(@start.position, @end.position) + + handleBufferChange: (event) -> + oldStartRow = @start.position.row + @start.handleBufferChange(event) + @end.handleBufferChange(event) + newStartRow = @start.position.row + + if newStartRow != oldStartRow + @lineFolder.unregisterFold(oldStartRow, this) + @lineFolder.registerFold(newStartRow, this) + +class Anchor + constructor: (@position) -> + + handleBufferChange: (e) -> + @position = e.newRange.end.add(@position.subtract(e.oldRange.end)) + diff --git a/src/atom/point.coffee b/src/atom/point.coffee index d18d3affe..ee26459e1 100644 --- a/src/atom/point.coffee +++ b/src/atom/point.coffee @@ -14,7 +14,6 @@ class Point constructor: (@row=0, @column=0) -> add: (other) -> - debugger unless other row = @row + other.row if other.row == 0 column = @column + other.column @@ -23,6 +22,15 @@ class Point 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 diff --git a/src/atom/screen-line-fragment.coffee b/src/atom/screen-line-fragment.coffee index 7ac5d9987..1ce4747e5 100644 --- a/src/atom/screen-line-fragment.coffee +++ b/src/atom/screen-line-fragment.coffee @@ -17,6 +17,7 @@ class ScreenLineFragment leftTokens = [] leftTextLength = 0 while leftTextLength < column + debugger unless rightTokens[0] if leftTextLength + rightTokens[0].value.length > column rightTokens[0..0] = @splitTokenAt(rightTokens[0], column - leftTextLength) nextToken = rightTokens.shift() From d229585cd4ebec906ef3c2ef616be0bb5f249e2b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 16:32:57 -0700 Subject: [PATCH 21/32] Handle changes to unfolded text directly preceding a fold placeholder --- spec/atom/line-folder-spec.coffee | 28 +++++++++++++++++++++++++--- src/atom/line-folder.coffee | 8 ++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 07ab31345..049a5225b 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -162,7 +162,7 @@ describe "LineFolder", -> fold2 = folder.createFold(new Range([7, 5], [8, 36])) changeHandler.reset() - describe "when the old range precedes a fold", -> + 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') @@ -184,8 +184,30 @@ describe "LineFolder", -> expect(event.oldRange).toEqual [[3, 0], [3, 56]] expect(event.newRange).toEqual [[3, 0], [6, 28]] - describe "when the old range follows a fold", -> - it "re-positions the change based on the preceding fold", -> + 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]] + + fdescribe "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", -> describe "when the line is updated", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 6d512aa0b..e4d992cee 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -7,11 +7,14 @@ 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) => @handleHighlighterChange(e) + @highlighter.on 'change', (e) => @lastHighlighterChangeEvent = e buildLineMap: -> @lineMap = new LineMap @@ -64,11 +67,12 @@ class LineFolder handleBufferChange: (e) -> for row, folds of @activeFolds fold.handleBufferChange(e) for fold in folds + @handleHighlighterChange(@lastHighlighterChangeEvent) handleHighlighterChange: (e) -> oldScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(e.oldRange)) lines = @buildLinesForBufferRows(e.newRange.start.row, e.newRange.end.row) - @lineMap.replaceScreenRows(e.oldRange.start.row, e.oldRange.end.row, lines) + @lineMap.replaceScreenRows(oldScreenRange.start.row, oldScreenRange.end.row, lines) newScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(e.newRange)) @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange From de5eab13d2691ad853e57a4686a5a94293f2ee98 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 17:08:54 -0700 Subject: [PATCH 22/32] Insertions at beginning/end of a fold are considered to be outside it. Also added a spec where text is changed on a line in between two placeholders and handled correctly. --- spec/atom/line-folder-spec.coffee | 14 +++++++++++--- src/atom/line-folder.coffee | 28 ++++++++++++++-------------- src/atom/point.coffee | 3 +++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 049a5225b..9e54d29e1 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -196,7 +196,7 @@ describe "LineFolder", -> expect(event.oldRange).toEqual [[5, 0], [6, 0]] expect(event.newRange).toEqual [[5, 0], [5, 6]] - fdescribe "when the old range contains unfolded text on the first line of a fold, preceding the fold placeholder", -> + 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') @@ -210,9 +210,17 @@ describe "LineFolder", -> 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", -> - describe "when the line is updated", -> + 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]] - describe "when lines are inserted", -> + 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 ensures the change is present when the fold is destroyed", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index e4d992cee..b1f3232f2 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -140,29 +140,29 @@ class LineFolder _.extend LineFolder.prototype, EventEmitter class Fold - constructor: (@lineFolder, {start, end}) -> - @start = new Anchor(start) - @end = new Anchor(end) + constructor: (@lineFolder, {@start, @end}) -> destroy: -> @lineFolder.destroyFold(this) getRange: -> - new Range(@start.position, @end.position) + new Range(@start, @end) handleBufferChange: (event) -> - oldStartRow = @start.position.row - @start.handleBufferChange(event) - @end.handleBufferChange(event) - newStartRow = @start.position.row + oldStartRow = @start.row + @start = @updateAnchorPoint(@start, event) + @end = @updateAnchorPoint(@end, event, false) - if newStartRow != oldStartRow + if @start.row != oldStartRow @lineFolder.unregisterFold(oldStartRow, this) - @lineFolder.registerFold(newStartRow, this) + @lineFolder.registerFold(@start.row, this) -class Anchor - constructor: (@position) -> + 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) - handleBufferChange: (e) -> - @position = e.newRange.end.add(@position.subtract(e.oldRange.end)) + newRange.end.add(point.subtract(oldRange.end)) diff --git a/src/atom/point.coffee b/src/atom/point.coffee index ee26459e1..3773accb6 100644 --- a/src/atom/point.coffee +++ b/src/atom/point.coffee @@ -59,6 +59,9 @@ class Point isGreaterThan: (other) -> @compare(other) > 0 + isGreaterThanOrEqual: (other) -> + @compare(other) >= 0 + inspect: -> "(#{@row}, #{@column})" From 422df7989a890db14e79bf0719f20a9a8f59ec79 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 17:28:55 -0700 Subject: [PATCH 23/32] Handle changes inside of folds. Don't emit an event since nothing changes (since it's all folded). But update the position of the fold's end marker so when it's unfolded, things render correctly. --- spec/atom/line-folder-spec.coffee | 15 ++++++++++++++- src/atom/line-folder.coffee | 12 ++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 9e54d29e1..67cb78097 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -223,7 +223,20 @@ describe "LineFolder", -> 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 ensures the change is present when the fold is destroyed", -> + 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 placeholder with the new text", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index b1f3232f2..3fe730c7e 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -22,7 +22,8 @@ class LineFolder logLines: (start=0, end=@lastRow())-> for row in [start..end] - console.log row, @lineForScreenRow(row).text + line = @lineForScreenRow(row).text + console.log row, line, line.length createFold: (bufferRange) -> fold = new Fold(this, bufferRange) @@ -70,12 +71,15 @@ class LineFolder @handleHighlighterChange(@lastHighlighterChangeEvent) handleHighlighterChange: (e) -> - oldScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(e.oldRange)) + 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 = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(e.newRange)) + newScreenRange = @screenRangeForBufferRange(e.newRange) + expandedNewScreenRange = @expandScreenRangeToLineEnds(newScreenRange) - @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange + unless oldScreenRange.isEmpty() and newScreenRange.isEmpty() + @trigger 'change', oldRange: expandedOldScreenRange, newRange: expandedNewScreenRange buildLinesForBufferRows: (start, end) -> lines = [@buildLine(@screenRowForBufferRow(start))] From 68bbe2708c9a270e3ee7cddb3661cd3237ba7be4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 22:11:22 -0700 Subject: [PATCH 24/32] :lipstick: --- src/atom/screen-line-fragment.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/src/atom/screen-line-fragment.coffee b/src/atom/screen-line-fragment.coffee index 1ce4747e5..7ac5d9987 100644 --- a/src/atom/screen-line-fragment.coffee +++ b/src/atom/screen-line-fragment.coffee @@ -17,7 +17,6 @@ class ScreenLineFragment leftTokens = [] leftTextLength = 0 while leftTextLength < column - debugger unless rightTokens[0] if leftTextLength + rightTokens[0].value.length > column rightTokens[0..0] = @splitTokenAt(rightTokens[0], column - leftTextLength) nextToken = rightTokens.shift() From c0d0768df8cac07fd88123fe1c4c85c0bede08e5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 22:12:21 -0700 Subject: [PATCH 25/32] A change to a range surrounding a fold removes the fold --- spec/atom/line-folder-spec.coffee | 9 ++++++++- src/atom/line-folder.coffee | 6 ++++++ src/atom/point.coffee | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 67cb78097..89c27db71 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -239,7 +239,14 @@ describe "LineFolder", -> expect(event.newRange).toEqual [[4, 0], [5, 28]] describe "when the old range surrounds a fold", -> - it "removes the fold and replaces the placeholder with the new text", -> + 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", -> diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 3fe730c7e..98df6c1e1 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -154,6 +154,12 @@ class Fold 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) diff --git a/src/atom/point.coffee b/src/atom/point.coffee index 3773accb6..c29883293 100644 --- a/src/atom/point.coffee +++ b/src/atom/point.coffee @@ -56,6 +56,12 @@ class Point other = Point.fromObject(other) @compare(other) == 0 + isLessThan: (other) -> + @compare(other) < 0 + + isLessThanOrEqual: (other) -> + @compare(other) <= 0 + isGreaterThan: (other) -> @compare(other) > 0 From 8eed1a4c9405fae7640cab8bb47d4ff90b35889c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 22:18:27 -0700 Subject: [PATCH 26/32] :lipstick: --- spec/atom/line-wrapper-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index 1bd92069f..9730e000e 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -14,7 +14,7 @@ describe "LineWrapper", -> 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.lineForScreenRow(3).tokens).toEqual(' var pivot = items.shift(), current, left = [], ') expect(tokensText wrapper.lineForScreenRow(4).tokens).toEqual('right = [];') From 0bdc45037f269ac33c0371a199a6bcafb445ea5a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 23 Feb 2012 22:19:42 -0700 Subject: [PATCH 27/32] Install the LineFolder into the editor LineWrapper now takes a LineFolder instead of a Highlighter. It's a cascade of processing steps. --- spec/atom/line-wrapper-spec.coffee | 7 +++++-- src/atom/editor.coffee | 4 +++- src/atom/line-wrapper.coffee | 8 ++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index 9730e000e..8987f9577 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -1,6 +1,7 @@ -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' @@ -10,7 +11,9 @@ describe "LineWrapper", -> 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 diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 3c4572ef7..6b8d78b63 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' @@ -134,7 +135,8 @@ class Editor extends View 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) diff --git a/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index 16ee097a4..b6c0830a1 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -6,9 +6,9 @@ Range = require 'range' module.exports = class LineWrapper - constructor: (@maxLength, @highlighter) -> + constructor: (@maxLength, @lineFolder) -> @buildLineMap() - @highlighter.on 'change', (e) => @handleChange(e) + @lineFolder.on 'change', (e) => @handleChange(e) setMaxLength: (@maxLength) -> oldRange = @rangeForAllLines() @@ -18,7 +18,7 @@ class LineWrapper buildLineMap: -> @lineMap = new LineMap - @lineMap.insertAtBufferRow 0, @buildScreenLinesForBufferRows(0, @highlighter.lastRow()) + @lineMap.insertAtBufferRow 0, @buildScreenLinesForBufferRows(0, @lineFolder.lastRow()) handleChange: (e) -> oldBufferRange = e.oldRange @@ -41,7 +41,7 @@ class LineWrapper new Range([0, 0], [endRow, endColumn]) buildScreenLinesForBufferRows: (start, end) -> - _(@highlighter + _(@lineFolder .linesForScreenRows(start, end) .map((screenLine) => @wrapScreenLine(screenLine))).flatten() From dfcf4a1629449dcf40e5f349e6339cb4d498a737 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 11:30:32 -0700 Subject: [PATCH 28/32] Alt-meta-f folds the current selection --- spec/atom/editor-spec.coffee | 7 +++++++ src/atom/editor.coffee | 4 ++++ src/atom/selection.coffee | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index cb2d9bbde..95d92acd4 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -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/src/atom/editor.coffee b/src/atom/editor.coffee index 6b8d78b63..dd667197b 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -58,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() @@ -76,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) @@ -273,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/selection.coffee b/src/atom/selection.coffee index 0efa856be..f1fb7dfad 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' @@ -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()) From c8c703355033069df2e6f2d9b9aed1c1ac6566de Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 11:53:18 -0700 Subject: [PATCH 29/32] Rename Cursor.set/getPosition to set/getScreenPosition Now that buffer positions don't always line up with screen positions, it's important that it's clear which one we're talking about. --- spec/atom/cursor-spec.coffee | 10 +- spec/atom/editor-spec.coffee | 154 ++++++++++++++--------------- spec/atom/selection-spec.coffee | 14 +-- spec/atom/vim-mode-spec.coffee | 132 ++++++++++++------------- src/atom/cursor.coffee | 44 ++++----- src/atom/editor.coffee | 10 +- src/atom/selection.coffee | 14 +-- src/atom/vim-mode/motions.coffee | 16 +-- src/atom/vim-mode/operators.coffee | 2 +- 9 files changed, 198 insertions(+), 198 deletions(-) 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 95d92acd4..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 () {" 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/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/editor.coffee b/src/atom/editor.coffee index dd667197b..cbcb6e551 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -95,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 @@ -107,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() @@ -141,7 +141,7 @@ class Editor extends View @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) @@ -254,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) diff --git a/src/atom/selection.coffee b/src/atom/selection.coffee index f1fb7dfad..e2c51c0f1 100644 --- a/src/atom/selection.coffee +++ b/src/atom/selection.coffee @@ -62,14 +62,14 @@ 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.screenRangeForBufferRange(@getRange()) @@ -98,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() @@ -142,7 +142,7 @@ class Selection extends View selectToPosition: (position) -> @modifySelection => - @cursor.setPosition(position) + @cursor.setScreenPosition(position) moveCursorToLineEnd: -> @cursor.moveToLineEnd() 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 From 63be776837d66c1c19ce94347a5776f16991e30f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 14:18:55 -0700 Subject: [PATCH 30/32] LineFolder emits 'fold' and 'unfold' events --- spec/atom/line-folder-spec.coffee | 19 +++++++++++++++++++ src/atom/line-folder.coffee | 2 ++ 2 files changed, 21 insertions(+) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 89c27db71..7a7f2703e 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -14,6 +14,25 @@ describe "LineFolder", -> 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 "when there is a single fold spanning multiple lines", -> it "replaces folded lines with a single line containing a placeholder and emits a change event", -> [line4, line5] = folder.linesForScreenRows(4, 5) diff --git a/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index 98df6c1e1..bda1a3979 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -39,6 +39,7 @@ class LineFolder newScreenRange.end.column += fragment.text.length @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange + @trigger 'fold', bufferRange fold destroyFold: (fold) -> @@ -56,6 +57,7 @@ class LineFolder newScreenRange = @expandScreenRangeToLineEnds(@screenRangeForBufferRange(bufferRange)) @trigger 'change', oldRange: oldScreenRange, newRange: newScreenRange + @trigger 'unfold', fold.getRange() registerFold: (bufferRow, fold) -> @activeFolds[bufferRow] ?= [] From b8ef7685de9ae20bf439fd9cef94cc0323eb983e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 14:20:06 -0700 Subject: [PATCH 31/32] LineWrapper translates positions correctly with respect to folding. --- spec/atom/line-wrapper-spec.coffee | 12 +++++++++++- src/atom/line-wrapper.coffee | 7 +++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/spec/atom/line-wrapper-spec.coffee b/spec/atom/line-wrapper-spec.coffee index 8987f9577..49e86284c 100644 --- a/spec/atom/line-wrapper-spec.coffee +++ b/spec/atom/line-wrapper-spec.coffee @@ -7,7 +7,7 @@ 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')) @@ -125,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 @@ -138,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/src/atom/line-wrapper.coffee b/src/atom/line-wrapper.coffee index b6c0830a1..2faa8b730 100644 --- a/src/atom/line-wrapper.coffee +++ b/src/atom/line-wrapper.coffee @@ -80,10 +80,13 @@ class LineWrapper @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)) lineForScreenRow: (screenRow) -> @linesForScreenRows(screenRow, screenRow)[0] From 33ff32f9a46531f7ff233247c151bf93e3f36675 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 24 Feb 2012 15:35:28 -0700 Subject: [PATCH 32/32] Add clipScreenPosition to LineFolder & LineMap --- spec/atom/line-folder-spec.coffee | 21 ++++++++++++++++++++- src/atom/line-folder.coffee | 3 +++ src/atom/line-map.coffee | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/spec/atom/line-folder-spec.coffee b/spec/atom/line-folder-spec.coffee index 7a7f2703e..7c9d26f90 100644 --- a/spec/atom/line-folder-spec.coffee +++ b/spec/atom/line-folder-spec.coffee @@ -32,7 +32,6 @@ describe "LineFolder", -> [[range]] = unfoldHandler.argsForCall expect(range).toEqual foldRange - describe "when there is a single fold spanning multiple lines", -> it "replaces folded lines with a single line containing a placeholder and emits a change event", -> [line4, line5] = folder.linesForScreenRows(4, 5) @@ -313,3 +312,23 @@ describe "LineFolder", -> 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/src/atom/line-folder.coffee b/src/atom/line-folder.coffee index bda1a3979..d098df11a 100644 --- a/src/atom/line-folder.coffee +++ b/src/atom/line-folder.coffee @@ -136,6 +136,9 @@ class LineFolder bufferPositionForScreenPosition: (screenPosition) -> @lineMap.bufferPositionForScreenPosition(screenPosition) + clipScreenPosition: (screenPosition) -> + @lineMap.clipScreenPosition(screenPosition) + screenRangeForBufferRange: (bufferRange) -> @lineMap.screenRangeForBufferRange(bufferRange) diff --git a/src/atom/line-map.coffee b/src/atom/line-map.coffee index 4fa2dea28..b63b14b7d 100644 --- a/src/atom/line-map.coffee +++ b/src/atom/line-map.coffee @@ -135,3 +135,18 @@ class LineMap 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 +