# Copyright (c) 2014 The xterm.js authors. All rights reserved. # Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) # Ported to GDScript by the GodotXterm authors. # License MIT extends Reference # The terminal's InputHandler, this handles all input from the Parser. # # Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand # each function's header comment. const Constants = preload("res://addons/godot_xterm/parser/constants.gd") const BufferConstants = preload("res://addons/godot_xterm/buffer/constants.gd") const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd") const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd") const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd") const EscapeSequenceParser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd") const Charsets = preload("res://addons/godot_xterm/data/charsets.gd") const Attributes = BufferConstants.Attributes const C0 = Constants.C0 const C1 = Constants.C1 const FgFlags = BufferConstants.FgFlags const BgFlags = BufferConstants.BgFlags const UnderlineStyle = BufferConstants.UnderlineStyle const CursorStyle = BufferConstants.CursorStyle const CHARSETS = Charsets.CHARSETS const Content = BufferConstants.Content const GLEVEL = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2} const MAX_PARSEBUFFER_LENGTH = 131072 const STACK_LIMIT = 10 var DEFAULT_ATTRIBUTE_DATA = AttributeData.new() signal line_fed signal cursor_moved signal bell_requested signal refresh_rows_requested(start_row, end_row) signal reset_requested signal scroll_requested signal windows_options_report_requested signal scrollbar_sync_requested var _buffer_service var _core_service var _charset_service var _options_service var _parser var _parse_buffer: Array = [] var _utf8_decoder = Decoder.Utf8ToUtf32.new() var _cur_attr_data = AttributeData.new() var _erase_attr_data_internal = AttributeData.new() var _work_cell = CellData.new() var _parse_thread: Thread = Thread.new() var _buffer setget ,_get_buffer var buffer setget _set_buffer,_get_buffer func _set_buffer(buffer) -> void: buffer = buffer func _get_buffer(): return _buffer_service.buffer func _init(buffer_service, core_service, charset_service, options_service, parser = EscapeSequenceParser.new()): _buffer_service = buffer_service _core_service = core_service _charset_service = charset_service _options_service = options_service _parser = parser buffer = _buffer_service.buffer _buffer_service.connect("buffer_activated", self, "_set_buffer") # Print handler _parser.set_print_handler(self, "print") # CSI handler _parser.set_csi_handler({"final": "@"}, self, "insert_chars") _parser.set_csi_handler({"intermediates": " ", "final": "@"}, self, "scroll_left") _parser.set_csi_handler({"final": "A"}, self, "cursor_up") _parser.set_csi_handler({"intermediates": " ", "final": "A"}, self, "scroll_right") _parser.set_csi_handler({"final": "B"}, self, "cursor_down") _parser.set_csi_handler({"final": "C"}, self, "cursor_forward") _parser.set_csi_handler({"final": "D"}, self, "cursor_backward") _parser.set_csi_handler({"final": "E"}, self, "cursor_nextLine") _parser.set_csi_handler({"final": "F"}, self, "cursor_precedingLine") _parser.set_csi_handler({"final": "G"}, self, "cursor_char_absolute") _parser.set_csi_handler({"final": "H"}, self, "cursor_position") _parser.set_csi_handler({"final": "I"}, self, "cursor_forward_tab") _parser.set_csi_handler({"final": "J"}, self, "erase_in_display") _parser.set_csi_handler({"prefix": "?", "final": "J"}, self, "erase_in_display") _parser.set_csi_handler({"final": "K"}, self, "erase_in_line") _parser.set_csi_handler({"prefix": "?", "final": "K"}, self, "erase_in_line") _parser.set_csi_handler({"final": "L"}, self, "insert_lines") _parser.set_csi_handler({"final": "M"}, self, "delete_lines") _parser.set_csi_handler({"final": "P"}, self, "delete_chars") _parser.set_csi_handler({"final": "S"}, self, "scroll_up") _parser.set_csi_handler({"final": "T"}, self, "scroll_down") _parser.set_csi_handler({"final": "X"}, self, "erase_chars") _parser.set_csi_handler({"final": "Z"}, self, "cursor_backward_tab") _parser.set_csi_handler({"final": "`"}, self, "char_pos_absolute") _parser.set_csi_handler({"final": "a"}, self, "h_position_relative") _parser.set_csi_handler({"final": "b"}, self, "repeat_preceding_character") _parser.set_csi_handler({"final": "c"}, self, "send_device_attributes_primary") _parser.set_csi_handler({"prefix": ">", "final": "c"}, self, "send_device_attributes_secondary") _parser.set_csi_handler({"final": "d"}, self, "line_pos_absolute") _parser.set_csi_handler({"final": "e"}, self, "v_position_relative") _parser.set_csi_handler({"final": "f"}, self, "h_v_position") _parser.set_csi_handler({"final": "g"}, self, "tab_clear") _parser.set_csi_handler({"final": "h"}, self, "set_mode") _parser.set_csi_handler({"prefix": "?", "final": "h"}, self, "set_mode_private") _parser.set_csi_handler({"final": "l"}, self, "reset_mode") _parser.set_csi_handler({"prefix": "?", "final": "l"}, self, "reset_mode_private") _parser.set_csi_handler({"final": "m"}, self, "char_attributes") _parser.set_csi_handler({"final": "n"}, self, "device_status") _parser.set_csi_handler({"prefix": "?", "final": "n"}, self, "device_status_private") _parser.set_csi_handler({"intermediates": "!", "final": "p"}, self, "soft_reset") _parser.set_csi_handler({"intermediates": " ", "final": "q"}, self, "set_cursor_style") _parser.set_csi_handler({"final": "r"}, self, "set_scroll_region") _parser.set_csi_handler({"final": "s"}, self, "save_cursor") _parser.set_csi_handler({"final": "t"}, self, "window_options") _parser.set_csi_handler({"final": "u"}, self, "restore_cursor") _parser.set_csi_handler({"intermediates": "\"", "final": "}"}, self, "insert_columns") _parser.set_csi_handler({"intermediates": "\"", "final": "~"}, self, "delete_columns") # execute handler _parser.set_execute_handler(C0.BEL, self, "bell") _parser.set_execute_handler(C0.LF, self, "line_feed") _parser.set_execute_handler(C0.VT, self, "line_feed") _parser.set_execute_handler(C0.FF, self, "line_feed") _parser.set_execute_handler(C0.CR, self, "carriage_return") _parser.set_execute_handler(C0.BS, self, "backspace") _parser.set_execute_handler(C0.HT, self, "tab"); _parser.set_execute_handler(C0.SO, self, "shift_out") _parser.set_execute_handler(C0.SI, self, "shift_in") # FIXME: What to do with missing? Old code just added those to print _parser.set_execute_handler(C1.IND, self, "index") _parser.set_execute_handler(C1.NEL, self, "next_line") _parser.set_execute_handler(C1.HTS, self, "tab_set") # ESC handlers _parser.set_esc_handler({"final": "7"}, self, "save_cursor") _parser.set_esc_handler({"final": "8"}, self, "restore_cursor") _parser.set_esc_handler({"final": "D"}, self, "index") _parser.set_esc_handler({"final": "E"}, self, "next_line") _parser.set_esc_handler({"final": "H"}, self, "tab_set") _parser.set_esc_handler({"final": "M"}, self, "reverse_index") _parser.set_esc_handler({"final": "="}, self, "keypad_application_mode") _parser.set_esc_handler({"final": ">"}, self, "keypad_numeric_mode") _parser.set_esc_handler({"final": "c"}, self, "full_reset") _parser.set_esc_handler({"final": "n"}, self, "set_glevel", 2) _parser.set_esc_handler({"final": "o"}, self, "set_glevel", 3) _parser.set_esc_handler({"final": "|"}, self, "set_glevel", 3) _parser.set_esc_handler({"final": "}"}, self, "set_glevel", 2) _parser.set_esc_handler({"final": "~"}, self, "set_glevel", 1) _parser.set_esc_handler({"intermediates": "%", "final": "@"}, self, "select_default_charset") _parser.set_esc_handler({"intermediates": "%", "final": "G"}, self, "select_default_charset") for flag in CHARSETS.keys(): _parser.set_esc_handler({"intermediates": "(", "final": flag}, self, "select_charset", "(" + flag) _parser.set_esc_handler({"intermediates": ")", "final": flag}, self, "select_charset", ")" + flag) _parser.set_esc_handler({"intermediates": "*", "final": flag}, self, "select_charset", "*" + flag) _parser.set_esc_handler({"intermediates": "+", "final": flag}, self, "select_charset", "+" + flag) _parser.set_esc_handler({"intermediates": "-", "final": flag}, self, "select_charset", "-" + flag) _parser.set_esc_handler({"intermediates": ".", "final": flag}, self, "select_charset", "." + flag) _parser.set_esc_handler({"intermediates": "/", "final": flag}, self, "select_charset", "/" + flag) # TODO: supported? _parser.set_esc_handler({"intermediates": "#", "final": "8"}, self, "screen_alignment_pattern") #func parse(data) -> void: # if _parse_thread.is_active(): # _parse_thread.wait_to_finish() # _parse_thread.start(self, "_parse_async", data) func parse(data) -> void: var buffer = _buffer_service.buffer var cursor_start_x = buffer.x var cursor_start_y = buffer.y var data_length = data.length() if typeof(data) == TYPE_STRING else data.size() # resize input buffer if needed if _parse_buffer.size() < data_length and _parse_buffer.size() < MAX_PARSEBUFFER_LENGTH: _parse_buffer.resize(min(data_length, MAX_PARSEBUFFER_LENGTH)) # process big data in smaller chunks if data_length > MAX_PARSEBUFFER_LENGTH: var i = 0 while i < data_length: var end = i + MAX_PARSEBUFFER_LENGTH if i + MAX_PARSEBUFFER_LENGTH < data_length else data_length var length match typeof(data): TYPE_STRING: length = _utf8_decoder.decode(data.to_utf8(), _parse_buffer) TYPE_RAW_ARRAY: length = _utf8_decoder.decode(data, _parse_buffer) TYPE_ARRAY: length = data.size() _parse_buffer = data.duplicate() _parser.parse(_parse_buffer, length) i += MAX_PARSEBUFFER_LENGTH else: var length match typeof(data): TYPE_STRING: length = _utf8_decoder.decode(data.to_utf8(), _parse_buffer) TYPE_RAW_ARRAY: length = _utf8_decoder.decode(data, _parse_buffer) TYPE_ARRAY: length = data.size() _parse_buffer = data.duplicate() _parser.parse(_parse_buffer, length) buffer = _buffer_service.buffer if (buffer.x != cursor_start_x or buffer.y != cursor_start_y): emit_signal("cursor_moved") # Refresh all rows. emit_signal("refresh_rows_requested") # TODO: Refresh only dirty rows accumulated as part of parsing. func _exit_tree(): _parse_thread.wait_to_finish() func print(data: Array, start: int, end: int) -> void: var code: int var ch_width: int var buffer = _buffer_service.buffer var charset = _charset_service.charset var screen_reader_mode = _options_service.options.screen_reader_mode var cols = _buffer_service.cols var wraparound_mode = true #TODO _core_service.modes.wraparound var insert_mode = false # TODO FIXME! _core_service.modes.insert_mode var cur_attr = _cur_attr_data var buffer_row = buffer.lines.get_el(buffer.ybase + buffer.y) # TODO: dirtyRowService stuff # handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char if buffer.x and end - start > 0 and buffer_row.get_width(buffer.x - 1) == 2: buffer_row.set_cell_from_codepoint(buffer.x - 1, 0, 1, cur_attr.fg, cur_attr.bg, cur_attr.extended) for pos in range(start, end): code = data[pos] # calculate print space # expensive call, therefore we save width in line buffer ch_width = char(code).length() # FIXME # get charset replacement character # charset is only defined for ASCII, therefore we only # search for an replacement char if code < 127 if code < 127 and charset: var ch = charset.get(char(code)) if ch: code = ch.ord_at(0) if screen_reader_mode: pass # TODO: Handle A11y # insert combining char at last cursor position # buffer.x should never be 0 for a combining char # since they always follow a cell consuming char # therefore we can test for buffer.x to avoid oveflow left if (not ch_width) and buffer.x: if not buffer_row.get_width(buffer.x - 1): # found empty cell after full_width, need to go 2 cells back # it is save to step 2 cells back here # since an empty cell is only set by full_width chars buffer_row.add_codepoint_to_cell(buffer.x - 2, code) else: buffer_row.add_codepoint_to_cell(buffer.x - 1, code) continue # goto next line if ch would overflow # NOTE: To avoid costly width checks here, # the terminal does not allow a cols < 2 if buffer.x + ch_width - 1 >= cols: # autowrap - DECAWM # automatically wraps to the beginning of the next line if wraparound_mode: while buffer.x < cols: buffer_row.set_cell_from_codepoint(buffer.x, 0, 1, cur_attr.fg, cur_attr.bg, cur_attr.extended) buffer.x += 1 buffer.x = 0 buffer.y += 1 if buffer.y == buffer.scroll_bottom + 1: buffer.y -= 1 emit_signal("scroll_requested", _erase_attr_data(), true) else: if buffer.y >= _buffer_service.rows: buffer.y = _buffer_service.rows - 1 # The line already exists (e.g. the initial viewport), mark it as a # wrapped line buffer.lines.get_el(buffer.ybase + buffer.y).is_wrapped = true # row changed, get it again buffer_row = buffer.lines.get_el(buffer.ybase + buffer.y) else: buffer.x = cols - 1 if ch_width == 2: # FIXME: check for xterm behavior # What to do here? We got a wide char that does not fit into last cell continue # insert mode: move characters to right if insert_mode: # right shift cells according to the width buffer_row.insert_cells(buffer.x, ch_width, buffer.get_null_cell(cur_attr), cur_attr) # test last cell - since the last cell has only room for # a halfwidth char any fullwidth shifted there is lost # and will be set to empty cell if buffer_row.get_width(cols - 1) == 2: buffer_row.set_cell_from_codepoint(cols - 1, Constants.NULL_CELL_CODE, Constants.NULL_CELL_WIDTH, cur_attr.fg, cur_attr.bg, cur_attr.extended) # write current char to buffer and advance cursor buffer_row.set_cell_from_codepoint(buffer.x, code, ch_width, cur_attr.fg, cur_attr.bg, cur_attr.extended) buffer.x += 1 # fullwidth char - also set next cell to placeholder stub and advance cursor # for graphemes bigger than fullwidth we can simply loop to zero # we already made sure above, that buffer.x + ch_width will not overflow right if ch_width > 0: ch_width -= 1 while ch_width: # other than a regular empty cell a cell following a wide char has no width buffer_row.set_cell_from_codepoint(buffer.x, 0, 0, cur_attr.fg, cur_attr.bg, cur_attr.extended) buffer.x += 1 ch_width -= 1 # Store last char in Parser.preceding_codepoint for REP to work correctly # This needs to check whether: # - fullwidth + surrogates: reset # - combining: only base char gets carried on (bug in xterm?) if end - start > 0: buffer_row.load_cell(buffer.x - 1, _work_cell) if _work_cell.get_width() == 2 or _work_cell.get_code() > 0xFFFF: _parser.preceding_codepoint = 0 elif _work_cell.is_combined(): _parser.preceding_codepoint = _work_cell.get_chars().ord_at(0) else: _parser.preceding_codepoint = _work_cell.content # handle wide chars: reset cell to the right if it is second cell of a wide char if buffer.x < cols and end - start > 0 and buffer_row.get_width(buffer.x) == 0 and not buffer_row.has_content(buffer.x): buffer_row.set_cell_from_codepoint(buffer.x, 0, 1, cur_attr.fg, cur_attr.bg, cur_attr.extended) # TODO dirty row stuff # _dirty_row_service.mark_dirty(buffer.y) func bell(): emit_signal("bell_requested") func line_feed(): var buffer = _buffer_service.buffer if _options_service.options.convert_eol: buffer.x = 0 buffer.y += 1 if buffer.y == buffer.scroll_bottom + 1: buffer.y -= 1 emit_signal("scroll_requested", _erase_attr_data()) elif buffer.y >= _buffer_service.rows: buffer.y = _buffer_service.rows - 1 # If the end of the line is hit, prevent this action from wrapping around to the next line. if buffer.x >= _buffer_service.cols: buffer.x -= 1 emit_signal("line_fed") func carriage_return(): _buffer_service.buffer.x = 0 func backspace(): var buffer = _buffer_service.buffer # reverse wrap-around is disabled if not _core_service.dec_private_modes.reverse_wraparound: _restrict_cursor() if buffer.x > 0: buffer.x -= 1 return # reverse wrap-around is enabled # other than for normal operation mode, reverse wrap-around allows the cursor # to be at x=cols to be able to address the last cell of a row by BS _restrict_cursor(_buffer_service.cols) if buffer.x > 0: buffer.x -= 1 else: # reverse wrap-around handling: # Our implementation deviates from xterm on purpose. Details: # - only previous soft NLs can be reversed (is_wrapped=true) # - only works within scrollborders (top/bottom, left/right not yet supported) # - cannot peek into scrollbuffer # - any cursor movement sequence keeps working as expected if buffer.x == 0 \ and buffer.y > buffer.scroll_top \ and buffer.y <= buffer.scroll_bottom \ and buffer.lines.get_el(buffer.ybase + buffer.y).is_wrapped: buffer.lines.get_el(buffer.ybase + buffer.y).is_wrapped = false buffer.y -= 1 buffer.x = _buffer_service.cols - 1 # find last taken cell - last can have 3 different states: # - has_content(true) + has_width(1): narrow char - we are done # - has_width(0): second part of a wide char - we are done # - has_content(false) + has_width(1): empty cell due to early wrapping wide char, go one cell further back var line = buffer.lines.get_el(buffer.ybase + buffer.y) if line.has_width(buffer.x) and line.has_content(buffer.x): buffer.x -= 1 # We do this only once, since width=1 + has_content= false currently happens only once before # early wrapping of a wide char. # This needs to be fixed once we support graphemes taking more than 2 cells. _restrict_cursor() func tab(): if _buffer_service.buffer.x >= _buffer_service.cols: return var original_x = _buffer_service.buffer.x _buffer_service.buffer.x = _buffer_service.buffer.next_stop() # TODO A11y # SO # Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the # G1 character set. # # @vt: #P[Only limited ISO-2022 charset support.] C0 SO "Shift Out" "\x0E" "Switch to an alternative character set." func shift_out(): _charset_service.set_glevel(1) # SI # Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 # character set (the default). # # @vt: #Y C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out." func shift_in(): _charset_service.set_glevel(0) func _restrict_cursor(max_col: int = _buffer_service.cols - 1) -> void: var buffer = _buffer_service.buffer self._buffer.x = min(max_col, max(0, self._buffer.x)) if _core_service.dec_private_modes.origin: self._buffer.y = min(self._buffer.scroll_bottom, max(self._buffer.scroll_top, self._buffer.y)) else: self._buffer.y = min(_buffer_service.rows - 1, max(0, self._buffer.y)) # _dirty_row_service.mark_dirty(_buffer_service.buffer.y) # Set absolute cursor position. func _set_cursor(x: int, y: int) -> void: # _dirty_row_service.mark_dirty(self._buffer.y) if _core_service.dec_private_modes.origin: self._buffer.x = x self._buffer.y = self._buffer.scroll_top + y else: self._buffer.x = x self._buffer.y = y # Set relative cursor position. func _move_cursor(x: int, y: int) -> void: # for relative changes we have to make sure we are within 0 .. cols/rows - 1 # before calculating the new position _restrict_cursor() _set_cursor(self._buffer.y + x, self._buffer.y + y) # ESC D # C1.IND # DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) # Moves the cursor down one line in the same column. # # @vt: #Y C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." # @vt: #Y ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." func index() -> void: _restrict_cursor() buffer.y += 1 if buffer.y == buffer.scroll_bottom + 1: buffer.y -= 1 emit_signal("scroll_requested", _erase_attr_data()) elif buffer.y >= _buffer_service.rows: buffer.y = _buffer_service.rows - 1 _restrict_cursor() # ESC E # C1.NEL # DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) # Moves cursor to first position on next line. # # @vt: #Y C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." # @vt: #Y ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." func next_line() -> void: buffer.x = 0 index() # ESC = # DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html) # Enables the numeric keypad to send application sequences to the host. func keypad_application_mode() -> void: _core_service.dec_private_modes.application_keypad = true emit_signal("scrollbar_sync_requested") # ESC > # DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html) # Enables the keypad to send numeric characters to the host. func keypad_numeric_mode() -> void: _core_service.dec_private_modes.application_keypad = false emit_signal("scrollbar_sync_requested") # ESC % @ # ESC % G # Select default character set. UTF-8 is not supported (string are unicode anyways) # therefore ESC % G does the same. func select_default_charset() -> void: _charset_service.set_glevel(0) _charset_service.set_gcharset(0, Charsets.DEFAULT_CHARSET) # US (default) # ESC ( C # Designate G0 Character Set, VT100, ISO 2022. # ESC ) C # Designate G1 Character Set (ISO 2022, VT100). # ESC * C # Designate G2 Character Set (ISO 2022, VT220). # ESC + C # Designate G3 Character Set (ISO 2022, VT220). # ESC - C # Designate G1 Character Set (VT300). # ESC . C # Designate G2 Character Set (VT300). # ESC / C # Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported? func select_charset(collect_and_flag: String) -> void: if collect_and_flag.length() != 2: select_default_charset() return if collect_and_flag.substr(0, 1) == "/": return # TODO: Is this supported? var g = GLEVEL[collect_and_flag.substr(0, 1)] var charset = CHARSETS[collect_and_flag.substr(1, 1)] _charset_service.set_gcharset(g, charset) # ESC H # C1.HTS # DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html) # Sets a horizontal tab stop at the column position indicated by # the value of the active column when the terminal receives an HTS. # # @vt: #Y C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." # @vt: #Y ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." func tab_set(): buffer.tabs[buffer.x] = true # CSI Ps @ # Insert Ps (Blank) Character(s) (default = 1) (ICH). # # @vt: #Y CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)." # The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters. # Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost. # # # FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) func insert_chars(params) -> void: _restrict_cursor() var line = buffer.lines.get_el(buffer.ybase + buffer.y) if line: line.insert_cells(buffer.x, params.get_param(0, 1), buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) # CSI Ps SP @ Scroll left Ps columns (default = 1) (SL) ECMA-48 # # Notation: (Pn) # Representation: CSI Pn 02/00 04/00 # Parameter default value: Pn = 1 # SL causes the data in the presentation component to be moved by n character positions # if the line orientation is horizontal, or by n line positions if the line orientation # is vertical, such that the data appear to move to the left; where n equals the value of Pn. # The active presentation position is not affected by this control function. # # Supported: # - always left shift (no line orientation setting respected) # # @vt: #Y CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." # SL moves the content of all lines within the scroll margins `Ps` times to the left. # SL has no effect outside of the scroll margins. func scroll_left(params) -> void: if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: return var param = params.get_param(0, 1) for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): var line = buffer.lines.get_el(buffer.ybase + y) line.delete_cells(0, param, buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) line.is_wrapped = false func cursor_up(params) -> void: # stop at scroll_top var diff_to_top = self._buffer.y - self._buffer.scroll_top if diff_to_top >= 0: _move_cursor(0, -min(diff_to_top, params.get_param(0, 1))) else: _move_cursor(0, -params.get_param(0, 1)) # CSI Ps SP A Scroll right Ps columns (default = 1) (SR) ECMA-48 # # Notation: (Pn) # Representation: CSI Pn 02/00 04/01 # Parameter default value: Pn = 1 # SR causes the data in the presentation component to be moved by n character positions # if the line orientation is horizontal, or by n line positions if the line orientation # is vertical, such that the data appear to move to the right; where n equals the value of Pn. # The active presentation position is not affected by this control function. # # Supported: # - always right shift (no line orientation setting respected) # # @vt: #Y CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." # SL moves the content of all lines within the scroll margins `Ps` times to the right. # Content at the right margin is lost. # SL has no effect outside of the scroll margins. func scroll_right(params) -> void: if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: return var param = params.get_param(0, 1) for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): var line = buffer.lines.get_el(buffer.ybase + y) line.insert_cells(0, param, buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) line.is_wrapped = false func cursor_down(params): # stop at scroll_bottom var diff_to_bottom = self._buffer.scroll_bottom - self._buffer.y if diff_to_bottom >= 0: _move_cursor(0, min(diff_to_bottom, params.get_param(0,1))) else: _move_cursor(0, params.get_param(0, 1)) func cursor_forward(params): _move_cursor(params.get_param(0, 1), 0) func cursor_backward(params): _move_cursor(-params.get_param(0, 1), 0) func cursor_next_line(params): cursor_down(params) self._buffer.x = 0 func cursor_preceding_line(params): cursor_up(params) self._buffer.x = 0 func cursor_char_absolute(params): _set_cursor(params.get_param(0, 1) - 1, self._buffer.y) func cursor_position(params): _set_cursor( # col (params.get_param(1, 1)) - 1 if params.length >= 2 else 0, # row (params.get_param(0, 1)) - 1 ) func char_pos_absolute(params) -> void: _set_cursor(params.get_param(0, 1) - 1, self._buffer.y) func h_position_relative(params): _move_cursor(params.get_param(0, 1), 0) func line_pos_absolute(params): _set_cursor(self._buffer.x, params.get_param(0, 1) - 1) func v_position_relative(params): _move_cursor(0, params.get_param(0, 1)) func h_v_position(params): cursor_position(params) # CSI Ps g Tab Clear (TBC). # Ps = 0 -> Clear Current Column (default). # Ps = 3 -> Clear All. # Potentially: # Ps = 2 -> Clear Stops on Line. # http://vt100.net/annarbor/aaa-ug/section6.html # # @vt: #Y CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." # Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported. func tab_clear(params) -> void: match params.get_param(0): 3: self._buffer.tabs = {} 0, _: self._buffer.tabs.erase(self._buffer.x) # CSI Ps I # Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). # # @vt: #Y CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." func cursor_forward_tab(params) -> void: if self._buffer.x >= self._buffer.cols: return var param = params.get_param(0, 1) while param: self._buffer.x = self._buffer.next_stop() param -= 1 func cursor_backward_tab(params) -> void: if self._buffer.x >= _buffer_service.cols: return var param = params.get_param(0, 1) while param: self._buffer.x = self._buffer.buffer.prev_stop() param -= 1 # Helper method to erase cells in a terminal row. # The cell gets replaced with the eraseChar of the terminal. # params: # - `y` row index # - `start` first cell index to be erased # - `end` end - 1 is last erased cell func _erase_in_buffer_line(y: int, start: int, end: int, clear_wrap: bool = false) -> void: var line = self._buffer.lines.get_el(self._buffer.ybase + y) line.replace_cells(start, end, self._buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) if clear_wrap: line.is_wrapped = false # Helper method to reset cells in a terminal row. # The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false. # @param y row index func _reset_buffer_line(y: int) -> void: var line = buffer.lines.get_line(buffer.ybase + y) line.fill(buffer.get_null_cell(_erase_attr_data())) line.is_wrapped = false func erase_in_display(params) -> void: _restrict_cursor() var j match params.get_param(0): 0: j = buffer.y # _dirty_row_service.mark_dirty(j) _erase_in_buffer_line(j, buffer.x, _buffer_service.cols, buffer.x == 0) j += 1 while j < _buffer_service.rows: _reset_buffer_line(j) j += 1 # _dirty_row_service.mark_dirty(j) 1: j = buffer.y # _dirty_row_service.mark_dirty(j) # Deleted front part of line and everything before. This line will no longer be wrapped. _erase_in_buffer_line(j, 0, buffer.x + 1, true) if buffer.x + 1 >= _buffer_service.cols: # Deleted entire previous line. This next line can no longer be wrapped. buffer.lines.get_el(j + 1).is_wrapped = false while j > 0: j -= 1 _reset_buffer_line(j) # _dirty_row_service.mark_dirty(0) 2: j = _buffer_service.rows # _dirty_row_service.mark_dirty(j - 1) while j > 0: j -= 1 _reset_buffer_line(j) # _dirty_row_sevice.mark_dirty(0) 3: # Clear scrollback (everything not in viewport) var scrollback_size = self._buffer.lines.length - _buffer_service.rows if scrollback_size > 0: self._buffer.lines.trim_start(scrollback_size) self._buffer.ybase = max(self._buffer.ybase - scrollback_size, 0) self._buffer.ydisp = max(self._buffer.ydisp - scrollback_size, 0) # Force a scroll to refresh viewport emit_signal("scroll_requested", 0) func erase_in_line(params): _restrict_cursor() match params.get_param(0): 0: _erase_in_buffer_line(buffer.y, buffer.x, _buffer_service.cols) 1: _erase_in_buffer_line(buffer.y, 0, buffer.x + 1) 2: _erase_in_buffer_line(buffer.y, 0, _buffer_service.cols) # CSI Ps L # Insert Ps Line(s) (default = 1) (IL). # # @vt: #Y CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." # For every inserted line at the scroll top one line at the scroll bottom gets removed. # The cursor is set to the first column. # IL has no effect if the cursor is outside the scroll margins. func insert_lines(params) -> void: _restrict_cursor() var param = params.get_param(0, 1) if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: return var row: int = buffer.ybase + buffer.y var scroll_bottom_row_offset = _buffer_service.rows - 1 - buffer.scroll_bottom var scroll_bottom_absolute = _buffer_service.rows - 1 + buffer.ybase - scroll_bottom_row_offset + 1 while param: # test: echo -e '\e[44m\e[1L\e[0m' # blank_line(true) - xterm/linux behavior buffer.lines.splice(scroll_bottom_absolute - 1, 1) buffer.lines.splice(row, 0, [buffer.get_blank_line(_erase_attr_data())]) param -= 1 buffer.x = 0 # see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? # CSI Ps M # Delete Ps Line(s) (default = 1) (DL). # # @vt: #Y CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." # For every deleted line at the scroll top one blank line at the scroll bottom gets appended. # The cursor is set to the first column. # DL has no effect if the cursor is outside the scroll margins. func delete_lines(params) -> void: _restrict_cursor() var param = params.get_param(0, 1) if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: return var row: int = buffer.ybase + buffer.y var j: int j = _buffer_service.rows - 1 - buffer.scroll_bottom j = _buffer_service.rows - 1 + buffer.ybase - j while param: # test echo -e '\e[44m\e[1M\e[0m' # blank_line(true) - xterm/linux behavior buffer.lines.splice(row, 1) buffer.lines.splice(j, 0, [buffer.get_blank_line(_erase_attr_data())]) param -= 1 # CSI Ps P # Delete Ps Character(s) (default = 1) (DCH). # # @vt: #Y CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)." # As characters are deleted, the remaining characters between the cursor and right margin move to the left. # Character attributes move with the characters. The terminal adds blank characters at the right margin. # # # FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) func delete_chars(params) -> void: _restrict_cursor() var line = buffer.lines.get_el(buffer.ybase + buffer.y) if line: line.delete_cells(buffer.x, params.get_param(0, 1), buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) #_dirty_row_service.markDirty(buffer.y) # CSI Ps S Scroll up Ps lines (default = 1) (SU). # # @vt: #Y CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." # # # FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) func scroll_up(params) -> void: var param = params.get_param(0, 1) while param: buffer.lines.splice(buffer.ybase + buffer.scroll_top, 1) buffer.lines.splice(buffer.ybase + buffer.scroll_bottom, 0, [buffer.get_blank_line(_erase_attr_data())]) param -= 1 # CSI Ps T Scroll down Ps lines (default = 1) (SD). # # @vt: #Y CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." func scroll_down(params) -> void: var param = params.get_param(0, 1) while param: buffer.lines.splice(buffer.ybase + buffer.scroll_bottom, 1) buffer.lines.splice(buffer.ybase + buffer.scroll_top, 0, buffer.get_blank_line(DEFAULT_ATTRIBUTE_DATA)) param -= 1 func erase_chars(params) -> void: _restrict_cursor() var line = buffer.lines.get_el(buffer.ybase + buffer.y) if line: line.replace_cells(buffer.x, buffer.x + params.get_param(0, 1), buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) #this._dirtyRowService.markDirty(this._bufferService.buffer.y) func repeat_preceding_character(params) -> void: if not _parser.preceding_codepoint: return # call print to insert the chars and handle correct wrapping var length = params.get_param(0, 1) var data = [] for _i in range(length): data.append(_parser.preceding_codepoint) self.print(data, 0, length) func send_device_attributes_primary(params): # TODO pass func send_device_attributes_secondary(params): # TODO pass func set_mode(params): # TODO pass func reset_mode(params) -> void: for param in params.params: match param: 4: _core_service.modes.insert_mode = false 20: #this._t.convertEol = false pass func char_attributes(params): # Optimize a single SGR0 if params.length == 1 and params.get_param(0) == 0: _cur_attr_data.fg = AttributeData.new().fg _cur_attr_data.bg = AttributeData.new().bg return var attr = _cur_attr_data for i in range(params.length): var p = params.get_param(i) if p >= 30 and p <= 37: # fg color 8 attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) attr.fg |= Attributes.CM_P16 | (p - 30) elif p >= 40 and p <= 47: # bg color 8 attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) attr.bg |= Attributes.CM_P16 | (p - 40) elif p >= 90 and p <= 97: # fg color 16 attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) attr.fg |= Attributes.CM_P16 | (p - 90) | 8 elif p >= 100 and p <= 107: # bg color 16 attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) attr.bg |= Attributes.CM_P16 | (p - 100) | 8 elif p == 0: # default attr.fg = DEFAULT_ATTRIBUTE_DATA.fg attr.bg = DEFAULT_ATTRIBUTE_DATA.bg elif p == 1: # bold text attr.fg |= FgFlags.BOLD elif p == 3: # italic text attr.bg |= BgFlags.ITALIC elif p == 4: # underlined text attr.fg |= FgFlags.UNDERLINE _process_underline(params.get_sub_params(i)[0] if params.has_sub_params(i) else UnderlineStyle.SINGLE, attr) elif p == 5: # blink # test with: echo -e '\e[5mblink\e[m' attr.fg |= FgFlags.BLINK elif p == 7: # inverse and positive # test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' attr.fg |= FgFlags.INVERSE elif p == 8: # invisible attr.fg |= FgFlags.INVISIBLE elif p == 2: # dimmed text attr.bg |= BgFlags.DIM elif p == 21: # double underline _process_underline(UnderlineStyle.DOUBLE, attr) elif p == 22: # not bold nor faint attr.fg &= ~FgFlags.BOLD attr.bg &= ~BgFlags.DIM elif p == 23: # not italic attr.bg &= ~BgFlags.ITALIC elif p == 24: # not underlined attr.fg &= ~FgFlags.UNDERLINE elif p == 25: # not blink attr.fg &= ~FgFlags.BLINK elif p == 27: # not inverse attr.fg &= ~FgFlags.INVERSE elif p == 28: # not invisible attr.fg &= ~FgFlags.INVISIBLE elif p == 39: # reset fg attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK) attr.fg |= AttributeData.new().fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK) elif p == 49: # reset bg attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK) attr.bg |= AttributeData.new().bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK) elif p == 38 or p == 48 or p == 58: # fg color 256 and RGB i += _extract_color(params, i, attr) elif p == 59: attr.extended = attr.extended.duplicate() attr.extended.underline_color = -1 attr.update_extended() elif p == 100: # FIXME: dead branch, p=100 already handled above! # TODO reset fg/bg pass func device_status(params): # TODO pass func device_status_private(params): # TODO pass # CSI ! p Soft terminal reset (DECSTR). # http://vt100.net/docs/vt220-rm/table4-10.html # # @vt: #Y CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." # There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, # DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. # # The following terminal attributes are reset to default values: # - IRM is reset (dafault = false) # - scroll margins are reset (default = viewport size) # - erase attributes are reset to default # - charsets are reset # - DECSC data is reset to initial values # - DECOM is reset to absolute mode # # # FIXME: there are several more attributes missing (see VT520 manual) func soft_reset(params): _core_service.is_cursor_hidden = false emit_signal("scrollbar_sync_requested") buffer.scroll_top = 0 buffer.scroll_bottom = _buffer_service.rows - 1 _cur_attr_data = DEFAULT_ATTRIBUTE_DATA _core_service.reset() _charset_service.reset() # reset DECSC data buffer.saved_x = 0 buffer.saved_y = buffer.ybase buffer.saved_cur_attr_data.fg = _cur_attr_data.fg buffer.saved_cur_attr_data.bg = _cur_attr_data.bg buffer.saved_charset = _charset_service.charset # reset DECOM _core_service.dec_private_modes.origin = false # CSI Ps SP q Set cursor style (DECSCUSR, VT520). # Ps = 0 -> blinking block. # Ps = 1 -> blinking block (default). # Ps = 2 -> steady block. # Ps = 3 -> blinking underline. # Ps = 4 -> steady underline. # Ps = 5 -> blinking bar (xterm). # Ps = 6 -> steady bar (xterm). # # @vt: #Y CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." # Supported cursor styles: # - empty, 0 or 1: steady block # - 2: blink block # - 3: steady underline # - 4: blink underline # - 5: steady bar # - 6: blink bar func set_cursor_style(params) -> void: var param = params.get_param(0, 1) match param: 1, 2: _options_service.options.cursor_style = CursorStyle.BLOCK 3, 4: _options_service.options.cursor_style = CursorStyle.UNDERLINE 5, 6: _options_service.options.cursor_style = CursorStyle.BAR var is_blinking = param % 2 == 1 _options_service.options.cursor_blink = is_blinking func set_scroll_region(params) -> void: var top = params.get_param(0, 1) var bottom = params.get_param(1, 0) if bottom > _buffer_service.rows or bottom == 0: bottom = _buffer_service.rows if bottom > top: buffer.scroll_top = top - 1 buffer.scroll_bottom = bottom - 1 _set_cursor(0, 0) func save_cursor(params = null): self._buffer.saved_x = self._buffer.x self._buffer.saved_y = self._buffer.ybase + self._buffer.y self._buffer.saved_cur_attr_data.fg = _cur_attr_data.fg self._buffer.saved_cur_attr_data.bg = _cur_attr_data.bg self._buffer.saved_charset = _charset_service.charset func window_options(params): var second = params.get_param(1, 0) match params.get_param(0): 14: pass 16: pass 18: pass 22: pass 23: pass # CSI Pm ' } # Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. # # @vt: #Y CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." # DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll margins, # moving content to the right. Content at the right margin is lost. # DECIC has no effect outside the scrolling margins. func insert_columns(params): if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: return var param = params.get_param(0, 1) for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): var line = buffer.lines.get_el(buffer.ybase + y) line.insert_cells(buffer.x, param, buffer.get_null_cells(_erase_attr_data()), _erase_attr_data()) line.is_wrapped = false # CSI Pm ' ~ # Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. # # @vt: #Y CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." # DECDC deletes `Ps` times columns at the cursor position for all lines with the scroll margins, # moving content to the left. Blank columns are added at the right margin. # DECDC has no effect outside the scrolling margins. func delete_columns(params): if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: return var param = params.get_param(0, 1) for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): var line = buffer.lines.get_el(buffer.ybase + y) line.delete_cells(buffer.x, param, buffer.get_null_cells(_erase_attr_data()), _erase_attr_data()) line.is_wrapped = false func set_mode_private(params) -> void: for param in params.params: match param: 1: _core_service.dec_private_modes.application_cursor_keys = true 2: _charset_service.set_gcharset(0, Charsets.DEFAULT_CHARSET) _charset_service.set_gcharset(1, Charsets.DEFAULT_CHARSET) _charset_service.set_gcharset(2, Charsets.DEFAULT_CHARSET) _charset_service.set_gcharset(3, Charsets.DEFAULT_CHARSET) # set VT100 mode here 3: # DECCOLM - 132 column mode. # This is only active if 'set_win_lines' (24) is enabled # through `options.window_options`. if _options_service.options.window_options.set_win_lines: _buffer_service.resize(132, _buffer_service.rows) emit_signal("reset_requested") 6: _core_service.dec_private_modes.origin = true _set_cursor(0, 0) 7: _core_service.dec_private_modes.wraparound = true 12: # cursor_blink = true # TODO handle cursor blink pass 45: _core_service.dec_private_modes.reverse_wraparound = true 66: _core_service.dec_private_modes.application_keypad = true emit_signal("scrollbar_sync_requested") 9: # X10 Mouse # no release, no motion, no wheel, no modifiers. # _core_mouse_service.active_protocal = 'X10' # TODO pass 1000: # vt200 mouse pass 1002: # button event mouse pass 1003: # any event mouse pass 1004: # send focusin/focusout events # focusin: ^[[I # focusout: ^[[O _core_service.dec_private_modes.send_focus = true 1005: # utf8 ext mode mouse - removed in # 2507 pass 1006: # sgr ext mode mouse pass 1015: pass 25: # show cursor _core_service.is_cursor_hidden = false 1048: # alt screen cursor save_cursor() 1049: # alt screen buffer cursor save_cursor() continue 47, 1047, 1049: # alt screen buffer _buffer_service.buffers.activate_alt_buffer(_erase_attr_data()) _core_service.is_cursor_initialized = true emit_signal("refresh_rows_requested", 0, _buffer_service.rows - 1) emit_signal("scrollbar_sync_requested") 2004: # bracketed paste mode (https://cirw.in/blog/bracketed-paste) _core_service.dec_private_modes.bracketed_paste_mode = true func reset_mode_private(params): for param in params.to_array(): match param: 1: _core_service.dec_private_modes.application_cursor_keys = false 3: # DECCOLM - 80 column mode. # This is only active if 'set_win_lines' (24) is enabled # through `options.windows_options`. if _options_service.options.window_options.get("set_win_lines", false): _buffer_service.resize(80, _buffer_service.rows) emit_signal("reset_requested") 6: _core_service.dec_private_modes.origin = false _set_cursor(0, 0) 7: _core_service.dec_private_modes.wraparound = false 12: # cursor_blink = false # TODO: Handle cursor_blink pass 45: _core_service.dec_private_modes.reverse_wraparound = false 66: _core_service.dec_private_modes.application_keypad = false emit_signal("scrollbar_sync_requested") 9, 1000, 1002, 1003: # X10 Mouse, vt200 mouse, button event mouse and any event mouse respectively. # TODO: Core mouse service # _core_mouse_service.active_protocal = "NONE" pass 1004: # send focusin/focusout events _core_service.dec_private_modes.send_focus = false 1005: # utf8 ext mode mouse - removed in #2507 pass 1006: # sgr ext mode mouse # TODO pass 1015: # urxvt ext mode mouse - removed in #2507 pass 25: # hide cursor _core_service.is_cursor_hidden = true pass 1048: # alt screen cursor restore_cursor() 1049, 47, 1047: # Ensure the selection manager has the correct buffer. _buffer_service.buffers.activate_normal_buffer() if param == 1049: restore_cursor() _core_service.is_cursor_initialized = true emit_signal("refresh_rows_requested", 0, _buffer_service.rows - 1) emit_signal("scrollbar_sync_requested") 2004: # bracketed paste mode (https://cirw.in/blog/bracketed-paste) _core_service.dec_private_modes.bracketed_paste_mode = false # Helper to write color information packed with color mode. func _update_attr_color(color: int, mode: int, c1: int, c2: int, c3: int) -> int: if mode == 2: color |= Attributes.CM_RGB color &= ~Attributes.RGB_MASK color |= AttributeData.from_color_rgb([c1, c2, c3]) elif mode == 5: color &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) color |= Attributes.CM_P256 | (c1 & 0xff) return color # Helper to extract and apply color params/subparams. # Returns advance for params index. func _extract_color(params, pos: int, attr) -> int: # normalize params # meaning: [target, CM, ign, val, val, val] # RGB : [ 38/34, 2, ign, r, g, b] # P256 : [ 38/34, 5, ign, v, ign, ign] var accu = [0, 0, -1, 0, 0, 0] # alignment placeholder for non color space sequences var c_space = 0 # return advance we took in params var advance = -1 while advance + pos < params.length and advance + c_space < accu.size(): accu[advance + c_space] = params.get_param(pos + advance) advance += 1 # TODO FIX and FINISH me return advance func restore_cursor(params = null) -> void: self._buffer.x = self._buffer.saved_x if self._buffer.saved_x else 0 self._buffer.y = max(self._buffer.saved_y - self._buffer.ybase, 0) _cur_attr_data.fg = self._buffer.saved_cur_attr_data.fg _cur_attr_data.bg = self._buffer.saved_cur_attr_data.bg # FIXME _charset_service.charset = _saved_charset if self._buffer.saved_charset: _charset_service.charset = self._buffer.saved_charset _restrict_cursor() # ESC M # C1.RI # DEC mnemonic: HTS # Moves the cursor up one line in the same column. If the cursor is at the top margin, # the page scrolls down. # # @vt: #Y ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." # func reverse_index() -> void: _restrict_cursor() if buffer.y == buffer.scroll_top: # possibly move the code below to term.reverse_srcoll() # test: echo -ne '\e[1;1H\e[44m\eM\e[0m' # blank_line(true) is xterm/linux behavior var scroll_region_height = buffer.scroll_bottom - buffer.scroll_top buffer.lines.shift_elements(buffer.ybase + buffer.y, scroll_region_height, 1) buffer.lines.set_line(buffer.ybase + buffer.y, buffer.get_blank_line(_erase_attr_data())) else: buffer.y -= 1 _restrict_cursor() # quickfix to not run out of bounds # ESC c # DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) # Reset to initial state. func full_reset() -> void: _parser.reset() emit_signal("reset_requested") func reset() -> void: _cur_attr_data = DEFAULT_ATTRIBUTE_DATA _erase_attr_data_internal = DEFAULT_ATTRIBUTE_DATA func _process_underline(style: int, attr) -> void: # treat extended attrs as immutable, thus always clone from old one # this is needed since the buffer only holds references to it attr.extended = attr.extended.duplicate() # default to 1 == single underline if not ~style or style > 5: style = 1 attr.extended.underline_style = style attr.fg |= FgFlags.UNDERLINE # 0 deactivates underline if style == 0: attr.fg &= ~FgFlags.UNDERLINE # update HAS_EXTENDED in BG attr.update_extended() # back_color_erase feature for xterm. func _erase_attr_data(): _erase_attr_data_internal.bg &= ~(Attributes.CM_MASK | 0xFFFFFF) _erase_attr_data_internal.bg |= _cur_attr_data.bg & ~0xFC000000 return _erase_attr_data_internal # ESC # 8 # DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html) # This control function fills the complete screen area with # a test pattern (E) used for adjusting screen alignment. # # @vt: #Y ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." func screen_alignment_pattern() -> void: # prepare cell data var cell = CellData.new() cell.content = 1 << Content.WIDTH_SHIFT | 'E'.ord_at(0) cell.fg = _cur_attr_data.fg cell.bg = _cur_attr_data.bg _set_cursor(0, 0) for y_offset in range(0, _buffer_service.rows): var row = buffer.ybase + buffer.y + y_offset var line = buffer.lines.get_line(row) if line: line.fill(cell) line.is_wrapped = false _set_cursor(0, 0)