+ # move cursor up
+ csi_A: (lines) ->
+ lines = parseInt @fix_esc_arg lines, '1'
+ @y -= lines
+ if @y < 0
+ @y = 0
+ return
+
+ # move cursor down
+ csi_B: (lines) ->
+ lines = parseInt @fix_esc_arg lines, '1'
+ @y += lines
+ if @y >= @height
+ @y = @height - 1
+ return
+
+ # move cursor right
+ csi_C: (cols) ->
+ cols = parseInt @fix_esc_arg cols, '1'
+ @x += cols
+ if @x >= @width
+ @x = @width - 1
+ return
+
+ # move cursor left
+ csi_D: (cols) ->
+ cols = parseInt @fix_esc_arg cols, '1'
+ @x -= cols
+ if @x < 0
+ @x = 0
+ return
+
+ # cursor set_row (y)
+ csi_d: (row) ->
+ row = -1 + parseInt @fix_esc_arg row, '1'
+ @set_row_clamped row
+ return
+
+ # cursor column (x)
+ csi_G: (col) ->
+ col = -1 + parseInt @fix_esc_arg col, '1'
+ @set_column_clamped col
+ return
+
+ # set cursor row (zero based)
+ set_row_clamped: (row) ->
+ if row < 0
+ row = 0
+ if row >= @height
+ row = @height - 1
+ @y = row
+ return
+ # set cursor column (zero based)
+ set_column_clamped: (column) ->
+ if column < 0
+ column = 0
+ if column >= @width
+ column = @width - 1
+ @x = column
+ return
+ # set cursor position (one based)
+ csi_H: (row, column) ->
+ # handle blank/missing args and convert to 0 base
+ row = -1 + parseInt @fix_esc_arg row, '1'
+ column = -1 + parseInt @fix_esc_arg column, '1'
+ @set_row_clamped row
+ @set_column_clamped column
+ return
+
+ # clear to screen edge(es)
+ csi_J: (direction) ->
+ switch @fix_esc_arg direction, '0'
+ when '0' # erase down
+ # rest of current line
+ @csi_K direction
+ # rest of lines
+ for row in [@y...@height]
+ for i in [0...@width]
+ @text[row][i] = ' '
+ @attributes[row][i] = @a
+ when '1' # erase up
+ # beginning of current line
+ @csi_K direction
+ # all previous lines
+ for row in [0..@y]
+ for i in [0...@width]
+ @text[row][i] = ' '
+ @attributes[row][i] = @a
+ when '2' # erase everything
+ for row in [0...@height]
+ for i in [0...@width]
+ @text[row][i] = ' '
+ @attributes[row][i] = @a
+ else
+ log "confusing arg for csi_J: #{direction}"
+ return
+
+ # clear (some or all of) current line
+ csi_K: (direction) ->
+ switch @fix_esc_arg direction, '0'
+ when '0' # erase to right
+ for i in [@x...@width]
+ @text[@y][i] = ' '
+ @attributes[@y][i] = @a
+ when '1' # erase to left
+ # @x can equal @width (after printing to right-most column)
+ if @x < @width
+ max = @x
+ else
+ max = @width - 1
+ for i in [0..max]
+ @text[@y][i] = ' '
+ @attributes[@y][i] = @a
+ when '2' # erase whole line
+ for i in [0...@width]
+ @text[@y][i] = ' '
+ @attributes[@y][i] = @a
+ else
+ log "confusing arg for csi_K: #{direction}"
+ return
+
+ # move lines downwards (arg is how far)
+ csi_L: (lines) ->
+ lines = parseInt @fix_esc_arg lines, '1'
+
+ rearrange = (a) =>
+ return [
+ a[0...@y]..., # keep everything above cursor
+ a[@scroll_bottom - lines + 1 .. @scroll_bottom]..., # we'll clear these shortly
+ a[@y..@scroll_bottom - lines]..., # lines that are moving down
+ a[@scroll_bottom + 1 ... @height]... # rest of screen
+ ]
+ @text = rearrange @text
+ @attributes = rearrange @attributes
+
+ # clear the lines we scrolled off (and put back in as "new")
+ for y in [@y...@y+lines]
+ for x in [0...@width]
+ @text[y][x] = ' '
+ @attributes[y][x] = 0x07
+
+ # move lines upwards (arg is how far)
+ # this obliterates the line under the cursor and arg-1 following it
+ csi_M: (lines) ->
+ lines = parseInt @fix_esc_arg lines, '1'
+
+ rearrange = (a) =>
+ return [
+ a[0 ... @y]..., # keep everything above cursor
+ a[@y + lines .. @scroll_bottom]..., # lines we're moving up
+ a[@y ... @y + lines]..., # recycle these
+ a[@scroll_bottom + 1 ... @height]... # keep the rest
+ ]
+ @text = rearrange @text
+ @attributes = rearrange @attributes
+
+ # clear the lines we're recycling
+ for y in [@scroll_bottom - lines + 1 .. @scroll_bottom]
+ for x in [0...@width]
+ @text[y][x] = ' '
+ @attributes[y][x] = 0x07
+
+ # delete chars (without moving the cursor)
+ csi_X: (chars) ->
+ chars = parseInt @fix_esc_arg chars, '1'
+ x = @x
+ for c in [0...chars]
+ if x >= @width
+ return
+ @text[@y][x] = ' '
+ @attributes[@y][x] = @a
+ x += 1
+ return
+ "csi_@": (chars) ->
+ chars = parseInt @fix_esc_arg chars, '1'
+ if chars < 1
+ return
+ if chars > @width - @x
+ chars = @width - @x
+ else
+ dest = @width - 1
+ if dest >= @width
+ dest = @width - 1
+ while dest - chars >= @x
+ @text[@y][dest] = @text[@y][dest - chars]
+ @attributes[@y][dest] = @attributes[@y][dest - chars]
+ dest -= 1
+ @csi_X chars # clear
+ csi_P: (chars) ->
+ chars = parseInt @fix_esc_arg chars, '1'
+ if chars < 1
+ return
+ if chars > @width - @x
+ chars = @width - @x
+ else
+ dest = @x
+ while dest < @width - chars
+ @text[@y][dest] = @text[@y][dest + chars]
+ @attributes[@y][dest] = @attributes[@y][dest + chars]
+ dest += 1
+ # clear the space moved out of
+ x = @width - chars
+ while x < @width
+ @text[@y][x] = ' '
+ @attributes[@y][x] = @a
+ x += 1
+
+ # set modes not starting with "?"
+ csi_h: (args...) ->
+ for i in args
+ arg = @fix_esc_arg i, ''
+ switch arg
+ when '0' # error (ignored
+ return
+ when '2' # KAM -- keyboard action
+ return
+ when '4' # put cursor in "INSERT" mode
+ return
+ when '12' # turn local echo off (or on?)
+ return
+ # TODO when '20' LNM linefeed/newline
+ else
+ log "Unimplemented arg for csi_h: #{arg}"
+ # set modes starting with "?"
+ csiq_h: (args...) ->
+ for i in args
+ arg = @fix_esc_arg i, ''
+ switch arg
+ when '1' # mode ?1
+ # numlock on
+ return
+ when '25'
+ @cursor_visible = true
+ when '1000'
+ # x11 normal mouse tracking
+ return
+ when '1049'
+ if @saved_normal_screen?
+ log "ignoring request to switch to the alt screen because we're already on the alt screen"
+ return
+ @saved_normal_screen = x: @x, y: @y, text: @text, attributes: @attributes
+ @text = []
+ @attributes = []
+ for y in [0...@height]
+ @text[y] = []
+ @attributes[y] = []
+ for x in [0...@width]
+ @text[y].push ' '
+ @attributes[y].push 0x07
+ else
+ log "Unimplemented arg for csiq_h: #{arg}"
+ return
+
+ # reset (turn off) modes
+ # note that modes starting with ? call csiq_l, not this
+ csi_l: ->
+ for i in arguments
+ arg = @fix_esc_arg i, ''
+ switch arg
+ when '0' # error (ignored
+ return
+ when '2' # KAM -- keyboard action
+ return
+ when '4' # put cursor in "REPLACE" mode
+ return
+ when '12' # turn local echo on (or off?)
+ return
+ # TODO when '20' LNM linefeed/newline
+
+ # reset (turn off) modes starting with ?
+ # in st source, these are priv=true
+ csiq_l: ->
+ args = []
+ for i in arguments
+ arg = @fix_esc_arg i, ''
+ switch arg
+ when '1' # mode ?1 reset
+ # numlock off
+ return
+ when '12' # mode ?12
+ # stop blinking the cursor (ignored)
+ return
+ when '25' # mode ?25 reset
+ @cursor_visible = false
+ when '0', '2', '3', '4', '8', '18', '19', '42', '12'
+ # st ignores all of these
+ return
+ when '9', '1000', '1002', '1003', '1004', '1006'
+ # mouse reporting and such (ignored)
+ return
+ when '1049'
+ if not @saved_normal_screen?
+ log "ignoring request to switch to the normal screen because we're already on the normal screen"
+ return
+ @x = @saved_normal_screen.x
+ @y = @saved_normal_screen.y
+ @text = @saved_normal_screen.text
+ @attributes = @saved_normal_screen.attributes
+ @saved_normal_screen = null
+ else
+ log "Unimplemented arg for csiq_l: #{arg}"
+ return
+
+ # set color, bold, underline, etc
+ csi_m: ->
+ args = []