Merge branch 'editor'

This commit is contained in:
Corey Johnson & Nathan Sobo
2012-02-02 17:06:18 -07:00
8 changed files with 351 additions and 56 deletions

View File

@@ -1,4 +1,6 @@
_ = require 'underscore'
fs = require 'fs'
Range = require 'range'
module.exports =
class Buffer
@@ -6,6 +8,7 @@ class Buffer
constructor: (@path) ->
@url = @path # we want this to be path on master, but let's not break it on a branch
@lines = ['']
if @path and fs.exists(@path)
@setText(fs.read(@path))
else
@@ -15,7 +18,10 @@ class Buffer
@lines.join('\n')
setText: (text) ->
@lines = text.split('\n')
@change(@getRange(), text)
getRange: ->
new Range([0, 0], [@lastRow(), @lastLine().length])
getTextInRange: (range) ->
if range.start.row == range.end.row
@@ -35,46 +41,38 @@ class Buffer
getLine: (row) ->
@lines[row]
change: (preRange, string) ->
@remove(preRange)
postRange = @insert(preRange.start, string)
@trigger 'change', { preRange, postRange, string }
remove: (range) ->
prefix = @lines[range.start.row][0...range.start.column]
suffix = @lines[range.end.row][range.end.column..]
@lines[range.start.row..range.end.row] = prefix + suffix
insert: ({row, column}, string) ->
postRange =
start: { row, column }
end: { row, column }
prefix = @lines[row][0...column]
suffix = @lines[row][column..]
lines = string.split('\n')
if lines.length == 1
@lines[row] = prefix + string + suffix
postRange.end.column += string.length
else
for line, i in lines
curRow = row + i
if i == 0 # replace first line
@lines[curRow] = prefix + line
else if i < lines.length - 1 # insert middle lines
@lines[curRow...curRow] = line
else # insert last line
@lines[curRow...curRow] = line + suffix
postRange.end.row = curRow
postRange.end.column = line.length
postRange
numLines: ->
@getLines().length
lastRow: ->
@getLines().length - 1
lastLine: ->
@getLine(@lastRow())
insert: (point, text) ->
@change(new Range(point, point), text)
change: (preRange, newText) ->
postRange = new Range(_.clone(preRange.start), _.clone(preRange.start))
prefix = @lines[preRange.start.row][0...preRange.start.column]
suffix = @lines[preRange.end.row][preRange.end.column..]
newTextLines = newText.split('\n')
if newTextLines.length == 1
postRange.end.column += newText.length
newTextLines = [prefix + newText + suffix]
else
lastLineIndex = newTextLines.length - 1
newTextLines[0] = prefix + newTextLines[0]
postRange.end.row += lastLineIndex
postRange.end.column = newTextLines[lastLineIndex].length
newTextLines[lastLineIndex] += suffix
@lines[preRange.start.row..preRange.end.row] = newTextLines
@trigger 'change', { preRange, postRange, string: newText }
save: ->
if not @path then throw new Error("Tried to save buffer with no url")
fs.write @path, @getText()
@@ -87,3 +85,14 @@ class Buffer
trigger: (eventName, event) ->
@eventHandlers?[eventName]?.forEach (handler) -> handler(event)
modeName: ->
extension = if @path then @path.split('/').pop().split('.').pop() else null
switch extension
when 'js' then 'javascript'
when 'coffee' then 'coffee'
when 'rb', 'ru' then 'ruby'
when 'c', 'h', 'cpp' then 'c_cpp'
when 'html', 'htm' then 'html'
when 'css' then 'css'
else 'text'

View File

@@ -3,7 +3,9 @@ Buffer = require 'buffer'
Point = require 'point'
Cursor = require 'cursor'
Selection = require 'selection'
Highlighter = require 'highlighter'
Range = require 'range'
$ = require 'jquery'
$$ = require 'template/builder'
_ = require 'underscore'
@@ -26,6 +28,7 @@ class Editor extends Template
initialize: () ->
requireStylesheet 'editor.css'
requireStylesheet 'theme/twilight.css'
@bindKeys()
@buildCursorAndSelection()
@handleEvents()
@@ -97,16 +100,23 @@ class Editor extends Template
@hiddenInput.width(@charWidth)
@focus()
buildLineElement: (lineText) ->
if lineText is ''
$$.pre class: "line", -> @raw('&nbsp;')
else
$$.pre class: "line", lineText
buildLineElement: (row) ->
tokens = @highlighter.tokensForRow(row)
$$.pre class: 'line', ->
if tokens.length
for token in tokens
classes = token.type.split('.').map((c) -> "ace_#{c}").join(' ')
@span { class: token.type.replace('.', ' ') }, token.value
else
@raw '&nbsp;'
setBuffer: (@buffer) ->
@highlighter = new Highlighter(@buffer)
@lines.empty()
for line in @buffer.getLines()
@lines.append @buildLineElement(line)
for row in [0..@buffer.lastRow()]
line = @buildLineElement(row)
@lines.append line
@setCursorPosition(row: 0, column: 0)
@@ -128,18 +138,13 @@ class Editor extends Template
else
@updateLineElement(row)
@selection.bufferChanged(e)
@cursor.bufferChanged(e)
updateLineElement: (row) ->
line = @buffer.getLine(row)
element = @getLineElement(row)
if line == ''
element.html('&nbsp;')
else
element.text(line)
@getLineElement(row).replaceWith(@buildLineElement(row))
insertLineElement: (row) ->
@getLineElement(row).before(@buildLineElement(@buffer.getLine(row)))
@getLineElement(row).before(@buildLineElement(row))
removeLineElement: (row) ->
@getLineElement(row).remove()

View File

@@ -0,0 +1,43 @@
_ = require 'underscore'
module.exports =
class Highlighter
buffer: null
tokenizer: null
lines: []
constructor: (@buffer) ->
@buildTokenizer()
@lines = @tokenizeRows('start', 0, @buffer.lastRow())
@buffer.on 'change', (e) => @handleBufferChange(e)
buildTokenizer: ->
Mode = require("ace/mode/#{@buffer.modeName()}").Mode
@tokenizer = (new Mode).getTokenizer()
handleBufferChange: (e) ->
{ preRange, postRange } = e
previousState = @lines[preRange.end.row].state
newLines = @tokenizeRows('start', postRange.start.row, postRange.end.row)
@lines[preRange.start.row..preRange.end.row] = newLines
for row in [postRange.end.row...@buffer.lastRow()]
break if @lines[row].state == previousState
nextRow = row + 1
previousState = @lines[nextRow].state
@lines[nextRow] = @tokenizeRow(@lines[row].state, nextRow)
tokenizeRows: (startState, startRow, endRow) ->
state = startState
for row in [startRow..endRow]
line = @tokenizeRow(state, row)
state = line.state
line
tokenizeRow: (state, row) ->
@tokenizer.getLineTokens(@buffer.getLine(row), state)
tokensForRow: (row) ->
@lines[row].tokens

View File

@@ -26,9 +26,6 @@ class Selection extends Template
@anchor = null
@updateAppearance()
bufferChanged: (e) ->
@cursor.setPosition(e.postRange.end)
updateAppearance: ->
@clearRegions()