From cf00753c9cd18588c8f7953285cd802286d2100a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 22 Feb 2012 14:02:51 -0700 Subject: [PATCH] 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])