Extract AnsiInterpreter from VT

openid
Marcin Kulik 12 years ago
parent d750bc1a45
commit 9e61d3d179

@ -5,6 +5,7 @@
//= require utf8
//= require extensions
//= require player/brush
//= require player/vt/ansi_interpreter
//= require_tree ./player/vt
//= require player/movie
//= require player/workers/main_worker

@ -9,6 +9,7 @@
//= require player/views/hud_view
//= require player/data_unpacker
//= require player/abstract_player
//= require player/vt/ansi_interpreter
//= require_tree ./player/vt
//= require player/movie
//= require player/player

@ -0,0 +1,277 @@
class AsciiIo.AnsiInterpreter
constructor: (@callback) ->
@cb = @callback
@sgrInterpreter = new AsciiIo.SgrInterpreter()
@reset()
reset: ->
@data = ''
parse: (data) ->
@data += data
while @data.length > 0
processed = @handleData @data
if processed is 0
# console.log "no kurwa: #{@formattedData(@data)}"
break
@data = @data.slice processed
@data
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]..|[\xc3-\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]
switch last
when "0"
if intermediate is '('
@cb 'setSpecialCharset'
when "7"
@cb 'saveTerminalState'
when "8"
@cb 'restoreTerminalState'
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 'insertCharacters', n
when "A"
@cb 'priorRow', n
when "B"
@cb 'nextRow', n
when "C"
@cb 'nextColumn', n
when "D"
@cb 'priorColumn', n
when "E"
@cb 'nextRowFirstColumn', n
when "F"
@cb 'priorRowFirstColumn', n
when "G"
@cb 'goToColumn', n
when "H"
@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 'insertLine', n or 1
when "M"
@cb 'deleteLine', 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 'clearScreen'
else if action is "l"
# Clear screen, switch to normal screen buffer, and restore cursor position.
@cb 'clearScreen'
@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) ->
# @buffer.setBrush @sgrInterpreter.buildBrush(@buffer.getBrush(), numbers)
formattedData: (data) ->
head = data.slice(0, 100)
hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head)
Utf8.decode(head) + " (" + hex.join() + ")"

@ -2,282 +2,23 @@ class AsciiIo.VT
constructor: (@cols, @lines) ->
_.extend(this, Backbone.Events)
@sgrInterpreter = new AsciiIo.SgrInterpreter()
@interpreter = new AsciiIo.AnsiInterpreter @onChange
@reset()
reset: ->
@data = ''
@resetTerminal()
onChange: (action, args...) =>
@[action](args...)
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]..|[\xc3-\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) ->
switch char
when "\x07"
@bell()
when "\x08"
@backspace()
when "\x09"
@buffer.goToNextHorizontalTabStop()
# @tab()
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"
handlePrintableCharacters: (text) ->
@buffer.print text
feed: (data) ->
rest = @interpreter.parse data
rest.length is 0
handleStandardEscSeq: (data) ->
last = data[data.length - 1]
intermediate = data[data.length - 2]
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()
handlePrivateEscSeq: (data) ->
last = data[data.length - 1]
intermediate = data[data.length - 2]
switch last
when "0"
if intermediate is '('
@setSpecialCharset()
when "7"
@saveTerminalState()
when "8"
@restoreTerminalState()
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 "@"
@buffer.insertCharacters n
when "A"
@buffer.priorRow n
when "B"
@buffer.nextRow n
when "C"
@buffer.nextColumn n
when "D"
@buffer.priorColumn n
when "E"
@buffer.nextRowFirstColumn n
when "F"
@buffer.priorRowFirstColumn n
when "G"
@buffer.goToColumn n
when "H"
@buffer.goToRowAndColumn n, m
when "I"
@buffer.goToNextHorizontalTabStop n
when "J"
if n is 2
@buffer.eraseScreen()
else if n is 1
@buffer.eraseFromScreenStart()
else
@buffer.eraseToScreenEnd()
when "K"
if n is 2
@buffer.eraseRow()
else if n is 1
@buffer.eraseFromRowStart()
else
@buffer.eraseToRowEnd()
when "L"
@buffer.insertLine n or 1
when "M"
@buffer.deleteLine n or 1
when "P" # DCH - Delete Character, from current position to end of field
@buffer.deleteCharacters n or 1
when "S"
@buffer.scrollUp n
when "T"
@buffer.scrollDown n
when "X"
@buffer.eraseCharacters n
when "Z"
@buffer.goToPriorHorizontalTabStop n
when "b"
@buffer.repeatLastCharacter n
when "d" # VPA - Vertical Position Absolute
@buffer.goToRow n
when "f"
@buffer.goToRowAndColumn n, m
when "g"
if !n or n is 0
@buffer.clearHorizontalTabStop()
else if n is 3
@buffer.clearAllHorizontalTabStops()
when "l" # l, Reset mode
console.log "(TODO) reset: " + n
when "m"
@handleSGR numbers
when "n"
@reportRowAndColumn()
when "r" # Set top and bottom margins (scroll region on VT100)
if n is undefined
n = 1
if m is undefined
m = @lines
@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"
@showCursor()
else if action is "l"
@hideCursor()
else if mode is 47
if action is "h"
@switchToAlternateBuffer()
else if action is "l"
@switchToNormalBuffer()
else if mode is 1049
if action is "h"
# Save cursor position, switch to alternate screen buffer, and clear screen.
@switchToAlternateBuffer()
@clearScreen()
else if action is "l"
# Clear screen, switch to normal screen buffer, and restore cursor position.
@clearScreen()
@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) ->
@buffer.setBrush @sgrInterpreter.buildBrush(@buffer.getBrush(), numbers)
reset: ->
@interpreter.reset()
@resetTerminal()
bell: ->
@trigger 'bell'
feed: (data) ->
@data += data
while @data.length > 0
processed = @handleData(@data)
if processed is 0
# console.log "no kurwa: #{@formattedData(@data)}"
break
@data = @data.slice(processed)
@data.length is 0
formattedData: (data) ->
head = data.slice(0, 100)
hex = ("0x#{c.charCodeAt(0).toString(16)}" for c in head)
Utf8.decode(head) + " (" + hex.join() + ")"
state: ->
changes: @buffer.changes()
cursorX: @buffer.cursorX
@ -397,6 +138,91 @@ class AsciiIo.VT
setSpecialCharset: ->
@buffer.setCharset('special')
# ---- new
print: (text) ->
@buffer.print text
insertCharacters: (n) ->
@buffer.insertCharacters 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()
insertLine: (n) ->
@buffer.insertLine n
deleteLine: (n) ->
@buffer.deleteLine 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()
# References:
# http://en.wikipedia.org/wiki/ANSI_escape_code

Loading…
Cancel
Save