Update the player to use backend-generated JSON frames
parent
a1cd6ca2ae
commit
ee636bf958
@ -1,286 +0,0 @@
|
||||
class AsciiIo.AnsiInterpreter
|
||||
|
||||
constructor: ->
|
||||
@sgrInterpreter = new AsciiIo.SgrInterpreter()
|
||||
|
||||
parse: (data) ->
|
||||
@commands = []
|
||||
|
||||
while data.length > 0
|
||||
processed = @handleData data
|
||||
|
||||
if processed is 0
|
||||
# console.log "no kurwa: #{@formattedData(@data)}"
|
||||
break
|
||||
|
||||
data = data.slice processed
|
||||
|
||||
[@commands, data]
|
||||
|
||||
cb: ->
|
||||
@commands.push arguments
|
||||
|
||||
handleData: (data) ->
|
||||
if data.match(/^\x1b[\x00-\x1f]/)
|
||||
@handleControlCharacter(data[1])
|
||||
return 2
|
||||
|
||||
else if match = data.match(/^(\x1b\x5d|\x9d).*?(\x1b\\|\x9c|\x07)/)
|
||||
# OSC seq
|
||||
return match[0].length
|
||||
|
||||
else if match = data.match(/^(\x1b[PX_^]|[\x90\x98\x9e\x9f]).*?(\x1b\\|\x9c)/)
|
||||
# DCS/SOS/PM/APC seq
|
||||
return match[0].length
|
||||
|
||||
else if match = data.match(/^(?:\x1b\x5b|\x9b)([\x30-\x3f]*?)[\x20-\x2f]*?[\x40-\x7e]/)
|
||||
# Control sequences
|
||||
@handleControlSequence(match[0], match[1], match)
|
||||
return match[0].length
|
||||
|
||||
else if match = data.match(/^\x1b[\x20-\x2f]*?[\x30-\x3f]/)
|
||||
@handlePrivateEscSeq(match[0])
|
||||
return match[0].length
|
||||
|
||||
else if match = data.match(/^\x1b[\x20-\x2f]*?[\x40-\x5a\x5c\x5e-\x7e]/)
|
||||
# excluding \x5b ([) and \x5d (])
|
||||
# they're both handled above
|
||||
@handleStandardEscSeq(match[0])
|
||||
return match[0].length
|
||||
|
||||
else if data.match(/^\x1b\x7f/) # DELETE
|
||||
return 2
|
||||
|
||||
else if data.match(/^[\x00-\x1a\x1c-\x1f]/) # excluding \x1b "ESC"
|
||||
@handleControlCharacter(data[0])
|
||||
return 1
|
||||
|
||||
else if match = data.match(/^([\x20-\x7e]|[\xe2-\xe8]..|[\xc2-\xc5].|[\xa1-\xfe])+/)
|
||||
@handlePrintableCharacters(match[0])
|
||||
return match[0].length
|
||||
|
||||
else if data[0] is "\x7f"
|
||||
# DELETE, always and everywhere ignored
|
||||
return 1
|
||||
|
||||
else if data.match(/^[\x80-\x9f]/)
|
||||
@handleControlCharacter(data[0])
|
||||
return 1
|
||||
|
||||
else if data[0] is "\xa0"
|
||||
# Same as SPACE (\x20)
|
||||
@handlePrintableCharacters(' ')
|
||||
return 1
|
||||
|
||||
else if data[0] is "\xff"
|
||||
# Same as DELETE (\x7f)
|
||||
return 1
|
||||
|
||||
else
|
||||
return 0
|
||||
|
||||
handleControlCharacter: (char) ->
|
||||
action = switch char
|
||||
when "\x07"
|
||||
'bell'
|
||||
when "\x08"
|
||||
'backspace'
|
||||
when "\x09"
|
||||
'goToNextHorizontalTabStop'
|
||||
when "\x0a"
|
||||
'lineFeed'
|
||||
when "\x0b"
|
||||
'verticalTab'
|
||||
when "\x0c"
|
||||
'formFeed'
|
||||
when "\x0d"
|
||||
'carriageReturn'
|
||||
when "\x84"
|
||||
'index' # "ESC D"
|
||||
when "\x85"
|
||||
'newLine' # "ESC E"
|
||||
when "\x88"
|
||||
'setHorizontalTabStop' # "ESC H"
|
||||
when "\x8d"
|
||||
'reverseIndex' # "ESC M"
|
||||
|
||||
@cb action if action
|
||||
|
||||
handlePrintableCharacters: (text) ->
|
||||
@cb 'print', text
|
||||
|
||||
handleStandardEscSeq: (data) ->
|
||||
last = data[data.length - 1]
|
||||
intermediate = data[data.length - 2]
|
||||
|
||||
action = switch last
|
||||
when "A"
|
||||
if intermediate is '('
|
||||
'setUkCharset'
|
||||
when "B"
|
||||
if intermediate is '('
|
||||
'setUsCharset'
|
||||
when "D"
|
||||
'index'
|
||||
when "E"
|
||||
'newLine'
|
||||
when "H"
|
||||
'setHorizontalTabStop'
|
||||
when "M"
|
||||
'reverseIndex'
|
||||
when "c"
|
||||
'resetTerminal'
|
||||
|
||||
@cb action if action
|
||||
|
||||
handlePrivateEscSeq: (data) ->
|
||||
last = data[data.length - 1]
|
||||
intermediate = data[data.length - 2]
|
||||
|
||||
action = switch last
|
||||
when "0"
|
||||
if intermediate is '('
|
||||
'setSpecialCharset'
|
||||
when "7"
|
||||
'saveTerminalState'
|
||||
when "8"
|
||||
'restoreTerminalState'
|
||||
|
||||
@cb action if action
|
||||
|
||||
handleControlSequence: (data, params, match) ->
|
||||
if params and params.match(/^[\x3c-\x3f]/)
|
||||
@handlePrivateControlSequence(data, params)
|
||||
else
|
||||
@handleStandardControlSequence(data, params)
|
||||
|
||||
handleStandardControlSequence: (data, params) ->
|
||||
term = data[data.length - 1]
|
||||
|
||||
numbers = @parseParams params
|
||||
n = numbers[0]
|
||||
m = numbers[1]
|
||||
|
||||
switch term
|
||||
when "@"
|
||||
@cb 'reserveCharacters', n
|
||||
when "A"
|
||||
n = 1 if n is undefined
|
||||
@cb 'priorRow', n
|
||||
when "B"
|
||||
n = 1 if n is undefined
|
||||
@cb 'nextRow', n
|
||||
when "C"
|
||||
n = 1 if n is undefined
|
||||
@cb 'nextColumn', n
|
||||
when "D"
|
||||
n = 1 if n is undefined
|
||||
@cb 'priorColumn', n
|
||||
when "E"
|
||||
@cb 'nextRowFirstColumn', n
|
||||
when "F"
|
||||
@cb 'priorRowFirstColumn', n
|
||||
when "G"
|
||||
n = 1 if n is undefined
|
||||
@cb 'goToColumn', n
|
||||
when "H"
|
||||
n = 1 if n is undefined
|
||||
m = 1 if m is undefined
|
||||
@cb 'goToRowAndColumn', n, m
|
||||
when "I"
|
||||
@cb 'goToNextHorizontalTabStop', n
|
||||
when "J"
|
||||
if n is 2
|
||||
@cb 'eraseScreen'
|
||||
else if n is 1
|
||||
@cb 'eraseFromScreenStart'
|
||||
else
|
||||
@cb 'eraseToScreenEnd'
|
||||
when "K"
|
||||
if n is 2
|
||||
@cb 'eraseRow'
|
||||
else if n is 1
|
||||
@cb 'eraseFromRowStart'
|
||||
else
|
||||
@cb 'eraseToRowEnd'
|
||||
when "L"
|
||||
@cb 'insertLines', n or 1
|
||||
when "M"
|
||||
@cb 'deleteLines', n or 1
|
||||
when "P" # DCH - Delete Character, from current position to end of field
|
||||
@cb 'deleteCharacters', n or 1
|
||||
when "S"
|
||||
@cb 'scrollUp', n
|
||||
when "T"
|
||||
@cb 'scrollDown', n
|
||||
when "X"
|
||||
@cb 'eraseCharacters', n
|
||||
when "Z"
|
||||
@cb 'goToPriorHorizontalTabStop', n
|
||||
when "b"
|
||||
@cb 'repeatLastCharacter', n
|
||||
when "d" # VPA - Vertical Position Absolute
|
||||
@cb 'goToRow', n
|
||||
when "f"
|
||||
@cb 'goToRowAndColumn', n, m
|
||||
when "g"
|
||||
if !n or n is 0
|
||||
@cb 'clearHorizontalTabStop'
|
||||
else if n is 3
|
||||
@cb 'clearAllHorizontalTabStops'
|
||||
when "l" # l, Reset mode
|
||||
console.log "(TODO) reset: " + n
|
||||
when "m"
|
||||
@handleSGR numbers
|
||||
when "n"
|
||||
@cb 'reportRowAndColumn'
|
||||
when "r" # Set top and bottom margins (scroll region on VT100)
|
||||
if n is undefined
|
||||
n = 1
|
||||
if m is undefined
|
||||
m = @lines
|
||||
@cb 'setScrollRegion', n, m
|
||||
|
||||
handlePrivateControlSequence: (data, params) ->
|
||||
action = data[data.length - 1]
|
||||
modes = @parseParams params
|
||||
|
||||
for mode in modes
|
||||
if mode is 25
|
||||
if action is "h"
|
||||
@cb 'showCursor'
|
||||
else if action is "l"
|
||||
@cb 'hideCursor'
|
||||
else if mode is 47
|
||||
if action is "h"
|
||||
@cb 'switchToAlternateBuffer'
|
||||
else if action is "l"
|
||||
@cb 'switchToNormalBuffer'
|
||||
else if mode is 1049
|
||||
if action is "h"
|
||||
# Save cursor position, switch to alternate screen buffer, and clear screen.
|
||||
@cb 'switchToAlternateBuffer'
|
||||
@cb 'eraseScreen'
|
||||
else if action is "l"
|
||||
# Clear screen, switch to normal screen buffer, and restore cursor position.
|
||||
@cb 'eraseScreen'
|
||||
@cb 'switchToNormalBuffer'
|
||||
|
||||
parseParams: (params) ->
|
||||
if params.length is 0
|
||||
numbers = []
|
||||
else
|
||||
numbers = _(params.replace(/[^0-9;]/, '').split(';')).map (n) ->
|
||||
if n is '' then undefined else parseInt(n, 10)
|
||||
|
||||
numbers
|
||||
|
||||
handleSGR: (numbers) ->
|
||||
numbers = [0] if numbers.length is 0
|
||||
changes = @sgrInterpreter.parse numbers
|
||||
@cb 'updateBrush', changes
|
||||
|
||||
formattedData: (data) ->
|
||||
head = data.slice(0, 100)
|
||||
hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head)
|
||||
Utf8.decode(head) + " (" + hex.join() + ")"
|
@ -1,365 +0,0 @@
|
||||
class AsciiIo.ScreenBuffer
|
||||
|
||||
constructor: (@cols, @lines, @scrollRegion, @tabStops) ->
|
||||
@lineData = []
|
||||
@dirtyLines = {}
|
||||
|
||||
@cursorX = 0
|
||||
@cursorY = 0
|
||||
|
||||
@setBrush AsciiIo.Brush.default()
|
||||
@setCharset 'us'
|
||||
|
||||
topMargin: ->
|
||||
@scrollRegion.getTop()
|
||||
|
||||
bottomMargin: ->
|
||||
@scrollRegion.getBottom()
|
||||
|
||||
updateLine: (n = @cursorY) ->
|
||||
@dirtyLines[n] = true
|
||||
|
||||
updateLines: (a, b) ->
|
||||
n = a
|
||||
while n <= b
|
||||
@updateLine n
|
||||
n++
|
||||
|
||||
updateScreen: ->
|
||||
@updateLine n for n in [0...@lines]
|
||||
|
||||
changes: ->
|
||||
changes = {}
|
||||
|
||||
for n, _ of @dirtyLines
|
||||
data = @lineData[n] || []
|
||||
|
||||
fragments = []
|
||||
currentBrush = AsciiIo.Brush.default()
|
||||
currentText = ''
|
||||
|
||||
for i in [0...@cols]
|
||||
d = data[i]
|
||||
|
||||
if d
|
||||
[char, brush] = d
|
||||
else
|
||||
[char, brush] = [' ', currentBrush]
|
||||
|
||||
if brush == currentBrush
|
||||
currentText += char
|
||||
else
|
||||
fragments.push([currentText, currentBrush]) if currentText.length > 0
|
||||
currentText = char
|
||||
currentBrush = brush
|
||||
|
||||
fragments.push([currentText, currentBrush]) if currentText.length > 0
|
||||
changes[n] = fragments
|
||||
|
||||
changes
|
||||
|
||||
clearChanges: ->
|
||||
@dirtyLines = {}
|
||||
|
||||
getLine: (n = @cursorY) ->
|
||||
throw "cant getLine " + n if n >= @lines
|
||||
|
||||
line = @lineData[n]
|
||||
|
||||
if typeof line is "undefined"
|
||||
line = @lineData[n] = []
|
||||
@fill n, 0, @cols, " "
|
||||
|
||||
line
|
||||
|
||||
_addEmptyLine: (l) ->
|
||||
@lineData.splice l, 0, []
|
||||
@clearLineData l
|
||||
|
||||
_removeLine: (l) ->
|
||||
@lineData.splice l, 1
|
||||
|
||||
reserveCharacters: (n) ->
|
||||
line = @getLine()
|
||||
@lineData[@cursorY] = line.slice(0, @cursorX).concat(" ".times(n).split(""), line.slice(@cursorX, @cols - n))
|
||||
@updateLine()
|
||||
|
||||
fill: (line, col, n, char, brush=@brush) ->
|
||||
lineArr = @getLine(line)
|
||||
|
||||
i = 0
|
||||
while i < n
|
||||
lineArr[col + i] = [char, brush]
|
||||
i++
|
||||
|
||||
deleteCharacters: (n) ->
|
||||
line = @getLine()
|
||||
brush = line[line.length-1][1]
|
||||
line.splice(@cursorX, n)
|
||||
@fill(@cursorY, @cols - n, n, ' ', brush)
|
||||
@updateLine()
|
||||
|
||||
print: (text) ->
|
||||
text = Utf8.decode(text)
|
||||
|
||||
startLine = @cursorY
|
||||
|
||||
i = 0
|
||||
while i < text.length
|
||||
if @cursorX >= @cols
|
||||
@goToNextRowFirstColumn()
|
||||
|
||||
@fill @cursorY, @cursorX, 1, @charsetModifier(text[i])
|
||||
@cursorX += 1
|
||||
i++
|
||||
|
||||
endLine = @cursorY
|
||||
|
||||
@updateLines(startLine, endLine)
|
||||
|
||||
clearLineData: (n) ->
|
||||
@fill n, 0, @cols, " "
|
||||
|
||||
# ----- Cursor control
|
||||
|
||||
correctCursorPos: ->
|
||||
if @cursorX < 0
|
||||
@cursorX = 0
|
||||
|
||||
if @cursorX >= @cols
|
||||
@cursorX = @cols - 1
|
||||
|
||||
if @cursorY < 0
|
||||
@cursorY = 0
|
||||
|
||||
if @cursorY >= @lines
|
||||
@cursorY = @lines - 1
|
||||
|
||||
priorRow: (n = 1) ->
|
||||
for i in [0...n]
|
||||
if @cursorY > 0
|
||||
@cursorY -= 1
|
||||
@updateLine @cursorY
|
||||
@updateLine @cursorY + 1
|
||||
|
||||
nextRow: (n = 1) ->
|
||||
for i in [0...n]
|
||||
if @cursorY + 1 < @lines
|
||||
@cursorY += 1
|
||||
@updateLine @cursorY - 1
|
||||
@updateLine @cursorY
|
||||
|
||||
nextColumn: (n = 1) ->
|
||||
@_cursorRight() for i in [0...n]
|
||||
|
||||
priorColumn: (n = 1) ->
|
||||
@_cursorLeft() for i in [0...n]
|
||||
|
||||
_cursorLeft: ->
|
||||
if @cursorX > 0
|
||||
@cursorX -= 1
|
||||
@updateLine()
|
||||
|
||||
_cursorRight: ->
|
||||
if @cursorX < @cols - 1
|
||||
@cursorX += 1
|
||||
@updateLine()
|
||||
|
||||
priorRowFirstColumn: (n = 1) ->
|
||||
@goToFirstColumn()
|
||||
@priorRow n
|
||||
|
||||
nextRowFirstColumn: (n = 1) ->
|
||||
@goToFirstColumn()
|
||||
@nextRow n
|
||||
|
||||
goToColumn: (col = 1) ->
|
||||
@cursorX = col - 1
|
||||
@correctCursorPos()
|
||||
@updateLine()
|
||||
|
||||
goToRow: (line = 1) ->
|
||||
oldLine = @cursorY
|
||||
@cursorY = line - 1
|
||||
@correctCursorPos()
|
||||
|
||||
if oldLine != @cursorY
|
||||
@updateLine oldLine
|
||||
|
||||
@updateLine()
|
||||
|
||||
goToRowAndColumn: (line = 1, col = 1) ->
|
||||
@goToRow line
|
||||
@goToColumn col
|
||||
|
||||
goToNextHorizontalTabStop: (n) ->
|
||||
x = @tabStops.next(@cursorX)
|
||||
@goToRowAndColumn(@cursorY + 1, x + 1)
|
||||
@updateLine()
|
||||
|
||||
goToPriorHorizontalTabStop: (n) ->
|
||||
x = @tabStops.prev(@cursorX)
|
||||
@goToRowAndColumn(@cursorY + 1, x + 1)
|
||||
@updateLine()
|
||||
|
||||
clearHorizontalTabStop: ->
|
||||
console.log 'clearHorizontalTabStop'
|
||||
|
||||
clearAllHorizontalTabStops: ->
|
||||
console.log 'clearAllHorizontalTabStops'
|
||||
|
||||
saveCursor: ->
|
||||
@savedCol = @cursorX
|
||||
@savedLine = @cursorY
|
||||
|
||||
restoreCursor: ->
|
||||
oldLine = @cursorY
|
||||
|
||||
@cursorY = @savedLine
|
||||
@cursorX = @savedCol
|
||||
|
||||
@updateLine oldLine
|
||||
@updateLine()
|
||||
|
||||
goToFirstColumn: ->
|
||||
@cursorX = 0
|
||||
@updateLine()
|
||||
|
||||
# ----- Attribute control
|
||||
|
||||
setBrush: (brush) ->
|
||||
@brush = brush
|
||||
|
||||
getBrush: ->
|
||||
@brush
|
||||
|
||||
saveBrush: ->
|
||||
@savedBrush = @brush
|
||||
|
||||
restoreBrush: ->
|
||||
@brush = @savedBrush
|
||||
|
||||
repeatLastCharacter: (n = 1) ->
|
||||
|
||||
updateBrush: (changes) ->
|
||||
@brush = @brush.applyChanges changes
|
||||
|
||||
# ----- Scroll control
|
||||
|
||||
inScrollRegion: ->
|
||||
@cursorY >= @topMargin() and @cursorY <= @bottomMargin()
|
||||
|
||||
scrollUp: (n = 1) ->
|
||||
@insertLines n, @topMargin()
|
||||
|
||||
scrollDown: (n = 1) ->
|
||||
@deleteLines n, @topMargin()
|
||||
|
||||
insertLines: (n, l = @cursorY) ->
|
||||
return unless @inScrollRegion()
|
||||
|
||||
i = 0
|
||||
while i < n
|
||||
@_removeLine @bottomMargin()
|
||||
@_addEmptyLine l
|
||||
i++
|
||||
|
||||
@updateLines(l, @bottomMargin())
|
||||
|
||||
deleteLines: (n, l = @cursorY) ->
|
||||
return unless @inScrollRegion()
|
||||
|
||||
i = 0
|
||||
while i < n
|
||||
@_removeLine l
|
||||
@_addEmptyLine @bottomMargin()
|
||||
i++
|
||||
|
||||
@updateLines(l, @bottomMargin())
|
||||
|
||||
goToPriorRow: ->
|
||||
if @cursorY is @topMargin()
|
||||
@scrollUp()
|
||||
else
|
||||
@priorRow()
|
||||
|
||||
goToNextRow: ->
|
||||
if @cursorY is @bottomMargin()
|
||||
@scrollDown()
|
||||
else
|
||||
@nextRow()
|
||||
|
||||
goToNextRowFirstColumn: ->
|
||||
@goToFirstColumn()
|
||||
@goToNextRow()
|
||||
|
||||
setLineWrap: (linewrap) ->
|
||||
|
||||
# ----- Erase control
|
||||
|
||||
eraseScreen: ->
|
||||
l = 0
|
||||
while l < @lines
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
eraseFromScreenStart: ->
|
||||
l = 0
|
||||
while l < @cursorY
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
@eraseFromRowStart()
|
||||
|
||||
eraseToScreenEnd: ->
|
||||
@eraseToRowEnd()
|
||||
|
||||
l = @cursorY + 1
|
||||
while l < @lines
|
||||
@clearLineData l
|
||||
@updateLine l
|
||||
l++
|
||||
|
||||
eraseRow: ->
|
||||
@fill @cursorY, 0, @cols, " "
|
||||
@updateLine()
|
||||
|
||||
eraseFromRowStart: ->
|
||||
@fill @cursorY, 0, @cursorX, " "
|
||||
@updateLine()
|
||||
|
||||
eraseToRowEnd: ->
|
||||
@fill @cursorY, @cursorX, @cols - @cursorX, " "
|
||||
@updateLine()
|
||||
|
||||
eraseCharacters: (n = 1) ->
|
||||
@fill @cursorY, @cursorX, n, " "
|
||||
@updateLine()
|
||||
|
||||
# ------ Charset control
|
||||
|
||||
setCharset: (charset) ->
|
||||
@charset = charset
|
||||
|
||||
switch charset
|
||||
when 'uk'
|
||||
@charsetModifier = @ukCharsetModifier
|
||||
when 'us'
|
||||
@charsetModifier = @usCharsetModifier
|
||||
when 'special'
|
||||
@charsetModifier = @specialCharsetModifier
|
||||
|
||||
getCharset: ->
|
||||
@charset
|
||||
|
||||
usCharsetModifier: (char) ->
|
||||
char
|
||||
|
||||
ukCharsetModifier: (char) ->
|
||||
char
|
||||
|
||||
specialCharsetModifier: (char) ->
|
||||
AsciiIo.SpecialCharset[char.charCodeAt(0)] or char
|
||||
|
@ -1,22 +0,0 @@
|
||||
class AsciiIo.ScrollRegion
|
||||
|
||||
constructor: (@top, @bottom) ->
|
||||
|
||||
setTop: (@top) ->
|
||||
|
||||
setBottom: (@bottom) ->
|
||||
|
||||
getTop: ->
|
||||
@top
|
||||
|
||||
getBottom: ->
|
||||
@bottom
|
||||
|
||||
save: ->
|
||||
@savedTop = @top
|
||||
@savedBottom = @bottom
|
||||
|
||||
restore: ->
|
||||
@top = @savedTop
|
||||
@bottom = @savedBottom
|
||||
|
@ -1,57 +0,0 @@
|
||||
class AsciiIo.SgrInterpreter
|
||||
|
||||
parse: (numbers) ->
|
||||
changes = {}
|
||||
|
||||
i = 0
|
||||
while i < numbers.length
|
||||
n = numbers[i]
|
||||
|
||||
if n is 0
|
||||
changes.fg = undefined
|
||||
changes.bg = undefined
|
||||
changes.blink = false
|
||||
changes.bright = false
|
||||
changes.italic = false
|
||||
changes.underline = false
|
||||
changes.reverse = false
|
||||
else if n is 1
|
||||
changes.bright = true
|
||||
else if n is 3
|
||||
changes.italic = true
|
||||
else if n is 4
|
||||
changes.underline = true
|
||||
else if n is 5
|
||||
changes.blink = true
|
||||
else if n is 7
|
||||
changes.reverse = true
|
||||
else if n is 23
|
||||
changes.italic = false
|
||||
else if n is 24
|
||||
changes.underline = false
|
||||
else if n is 25
|
||||
changes.blink = false
|
||||
else if n is 27
|
||||
changes.reverse = false
|
||||
else if n >= 30 and n <= 37
|
||||
changes.fg = n - 30
|
||||
else if n is 38
|
||||
changes.fg = numbers[i + 2]
|
||||
i += 2
|
||||
else if n is 39
|
||||
changes.fg = undefined
|
||||
else if n >= 40 and n <= 47
|
||||
changes.bg = n - 40
|
||||
else if n is 48
|
||||
changes.bg = numbers[i + 2]
|
||||
i += 2
|
||||
else if n is 49
|
||||
changes.bg = undefined
|
||||
else if n >= 90 and n <= 97
|
||||
changes.fg = n - 90
|
||||
else if n >= 100 and n <= 107
|
||||
changes.bg = n - 100
|
||||
|
||||
i++
|
||||
|
||||
changes
|
@ -1,95 +0,0 @@
|
||||
AsciiIo.SpecialCharset =
|
||||
33: "!"
|
||||
34: '"'
|
||||
35: "#"
|
||||
36: "$"
|
||||
37: "%"
|
||||
38: "&"
|
||||
39: "'"
|
||||
40: "("
|
||||
41: ")"
|
||||
42: "*"
|
||||
43: "+"
|
||||
44: ","
|
||||
45: "-"
|
||||
46: "."
|
||||
47: "/"
|
||||
48: "0"
|
||||
49: "1"
|
||||
50: "2"
|
||||
51: "3"
|
||||
52: "4"
|
||||
53: "5"
|
||||
54: "6"
|
||||
55: "7"
|
||||
56: "8"
|
||||
57: "9"
|
||||
58: ":"
|
||||
59: ";"
|
||||
60: "<"
|
||||
61: "="
|
||||
62: ">"
|
||||
63: "?"
|
||||
64: "@"
|
||||
65: "↑"
|
||||
66: "↓"
|
||||
67: "→"
|
||||
68: "←"
|
||||
69: "█"
|
||||
70: "▚"
|
||||
71: "☃"
|
||||
72: "H"
|
||||
73: "I"
|
||||
74: "J"
|
||||
75: "K"
|
||||
76: "L"
|
||||
77: "M"
|
||||
78: "N"
|
||||
79: "O"
|
||||
80: "P"
|
||||
81: "Q"
|
||||
82: "R"
|
||||
83: "S"
|
||||
84: "T"
|
||||
85: "U"
|
||||
86: "V"
|
||||
87: "W"
|
||||
88: "X"
|
||||
89: "Y"
|
||||
90: "Z"
|
||||
91: "["
|
||||
92: "\\"
|
||||
93: "]"
|
||||
94: "^"
|
||||
95: "_"
|
||||
96: "◆"
|
||||
97: "▒"
|
||||
98: "␉"
|
||||
99: "␌"
|
||||
100: "␍"
|
||||
101: "␊"
|
||||
102: "°"
|
||||
103: "±"
|
||||
104: ""
|
||||
105: "␋"
|
||||
106: "┘"
|
||||
107: "┐"
|
||||
108: "┌"
|
||||
109: "└"
|
||||
110: "┼"
|
||||
111: "⎺"
|
||||
112: "⎻"
|
||||
113: "─"
|
||||
114: "⎼"
|
||||
115: "⎽"
|
||||
116: "├"
|
||||
117: "┤"
|
||||
118: "┴"
|
||||
119: "┬"
|
||||
120: "│"
|
||||
121: "≤"
|
||||
122: "≥"
|
||||
123: "π"
|
||||
124: "≠"
|
||||
125: "£"
|
||||
126: "·"
|
@ -1,28 +0,0 @@
|
||||
class AsciiIo.TabStops
|
||||
|
||||
constructor: (@cols) ->
|
||||
@stops = (x for x in [0...@cols] when x % 8 is 0)
|
||||
|
||||
add: (col) ->
|
||||
unless _(@stops).include(col)
|
||||
pos = _(@stops).sortedIndex(col)
|
||||
@stops.splice(pos, 0, col)
|
||||
|
||||
next: (cursorX) ->
|
||||
for x in @stops
|
||||
if x > cursorX
|
||||
return x
|
||||
|
||||
@cols
|
||||
|
||||
prev: (cursorX) ->
|
||||
ret = 0
|
||||
|
||||
for x in @stops
|
||||
if x > cursorX
|
||||
break
|
||||
|
||||
ret = x
|
||||
|
||||
ret
|
||||
|
@ -1,234 +0,0 @@
|
||||
class AsciiIo.VT
|
||||
|
||||
constructor: (@cols, @lines) ->
|
||||
_.extend(this, Backbone.Events)
|
||||
@interpreter = new AsciiIo.AnsiInterpreter
|
||||
@reset()
|
||||
|
||||
feed: (data) ->
|
||||
@data += data
|
||||
[commands, @data] = @interpreter.parse @data
|
||||
|
||||
for command in commands
|
||||
[name, args...] = command
|
||||
@[name].apply this, args
|
||||
|
||||
@data.length is 0
|
||||
|
||||
reset: ->
|
||||
@data = ''
|
||||
@resetTerminal()
|
||||
|
||||
bell: ->
|
||||
@trigger 'bell'
|
||||
|
||||
state: ->
|
||||
changes: @buffer.changes()
|
||||
cursorX: @buffer.cursorX
|
||||
cursorY: @buffer.cursorY
|
||||
|
||||
clearChanges: ->
|
||||
@buffer.clearChanges()
|
||||
|
||||
# ==== Screen buffer operations
|
||||
|
||||
switchToNormalBuffer: ->
|
||||
@buffer = @normalBuffer
|
||||
@updateScreen()
|
||||
|
||||
switchToAlternateBuffer: ->
|
||||
@alternateBuffer.setBrush(@normalBuffer.getBrush())
|
||||
@alternateBuffer.setCharset(@normalBuffer.getCharset())
|
||||
@buffer = @alternateBuffer
|
||||
@updateScreen()
|
||||
|
||||
updateScreen: ->
|
||||
@buffer.updateScreen()
|
||||
|
||||
carriageReturn: ->
|
||||
@buffer.goToFirstColumn()
|
||||
|
||||
backspace: ->
|
||||
@buffer.priorColumn()
|
||||
|
||||
# === ANSI handlers
|
||||
|
||||
# ------ Cursor control
|
||||
|
||||
showCursor: ->
|
||||
@trigger 'cursor-visibility', true
|
||||
|
||||
hideCursor: ->
|
||||
@trigger 'cursor-visibility', false
|
||||
|
||||
# ----- Scroll control
|
||||
|
||||
reverseIndex: ->
|
||||
@buffer.goToPriorRow()
|
||||
|
||||
lineFeed: ->
|
||||
@buffer.goToNextRow()
|
||||
|
||||
verticalTab: ->
|
||||
@buffer.goToNextRow()
|
||||
|
||||
formFeed: ->
|
||||
@buffer.goToNextRow()
|
||||
|
||||
index: ->
|
||||
@buffer.goToNextRow()
|
||||
|
||||
newLine: ->
|
||||
@buffer.goToNextRowFirstColumn()
|
||||
|
||||
|
||||
# === Commands
|
||||
|
||||
# ----- Scroll control
|
||||
|
||||
setScrollRegion: (top, bottom) ->
|
||||
if top < 1
|
||||
top = 1
|
||||
|
||||
if bottom > @lines
|
||||
bottom = @lines
|
||||
|
||||
if bottom > top
|
||||
@scrollRegion.setTop(top - 1)
|
||||
@scrollRegion.setBottom(bottom - 1)
|
||||
|
||||
setHorizontalTabStop: ->
|
||||
@tabStops.add(@cursorX)
|
||||
|
||||
# ----- Terminal control
|
||||
|
||||
resetTerminal: ->
|
||||
@scrollRegion = new AsciiIo.ScrollRegion(0, @lines - 1)
|
||||
@tabStops = new AsciiIo.TabStops(@cols)
|
||||
|
||||
@normalBuffer = new AsciiIo.ScreenBuffer(@cols, @lines, @scrollRegion, @tabStops)
|
||||
@alternateBuffer = new AsciiIo.ScreenBuffer(@cols, @lines, @scrollRegion, @tabStops)
|
||||
@buffer = @normalBuffer
|
||||
|
||||
@fg = @bg = undefined
|
||||
@bright = false
|
||||
@underline = false
|
||||
@italic = false
|
||||
|
||||
@updateScreen()
|
||||
|
||||
saveTerminalState: ->
|
||||
@buffer.saveCursor()
|
||||
@scrollRegion.save()
|
||||
@buffer.saveBrush()
|
||||
|
||||
restoreTerminalState: ->
|
||||
@buffer.restoreBrush()
|
||||
@scrollRegion.restore()
|
||||
@buffer.restoreCursor()
|
||||
|
||||
reportRowAndColumn: ->
|
||||
|
||||
setUkCharset: ->
|
||||
@buffer.setCharset('uk')
|
||||
|
||||
setUsCharset: ->
|
||||
@buffer.setCharset('us')
|
||||
|
||||
setSpecialCharset: ->
|
||||
@buffer.setCharset('special')
|
||||
|
||||
print: (text) ->
|
||||
@buffer.print text
|
||||
|
||||
reserveCharacters: (n) ->
|
||||
@buffer.reserveCharacters n
|
||||
|
||||
priorRow: (n) ->
|
||||
@buffer.priorRow n
|
||||
|
||||
nextRow: (n) ->
|
||||
@buffer.nextRow n
|
||||
|
||||
nextColumn: (n) ->
|
||||
@buffer.nextColumn n
|
||||
|
||||
priorColumn: (n) ->
|
||||
@buffer.priorColumn n
|
||||
|
||||
nextRowFirstColumn: (n) ->
|
||||
@buffer.nextRowFirstColumn n
|
||||
|
||||
priorRowFirstColumn: (n) ->
|
||||
@buffer.priorRowFirstColumn n
|
||||
|
||||
goToColumn: (n) ->
|
||||
@buffer.goToColumn n
|
||||
|
||||
goToRowAndColumn: (n, m) ->
|
||||
@buffer.goToRowAndColumn n, m
|
||||
|
||||
goToNextHorizontalTabStop: (n = 1) ->
|
||||
@buffer.goToNextHorizontalTabStop n
|
||||
|
||||
eraseScreen: ->
|
||||
@buffer.eraseScreen()
|
||||
|
||||
eraseFromScreenStart: ->
|
||||
@buffer.eraseFromScreenStart()
|
||||
|
||||
eraseToScreenEnd: ->
|
||||
@buffer.eraseToScreenEnd()
|
||||
|
||||
eraseRow: ->
|
||||
@buffer.eraseRow()
|
||||
|
||||
eraseFromRowStart: ->
|
||||
@buffer.eraseFromRowStart()
|
||||
|
||||
eraseToRowEnd: ->
|
||||
@buffer.eraseToRowEnd()
|
||||
|
||||
insertLines: (n) ->
|
||||
@buffer.insertLines n
|
||||
|
||||
deleteLines: (n) ->
|
||||
@buffer.deleteLines n
|
||||
|
||||
deleteCharacters: (n) ->
|
||||
@buffer.deleteCharacters n
|
||||
|
||||
scrollUp: (n) ->
|
||||
@buffer.scrollUp n
|
||||
|
||||
scrollDown: (n) ->
|
||||
@buffer.scrollDown n
|
||||
|
||||
eraseCharacters: (n) ->
|
||||
@buffer.eraseCharacters n
|
||||
|
||||
goToPriorHorizontalTabStop: (n) ->
|
||||
@buffer.goToPriorHorizontalTabStop n
|
||||
|
||||
repeatLastCharacter: (n) ->
|
||||
@buffer.repeatLastCharacter n
|
||||
|
||||
goToRow: (n) ->
|
||||
@buffer.goToRow n
|
||||
|
||||
clearHorizontalTabStop: ->
|
||||
@buffer.clearHorizontalTabStop()
|
||||
|
||||
clearAllHorizontalTabStops: ->
|
||||
@buffer.clearAllHorizontalTabStops()
|
||||
|
||||
updateBrush: (attrs) ->
|
||||
@buffer.updateBrush attrs
|
||||
|
||||
# References:
|
||||
# http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
# http://ttssh2.sourceforge.jp/manual/en/about/ctrlseq.html
|
||||
# http://real-world-systems.com/docs/ANSIcode.html
|
||||
# http://www.shaels.net/index.php/propterm/documents
|
||||
# http://manpages.ubuntu.com/manpages/lucid/man7/urxvt.7.html
|
||||
# http://vt100.net/docs/vt102-ug/chapter5.html
|
@ -1,15 +1,7 @@
|
||||
class AsciicastSerializer
|
||||
|
||||
def initialize(asciicast)
|
||||
@asciicast = asciicast
|
||||
end
|
||||
|
||||
def as_json(*)
|
||||
asciicast.as_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :asciicast
|
||||
class AsciicastSerializer < ActiveModel::Serializer
|
||||
self.root = false
|
||||
|
||||
attributes :id, :duration, :stdout_frames_url, :snapshot
|
||||
attribute :terminal_columns, key: :width
|
||||
attribute :terminal_lines, key: :height
|
||||
end
|
||||
|
@ -1,53 +0,0 @@
|
||||
class AsciicastStreamer
|
||||
|
||||
delegate :each, :to => :json_streamer
|
||||
|
||||
def initialize(asciicast)
|
||||
build_json_streamer(asciicast)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :json_streamer
|
||||
|
||||
def build_json_streamer(asciicast)
|
||||
attributes = attributes_for_streaming(asciicast)
|
||||
@json_streamer = JsonStreamer.new(attributes)
|
||||
end
|
||||
|
||||
def attributes_for_streaming(asciicast)
|
||||
attributes = AsciicastSerializer.new(asciicast).as_json
|
||||
saved_time = prepare_stdout(attributes, asciicast)
|
||||
prepare_duration(attributes, asciicast, saved_time)
|
||||
|
||||
attributes
|
||||
end
|
||||
|
||||
def prepare_stdout(attributes, asciicast)
|
||||
saved_time = 0
|
||||
|
||||
attributes['stdout'] = lambda do |&blk|
|
||||
blk.call('[')
|
||||
|
||||
asciicast.stdout.each do |delay, frame_bytes|
|
||||
if asciicast.time_compression && delay > Asciicast::MAX_DELAY
|
||||
saved_time += (delay - Asciicast::MAX_DELAY)
|
||||
delay = Asciicast::MAX_DELAY
|
||||
end
|
||||
|
||||
blk.call(%([#{delay},#{frame_bytes.bytes.to_a}],))
|
||||
end
|
||||
|
||||
blk.call('[]]')
|
||||
end
|
||||
|
||||
saved_time
|
||||
end
|
||||
|
||||
def prepare_duration(attributes, asciicast, saved_time)
|
||||
attributes['duration'] = lambda do |&blk|
|
||||
blk.call(asciicast.duration - saved_time)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -1,28 +0,0 @@
|
||||
class JsonStreamer
|
||||
|
||||
def initialize(object)
|
||||
@object = object
|
||||
end
|
||||
|
||||
def each(&blk)
|
||||
yield '{'
|
||||
|
||||
@object.each_with_index do |key_value, i|
|
||||
key, value = key_value
|
||||
yield %("#{key}":)
|
||||
|
||||
if value.respond_to?(:call)
|
||||
value.call do |v|
|
||||
yield v.to_s
|
||||
end
|
||||
else
|
||||
yield value.to_json
|
||||
end
|
||||
|
||||
yield ',' unless i + 1 == @object.size
|
||||
end
|
||||
|
||||
yield '}'
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,7 @@
|
||||
class StdoutFramesUploader < BaseUploader
|
||||
|
||||
def filename
|
||||
'stdout.json' if original_filename.present?
|
||||
end
|
||||
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
<div class="player"></div>
|
||||
<script>
|
||||
$(function() {
|
||||
var playerClass = <%= player_class.html_safe %>;
|
||||
window.player = new playerClass({
|
||||
el: $('.player'),
|
||||
cols: <%= cols %>,
|
||||
lines: <%= lines %>,
|
||||
speed: <%= speed %>,
|
||||
benchmark: <%= benchmark %>,
|
||||
model: new AsciiIo.Asciicast({ id: <%= asciicast_id %> }),
|
||||
containerWidth: <%= container_width || 'null' %>,
|
||||
rendererClass: <%= renderer_class.html_safe %>,
|
||||
autoPlay: <%= auto_play %>,
|
||||
hud: <%= hud %>,
|
||||
snapshot: <%= snapshot.html_safe %>
|
||||
});
|
||||
});
|
||||
</script>
|
@ -0,0 +1,16 @@
|
||||
.player
|
||||
|
||||
javascript:
|
||||
$(function() {
|
||||
var playerClass = #{player_class.html_safe};
|
||||
window.player = new playerClass({
|
||||
el: $('.player'),
|
||||
speed: #{speed},
|
||||
benchmark: #{benchmark},
|
||||
model: new AsciiIo.Asciicast(#{asciicast.html_safe}),
|
||||
containerWidth: #{container_width || 'null'},
|
||||
rendererClass: #{renderer_class.html_safe},
|
||||
autoPlay: #{auto_play},
|
||||
hud: #{hud}
|
||||
});
|
||||
});
|
@ -1,593 +0,0 @@
|
||||
describe 'AsciiIo.AnsiInterpreter', ->
|
||||
data = interpreter = calls = checkNumber = undefined
|
||||
|
||||
parse = (data, expectedRest) ->
|
||||
[commands, rest] = interpreter.parse data
|
||||
|
||||
calls = commands
|
||||
checkNumber = 0
|
||||
|
||||
if arguments.length is 2
|
||||
expect(rest).toEqual expectedRest
|
||||
|
||||
expectCall = (args...) ->
|
||||
n = checkNumber
|
||||
checkNumber += 1
|
||||
# console.log calls[n]
|
||||
expect(calls[n]).toEqual args
|
||||
|
||||
expectNoCall = ->
|
||||
# console.log calls[0]
|
||||
expect(calls.length).toEqual 0
|
||||
|
||||
isSwallowed = (d = data) ->
|
||||
parse d, ''
|
||||
expectNoCall()
|
||||
|
||||
beforeEach ->
|
||||
calls = []
|
||||
interpreter = new AsciiIo.AnsiInterpreter
|
||||
|
||||
describe '#parse', ->
|
||||
|
||||
it 'returns not parsed data back', ->
|
||||
parse '\x1b', '\x1b'
|
||||
expectNoCall()
|
||||
|
||||
it 'returns empty string if all data was parsed', ->
|
||||
parse 'abc def', ''
|
||||
|
||||
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 'x07', ->
|
||||
it 'calls bell', ->
|
||||
parse '\x07'
|
||||
expectCall 'bell'
|
||||
|
||||
describe 'x08', ->
|
||||
it 'calls backspace', ->
|
||||
parse '\x08'
|
||||
expectCall 'backspace'
|
||||
|
||||
describe 'x09', ->
|
||||
it 'calls goToNextHorizontalTabStop', ->
|
||||
parse '\x09'
|
||||
expectCall 'goToNextHorizontalTabStop'
|
||||
|
||||
describe 'x0a', ->
|
||||
it 'calls lineFeed', ->
|
||||
parse '\x0a'
|
||||
expectCall 'lineFeed'
|
||||
|
||||
describe 'x0b', ->
|
||||
it 'calls verticalTab', ->
|
||||
parse '\x0b'
|
||||
expectCall 'verticalTab'
|
||||
|
||||
describe 'x0c', ->
|
||||
it 'calls formFeed', ->
|
||||
parse '\x0c'
|
||||
expectCall 'formFeed'
|
||||
|
||||
describe 'x0d', ->
|
||||
it 'calls carriageReturn', ->
|
||||
parse '\x0d'
|
||||
expectCall 'carriageReturn'
|
||||
|
||||
describe 'x84', ->
|
||||
it 'calls index', ->
|
||||
parse '\x84'
|
||||
expectCall 'index'
|
||||
|
||||
describe 'x85', ->
|
||||
it 'calls newLine', ->
|
||||
parse '\x85'
|
||||
expectCall 'newLine'
|
||||
|
||||
describe 'x88', ->
|
||||
it 'calls setHorizontalTabStop', ->
|
||||
parse '\x88'
|
||||
expectCall 'setHorizontalTabStop'
|
||||
|
||||
describe 'x8d', ->
|
||||
it 'calls reverseIndex', ->
|
||||
parse '\x8d'
|
||||
expectCall 'reverseIndex'
|
||||
|
||||
describe 'other', ->
|
||||
it "is swallowed", ->
|
||||
for c in ['\x00', '\x0e', '\x0f', '\x82', '\x94']
|
||||
isSwallowed c
|
||||
|
||||
describe 'printable character', ->
|
||||
|
||||
describe 'from ASCII range (0x20-0x7e)', ->
|
||||
it 'calls print', ->
|
||||
parse '\x20foobar\x7e', ''
|
||||
expectCall 'print', '\x20foobar\x7e'
|
||||
|
||||
describe 'from Unicode', ->
|
||||
it 'calls print', ->
|
||||
parse '\xe2ab\xe8ZZ', ''
|
||||
expectCall 'print', '\xe2ab\xe8ZZ'
|
||||
|
||||
parse '\xc2\x09\xc4b\xc5c', ''
|
||||
expectCall 'print', '\xc2\x09\xc4b\xc5c'
|
||||
|
||||
parse '\xa1A\xc9\xfe', ''
|
||||
expectCall 'print', '\xa1A\xc9\xfe'
|
||||
|
||||
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 C0 control nested inside another escape sequence', ->
|
||||
# C0 Control = 00-1F
|
||||
# Interpret the character, then resume processing the sequence.
|
||||
# Example: CR, LF, XON, and XOFF work as normal within an ESCape
|
||||
# sequence.
|
||||
|
||||
# it 'stops interpreting current seq and handles nested C0 control char', ->
|
||||
# data += '[1\x1b\x0dm'
|
||||
# parse data, ''
|
||||
# expectCall 'cr'
|
||||
# expectCall 'updateBrush', bright: true
|
||||
|
||||
describe 'with intermediate', ->
|
||||
# Intermediate = 20-2F !"#$%&'()*+,-./
|
||||
# Expect zero or more intermediates, a parameter terminates a private
|
||||
# function, an alphabetic terminates a standard sequence. Example: ESC
|
||||
# ( A defines standard character set, ESC ( 0 a DEC set.
|
||||
|
||||
describe '(', ->
|
||||
|
||||
beforeEach ->
|
||||
data += '('
|
||||
|
||||
describe '0', ->
|
||||
|
||||
beforeEach ->
|
||||
data += '0'
|
||||
|
||||
it 'calls setSpecialCharset', ->
|
||||
parse data
|
||||
expectCall 'setSpecialCharset'
|
||||
|
||||
describe 'A', ->
|
||||
|
||||
beforeEach ->
|
||||
data += 'A'
|
||||
|
||||
it 'calls setUkCharset', ->
|
||||
parse data
|
||||
expectCall 'setUkCharset'
|
||||
|
||||
describe 'B', ->
|
||||
|
||||
beforeEach ->
|
||||
data += 'B'
|
||||
|
||||
it 'calls setUsCharset', ->
|
||||
parse data
|
||||
expectCall 'setUsCharset'
|
||||
|
||||
describe 'followed by parameter', ->
|
||||
# private function
|
||||
|
||||
describe 'followed by an alphabetic', ->
|
||||
# standard sequence
|
||||
|
||||
describe 'with parameter', ->
|
||||
# Parameter = 30-3F 0123456789:;<=>?
|
||||
# End of a private 2-character escape sequence. Example: ESC = sets
|
||||
# special keypad mode, ESC > clears it.
|
||||
|
||||
describe '7', ->
|
||||
beforeEach ->
|
||||
data += '7'
|
||||
|
||||
it 'saves terminal state', ->
|
||||
parse data
|
||||
expectCall 'saveTerminalState'
|
||||
|
||||
describe '8', ->
|
||||
beforeEach ->
|
||||
data += '8'
|
||||
|
||||
it 'restores terminal state', ->
|
||||
parse data
|
||||
expectCall 'restoreTerminalState'
|
||||
|
||||
describe '=', ->
|
||||
beforeEach ->
|
||||
data += '='
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
describe '>', ->
|
||||
beforeEach ->
|
||||
data += '>'
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
describe 'with uppercase', ->
|
||||
# Uppercase = 40-5F @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
|
||||
# Translate it into a C1 control character and act on it. Example: ESC
|
||||
# D does indexes down, ESC M indexes up. (CSI is special)
|
||||
|
||||
describe 'M', ->
|
||||
# Reverse Index, go up one line, reverse scroll if necessary
|
||||
|
||||
beforeEach ->
|
||||
data += 'M'
|
||||
|
||||
it 'goes up 1 line', ->
|
||||
parse data
|
||||
expectCall 'reverseIndex'
|
||||
|
||||
describe 'P', ->
|
||||
# Device Control String, terminated by ST
|
||||
|
||||
beforeEach ->
|
||||
data += 'Pfoobar\\'
|
||||
|
||||
# it 'is swallowed', isSwallowed
|
||||
|
||||
describe ']', ->
|
||||
# Operating system command
|
||||
|
||||
describe '0;...BELL', ->
|
||||
beforeEach ->
|
||||
data += ']0;foobar\x07'
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
describe '1;...BELL', ->
|
||||
beforeEach ->
|
||||
data += ']1;foobar\x07'
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
describe '2;...BELL', ->
|
||||
beforeEach ->
|
||||
data += ']2;foobar\x07'
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
|
||||
describe 'with lowercase', ->
|
||||
# Lowercase = 60-7E `abcdefghijlkmnopqrstuvwxyz{|}~
|
||||
# End of a standard 2-character escape sequence. Example: ESC c resets
|
||||
# the terminal.
|
||||
|
||||
describe 'with delete', ->
|
||||
# Delete = 7F
|
||||
# Ignore it, and continue interpreting the ESCape sequence C1 and G1:
|
||||
# Treat the same as their 7-bit counterparts
|
||||
|
||||
describe 'with 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 '@', ->
|
||||
it 'calls reserveCharacters', ->
|
||||
data += '3@'
|
||||
parse data
|
||||
expectCall 'reserveCharacters', 3
|
||||
|
||||
describe 'A', ->
|
||||
it 'calls priorRow(1) if no number given', ->
|
||||
data += 'A'
|
||||
parse data
|
||||
expectCall 'priorRow', 1
|
||||
|
||||
it 'calls priorRow(n) if number given', ->
|
||||
data += '3A'
|
||||
parse data
|
||||
expectCall 'priorRow', 3
|
||||
|
||||
describe 'B', ->
|
||||
it 'calls nextRow(1) if no number given', ->
|
||||
data += 'B'
|
||||
parse data
|
||||
expectCall 'nextRow', 1
|
||||
|
||||
it 'calls nextRow(n) if number given', ->
|
||||
data += '3B'
|
||||
parse data
|
||||
expectCall 'nextRow', 3
|
||||
|
||||
describe 'C', ->
|
||||
it 'calls nextColumn(1) if no number given', ->
|
||||
data += 'C'
|
||||
parse data
|
||||
expectCall 'nextColumn', 1
|
||||
|
||||
it 'calls nextColumn(n) if number given', ->
|
||||
data += '3C'
|
||||
parse data
|
||||
expectCall 'nextColumn', 3
|
||||
|
||||
describe 'D', ->
|
||||
it 'calls priorColumn(1) if no number given', ->
|
||||
data += 'D'
|
||||
parse data
|
||||
expectCall 'priorColumn', 1
|
||||
|
||||
it 'calls priorColumn(n) if number given', ->
|
||||
data += '3D'
|
||||
parse data
|
||||
expectCall 'priorColumn', 3
|
||||
|
||||
describe 'E', ->
|
||||
it 'calls nextRowFirstColumn(n)', ->
|
||||
data += '3E'
|
||||
parse data
|
||||
expectCall 'nextRowFirstColumn', 3
|
||||
|
||||
describe 'F', ->
|
||||
it 'calls priorRowFirstColumn(n)', ->
|
||||
data += '3F'
|
||||
parse data
|
||||
expectCall 'priorRowFirstColumn', 3
|
||||
|
||||
describe 'G', ->
|
||||
it 'calls goToColumn(n)', ->
|
||||
data += '3G'
|
||||
parse data
|
||||
expectCall 'goToColumn', 3
|
||||
|
||||
describe 'H', ->
|
||||
it 'calls goToRowAndColumn(n, m) when n and m given', ->
|
||||
data += '3;4H'
|
||||
parse data
|
||||
expectCall 'goToRowAndColumn', 3, 4
|
||||
|
||||
it 'calls goToRowAndColumn(1, m) when no n given', ->
|
||||
data += ';3H'
|
||||
parse data
|
||||
expectCall 'goToRowAndColumn', 1, 3
|
||||
|
||||
it 'calls goToRowAndColumn(n, 1) when no m given', ->
|
||||
data += '3;H'
|
||||
parse data
|
||||
expectCall 'goToRowAndColumn', 3, 1
|
||||
|
||||
it 'calls goToRowAndColumn(n, 1) when no m given (no semicolon)', ->
|
||||
data += '3H'
|
||||
parse data
|
||||
expectCall 'goToRowAndColumn', 3, 1
|
||||
|
||||
it 'calls goToRowAndColumn(1, 1) when no n nor m given', ->
|
||||
data += 'H'
|
||||
parse data
|
||||
expectCall 'goToRowAndColumn', 1, 1
|
||||
|
||||
describe 'I', ->
|
||||
it 'calls goToNextHorizontalTabStop', ->
|
||||
data += '2I'
|
||||
parse data
|
||||
expectCall 'goToNextHorizontalTabStop', 2
|
||||
|
||||
describe 'J', ->
|
||||
it 'calls eraseToScreenEnd when no n given', ->
|
||||
data += 'J'
|
||||
parse data
|
||||
expectCall 'eraseToScreenEnd'
|
||||
|
||||
it 'calls eraseToScreenEnd when 0 given', ->
|
||||
data += '0J' # TODO check if it's 0 or 3
|
||||
parse data
|
||||
expectCall 'eraseToScreenEnd'
|
||||
|
||||
it 'calls eraseFromScreenStart when 1 given', ->
|
||||
data += '1J'
|
||||
parse data
|
||||
expectCall 'eraseFromScreenStart'
|
||||
|
||||
it 'calls eraseScreen when 2 given', ->
|
||||
data += '2J'
|
||||
parse data
|
||||
expectCall 'eraseScreen'
|
||||
|
||||
describe 'K', ->
|
||||
it 'calls eraseToRowEnd when no n given', ->
|
||||
data += 'K'
|
||||
parse data
|
||||
expectCall 'eraseToRowEnd'
|
||||
|
||||
it 'calls eraseToRowEnd when 0 given', ->
|
||||
data += '0K' # TODO: check if its 0 or 3
|
||||
parse data
|
||||
expectCall 'eraseToRowEnd'
|
||||
|
||||
it 'calls eraseFromRowStart when 1 given', ->
|
||||
data += '1K'
|
||||
parse data
|
||||
expectCall 'eraseFromRowStart'
|
||||
|
||||
it 'calls eraseRow when 2 given', ->
|
||||
data += '2K'
|
||||
parse data
|
||||
expectCall 'eraseRow'
|
||||
|
||||
describe 'L', ->
|
||||
it 'calls insertLines(1) when no n given', ->
|
||||
data += 'L'
|
||||
parse data
|
||||
expectCall 'insertLines', 1
|
||||
|
||||
it 'calls insertLines(n) when n given', ->
|
||||
data += '3L'
|
||||
parse data
|
||||
expectCall 'insertLines', 3
|
||||
|
||||
describe 'M', ->
|
||||
it 'calls deleteLines(1) when no n given', ->
|
||||
data += 'M'
|
||||
parse data
|
||||
expectCall 'deleteLines', 1
|
||||
|
||||
it 'calls deleteLines(n) when n given', ->
|
||||
data += '3M'
|
||||
parse data
|
||||
expectCall 'deleteLines', 3
|
||||
|
||||
describe 'P', ->
|
||||
it 'calls deleteCharacters(1) when no n given', ->
|
||||
data += 'P'
|
||||
parse data
|
||||
expectCall 'deleteCharacters', 1
|
||||
|
||||
it 'calls deleteCharacters(n) when n given', ->
|
||||
data += '3P'
|
||||
parse data
|
||||
expectCall 'deleteCharacters', 3
|
||||
|
||||
describe 'S', ->
|
||||
it 'calls scrollUp(n)', ->
|
||||
data += '4S'
|
||||
parse data
|
||||
expectCall 'scrollUp', 4
|
||||
|
||||
describe 'T', ->
|
||||
it 'calls scrollDown(n)', ->
|
||||
data += '4T'
|
||||
parse data
|
||||
expectCall 'scrollDown', 4
|
||||
|
||||
describe 'X', ->
|
||||
it 'calls eraseCharacters(n)', ->
|
||||
data += '4X'
|
||||
parse data
|
||||
expectCall 'eraseCharacters', 4
|
||||
|
||||
describe 'Z', ->
|
||||
it 'calls goToPriorHorizontalTabStop(n)', ->
|
||||
data += '5Z'
|
||||
parse data
|
||||
expectCall 'goToPriorHorizontalTabStop', 5
|
||||
|
||||
describe 'b', ->
|
||||
# TODO
|
||||
|
||||
describe 'c', ->
|
||||
beforeEach ->
|
||||
data += '>c'
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
describe 'd', ->
|
||||
it 'calls goToRow(n)', ->
|
||||
data += '3d'
|
||||
parse data
|
||||
expectCall 'goToRow', 3
|
||||
|
||||
describe 'f', ->
|
||||
# TODO
|
||||
|
||||
describe 'g', ->
|
||||
# TODO
|
||||
|
||||
describe 'l', ->
|
||||
# TODO
|
||||
|
||||
describe 'm', ->
|
||||
it 'calls handleSGR([n, m, ...]) when n and m given', ->
|
||||
data += '1;4;33m'
|
||||
spyOn interpreter, 'handleSGR'
|
||||
parse data
|
||||
expect(interpreter.handleSGR).toHaveBeenCalledWith([1, 4, 33])
|
||||
|
||||
it 'calls handleSGR([]) when no n nor m given', ->
|
||||
data += 'm'
|
||||
spyOn interpreter, 'handleSGR'
|
||||
parse data
|
||||
expect(interpreter.handleSGR).toHaveBeenCalledWith([])
|
||||
|
||||
describe 'n', ->
|
||||
# TODO
|
||||
|
||||
describe 'r', ->
|
||||
# TODO
|
||||
|
||||
describe 'from private standards', ->
|
||||
# first character after CSI is one of: " < = > (074-077 octal, 3C-3F )
|
||||
|
||||
describe 'DEC/xterm specific', ->
|
||||
describe '$~', ->
|
||||
beforeEach ->
|
||||
data += '$~'
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
describe '?', ->
|
||||
beforeEach ->
|
||||
data += '?'
|
||||
|
||||
describe '1h', ->
|
||||
beforeEach ->
|
||||
data += '1h'
|
||||
|
||||
it 'is swallowed', isSwallowed
|
||||
|
||||
describe '25h', ->
|
||||
beforeEach ->
|
||||
data += '25h'
|
||||
|
||||
it 'shows cursor', ->
|
||||
parse data
|
||||
expectCall 'showCursor'
|
||||
|
||||
describe '25l', ->
|
||||
beforeEach ->
|
||||
data += '25l'
|
||||
|
||||
it 'hides cursor', ->
|
||||
parse data
|
||||
expectCall 'hideCursor'
|
||||
|
||||
describe '47h', ->
|
||||
beforeEach ->
|
||||
data += '47h'
|
||||
|
||||
it 'switches to alternate buffer', ->
|
||||
parse data
|
||||
expectCall 'switchToAlternateBuffer'
|
||||
|
||||
describe '47l', ->
|
||||
beforeEach ->
|
||||
data += '47l'
|
||||
|
||||
it 'switches to normal buffer', ->
|
||||
parse data
|
||||
expectCall 'switchToNormalBuffer'
|
||||
|
||||
describe '1049h', ->
|
||||
beforeEach ->
|
||||
data += '1049h'
|
||||
|
||||
it 'saves cursor position, switches to alternate buffer and clear screen', ->
|
||||
parse data
|
||||
expectCall 'switchToAlternateBuffer'
|
||||
expectCall 'eraseScreen'
|
||||
|
||||
describe '1049l', ->
|
||||
beforeEach ->
|
||||
data += '1049l'
|
||||
|
||||
it 'clears screen, switches to normal buffer and restores cursor position', ->
|
||||
parse data
|
||||
expectCall 'eraseScreen'
|
||||
expectCall 'switchToNormalBuffer'
|
@ -1,70 +0,0 @@
|
||||
describe 'AsciiIo.SgrInterpreter', ->
|
||||
interpreter = new AsciiIo.SgrInterpreter
|
||||
|
||||
expectChange = (numbers, hash) ->
|
||||
attrs = interpreter.parse numbers
|
||||
expect(attrs).toEqual hash
|
||||
|
||||
describe '#parse', ->
|
||||
|
||||
it 'resets brush for 0', ->
|
||||
expectChange [0], AsciiIo.Brush.default().attributes()
|
||||
|
||||
it 'sets bright attr for 1', ->
|
||||
expectChange [1], bright: true
|
||||
|
||||
it 'sets italic attr for 3', ->
|
||||
expectChange [3], italic: true
|
||||
|
||||
it 'sets underline attr for 4', ->
|
||||
expectChange [4], underline: true
|
||||
|
||||
it 'sets blink attr for 5', ->
|
||||
expectChange [5], blink: true
|
||||
|
||||
it 'sets reverse attr for 7', ->
|
||||
expectChange [7], reverse: true
|
||||
|
||||
it 'unsets italic for 23', ->
|
||||
expectChange [23], italic: false
|
||||
|
||||
it 'unsets underline attr for 24', ->
|
||||
expectChange [24], underline: false
|
||||
|
||||
it 'unsets blink attr for 25', ->
|
||||
expectChange [25], blink: false
|
||||
|
||||
it 'unsets reverse attr for 27', ->
|
||||
expectChange [27], reverse: false
|
||||
|
||||
it 'sets foreground for 30-37', ->
|
||||
expectChange [30], fg: 0
|
||||
expectChange [32], fg: 2
|
||||
expectChange [37], fg: 7
|
||||
|
||||
it 'sets foreground for 38;5;x', ->
|
||||
expectChange [38, 5, 100], fg: 100
|
||||
|
||||
it 'resets foreground for 39', ->
|
||||
expectChange [39], fg: undefined
|
||||
|
||||
it 'sets background for 40-47', ->
|
||||
expectChange [40], bg: 0
|
||||
expectChange [44], bg: 4
|
||||
expectChange [47], bg: 7
|
||||
|
||||
it 'sets background for 48;5;x', ->
|
||||
expectChange [48, 5, 200], bg: 200
|
||||
|
||||
it 'resets background for 49', ->
|
||||
expectChange [49], bg: undefined
|
||||
|
||||
it 'sets foreground for 90-97', ->
|
||||
expectChange [90], fg: 0
|
||||
expectChange [93], fg: 3
|
||||
expectChange [97], fg: 7
|
||||
|
||||
it 'sets background for 100-107', ->
|
||||
expectChange [100], bg: 0
|
||||
expectChange [103], bg: 3
|
||||
expectChange [107], bg: 7
|
@ -0,0 +1,15 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe AsciicastSerializer do
|
||||
let(:serializer) { described_class.new(asciicast) }
|
||||
let(:asciicast) { create(:asciicast) }
|
||||
|
||||
describe '#to_json' do
|
||||
subject { JSON.parse(serializer.to_json) }
|
||||
|
||||
it { should eq({ "id" => 1, "duration" => 11.146430015563965,
|
||||
"stdout_frames_url" => nil, "snapshot" => nil,
|
||||
"width" => 96, "height" => 26 }) }
|
||||
end
|
||||
|
||||
end
|
@ -1,28 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe JsonStreamer do
|
||||
|
||||
let(:streamer) { JsonStreamer.new(object) }
|
||||
let(:object) { {
|
||||
:foo => 'bar',
|
||||
'baz' => 1337,
|
||||
'qux' => true,
|
||||
'arr' => [1, 2],
|
||||
'blk' => lambda { |&blk| blk['12.']; blk[3] }
|
||||
} }
|
||||
|
||||
describe '#each' do
|
||||
it 'calls supplied block with each key value pair' do
|
||||
expect { |b| streamer.each(&b) }.
|
||||
to yield_successive_args(
|
||||
'{',
|
||||
'"foo":', '"bar"', ',',
|
||||
'"baz":', '1337' , ',',
|
||||
'"qux":', 'true' , ',',
|
||||
'"arr":', '[1,2]', ',',
|
||||
'"blk":', '12.' , '3',
|
||||
'}')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue