Moah refactoringz, moah specs
This commit is contained in:
parent
ed6d1146da
commit
a591423e3b
@ -1,14 +1,88 @@
|
||||
class AsciiIo.AnsiInterpreter
|
||||
constructor: (terminal) ->
|
||||
@terminal = terminal
|
||||
|
||||
constructor: (screenBuffer) ->
|
||||
@sb = screenBuffer
|
||||
|
||||
@fg = @bg = undefined
|
||||
@bright = false
|
||||
@underline = false
|
||||
|
||||
@compilePatterns()
|
||||
|
||||
noop: ->
|
||||
|
||||
# handleC0ControlSet: (data) ->
|
||||
# console.log 'handling C0'
|
||||
|
||||
handlePrintableCharacters: (text) ->
|
||||
|
||||
# handleC1ControlSet: (data) ->
|
||||
|
||||
# handleControlSequence: (data) ->
|
||||
|
||||
_C0_PATTERNS:
|
||||
# C0 set of 7-bit control characters
|
||||
"[\x00-\x1f]":
|
||||
# bell
|
||||
"\x07": (data) -> @sb.bell()
|
||||
|
||||
# backspace
|
||||
"\x08": (data) -> @sb.backspace()
|
||||
|
||||
# Move the cursor to the next tab stop
|
||||
"\x09": (data) ->
|
||||
|
||||
"\x0a": (data) -> @sb.cursorDown 1
|
||||
|
||||
"\x0d": (data) -> @sb.cr()
|
||||
|
||||
"\x0e": (data) ->
|
||||
|
||||
"\x0f": (data) ->
|
||||
|
||||
# Reserved (?)
|
||||
"\x82": (data) ->
|
||||
|
||||
# Cancel Character, ignore previous character
|
||||
"\x94": (data) ->
|
||||
|
||||
# Escape sequence
|
||||
"\x1b": _.extend({}, @_C0_PATTERNS, {
|
||||
|
||||
# Control sequence
|
||||
"\x1b\\[": @_CS_PATTERNS
|
||||
})
|
||||
|
||||
_CS_PATTERNS:
|
||||
"sth": 1
|
||||
|
||||
_PATTERNS: _.extend({}, @_C0_PATTERNS, {
|
||||
# Printable characters
|
||||
"([\x20-\x7e])+": @handlePrintableCharacters
|
||||
|
||||
# "Delete", always and everywhere ignored
|
||||
"[\x7f\xff]": @noop
|
||||
|
||||
# C1 control set
|
||||
"[\x80-\x9f]":
|
||||
|
||||
# Control sequence
|
||||
"\x9b": @_CS_PATTERNS
|
||||
|
||||
# G1 Displayable, 94 additional displayable characters
|
||||
"[\xa1-\xfe]": @handlePrintableCharacters
|
||||
|
||||
# Always and everywhere a blank space
|
||||
"\xa0": -> @handlePrintableCharacters(' ')
|
||||
|
||||
})
|
||||
|
||||
PATTERNS:
|
||||
"\x07": (data) -> # bell
|
||||
"\x08": (data) -> @terminal.bs()
|
||||
"\x07": (data) -> @sb.bell()
|
||||
"\x08": (data) -> @sb.backspace()
|
||||
"\x09": (data) -> # Moves the cursor to the next tab stop
|
||||
"\x0a": (data) -> @terminal.cursorDown 1
|
||||
"\x0d": (data) -> @terminal.cr()
|
||||
"\x0a": (data) -> @sb.cursorDown 1
|
||||
"\x0d": (data) -> @sb.cr()
|
||||
"\x0e": (data) ->
|
||||
"\x0f": (data) ->
|
||||
"\x82": (data) -> # Reserved (?)
|
||||
@ -16,18 +90,19 @@ class AsciiIo.AnsiInterpreter
|
||||
|
||||
# 20 - 7e
|
||||
"([\x20-\x7e]|\xe2..|[\xc2\xc4\xc5].)+": (data, match) ->
|
||||
@terminal.print match[0]
|
||||
@sb.print match[0]
|
||||
|
||||
"\x1b\\(B": (data) -> # SCS (Set G0 Character SET)
|
||||
|
||||
"\x1b\\[(?:[0-9]+)?(?:;[0-9]+)*([\x40-\x7e])": (data, match) ->
|
||||
"\x1b\\[([0-9;]*)([\x40-\x7e])": (data, match) ->
|
||||
if match[1].length == 0
|
||||
@params = []
|
||||
re = /(\d+)/g
|
||||
m = undefined
|
||||
@params.push parseInt(m[1]) while m = re.exec(match[0])
|
||||
else
|
||||
@params = _(match[1].split(';')).map (n) -> if n is '' then undefined else parseInt(n)
|
||||
|
||||
@n = @params[0]
|
||||
@m = @params[1]
|
||||
@handleCSI match[1]
|
||||
@handleCSI match[2]
|
||||
|
||||
# private standards
|
||||
"\x1b\\[\\?([\x30-\x3f]+)([hlsr])": (data, match) ->
|
||||
@ -51,14 +126,14 @@ class AsciiIo.AnsiInterpreter
|
||||
# steady cursor
|
||||
else if mode is "25"
|
||||
if action is "h"
|
||||
@terminal.showCursor true
|
||||
@sb.showCursor true
|
||||
else if action is "l"
|
||||
@terminal.showCursor false
|
||||
@sb.showCursor false
|
||||
else if mode is "47"
|
||||
if action is "h"
|
||||
@terminal.switchToAlternateBuffer()
|
||||
@sb.switchToAlternateBuffer()
|
||||
else if action is "l"
|
||||
@terminal.switchToNormalBuffer()
|
||||
@sb.switchToNormalBuffer()
|
||||
else if mode is "1000"
|
||||
# Enables/disables normal mouse tracking
|
||||
else if mode is "1001"
|
||||
@ -68,14 +143,14 @@ class AsciiIo.AnsiInterpreter
|
||||
else if mode is "1049"
|
||||
if action is "h"
|
||||
# Save cursor position, switch to alternate screen buffer, and clear screen.
|
||||
@terminal.saveCursor()
|
||||
@terminal.switchToAlternateBuffer()
|
||||
@terminal.clearScreen()
|
||||
@sb.saveCursor()
|
||||
@sb.switchToAlternateBuffer()
|
||||
@sb.clearScreen()
|
||||
else if action is "l"
|
||||
# Clear screen, switch to normal screen buffer, and restore cursor position.
|
||||
@terminal.clearScreen()
|
||||
@terminal.switchToNormalBuffer()
|
||||
@terminal.restoreCursor()
|
||||
@sb.clearScreen()
|
||||
@sb.switchToNormalBuffer()
|
||||
@sb.restoreCursor()
|
||||
else
|
||||
throw "unknown mode: " + mode + action
|
||||
|
||||
@ -90,50 +165,92 @@ class AsciiIo.AnsiInterpreter
|
||||
"\x1bP([^\\\\])*?\\\\": (data) -> # DCS, Device Control String
|
||||
|
||||
"\x1bM": ->
|
||||
@terminal.ri @n or 1
|
||||
@sb.ri @n or 1
|
||||
|
||||
"\x1b\x37": (data) -> # save cursor pos and char attrs
|
||||
@terminal.saveCursor()
|
||||
@sb.saveCursor()
|
||||
|
||||
"\x1b\x38": (data) -> # restore cursor pos and char attrs
|
||||
@terminal.restoreCursor()
|
||||
@sb.restoreCursor()
|
||||
|
||||
handleCSI: (term) ->
|
||||
switch term
|
||||
when "@"
|
||||
@terminal.reserveCharacters @n
|
||||
@sb.reserveCharacters @n
|
||||
when "A"
|
||||
@terminal.cursorUp @n or 1
|
||||
@sb.cursorUp @n or 1
|
||||
when "B"
|
||||
@terminal.cursorDown @n or 1
|
||||
@sb.cursorDown @n or 1
|
||||
when "C"
|
||||
@terminal.cursorForward @n or 1
|
||||
@sb.cursorForward @n or 1
|
||||
when "D"
|
||||
@terminal.cursorBack @n or 1
|
||||
@sb.cursorBack @n or 1
|
||||
when "G"
|
||||
@terminal.setCursorColumn @n
|
||||
@sb.setCursorColumn @n
|
||||
when "H"
|
||||
@terminal.setCursorPos @n or 1, @m or 1
|
||||
@sb.setCursorPos @n or 1, @m or 1
|
||||
when "J"
|
||||
@terminal.eraseData @n or 0
|
||||
@sb.eraseData @n or 0
|
||||
when "K"
|
||||
@terminal.eraseInLine @n or 0
|
||||
@sb.eraseInLine @n or 0
|
||||
when "L"
|
||||
@terminal.insertLines @cursorY, @n or 1
|
||||
@sb.insertLines @n or 1
|
||||
when "M"
|
||||
@terminal.deleteLines @cursorY, @n or 1
|
||||
@sb.deleteLines @n or 1
|
||||
when "d" # VPA - Vertical Position Absolute
|
||||
@terminal.setCursorLine(@n)
|
||||
@sb.setCursorLine(@n)
|
||||
when "l" # l, Reset mode
|
||||
console.log "(TODO) reset: " + @n
|
||||
when "m"
|
||||
@terminal.setSGR @params
|
||||
@handleSGR @params
|
||||
when "P" # DCH - Delete Character, from current position to end of field
|
||||
@terminal.deleteCharacter @n or 1
|
||||
@sb.deleteCharacter @n or 1
|
||||
when "r" # Set top and bottom margins (scroll region on VT100)
|
||||
else
|
||||
throw "no handler for CSI term: " + term
|
||||
|
||||
handleSGR: (numbers) ->
|
||||
numbers = [0] if numbers.length is 0
|
||||
|
||||
i = 0
|
||||
while i < numbers.length
|
||||
n = numbers[i]
|
||||
|
||||
if n is 0
|
||||
@fg = @bg = undefined
|
||||
@bright = false
|
||||
@underline = false
|
||||
else if n is 1
|
||||
@bright = true
|
||||
else if n is 4
|
||||
@underline = true
|
||||
else if n is 24
|
||||
@underline = false
|
||||
else if n >= 30 and n <= 37
|
||||
@fg = n - 30
|
||||
else if n is 38
|
||||
@fg = numbers[i + 2]
|
||||
i += 2
|
||||
else if n is 39
|
||||
@fg = undefined
|
||||
else if n >= 40 and n <= 47
|
||||
@bg = n - 40
|
||||
else if n is 48
|
||||
@bg = numbers[i + 2]
|
||||
i += 2
|
||||
else if n is 49
|
||||
@bg = undefined
|
||||
|
||||
i++
|
||||
|
||||
props = {}
|
||||
props.fg = @fg if @fg
|
||||
props.bg = @bg if @bg
|
||||
props.bright = true if @bright
|
||||
props.underline = true if @underline
|
||||
|
||||
@sb.setBrush AsciiIo.Brush.create(props)
|
||||
|
||||
compilePatterns: ->
|
||||
@COMPILED_PATTERNS = ([new RegExp("^" + re), f] for re, f of @PATTERNS)
|
||||
|
||||
@ -152,5 +269,4 @@ class AsciiIo.AnsiInterpreter
|
||||
|
||||
break unless match
|
||||
|
||||
@terminal.render()
|
||||
data
|
||||
|
@ -1,2 +1,5 @@
|
||||
class AsciiIo.HudView extends Backbone.View
|
||||
tagName: 'div'
|
||||
className: 'hud'
|
||||
|
||||
initialize: (options) ->
|
||||
|
@ -3,57 +3,74 @@ class AsciiIo.Movie
|
||||
SPEED: 1.0
|
||||
|
||||
constructor: (@data, @timing) ->
|
||||
@frameNo = 0
|
||||
@dataIndex = 0
|
||||
@currentTime = 0
|
||||
@processedFramesTime = 0
|
||||
_.extend(this, Backbone.Events)
|
||||
|
||||
play: ->
|
||||
@nextFrame()
|
||||
|
||||
togglePlay: ->
|
||||
pause: ->
|
||||
# TODO
|
||||
|
||||
togglePlay: ->
|
||||
# TODO
|
||||
|
||||
seek: (percent) ->
|
||||
# TODO
|
||||
|
||||
nextFrame: () ->
|
||||
return if @currentData.length > 100
|
||||
|
||||
frame = @timing[@frameNo]
|
||||
|
||||
unless frame
|
||||
@terminal.stopCursorBlink()
|
||||
console.log "finished in #{((new Date()).getTime() - @startTime) / 1000} seconds"
|
||||
return
|
||||
|
||||
@frameNo += 1
|
||||
# return if @currentData.length > 100
|
||||
|
||||
if frame = @timing[@frameNo++] # @frameNo += 1
|
||||
[delay, count] = frame
|
||||
|
||||
if delay > @MIN_DELAY
|
||||
realDelay = delay * 1000 * (1.0 / @SPEED)
|
||||
|
||||
setTimeout(
|
||||
=>
|
||||
@terminal.restartCursorBlink()
|
||||
@processFrame(count)
|
||||
@nextFrame()
|
||||
realDelay
|
||||
)
|
||||
else
|
||||
@processFrame(count)
|
||||
@nextFrame()
|
||||
|
||||
processFrame: (count) ->
|
||||
@currentData += @data.slice(@dataIndex, @dataIndex + count)
|
||||
frameData = @data.slice(@dataIndex, @dataIndex + count)
|
||||
@dataIndex += count
|
||||
|
||||
@currentData = @interpreter.feed(@currentData)
|
||||
if delay <= @MIN_DELAY
|
||||
@triggerAndSchedule(frameData)
|
||||
else
|
||||
realDelay = delay * 1000 * (1.0 / @SPEED)
|
||||
setTimeout(
|
||||
=>
|
||||
@trigger('movie-awake') # @terminal.restartCursorBlink()
|
||||
@triggerAndSchedule(frameData)
|
||||
realDelay
|
||||
)
|
||||
|
||||
if @currentData.length > 0
|
||||
@logStatus(count)
|
||||
true
|
||||
else
|
||||
@trigger('movie-finished')
|
||||
# @terminal.stopCursorBlink()
|
||||
# console.log "finished in #{((new Date()).getTime() - @startTime) / 1000} seconds"
|
||||
|
||||
logStatus: (count) ->
|
||||
console.log 'rest: ' + Utf8.decode(@currentData)
|
||||
false
|
||||
|
||||
if @currentData.length > 100
|
||||
head = @currentData.slice(0, 100)
|
||||
hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head)
|
||||
console.log "failed matching: '" + Utf8.decode(head) + "' (" + hex.join() + ") [pos: " + (@dataIndex - count) + "]"
|
||||
return
|
||||
triggerAndSchedule: (data) ->
|
||||
@trigger('movie-frame', data)
|
||||
@nextFrame()
|
||||
|
||||
# processFrame: (count) ->
|
||||
# # return
|
||||
# # @currentData += @data.slice(@dataIndex, @dataIndex + count)
|
||||
# data = @data.slice(@dataIndex, @dataIndex + count)
|
||||
# # console.log data
|
||||
# @dataIndex += count
|
||||
# @trigger('movie-frame', data)
|
||||
|
||||
# @currentData = @interpreter.feed(@currentData)
|
||||
|
||||
# if @currentData.length > 0
|
||||
# @logStatus(count)
|
||||
|
||||
# logStatus: (count) ->
|
||||
# console.log 'rest: ' + Utf8.decode(@currentData)
|
||||
|
||||
# if @currentData.length > 100
|
||||
# head = @currentData.slice(0, 100)
|
||||
# hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head)
|
||||
# console.log "failed matching: '" + Utf8.decode(head) + "' (" + hex.join() + ") [pos: " + (@dataIndex - count) + "]"
|
||||
# return
|
||||
|
@ -1,19 +1,41 @@
|
||||
class AsciiIo.PlayerView extends Backbone.View
|
||||
|
||||
initialize: (options) ->
|
||||
@element = @$el
|
||||
|
||||
terminalElement = $('<pre class="terminal">')
|
||||
hudElement = $('<div class="hud">')
|
||||
|
||||
@element.append(terminalElement)
|
||||
@element.append(hudElement)
|
||||
|
||||
# @interpreter ?
|
||||
@terminal = new AsciiIo.TerminalView({
|
||||
el: terminalElement[0], cols: options.cols, lines: options.lines
|
||||
})
|
||||
@hud = new AsciiIo.HudView({ el: hudElement[0] })
|
||||
@movie = new AsciiIo.Movie(options.data, options.timing)
|
||||
@screenBuffer = new AsciiIo.ScreenBuffer(options.cols, options.lines)
|
||||
@interpreter = new AsciiIo.AnsiInterpreter(@screenBuffer)
|
||||
|
||||
@terminal.on 'terminal-click', =>
|
||||
@createChildViews()
|
||||
@bindEvents()
|
||||
|
||||
createChildViews: ->
|
||||
@terminalView = new AsciiIo.TerminalView(
|
||||
cols: this.options.cols
|
||||
lines: this.options.lines
|
||||
)
|
||||
@$el.append(@terminalView.$el)
|
||||
|
||||
@hudView = new AsciiIo.HudView()
|
||||
@$el.append(@hudView.$el)
|
||||
|
||||
bindEvents: ->
|
||||
@terminalView.on 'terminal-click', =>
|
||||
@movie.togglePlay()
|
||||
|
||||
@hudView.on 'hud-play-click', =>
|
||||
@movie.togglePlay()
|
||||
|
||||
@hudView.on 'hud-seek-click', (percent) =>
|
||||
@movie.seek(percent)
|
||||
|
||||
@movie.on 'movie-frame', (frame) =>
|
||||
@interpreter.feed(frame)
|
||||
changes = @screenBuffer.changes()
|
||||
@terminalView.render(changes)
|
||||
@screenBuffer.clearChanges()
|
||||
|
||||
@movie.on 'movie-finished', =>
|
||||
@terminalView.stopCursorBlink()
|
||||
|
||||
play: ->
|
||||
@movie.play()
|
||||
|
237
app/assets/javascripts/screen_buffer.js.coffee
Normal file
237
app/assets/javascripts/screen_buffer.js.coffee
Normal file
@ -0,0 +1,237 @@
|
||||
class AsciiIo.ScreenBuffer
|
||||
|
||||
constructor: (@cols, @lines) ->
|
||||
@cursorX = 0
|
||||
@cursorY = 0
|
||||
@normalBuffer = []
|
||||
@alternateBuffer = []
|
||||
@lineData = @normalBuffer
|
||||
@dirtyLines = {}
|
||||
|
||||
setBrush: (brush) ->
|
||||
@brush = brush
|
||||
|
||||
getLine: (n) ->
|
||||
n = (if typeof n isnt "undefined" then n else @cursorY)
|
||||
|
||||
throw "cant getLine " + n if n >= @lines
|
||||
|
||||
line = @lineData[n]
|
||||
|
||||
if typeof line is "undefined"
|
||||
line = @lineData[n] = []
|
||||
@fill n, 0, @cols, " "
|
||||
|
||||
line
|
||||
|
||||
switchToNormalBuffer: ->
|
||||
@lineData = @normalBuffer
|
||||
@updateScreen()
|
||||
|
||||
switchToAlternateBuffer: ->
|
||||
@lineData = @alternateBuffer
|
||||
@updateScreen()
|
||||
|
||||
updateLine: (n) ->
|
||||
n = (if typeof n isnt "undefined" then n else @cursorY)
|
||||
@dirtyLines[n] = n
|
||||
|
||||
updateScreen: ->
|
||||
@dirtyLines[n] = n for n in [0...@lines]
|
||||
|
||||
setCursorLine: (line) ->
|
||||
oldLine = @cursorY
|
||||
@cursorY = line - 1
|
||||
@updateLine oldLine
|
||||
@updateLine()
|
||||
|
||||
setCursorColumn: (col) ->
|
||||
@cursorX = col - 1
|
||||
@updateLine()
|
||||
|
||||
setCursorPos: (line, col) ->
|
||||
@setCursorLine(line)
|
||||
@setCursorColumn(col)
|
||||
|
||||
saveCursor: ->
|
||||
@savedCol = @cursorX
|
||||
@savedLine = @cursorY
|
||||
|
||||
restoreCursor: ->
|
||||
oldLine = @cursorY
|
||||
|
||||
@cursorY = @savedLine
|
||||
@cursorX = @savedCol
|
||||
|
||||
@updateLine oldLine
|
||||
@updateLine()
|
||||
|
||||
cursorLeft: ->
|
||||
if @cursorX > 0
|
||||
@cursorX -= 1
|
||||
@updateLine()
|
||||
|
||||
cursorRight: ->
|
||||
if @cursorX < @cols
|
||||
@cursorX += 1
|
||||
@updateLine()
|
||||
|
||||
cursorUp: (n) ->
|
||||
for i in [0...n]
|
||||
if @cursorY > 0
|
||||
@cursorY -= 1
|
||||
@updateLine @cursorY
|
||||
@updateLine @cursorY + 1
|
||||
|
||||
cursorDown: (n) ->
|
||||
for i in [0...n]
|
||||
if @cursorY + 1 < @lines
|
||||
@cursorY += 1
|
||||
@updateLine @cursorY - 1
|
||||
@updateLine @cursorY
|
||||
else
|
||||
@lineData.splice 0, 1
|
||||
@updateScreen()
|
||||
|
||||
cursorForward: (n) ->
|
||||
@cursorRight() for i in [0...n]
|
||||
|
||||
cursorBack: (n) ->
|
||||
@cursorLeft() for i in [0...n]
|
||||
|
||||
cr: ->
|
||||
@cursorX = 0
|
||||
@updateLine()
|
||||
|
||||
backspace: ->
|
||||
if @cursorX > 0
|
||||
@cursorLeft()
|
||||
@updateLine()
|
||||
|
||||
bell: ->
|
||||
# nothing to do
|
||||
|
||||
print: (text) ->
|
||||
text = Utf8.decode(text)
|
||||
|
||||
i = 0
|
||||
while i < text.length
|
||||
if @cursorX >= @cols
|
||||
@cursorY += 1
|
||||
@cursorX = 0
|
||||
|
||||
@fill @cursorY, @cursorX, 1, text[i]
|
||||
@cursorX += 1
|
||||
i++
|
||||
|
||||
@updateLine()
|
||||
|
||||
eraseData: (n) ->
|
||||
if n is 0
|
||||
@eraseInLine 0
|
||||
|
||||
l = @cursorY + 1
|
||||
while l < @lines
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
else if n is 1
|
||||
l = 0
|
||||
while l < @cursorY
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
@eraseInLine n
|
||||
|
||||
else if n is 2
|
||||
l = 0
|
||||
while l < @lines
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
eraseInLine: (n) ->
|
||||
if n is 0
|
||||
@fill @cursorY, @cursorX, @cols - @cursorX, " "
|
||||
else if n is 1
|
||||
@fill @cursorY, 0, @cursorX, " "
|
||||
else if n is 2
|
||||
@fill @cursorY, 0, @cols, " "
|
||||
|
||||
@updateLine()
|
||||
|
||||
clearLineData: (n) ->
|
||||
@fill n, 0, @cols, " "
|
||||
|
||||
deleteCharacter: (n) ->
|
||||
@getLine().splice(@cursorX, n)
|
||||
@updateLine()
|
||||
|
||||
reserveCharacters: (n) ->
|
||||
line = @getLine()
|
||||
@lineData[@cursorY] = line.slice(0, @cursorX).concat(" ".times(n).split(""), line.slice(@cursorX, @cols - n))
|
||||
@updateLine()
|
||||
|
||||
ri: (n) ->
|
||||
i = 0
|
||||
while i < n
|
||||
if @cursorY is 0
|
||||
@insertLines n, 0
|
||||
else
|
||||
@cursorUp()
|
||||
i++
|
||||
|
||||
insertLines: (n, l = @cursorY) ->
|
||||
i = 0
|
||||
while i < n
|
||||
@lineData.splice l, 0, []
|
||||
@clearLineData l
|
||||
i++
|
||||
|
||||
# trim lineData to max size
|
||||
@lineData.length = @lines
|
||||
|
||||
@updateScreen()
|
||||
|
||||
deleteLines: (n, l = @cursorY) ->
|
||||
@lineData.splice l, n
|
||||
|
||||
# expand lineData to max size
|
||||
@lineData.length = @lines
|
||||
|
||||
@updateScreen()
|
||||
|
||||
fill: (line, col, n, char) ->
|
||||
prefix = ""
|
||||
postfix = ""
|
||||
|
||||
if @fg isnt undefined or @bg isnt undefined or @bright or @underline
|
||||
prefix = "<span class=\""
|
||||
brightOffset = (if @bright then 8 else 0)
|
||||
|
||||
if @fg isnt undefined
|
||||
prefix += " fg" + (@fg + brightOffset)
|
||||
else if @bright
|
||||
prefix += " bright"
|
||||
|
||||
if @underline
|
||||
prefix += " underline"
|
||||
|
||||
prefix += " bg" + @bg if @bg isnt undefined
|
||||
prefix += "\">"
|
||||
postfix = "</span>"
|
||||
|
||||
char = prefix + char + postfix
|
||||
lineArr = @getLine(line)
|
||||
|
||||
i = 0
|
||||
while i < n
|
||||
lineArr[col + i] = char
|
||||
i++
|
||||
|
||||
changes: ->
|
||||
{}
|
||||
|
||||
clearChanges: ->
|
@ -1,18 +1,10 @@
|
||||
class AsciiIo.TerminalView extends Backbone.View
|
||||
tagName: 'pre'
|
||||
className: 'terminal'
|
||||
|
||||
initialize: (options) ->
|
||||
# @element = $(element) # $(".player .term")
|
||||
@element = @$el
|
||||
@cols = options.cols
|
||||
@lines = options.lines
|
||||
@cursorX = 0
|
||||
@cursorY = 0
|
||||
@normalBuffer = []
|
||||
@alternateBuffer = []
|
||||
@lineData = @normalBuffer
|
||||
@dirtyLines = {}
|
||||
@fg = @bg = undefined
|
||||
@underline = false
|
||||
|
||||
@createChildElements()
|
||||
@showCursor true
|
||||
@ -28,35 +20,20 @@ class AsciiIo.TerminalView extends Backbone.View
|
||||
|
||||
while i < @lines
|
||||
row = $("<span class=\"line\">")
|
||||
@element.append row
|
||||
@element.append "\n"
|
||||
@$el.append row
|
||||
@$el.append "\n"
|
||||
i++
|
||||
|
||||
getLine: (n) ->
|
||||
n = (if typeof n isnt "undefined" then n else @cursorY)
|
||||
|
||||
throw "cant getLine " + n if n >= @lines
|
||||
|
||||
line = @lineData[n]
|
||||
|
||||
if typeof line is "undefined"
|
||||
line = @lineData[n] = []
|
||||
@fill n, 0, @cols, " "
|
||||
|
||||
line
|
||||
|
||||
clearScreen: ->
|
||||
# this.lineData.length = 0;
|
||||
@cursorY = @cursorX = 0
|
||||
@element.find(".line").empty()
|
||||
@$el.find(".line").empty()
|
||||
|
||||
switchToNormalBuffer: ->
|
||||
@lineData = @normalBuffer
|
||||
@updateScreen()
|
||||
render: ->
|
||||
for _, n of @dirtyLines
|
||||
@renderLine n
|
||||
|
||||
switchToAlternateBuffer: ->
|
||||
@lineData = @alternateBuffer
|
||||
@updateScreen()
|
||||
# @dirtyLines = {}
|
||||
|
||||
renderLine: (n) ->
|
||||
html = @getLine(n)
|
||||
@ -64,251 +41,16 @@ class AsciiIo.TerminalView extends Backbone.View
|
||||
if n is @cursorY
|
||||
html = html.slice(0, @cursorX).concat([ "<span class=\"cursor\">" + (html[@cursorX] or "") + "</span>" ], html.slice(@cursorX + 1) or [])
|
||||
|
||||
@element.find(".line:eq(" + n + ")").html html.join("")
|
||||
|
||||
render: ->
|
||||
for _, n of @dirtyLines
|
||||
@renderLine n
|
||||
|
||||
@dirtyLines = {}
|
||||
|
||||
updateLine: (n) ->
|
||||
n = (if typeof n isnt "undefined" then n else @cursorY)
|
||||
@dirtyLines[n] = n
|
||||
|
||||
updateScreen: ->
|
||||
@dirtyLines[n] = n for n in [0...@lines]
|
||||
@$el.find(".line:eq(" + n + ")").html html.join("")
|
||||
|
||||
showCursor: (show) ->
|
||||
if show
|
||||
@element.addClass "cursor-on"
|
||||
@$el.addClass "cursor-on"
|
||||
else
|
||||
@element.removeClass "cursor-on"
|
||||
|
||||
setSGR: (codes) ->
|
||||
codes = [0] if codes.length is 0
|
||||
|
||||
i = 0
|
||||
while i < codes.length
|
||||
n = codes[i]
|
||||
|
||||
if n is 0
|
||||
@fg = @bg = undefined
|
||||
@bright = false
|
||||
@underline = false
|
||||
else if n is 1
|
||||
@bright = true
|
||||
else if n is 4
|
||||
@underline = true
|
||||
else if n is 24
|
||||
@underline = false
|
||||
else if n >= 30 and n <= 37
|
||||
@fg = n - 30
|
||||
else if n is 38
|
||||
@fg = codes[i + 2]
|
||||
i += 2
|
||||
else if n is 39
|
||||
@fg = undefined
|
||||
else if n >= 40 and n <= 47
|
||||
@bg = n - 40
|
||||
else if n is 48
|
||||
@bg = codes[i + 2]
|
||||
i += 2
|
||||
else if n is 49
|
||||
@bg = undefined
|
||||
i++
|
||||
|
||||
setCursorLine: (line) ->
|
||||
oldLine = @cursorY
|
||||
@cursorY = line - 1
|
||||
@updateLine oldLine
|
||||
@updateLine()
|
||||
|
||||
setCursorColumn: (col) ->
|
||||
@cursorX = col - 1
|
||||
@updateLine()
|
||||
|
||||
setCursorPos: (line, col) ->
|
||||
@setCursorLine(line)
|
||||
@setCursorColumn(col)
|
||||
|
||||
saveCursor: ->
|
||||
@savedCol = @cursorX
|
||||
@savedLine = @cursorY
|
||||
|
||||
restoreCursor: ->
|
||||
oldLine = @cursorY
|
||||
|
||||
@cursorY = @savedLine
|
||||
@cursorX = @savedCol
|
||||
|
||||
@updateLine oldLine
|
||||
@updateLine()
|
||||
|
||||
cursorLeft: ->
|
||||
if @cursorX > 0
|
||||
@cursorX -= 1
|
||||
@updateLine()
|
||||
|
||||
cursorRight: ->
|
||||
if @cursorX < @cols
|
||||
@cursorX += 1
|
||||
@updateLine()
|
||||
|
||||
cursorUp: (n) ->
|
||||
for i in [0...n]
|
||||
if @cursorY > 0
|
||||
@cursorY -= 1
|
||||
@updateLine @cursorY
|
||||
@updateLine @cursorY + 1
|
||||
|
||||
cursorDown: (n) ->
|
||||
for i in [0...n]
|
||||
if @cursorY + 1 < @lines
|
||||
@cursorY += 1
|
||||
@updateLine @cursorY - 1
|
||||
@updateLine @cursorY
|
||||
else
|
||||
@lineData.splice 0, 1
|
||||
@updateScreen()
|
||||
|
||||
cursorForward: (n) ->
|
||||
@cursorRight() for i in [0...n]
|
||||
|
||||
cursorBack: (n) ->
|
||||
@cursorLeft() for i in [0...n]
|
||||
|
||||
cr: ->
|
||||
@cursorX = 0
|
||||
@updateLine()
|
||||
|
||||
bs: ->
|
||||
if @cursorX > 0
|
||||
@cursorLeft()
|
||||
@updateLine()
|
||||
|
||||
print: (text) ->
|
||||
text = Utf8.decode(text)
|
||||
|
||||
i = 0
|
||||
while i < text.length
|
||||
if @cursorX >= @cols
|
||||
@cursorY += 1
|
||||
@cursorX = 0
|
||||
|
||||
@fill @cursorY, @cursorX, 1, text[i]
|
||||
@cursorX += 1
|
||||
i++
|
||||
|
||||
@updateLine()
|
||||
|
||||
eraseData: (n) ->
|
||||
if n is 0
|
||||
@eraseInLine 0
|
||||
|
||||
l = @cursorY + 1
|
||||
while l < @lines
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
else if n is 1
|
||||
l = 0
|
||||
while l < @cursorY
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
@eraseInLine n
|
||||
|
||||
else if n is 2
|
||||
l = 0
|
||||
while l < @lines
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
eraseInLine: (n) ->
|
||||
if n is 0
|
||||
@fill @cursorY, @cursorX, @cols - @cursorX, " "
|
||||
else if n is 1
|
||||
@fill @cursorY, 0, @cursorX, " "
|
||||
else if n is 2
|
||||
@fill @cursorY, 0, @cols, " "
|
||||
|
||||
@updateLine()
|
||||
|
||||
clearLineData: (n) ->
|
||||
@fill n, 0, @cols, " "
|
||||
|
||||
deleteCharacter: (n) ->
|
||||
@getLine().splice(@cursorX, n)
|
||||
@updateLine()
|
||||
|
||||
reserveCharacters: (n) ->
|
||||
line = @getLine()
|
||||
@lineData[@cursorY] = line.slice(0, @cursorX).concat(" ".times(n).split(""), line.slice(@cursorX, @cols - n))
|
||||
@updateLine()
|
||||
|
||||
ri: (n) ->
|
||||
i = 0
|
||||
while i < n
|
||||
if @cursorY is 0
|
||||
@insertLines 0, n
|
||||
else
|
||||
@cursorUp()
|
||||
i++
|
||||
|
||||
insertLines: (l, n) ->
|
||||
i = 0
|
||||
while i < n
|
||||
@lineData.splice l, 0, []
|
||||
@clearLineData l
|
||||
i++
|
||||
|
||||
# trim lineData to max size
|
||||
@lineData.length = @lines
|
||||
|
||||
@updateScreen()
|
||||
|
||||
deleteLines: (l, n) ->
|
||||
@lineData.splice l, n
|
||||
|
||||
# expand lineData to max size
|
||||
@lineData.length = @lines
|
||||
|
||||
@updateScreen()
|
||||
|
||||
fill: (line, col, n, char) ->
|
||||
prefix = ""
|
||||
postfix = ""
|
||||
|
||||
if @fg isnt undefined or @bg isnt undefined or @bright or @underline
|
||||
prefix = "<span class=\""
|
||||
brightOffset = (if @bright then 8 else 0)
|
||||
|
||||
if @fg isnt undefined
|
||||
prefix += " fg" + (@fg + brightOffset)
|
||||
else if @bright
|
||||
prefix += " bright"
|
||||
|
||||
if @underline
|
||||
prefix += " underline"
|
||||
|
||||
prefix += " bg" + @bg if @bg isnt undefined
|
||||
prefix += "\">"
|
||||
postfix = "</span>"
|
||||
|
||||
char = prefix + char + postfix
|
||||
lineArr = @getLine(line)
|
||||
|
||||
i = 0
|
||||
while i < n
|
||||
lineArr[col + i] = char
|
||||
i++
|
||||
@$el.removeClass "cursor-on"
|
||||
|
||||
blinkCursor: ->
|
||||
cursor = @element.find(".cursor")
|
||||
cursor = @$el.find(".cursor")
|
||||
if cursor.hasClass("inverted")
|
||||
cursor.removeClass "inverted"
|
||||
else
|
||||
|
@ -19,7 +19,17 @@ module AsciicastsHelper
|
||||
var time = #{j var_time};
|
||||
var cols = #{asciicast.terminal_columns};
|
||||
var lines = #{asciicast.terminal_lines};
|
||||
$(function() { new AsciiIo.Player(cols, lines, data, time); });
|
||||
$(function() {
|
||||
window.player = new AsciiIo.PlayerView({
|
||||
el: $('.player'),
|
||||
cols: cols,
|
||||
lines: lines,
|
||||
data: data,
|
||||
timing: time
|
||||
});
|
||||
|
||||
window.player.play();
|
||||
});
|
||||
</script>
|
||||
EOS
|
||||
end
|
||||
|
@ -1,3 +1,389 @@
|
||||
describe AsciiIo.AnsiInterpreter, ->
|
||||
it 'rocks', ->
|
||||
expect(1).toBeTruthy()
|
||||
interpreter = screenBuffer = data = undefined
|
||||
cols = 80
|
||||
lines = 24
|
||||
|
||||
beforeEach ->
|
||||
screenBuffer = new AsciiIo.ScreenBuffer(cols, lines)
|
||||
interpreter = new AsciiIo.AnsiInterpreter(screenBuffer)
|
||||
data = ''
|
||||
|
||||
describe '#feed', ->
|
||||
describe 'C0 set control character', ->
|
||||
# A single character with an ASCII code within the ranges: 000 to 037 and
|
||||
# 200 to 237 octal, 00 - 1F and 80 - 9F hex.
|
||||
|
||||
describe 'x00', ->
|
||||
beforeEach ->
|
||||
screenBuffer = {} # will throw 'undefined is not a function'
|
||||
interpreter = new AsciiIo.AnsiInterpreter(screenBuffer)
|
||||
|
||||
it 'is ignored', ->
|
||||
data += '\x00'
|
||||
interpreter.feed(data)
|
||||
|
||||
describe 'x07', ->
|
||||
it 'calls bell', ->
|
||||
data += '\x07'
|
||||
spyOn screenBuffer, 'bell'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.bell).toHaveBeenCalled()
|
||||
|
||||
describe 'x08', ->
|
||||
it 'calls backspace', ->
|
||||
data += '\x08'
|
||||
spyOn screenBuffer, 'backspace'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.backspace).toHaveBeenCalled()
|
||||
|
||||
describe 'x0a', ->
|
||||
it 'calls cursorDown(1)', ->
|
||||
data += '\x0a'
|
||||
spyOn screenBuffer, 'cursorDown'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorDown).toHaveBeenCalledWith(1)
|
||||
|
||||
describe 'x0d', ->
|
||||
it 'calls cr', ->
|
||||
data += '\x0d'
|
||||
spyOn screenBuffer, 'cr'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cr).toHaveBeenCalled()
|
||||
|
||||
describe 'printable character', ->
|
||||
describe 'from ASCII range (0x20-0x7e)', ->
|
||||
it 'calls print', ->
|
||||
data += '\x20foobar\x7e'
|
||||
spyOn screenBuffer, 'print'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.print).toHaveBeenCalledWith(data)
|
||||
|
||||
describe 'from Unicode', ->
|
||||
it 'calls print', ->
|
||||
data += '\xe2ab\xe2ZZ'
|
||||
spyOn screenBuffer, 'print'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.print).toHaveBeenCalledWith(data)
|
||||
|
||||
describe 'from Unicode (really ???)', ->
|
||||
it 'calls print', ->
|
||||
data += '\xc2a\xc4b\xc5c'
|
||||
spyOn screenBuffer, 'print'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.print).toHaveBeenCalledWith(data)
|
||||
|
||||
|
||||
describe 'escape sequence', ->
|
||||
# 2 or 3 character string starting with ESCape. (Four or more character
|
||||
# strings are allowed but not defined.)
|
||||
|
||||
beforeEach ->
|
||||
data += '\x1b'
|
||||
|
||||
describe 'with ESC + control character inside', ->
|
||||
# Interpret the character, then resume processing the sequence.
|
||||
|
||||
# it 'aaa', ->
|
||||
# data += '[1\x1b\x0dm'
|
||||
# spyOn screenBuffer, 'cr'
|
||||
# spyOn screenBuffer, 'setSGR'
|
||||
# interpreter.feed(data)
|
||||
# expect(screenBuffer.cr).toHaveBeenCalled()
|
||||
# expect(screenBuffer.setSGR).toHaveBeenCalledWith([1])
|
||||
|
||||
describe 'control sequence', ->
|
||||
# A string starting with CSI (233 octal, 9B hex) or with ESC[
|
||||
# (Left-Bracket) and terminated by an alphabetic character. Any number of
|
||||
# parameter characters (digits 0 to 9, semicolon, and question mark) may
|
||||
# appear within the Control Sequence. The terminating character may be
|
||||
# preceded by an intermediate character (such as space).
|
||||
|
||||
beforeEach ->
|
||||
data += '['
|
||||
|
||||
describe 'buffering', ->
|
||||
it 'allows parsing in chunks', ->
|
||||
spyOn interpreter, 'handleSGR'
|
||||
interpreter.feed(data)
|
||||
interpreter.feed('m')
|
||||
expect(interpreter.handleSGR).toHaveBeenCalled()
|
||||
|
||||
describe '@', ->
|
||||
it 'calls reserveCharacters', ->
|
||||
data += '3@'
|
||||
spyOn screenBuffer, 'reserveCharacters'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.reserveCharacters).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'A', ->
|
||||
it 'calls cursorUp(1) if no number given', ->
|
||||
data += 'A'
|
||||
spyOn screenBuffer, 'cursorUp'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorUp).toHaveBeenCalledWith(1)
|
||||
|
||||
it 'calls cursorUp(n) if number given', ->
|
||||
data += '3A'
|
||||
spyOn screenBuffer, 'cursorUp'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorUp).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'B', ->
|
||||
it 'calls cursorDown(1) if no number given', ->
|
||||
data += 'B'
|
||||
spyOn screenBuffer, 'cursorDown'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorDown).toHaveBeenCalledWith(1)
|
||||
|
||||
it 'calls cursorDown(n) if number given', ->
|
||||
data += '3B'
|
||||
spyOn screenBuffer, 'cursorDown'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorDown).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'C', ->
|
||||
it 'calls cursorForward(1) if no number given', ->
|
||||
data += 'C'
|
||||
spyOn screenBuffer, 'cursorForward'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorForward).toHaveBeenCalledWith(1)
|
||||
|
||||
it 'calls cursorForward(n) if number given', ->
|
||||
data += '3C'
|
||||
spyOn screenBuffer, 'cursorForward'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorForward).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'D', ->
|
||||
it 'calls cursorBack(1) if no number given', ->
|
||||
data += 'D'
|
||||
spyOn screenBuffer, 'cursorBack'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorBack).toHaveBeenCalledWith(1)
|
||||
|
||||
it 'calls cursorBack(n) if number given', ->
|
||||
data += '3D'
|
||||
spyOn screenBuffer, 'cursorBack'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.cursorBack).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'G', ->
|
||||
it 'calls setCursorColumn(n)', ->
|
||||
data += '3G'
|
||||
spyOn screenBuffer, 'setCursorColumn'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.setCursorColumn).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'H', ->
|
||||
it 'calls setCursorPos(n, m) when n and m given', ->
|
||||
data += '3;4H'
|
||||
spyOn screenBuffer, 'setCursorPos'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.setCursorPos).toHaveBeenCalledWith(3, 4)
|
||||
|
||||
it 'calls setCursorPos(1, m) when no n given', ->
|
||||
data += ';3H'
|
||||
spyOn screenBuffer, 'setCursorPos'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.setCursorPos).toHaveBeenCalledWith(1, 3)
|
||||
|
||||
it 'calls setCursorPos(n, 1) when no m given', ->
|
||||
data += '3;H'
|
||||
spyOn screenBuffer, 'setCursorPos'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.setCursorPos).toHaveBeenCalledWith(3, 1)
|
||||
|
||||
it 'calls setCursorPos(n, 1) when no m given (no semicolon)', ->
|
||||
data += '3H'
|
||||
spyOn screenBuffer, 'setCursorPos'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.setCursorPos).toHaveBeenCalledWith(3, 1)
|
||||
|
||||
it 'calls setCursorPos(1, 1) when no n nor m given', ->
|
||||
data += 'H'
|
||||
spyOn screenBuffer, 'setCursorPos'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.setCursorPos).toHaveBeenCalledWith(1, 1)
|
||||
|
||||
describe 'J', ->
|
||||
it 'calls eraseData(0) when no n given', ->
|
||||
data += 'J'
|
||||
spyOn screenBuffer, 'eraseData'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.eraseData).toHaveBeenCalledWith(0)
|
||||
|
||||
it 'calls eraseData(n) when n given', ->
|
||||
data += '3J'
|
||||
spyOn screenBuffer, 'eraseData'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.eraseData).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'K', ->
|
||||
it 'calls eraseInLine(0) when no n given', ->
|
||||
data += 'K'
|
||||
spyOn screenBuffer, 'eraseInLine'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.eraseInLine).toHaveBeenCalledWith(0)
|
||||
|
||||
it 'calls eraseInLine(n) when n given', ->
|
||||
data += '3K'
|
||||
spyOn screenBuffer, 'eraseInLine'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.eraseInLine).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'L', ->
|
||||
it 'calls insertLines(1) when no n given', ->
|
||||
data += 'L'
|
||||
spyOn screenBuffer, 'insertLines'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.insertLines).toHaveBeenCalledWith(1)
|
||||
|
||||
it 'calls insertLines(n) when n given', ->
|
||||
data += '3L'
|
||||
spyOn screenBuffer, 'insertLines'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.insertLines).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'M', ->
|
||||
it 'calls deleteLines(1) when no n given', ->
|
||||
data += 'M'
|
||||
spyOn screenBuffer, 'deleteLines'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.deleteLines).toHaveBeenCalledWith(1)
|
||||
|
||||
it 'calls deleteLines(n) when n given', ->
|
||||
data += '3M'
|
||||
spyOn screenBuffer, 'deleteLines'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.deleteLines).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'd', ->
|
||||
it 'calls setCursorLine(n)', ->
|
||||
data += '3d'
|
||||
spyOn screenBuffer, 'setCursorLine'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.setCursorLine).toHaveBeenCalledWith(3)
|
||||
|
||||
describe 'm', ->
|
||||
it 'calls handleSGR([n, m, ...]) when n and m given', ->
|
||||
data += '1;4;33m'
|
||||
spyOn interpreter, 'handleSGR'
|
||||
interpreter.feed(data)
|
||||
expect(interpreter.handleSGR).toHaveBeenCalledWith([1, 4, 33])
|
||||
|
||||
it 'calls handleSGR([]) when no n nor m given', ->
|
||||
data += 'm'
|
||||
spyOn interpreter, 'handleSGR'
|
||||
interpreter.feed(data)
|
||||
expect(interpreter.handleSGR).toHaveBeenCalledWith([])
|
||||
|
||||
describe 'P', ->
|
||||
it 'calls deleteCharacter(1) when no n given', ->
|
||||
data += 'P'
|
||||
spyOn screenBuffer, 'deleteCharacter'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.deleteCharacter).toHaveBeenCalledWith(1)
|
||||
|
||||
it 'calls deleteCharacter(n) when n given', ->
|
||||
data += '3P'
|
||||
spyOn screenBuffer, 'deleteCharacter'
|
||||
interpreter.feed(data)
|
||||
expect(screenBuffer.deleteCharacter).toHaveBeenCalledWith(3)
|
||||
|
||||
describe '#handleSGR', ->
|
||||
numbers = undefined
|
||||
|
||||
it 'resets brush for 0', ->
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
|
||||
numbers = [31]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ fg: 1 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
numbers = [0]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({})
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'sets bright attr for 1', ->
|
||||
numbers = [1]
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ bright: true })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'sets underline attr for 4', ->
|
||||
numbers = [4]
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ underline: true })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'unsets underline attr for 24', ->
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
|
||||
numbers = [31, 4]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ fg: 1, underline: true })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
numbers = [24]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ fg: 1 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'sets foreground for 30-37', ->
|
||||
numbers = [32]
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ fg: 2 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'sets foreground for 38;5;x', ->
|
||||
numbers = [38, 5, 100]
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ fg: 100 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'unsets foreground for 39', ->
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
|
||||
numbers = [32]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ fg: 2 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
numbers = [39]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({})
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'sets background for 40-47', ->
|
||||
numbers = [42]
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ bg: 2 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'sets background for 48;5;x', ->
|
||||
numbers = [48, 5, 200]
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ bg: 200 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
it 'unsets background for 49', ->
|
||||
spyOn screenBuffer, 'setBrush'
|
||||
|
||||
numbers = [42]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({ bg: 2 })
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
numbers = [49]
|
||||
interpreter.handleSGR(numbers)
|
||||
expectedBrush = AsciiIo.Brush.create({})
|
||||
expect(screenBuffer.setBrush).toHaveBeenCalledWith(expectedBrush)
|
||||
|
||||
|
@ -1,21 +1,76 @@
|
||||
describe AsciiIo.Movie, ->
|
||||
movie = data = timing = null
|
||||
|
||||
beforeEach ->
|
||||
movie = null
|
||||
data = ''
|
||||
timing = []
|
||||
|
||||
describe '#play', ->
|
||||
it 'calls nextFrame', ->
|
||||
movie = new AsciiIo.Movie('', [])
|
||||
movie = new AsciiIo.Movie(data, timing)
|
||||
spyOn movie, 'nextFrame'
|
||||
movie.play()
|
||||
expect(movie.nextFrame).toHaveBeenCalled()
|
||||
|
||||
describe '#pause', ->
|
||||
|
||||
describe '#toggle', ->
|
||||
describe '#togglePlay', ->
|
||||
|
||||
describe '#seek', ->
|
||||
|
||||
describe '#nextFrame', ->
|
||||
|
||||
describe 'when playing', ->
|
||||
it 'triggers movie-frame event', ->
|
||||
beforeEach ->
|
||||
data = 'X'
|
||||
|
||||
it 'triggers movie-frame event immediately if delay is < MIN_DELAY', ->
|
||||
timing = [[0.8 * AsciiIo.Movie::MIN_DELAY, 1]]
|
||||
movie = new AsciiIo.Movie(data, timing)
|
||||
obj = { callback: -> true }
|
||||
movie.on('movie-frame', (arg) -> obj.callback(arg))
|
||||
spyOn(obj, 'callback')
|
||||
|
||||
movie.nextFrame()
|
||||
|
||||
expect(obj.callback).toHaveBeenCalledWith('X')
|
||||
|
||||
it 'triggers movie-frame event after <delay> if delay is > MIN_DELAY', ->
|
||||
timing = [[20 * AsciiIo.Movie::MIN_DELAY, 1]]
|
||||
movie = new AsciiIo.Movie(data, timing)
|
||||
obj = { callback: -> true }
|
||||
movie.on('movie-frame', (arg) -> obj.callback(arg))
|
||||
spyOn(obj, 'callback')
|
||||
|
||||
nextFrameCallTime = (new Date).getTime()
|
||||
ret = movie.nextFrame()
|
||||
|
||||
expect(ret).toBe(true)
|
||||
|
||||
waitsFor(
|
||||
->
|
||||
called =
|
||||
obj.callback.callCount == 1 and obj.callback.argsForCall[0][0] == data
|
||||
actualDelay = ((new Date).getTime() - nextFrameCallTime) / 1000 # seconds
|
||||
diff = actualDelay - (timing[0][0] * (1.0 / AsciiIo.Movie::SPEED))
|
||||
isProperDelay = diff < Math.abs(0.02)
|
||||
called and isProperDelay
|
||||
'movie-frame event not triggered in <delay> time'
|
||||
1000
|
||||
)
|
||||
|
||||
describe 'when finished', ->
|
||||
beforeEach ->
|
||||
timing = []
|
||||
movie = new AsciiIo.Movie(data, timing)
|
||||
|
||||
it 'triggers movie-finished event', ->
|
||||
obj = { callback: -> true }
|
||||
movie.on('movie-finished', -> obj.callback())
|
||||
spyOn(obj, 'callback')
|
||||
|
||||
ret = movie.nextFrame()
|
||||
|
||||
expect(ret).toBe(false)
|
||||
expect(obj.callback).toHaveBeenCalled()
|
||||
|
@ -25,19 +25,17 @@ describe AsciiIo.PlayerView, ->
|
||||
})
|
||||
|
||||
expect(AsciiIo.TerminalView).toHaveBeenCalledWith({
|
||||
el: element.find('.terminal')[0], cols: cols, lines: lines
|
||||
cols: cols, lines: lines
|
||||
})
|
||||
|
||||
it 'creates HudView instance passing proper DOM element', ->
|
||||
spyOn(AsciiIo, 'HudView')
|
||||
spyOn(AsciiIo, 'HudView').andReturn({ on: -> 'foo' })
|
||||
|
||||
player = new AsciiIo.PlayerView({
|
||||
el: element, cols: cols, lines: lines, data: data, timing: timing
|
||||
})
|
||||
|
||||
expect(AsciiIo.HudView).toHaveBeenCalledWith({
|
||||
el: element.find('.hud')[0]
|
||||
})
|
||||
expect(AsciiIo.HudView).toHaveBeenCalled()
|
||||
|
||||
it 'creates Movie instance', ->
|
||||
spyOn(AsciiIo, 'Movie').andCallThrough()
|
||||
@ -49,22 +47,71 @@ describe AsciiIo.PlayerView, ->
|
||||
expect(AsciiIo.Movie).toHaveBeenCalledWith(data, timing)
|
||||
|
||||
describe 'events', ->
|
||||
it 'toggles movie playback when terminal-click is fired on terminal', ->
|
||||
player = null
|
||||
|
||||
beforeEach ->
|
||||
player = new AsciiIo.PlayerView({
|
||||
el: element, cols: cols, lines: lines, data: data, timing: timing
|
||||
})
|
||||
|
||||
it 'toggles movie playback when terminal-click is fired on terminal', ->
|
||||
spyOn player.movie, 'togglePlay'
|
||||
|
||||
player.terminal.trigger 'terminal-click'
|
||||
player.terminalView.trigger 'terminal-click'
|
||||
|
||||
expect(player.movie.togglePlay).toHaveBeenCalled()
|
||||
|
||||
it 'toggles movie playback when hud-play-click is fired on hud', ->
|
||||
spyOn player.movie, 'togglePlay'
|
||||
|
||||
player.hudView.trigger 'hud-play-click'
|
||||
|
||||
expect(player.movie.togglePlay).toHaveBeenCalled()
|
||||
|
||||
it 'seeks movie playback when hud-seek-click is fired on hud', ->
|
||||
spyOn player.movie, 'seek'
|
||||
|
||||
player.hudView.trigger 'hud-seek-click', 55
|
||||
|
||||
expect(player.movie.seek).toHaveBeenCalledWith(55)
|
||||
|
||||
it 'toggles fullscreen view when hud-fullscreen-click is fired on hud', ->
|
||||
# pending functionality
|
||||
|
||||
it 'stops cursor blinking when movie-finished is fired on movie', ->
|
||||
spyOn player.terminalView, 'stopCursorBlink'
|
||||
|
||||
# it ' when movie-frame is fired on movie', ->
|
||||
player.movie.trigger 'movie-finished'
|
||||
|
||||
expect(player.terminalView.stopCursorBlink).toHaveBeenCalled()
|
||||
|
||||
it 'feeds interpreter when movie-frame is fired on movie', ->
|
||||
frame = { some: 'Frame' }
|
||||
spyOn player.interpreter, 'feed'
|
||||
|
||||
player.movie.trigger 'movie-frame', frame
|
||||
|
||||
expect(player.interpreter.feed).toHaveBeenCalledWith(frame)
|
||||
|
||||
it 'renders and clears buffer changes when movie-frame is fired on movie', ->
|
||||
frame = { some: 'Frame' }
|
||||
changes = { someChanges: 'here' }
|
||||
spyOn player.terminalView, 'render'
|
||||
spyOn(player.screenBuffer, 'changes').andReturn(changes)
|
||||
spyOn(player.screenBuffer, 'clearChanges')
|
||||
|
||||
player.movie.trigger 'movie-frame', frame
|
||||
|
||||
expect(player.terminalView.render).toHaveBeenCalledWith(changes)
|
||||
expect(player.screenBuffer.clearChanges).toHaveBeenCalled()
|
||||
|
||||
describe '#play', ->
|
||||
it 'starts movie playback', ->
|
||||
player = new AsciiIo.PlayerView({
|
||||
el: element, cols: cols, lines: lines, data: data, timing: timing
|
||||
})
|
||||
spyOn player.movie, 'play'
|
||||
|
||||
player.play()
|
||||
|
||||
expect(player.movie.play).toHaveBeenCalled()
|
||||
|
3
spec/javascripts/screen_buffer.coffee
Normal file
3
spec/javascripts/screen_buffer.coffee
Normal file
@ -0,0 +1,3 @@
|
||||
describe AsciiIo.ScreenBuffer, ->
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user