Merge branch 'master' into chrome

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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