Add/update more files

This commit is contained in:
Leroy Hopson 2020-05-10 22:56:49 +12:00
parent 3307231b65
commit 0769592a1b
44 changed files with 4188 additions and 362 deletions

View file

@ -0,0 +1,127 @@
# Copyright (c) 2018 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const Attributes = Constants.Attributes
const FgFlags = Constants.FgFlags
const BgFlags = Constants.BgFlags
const UnderlineStyle = Constants.UnderlineStyle
var fg = 0
var bg = 0
var extended = ExtendedAttrs.new()
# flags
func is_inverse() -> int:
return fg & FgFlags.INVERSE
func is_bold() -> int:
return fg & FgFlags.BOLD
func is_underline() -> int:
return fg & FgFlags.UNDERLINE
func is_blink() -> int:
return fg & FgFlags.BLINK
func is_invisible() -> int:
return fg & FgFlags.INVISIBLE
func is_italic() -> int:
return fg & BgFlags.ITALIC
func is_dim() -> int:
return fg & BgFlags.DIM
# color modes
func get_fg_color_mode() -> int:
return fg & Attributes.CM_MASK
func get_bg_color_mode() -> int:
return bg & Attributes.CM_MASK
func is_fg_rgb() -> bool:
return (fg & Attributes.CM_MASK) == Attributes.CM_RGB
func is_bg_rgb() -> bool:
return (bg & Attributes.CM_MASK) == Attributes.CM_RGB
func is_fg_palette() -> bool:
return (fg & Attributes.CM_MASK) == Attributes.CM_P16 or (fg & Attributes.CM_MASK) == Attributes.CM_P256
func is_bg_palette() -> bool:
return (bg & Attributes.CM_MASK) == Attributes.CM_P16 or (bg & Attributes.CM_MASK) == Attributes.CM_P256
func is_fg_default() -> bool:
return (fg & Attributes.CM_MASK) == 0
func is_bg_default() -> bool:
return (bg & Attributes.CM_MASK) == 0
func is_attribute_default() -> bool:
return fg == 0 && bg == 0
func get_fg_color() -> int:
match fg & Attributes.CM_MASK:
Attributes.CM_P16, Attributes.CM_P256:
return fg & Attributes.PCOLOR_MASK
Attributes.CM_RGB:
return fg & Attributes.RGB_MASK
_:
return -1
func has_extended_attrs() -> int:
return bg & BgFlags.HAS_EXTENDED
func get_underline_color() -> int:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
match extended.underline_color & Attributes.CM_MASK:
Attributes.CM_P16, Attributes.CM_P256:
return extended.underline_color & Attributes.PCOLOR_MASK
Attributes.CM_RGB:
return extended.underline_color & Attributes.RGB_MASK
_:
return get_fg_color()
else:
return get_fg_color()
func get_underline_color_mode() -> int:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK
else:
return get_fg_color_mode()
func is_underline_color_rgb() -> bool:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK == Attributes.CM_RGB
else:
return is_fg_rgb()
func is_underline_color_palette() -> bool:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK == Attributes.CM_P16 \
or extended.underline_color & Attributes.CM_MASK == Attributes.CM_P256
else:
return is_fg_palette()
func is_underline_color_default() -> bool:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK == 0
else:
return is_fg_default()
func get_underline_style():
if fg & FgFlags.UNDERLINE:
return extended.underline_style if bg & BgFlags.HAS_EXTENDED else UnderlineStyle.SINGLE
else:
return UnderlineStyle.NONE
class ExtendedAttrs:
var underline_style = UnderlineStyle.NONE
var underline_color: int = -1
func _init():
underline_style

View file

@ -0,0 +1,141 @@
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const BufferLine = preload("res://addons/godot_xterm/buffer/buffer_line.gd")
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const Charsets = preload("res://addons/godot_xterm/data/charsets.gd")
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const CircularList = preload("res://addons/godot_xterm/circular_list.gd")
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const MAX_BUFFER_SIZE = 4294967295 # 2^32 - 1
var lines
var ydisp: int = 0
var ybase: int = 0
var y: int = 0
var x: int = 0
var scroll_bottom: int
var scroll_top: int
var tabs = {}
var saved_y: int = 0
var saved_x: int = 0
var saved_cur_attr_data = AttributeData.new()
var saved_charset = Charsets.DEFAULT_CHARSET
var markers: Array = []
var _null_cell = CellData.from_char_data([0, Constants.NULL_CELL_CHAR,
Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE])
var _whitespace_cell = CellData.from_char_data([0, Constants.WHITESPACE_CELL_CHAR,
Constants.WHITESPACE_CELL_WIDTH, Constants.WHITESPACE_CELL_CODE])
var _cols: int
var _rows: int
var _has_scrollback
var _options_service
var _buffer_service
func _init(has_scrollback: bool, options_service, buffer_service):
_has_scrollback = has_scrollback
_options_service = options_service
_buffer_service = buffer_service
_cols = buffer_service.cols
_rows = buffer_service.rows
lines = CircularList.new(_get_correct_buffer_length(_rows))
scroll_top = 0
scroll_bottom = _rows - 1
setup_tab_stops()
func get_null_cell(attr = null):
if attr:
_null_cell.fg = attr.fg
_null_cell.bg = attr.bg
_null_cell.extended = attr.extended
else:
_null_cell.fg = 0
_null_cell.bg = 0
_null_cell.extended = AttributeData.ExtendedAttrs.new()
return _null_cell
func get_blank_line(attr, is_wrapped: bool = false):
return BufferLine.new(_buffer_service.cols, get_null_cell(attr), is_wrapped)
func _get_correct_buffer_length(rows: int) -> int:
if not _has_scrollback:
return rows
else:
var correct_buffer_length = rows + _options_service.options.scrollback
return correct_buffer_length if correct_buffer_length < MAX_BUFFER_SIZE else MAX_BUFFER_SIZE
# Fills the viewport with blank lines.
func fill_viewport_rows(fill_attr = null) -> void:
if lines.length == 0:
if not fill_attr:
fill_attr = AttributeData.new()
var i = _rows
while i:
lines.push(get_blank_line(fill_attr))
i -= 1
# Clears the buffer to it's initial state, discarding all previous data.
func clear() -> void:
ydisp = 0
ybase = 0
y = 0
x = 0
lines = CircularList.new(_get_correct_buffer_length(_rows))
scroll_top = 0
scroll_bottom = _rows - 1
setup_tab_stops()
func get_wrapped_range_for_line(y: int) -> Dictionary:
var first = y
var last = y
# Scan upwards for wrapped lines
while first > 0 and lines.get_el(first).is_wrapped:
first -= 1
# Scan downwards for wrapped lines
while last + 1 < lines.length and lines.get_el(last + 1).is_wrapped:
last += 1
return {"first": first, "last": last}
func setup_tab_stops(i = null) -> void:
if i == null:
return
if not tabs.get(i):
i = prev_stop(i)
else:
tabs = {}
i = 0
while i < _cols:
tabs[i] = true
i += _options_service.options.tab_stop_width
func prev_stop(x: int) -> int:
if x == null:
x = self.x
while not tabs.get(x - 1, false) and x - 1 > 0:
x - 1
return _cols - 1 if x > _cols else 0 if x < 0 else x

View file

@ -0,0 +1,247 @@
# Copyright (c) 2018 The xterm.js authors. All rights reserved
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const Content = Constants.Content
const BgFlags = Constants.BgFlags
const CELL_SIZE = 3
enum Cell {
CONTENT
FG
BG
}
var _data: Array
var _combined: Dictionary = {}
var _extended_attrs: Dictionary = {}
var length: int
var is_wrapped
func _init(cols: int, fill_cell_data = null, is_wrapped: bool = false):
self.is_wrapped = is_wrapped
_data = []
_data.resize(cols * CELL_SIZE)
var cell = fill_cell_data if fill_cell_data \
else CellData.from_char_data([0, Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE])
for i in range(cols):
set_cell(i, cell)
length = cols
func get_cell(index: int):
return _data[index * CELL_SIZE + Cell.CONTENT]
func get_width(index: int) -> int:
return _data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT
func has_content(index: int) -> int:
return _data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK
# Get codepoint of the cell.
# To be in line with `code` in CharData this either returns
# a single UTF32 codepoint or the last codepoint of a combined string.
func get_codepoint(index: int) -> int:
var content = _data[index * CELL_SIZE + Cell.CONTENT]
if content & Content.IS_COMBINED_MASK:
return _combined[index].ord_at(_combined[index].length() - 1)
else:
return content & Content.CODEPOINT_MASK
func load_cell(index: int, cell):
var start_index = index * CELL_SIZE
cell.content = _data[start_index + Cell.CONTENT]
cell.fg = _data[start_index + Cell.FG]
cell.bg = _data[start_index + Cell.BG]
if cell.content and cell.content & Content.IS_COMBINED_MASK:
cell.combined_data = _combined[index]
if cell.bg & BgFlags.HAS_EXTENDED:
cell.extended = _extended_attrs[index]
return cell
func set_cell(index: int, cell) -> void:
if cell.content & Content.IS_COMBINED_MASK:
_combined[index] = cell.combined_data
if cell.bg & BgFlags.HAS_EXTENDED:
_extended_attrs[index] = cell.extended
_data[index * CELL_SIZE + Cell.CONTENT] = cell.content
_data[index * CELL_SIZE + Cell.FG] = cell.fg
_data[index * CELL_SIZE + Cell.BG] = cell.bg
func set_cell_from_codepoint(index: int, codepoint: int, width: int, fg: int, bg: int, e_attrs) -> void:
if bg & BgFlags.HAS_EXTENDED:
_extended_attrs[index] = e_attrs
_data[index * CELL_SIZE + Cell.CONTENT] = codepoint | (width << Content.WIDTH_SHIFT)
_data[index * CELL_SIZE + Cell.FG] = fg
_data[index * CELL_SIZE + Cell.BG] = bg
# Add a codepoint to a cell from input handler
# During input stage combining chars with a width of 0 follow and stack
# onto a leading char. Since we already set the attrs
# by the previous `set_data_from_code_pont` call, we can omit it here.
func add_codepoint_to_cell(index: int, codepoint: int) -> void:
var content = _data[index * CELL_SIZE + Cell.CONTENT]
if content & Content.IS_COMBINED_MASK:
# we already have a combined string, simply add
_combined[index] += char(codepoint)
else:
if content & Content.CODEPOINT_MASK:
# normal case for combining chars:
# - move current leading char + new one into combined string
# - set combined flag
_combined[index] = char(content & Content.CODEPOINT_MASK) + char(codepoint)
content &= ~Content.CODEPOINT_MASK # set codepoint in buffer to 0
content |= Content.IS_COMBINED_MASK
else:
# should not happen - we actually have no data in the cell yet
# simply set the data in the cell buffer with a width of 1
content = codepoint | (1 << Content.WIDTH_SHIFT)
_data[index * CELL_SIZE + Cell.CONTENT] = content
func insert_cells(pos: int, n: int, fill_cell_data, erase_attr = null) -> void:
pos %= length
# handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
var fg = erase_attr.fg if erase_attr and erase_attr.fg else 0
var bg = erase_attr.bg if erase_attr and erase_attr.bg else 0
var extended = erase_attr.extended if erase_attr and erase_attr.extended else AttributeData.ExtendedAttrs.new()
if pos and get_width(pos - 1) == 2:
set_cell_from_codepoint(pos - 1, 0, 1, fg, bg, extended)
if n < length - pos:
var cell = CellData.new()
var i = length - pos - n - 1
while i >= 0:
set_cell(pos + n + i, load_cell(pos + i, cell))
i -= 1
for i in range(n):
set_cell(pos + i, fill_cell_data)
else:
for i in range(pos, length):
set_cell(i, fill_cell_data)
# handle fullwidth at line end: reset last cell if it is first cell of a wide char
if get_width(length - 1) == 2:
set_cell_from_codepoint(length - 1, 0, 1, fg, bg, extended)
func delete_cells(pos: int, n: int, fill_cell_data, erase_attr = null) -> void:
pos %= length
if n < length - pos:
var cell = CellData.new()
for i in range(length - pos - n):
set_cell(pos + i, load_cell(pos + n + i, cell))
for i in range(length - n, length):
set_cell(i, fill_cell_data)
else:
for i in range(pos, length):
set_cell(i, fill_cell_data)
# handle fullwidth at pos:
# - reset pos-1 if wide char
# - reset pos if width==0 (previous second cell of a wide char)
var fg = erase_attr.fg if erase_attr and erase_attr.fg else 0
var bg = erase_attr.bg if erase_attr and erase_attr.bg else 0
var extended = erase_attr.extended if erase_attr and erase_attr.extended else AttributeData.ExtendedAttrs.new()
if pos and get_width(pos - 1) == 2:
set_cell_from_codepoint(pos - 1, 0, 1, fg, bg, extended)
if get_width(pos) == 0 and not has_content(pos):
set_cell_from_codepoint(pos, 0, 1, fg, bg, extended)
func replace_cells(start: int, end: int, fill_cell_data, erase_attr = null) -> void:
var fg = erase_attr.fg if erase_attr and erase_attr.fg else 0
var bg = erase_attr.bg if erase_attr and erase_attr.bg else 0
var extended = erase_attr.extended if erase_attr and erase_attr.extended else AttributeData.ExtendedAttrs.new()
# handle fullwidth at start: reset cell one to left if start is second cell of a wide char
if start and get_width(start - 1) == 2:
set_cell_from_codepoint(start - 1, 0, 1, fg, bg, extended)
# handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char
if end < length and get_width(end - 1) == 2:
set_cell_from_codepoint(end, 0, 1, fg, bg, extended)
while start < end and start < length:
set_cell(start, fill_cell_data)
start += 1
func resize(cols: int, fill_cell_data) -> void:
if cols == length:
return
if cols > length:
var data = []
if length:
if cols * CELL_SIZE < _data.size():
data = _data.slice(0, cols * CELL_SIZE - 1)
else:
data = _data.duplicate()
data.resize(cols * CELL_SIZE)
_data = data
var i = length
while i < cols:
set_cell(i, fill_cell_data)
i += 1
else:
if cols:
var data = []
data = _data.slice(0, cols * CELL_SIZE - 1)
data.resize(cols * CELL_SIZE)
_data = data
# Remove any cut off combined data, FIXME: repeat this for extended attrs
for key in _combined.keys():
if key as int > cols:
_combined.erase(key)
else:
_data = []
_combined = {}
length = cols
# Fill a line with `fill_cell_data`.
func fill(fill_cell_data) -> void:
_combined = {}
_extended_attrs = {}
for i in range(length):
set_cell(i, fill_cell_data)
func get_trimmed_length() -> int:
for i in range(length - 1, 0, -1):
if _data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK:
return i + (_data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT)
return 0
func translate_to_string(trim_right: bool = false, start_col: int = 0, end_col: int = -1) -> String:
if end_col == -1:
end_col = length
if trim_right:
end_col = min(end_col, get_trimmed_length())
var result = ""
while start_col < end_col:
var content = _data[start_col * CELL_SIZE + Cell.CONTENT]
var cp = content & Content.CODEPOINT_MASK
if content & Content.IS_COMBINED_MASK:
result += _combined[start_col]
elif cp:
result += char(cp)
else:
result += Constants.WHITESPACE_CELL_CHAR
start_col += max(content >> Content.WIDTH_SHIFT, 1) # always advance by 1
return result

View file

@ -0,0 +1,55 @@
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const Buffer = preload("res://addons/godot_xterm/buffer/buffer.gd")
signal buffer_activated(active_buffer, inactive_buffer)
var normal
var alt
var active
func _init(options_service, buffer_service):
normal = Buffer.new(true, options_service, buffer_service)
normal.fill_viewport_rows()
# The alt buffer should never have scrollback.
# See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
alt = Buffer.new(false, options_service, buffer_service)
active = normal
# TODO
#setup_tab_stops()
# Sets the normal Bufer of the BufferSet as its currently active Buffer.
func activate_normal_buffer() -> void:
if active == normal:
return
normal.x = alt.x
normal.y = alt.y
# The alt buffer should always be cleared when we switch to the normal
# buffer. This frees up memory since the alt buffer should always be new
# when activated.
alt.clear()
active = normal
emit_signal("buffer_activated", normal, alt)
# Sets the alt Buffer of the BufferSet as its currently active Buffer.
func activate_alt_buffer(fill_attr = null) -> void:
if active == alt:
return
# Since the alt buffer is always cleared when the normal buffer is
# activated, we want to fill it when switching to it.
alt.fill_viewport_rows(fill_attr)
alt.x = normal.x
alt.y = normal.y
active = alt
emit_signal("buffer_activated", alt, normal)

View file

@ -0,0 +1,68 @@
# Copyright (c) 2018 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends "res://addons/godot_xterm/buffer/attribute_data.gd"
# CellData - represents a single cell in the terminal buffer.
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
const Content = Constants.Content
# Helper to create CellData from CharData
static func from_char_data(value):
# Workaround as per: https://github.com/godotengine/godot/issues/19345#issuecomment-471218401
var char_data = load("res://addons/godot_xterm/buffer/cell_data.gd").new()
char_data.set_from_char_data(value)
return char_data
# Primitives from terminal buffer
var content = 0
var combined_data = ''
# Whether cell contains a combined string
func is_combined() -> int:
return content & Content.IS_COMBINED_MASK
func get_width() -> int:
return content >> Content.WIDTH_SHIFT
func get_chars() -> String:
if content & Content.IS_COMBINED_MASK:
return combined_data
elif content & Content.CODEPOINT_MASK:
return char(content & Content.CODEPOINT_MASK)
else:
return Constants.NULL_CELL_CHAR
func get_code() -> int:
if is_combined():
return combined_data.ord_at(combined_data.length() - 1)
else:
return content & Content.CODEPOINT_MASK
func set_from_char_data(value) -> void:
var attr: int = value[Constants.CHAR_DATA_ATTR_INDEX]
var character: String = value[Constants.CHAR_DATA_CHAR_INDEX]
var width: int = value[Constants.CHAR_DATA_WIDTH_INDEX]
var code: int = value[Constants.CHAR_DATA_CODE_INDEX]
fg = attr
bg = 0
# combined strings need special treatment. Javascript uses utf16 for strings
# whereas Godot uses utf8, therefore we don't need any of the special
# handling of surrogates in the original xterm.js code.
if character.length() >= 2:
combined_data = character
content = Content.IS_COMBINED_MASK | (width << Content.WIDTH_SHIFT)
else:
content = (character.ord_at(0) if character.length() else 0) | (width << Content.WIDTH_SHIFT)
func get_as_char_data():
return [fg, get_chars(), get_width(), get_code()]

View file

@ -0,0 +1,93 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const DEFAULT_COLOR = 256
const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0)
const CHAR_DATA_ATTR_INDEX = 0
const CHAR_DATA_CHAR_INDEX = 1
const CHAR_DATA_WIDTH_INDEX = 2
const CHAR_DATA_CODE_INDEX = 3
# Null cell - a real empty cell (containing nothing).
# Note that code should always be 0 for a null cell as
# several test condition of the buffer line rely on this.
const NULL_CELL_CHAR = ''
const NULL_CELL_WIDTH = 1
const NULL_CELL_CODE = 0
# Whitespace cell.
# This is meant as a replacement for empty cells when needed
# during rendering lines to preserve correct alignment.
const WHITESPACE_CELL_CHAR = ' '
const WHITESPACE_CELL_WIDTH = 1
const WHITESPACE_CELL_CODE = 32
# Bitmasks for accessing data in `content`.
enum Content {
CODEPOINT_MASK = 0x1FFFFF
IS_COMBINED_MASK = 0x200000
HAS_CONTENT_MASK = 0x3FFFFF
WIDTH_MASK = 0xC00000
WIDTH_SHIFT = 22
}
enum Attributes {
# bit 1..8 blue in RGB, color in P256 and P16
BLUE_MASK = 0xFF
BLUE_SHIFT = 0
PCOLOR_MASK = 0xFF
PCOLOR_SHIFT = 0
# bit 9..16 green in RGB
GREEN_MASK = 0xFF00
GREEN_SHIFT = 8
# bit 17..24 red in RGB
RED_MASK = 0xFF0000
RED_SHIFT = 16
# bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)
CM_MASK = 0x3000000
CM_DEFAULT = 0
CM_P16 = 0x1000000
CM_P256 = 0x2000000
CM_RGB = 0x3000000
# bit 1..24 RGB room
RGB_MASK = 0xFFFFFF
}
enum FgFlags {
# bit 27..31 (32th bit unused)
INVERSE = 0x4000000
BOLD = 0x8000000
UNDERLINE = 0x10000000
BLINK = 0x20000000
INVISIBLE = 0x40000000
}
enum BgFlags {
# bit 27..32 (upper 3 unused)
ITALIC = 0x4000000
DIM = 0x8000000
HAS_EXTENDED = 0x10000000
}
enum UnderlineStyle {
NONE
SINGLE
DOUBLE
CURLY
DOTTED
DASHED
}

View file

@ -0,0 +1,75 @@
# Copyright (c) 2016 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
# Represents a circular list; a list with a maximum size that wraps around when push is called,
# overriding values at the start of the list.
signal deleted
signal inserted
signal trimmed
var _array
var _start_index: int
var length: int = 0 setget _set_length,_get_length
var max_length: int setget _set_max_length,_get_max_length
func _set_length(new_length: int):
if new_length > length:
for i in range(length, new_length):
_array[i] = null
length = new_length
func _get_length():
return length
func _set_max_length(new_max_length):
if max_length == new_max_length:
return
# Reconstruct array, starting at index 0.
# Only transfer values from the indexes 0 to length.
var new_array = []
new_array.resize(new_max_length)
for i in range(0, min(new_max_length, length)):
new_array[i] = _array[_get_cyclic_index(i)]
_array = new_array
max_length = new_max_length
_start_index = 0
func _get_max_length():
return max_length
func _init(max_length):
self.max_length = max_length
_array = []
_array.resize(max_length)
_start_index = 0
func get_el(index: int):
return _array[_get_cyclic_index(index)]
func set_el(index: int, value) -> void:
_array[_get_cyclic_index(index)] = value
func push(value) -> void:
_array[_get_cyclic_index(length)] = value
if length == max_length:
_start_index += 1
_start_index %= max_length
emit_signal("trimmed", 1)
else:
length += 1
func _get_cyclic_index(index: int) -> int:
return _start_index + index % max_length

View file

@ -0,0 +1,108 @@
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
# Xterm.js stores colors in both css and rgba formats. In this case we only need
# to store the colors in godots RGBA Color format.
static func _generate_default_ansi_colors() -> PoolColorArray:
var colors = PoolColorArray([
# dark:
Color('#2e3436'),
Color('#cc0000'),
Color('#4e9a06'),
Color('#c4a000'),
Color('#3465a4'),
Color('#75507b'),
Color('#06989a'),
Color('#d3d7cf'),
# bright:
Color('#555753'),
Color('#ef2929'),
Color('#8ae234'),
Color('#fce94f'),
Color('#729fcf'),
Color('#ad7fa8'),
Color('#34e2e2'),
Color('#eeeeec'),
])
# Fill in the remaining 240 ANSI colors.
# Generate colors (16-231)
var v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
for i in range(0, 216):
var r = v[(i / 36) % 6 | 0]
var g = v[(i / 6) % 6 | 0]
var b = v[i % 6]
colors.append(Color(r, g, b))
# Generate greys (232-255)
for i in range(0, 24):
var c = 8 + i * 10
colors.append(Color(c, c, c))
return colors
const DEFAULT_FOREGROUND = Color('#ffffff')
const DEFAULT_BACKGROUND = Color('#000000')
const DEFAULT_CURSOR = Color('#ffffff')
const DEFAULT_CURSOR_ACCENT = Color('#000000')
const DEFAULT_SELECTION = Color(1, 1, 1, 0.3)
var DEFAULT_ANSI_COLORS = _generate_default_ansi_colors()
var colors
var _litmus_color: Gradient = Gradient.new()
var _contrast_cache: Dictionary = {}
func _init():
colors = {
'foreground': DEFAULT_FOREGROUND,
'background': DEFAULT_BACKGROUND,
'cursor': DEFAULT_CURSOR,
'cursor_accent': DEFAULT_CURSOR_ACCENT,
'selection': DEFAULT_SELECTION,
'selection_opaque': DEFAULT_BACKGROUND.blend(DEFAULT_SELECTION),
'ansi': DEFAULT_ANSI_COLORS,
'contrast_cache': _contrast_cache,
}
func on_options_change(key: String) -> void:
if key == 'minimum_contrast_ratio':
_contrast_cache.clear()
# Sets the terminal's theme.
# If a partial theme is provided then default
# colors will be used where colors are not defined.
func set_theme(theme: Dictionary = {}) -> void:
colors['foreground'] = theme.get('foreground', DEFAULT_FOREGROUND)
colors['bakcground'] = theme.get('background', DEFAULT_BACKGROUND)
colors['cursor'] = theme.get('cursor', DEFAULT_CURSOR)
colors['cursor_accent'] = theme.get('cursor_accent', DEFAULT_CURSOR_ACCENT)
colors['selection'] = theme.get('selection', DEFAULT_SELECTION)
colors['selection_opaque'] = theme.get('selection_opaque', colors['selection_opaque'])
colors['ansi'][0] = theme.get('black', DEFAULT_ANSI_COLORS[0])
colors['ansi'][1] = theme.get('red', DEFAULT_ANSI_COLORS[1])
colors['ansi'][2] = theme.get('green', DEFAULT_ANSI_COLORS[2])
colors['ansi'][3] = theme.get('yellow', DEFAULT_ANSI_COLORS[3])
colors['ansi'][4] = theme.get('blue', DEFAULT_ANSI_COLORS[4])
colors['ansi'][5] = theme.get('magenta', DEFAULT_ANSI_COLORS[5])
colors['ansi'][6] = theme.get('cyan', DEFAULT_ANSI_COLORS[6])
colors['ansi'][7] = theme.get('white', DEFAULT_ANSI_COLORS[7])
colors['ansi'][8] = theme.get('bright_black', DEFAULT_ANSI_COLORS[8])
colors['ansi'][9] = theme.get('bright_red', DEFAULT_ANSI_COLORS[9])
colors['ansi'][10] = theme.get('bright_green', DEFAULT_ANSI_COLORS[10])
colors['ansi'][11] = theme.get('bright_yellow', DEFAULT_ANSI_COLORS[11])
colors['ansi'][12] = theme.get('bright_blue', DEFAULT_ANSI_COLORS[12])
colors['ansi'][13] = theme.get('bright_magenta', DEFAULT_ANSI_COLORS[13])
colors['ansi'][14] = theme.get('bright_cyan', DEFAULT_ANSI_COLORS[14])
colors['ansi'][15] = theme.get('bright_white', DEFAULT_ANSI_COLORS[15])
_contrast_cache.clear()

View file

@ -0,0 +1,24 @@
# Copyrigth (c) 2016 The xterm.js authors. All rights reserved
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
# The character sets supported by the terminal. These enable several languages
# to be represented within the terminal with only 8-bit encoding. See ISO 2022
# for a discussion on character sets. Only VT100 character sets are supported.
const CHARSETS = {
# British character set
# ESC (A
# Reference: http://vt100.net/docs/vt220-rm/table2-5.html
'A': {
'#': '£'
},
# United States character set
# ESC (B
'B': null,
}
# The default character set, US.
const DEFAULT_CHARSET = CHARSETS['B']

View file

@ -3,4 +3,5 @@
[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.ttf" type="DynamicFontData" id=1]
[resource]
size = 20
font_data = ExtResource( 1 )

View file

@ -28,22 +28,19 @@ static func utf32_to_utf8(codepoint: int):
return utf8
# Convert UTF32 codepoint into a String.
static func string_from_codepoint(codepoint: int):
var utf8 = utf32_to_utf8(codepoint)
return utf8.get_string_from_utf8()
# Covert UTF32 char codes into a String.
# Basically the same as `string_from_codepoint` but for multiple codepoints
# Basically the same as `char` but for multiple codepoints
# in a loop (which is a lot faster).
static func utf32_to_string(data: Array, start: int = 0, end: int = -1):
if end == -1:
end = data.size()
var result = ''
for i in range(start, end):
result += string_from_codepoint(data[i])
result += char(data[i])
return result
# Utf8Decoder - decodes UTF8 byte sequences into UTF32 codepoints.
class Utf8ToUtf32:
var interim = PoolByteArray()

File diff suppressed because it is too large Load diff

View file

@ -244,7 +244,7 @@ func parse(data: Array, length: int):
handlers.invert()
for handler in handlers:
# undefined or true means success and to stop bubbling
if handler['target'].call(handler['method'], params.to_array()):
if handler['target'].call(handler['method'], params):
continue
handlers.invert()
if handlers.empty():

View file

@ -53,6 +53,17 @@ func _init(max_length: int = 32, max_sub_params_length: int = 32):
sub_params.resize(max_sub_params_length)
sub_params_idx.resize(max_length)
# Gets param at `index` from param if it exists and is non-zero.
# Otherwise returns `default` (which is zero anyway due to zero default
# mode (ZDM), but allows the caller to specify a non-zero default value).
func get_param(index: int, default = 0) -> int:
if index < params.size() and params[index]:
return params[index]
else:
return default
func add_param(value: int):
digit_is_sub = false
if length >= _max_length:
@ -78,7 +89,6 @@ func add_sub_param(value: int):
sub_params_idx[length - 1] += 1
func add_digit(value: int):
print("adding digit: ", value, " is sub: ", digit_is_sub)
var _length = sub_params_length if digit_is_sub else length
if _reject_digits or (not _length) or (digit_is_sub and _reject_sub_digits):
return
@ -86,6 +96,11 @@ func add_digit(value: int):
var cur = store[_length - 1]
store[_length - 1] = min(cur * 10 + value, MAX_VALUE) if ~cur else value
func size():
return params.size()
func to_array():
var res = []
for i in range(length):

View file

@ -1,5 +1,5 @@
# Copyright (c) 2020 The GodotXterm authors.
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference

View file

@ -1,3 +1,5 @@
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
# License MIT
tool
extends EditorPlugin

View file

@ -0,0 +1,250 @@
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const CanvasRenderingContext2D = preload("res://addons/godot_xterm/renderer/canvas_rendering_context_2d.gd")
const Attributes = Constants.Attributes
# TODO: Something about these consts and atlas
const INVERTED_DEFAULT_COLOR = Color(0, 0, 0, 1)
const DEFAULT_COLOR = Color(1, 1, 1, 0)
var _container: Node
var id: String
var z_index: int
var _alpha: bool
var _colors
var _renderer_id: int
var _buffer_service
var _options_service
var _ctx: CanvasRenderingContext2D
var _scaled_char_width: int = 0
var _scaled_char_height: int = 0
var _scaled_cell_width: int = 0
var _scaled_cell_height: int = 0
var _scaled_char_left: int = 0
var _scaled_char_top: int = 0
var _char_atlas
# An object that's reused when drawing glyphs in order to reduce GC.
class GlyphIdentifier:
extends Reference
var chars = ''
var code = 0
var bg = 0
var fg = 0
var bold = false
var dim = false
var italic = false
var _current_glyph_identifier = GlyphIdentifier.new()
func _init(container: Node, id: String, z_index: int, alpha: bool,
colors: Dictionary, renderer_id: int, buffer_service, options_service):
_container = container
self.id = id
self.z_index = z_index
_alpha = alpha
_colors = colors
_renderer_id = renderer_id
_buffer_service = buffer_service
_options_service = options_service
_ctx = CanvasRenderingContext2D.new()
_ctx.z_index = z_index
_container.add_child(_ctx)
func on_grid_changed(start_row: int, end_row: int) -> void:
pass
func resize(dim) -> void:
_scaled_cell_width = dim.scaled_cell_width
_scaled_cell_height = dim.scaled_cell_height
_scaled_char_width = dim.scaled_char_width
_scaled_char_height = dim.scaled_char_height
_scaled_char_left = dim.scaled_char_left
_scaled_char_top = dim.scaled_char_top
#_canvas_width = dim.scaled_canvas_width
#_canvas_height = dim.scaled_canvas_height
#this._canvas.style.width = `${dim.canvasWidth}px`;
#this._canvas.style.height = `${dim.canvasHeight}px`;
func _fill_cells(x: int, y: int, width: int, height: int) -> void:
_ctx.fill_rect(Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
width * _scaled_cell_width, height * _scaled_cell_height))
func _clear_cells(x: int, y: int, width: int, height: int) -> void:
var scaled = Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
width * _scaled_cell_width, height * _scaled_cell_height)
if _alpha:
_ctx.clear_rect(scaled)
else:
_ctx.fill_style = _colors.background
_ctx.fill_rect(scaled)
func _draw_chars(cell, x, y) -> void:
# TODO
#var contrast_color = _get_contrast_color(cell)
var contrast_color = null
# skip cache right away if we draw in RGB
# Note: to avoid bad runtime JoinedCellData will be skipped
# in the cache handler itself (atlasDidDraw == false) and
# fall through to uncached later down below
if contrast_color or cell.is_fg_rgb() or cell.is_bg_rgb():
_draw_uncached_chars(cell, x, y, contrast_color)
return
var fg
var bg
if cell.is_inverse():
fg = INVERTED_DEFAULT_COLOR if cell.is_bg_default() else cell.get_bg_color()
bg = INVERTED_DEFAULT_COLOR if cell.is_fg_default() else cell.get_fg_color()
else:
bg = DEFAULT_COLOR if cell.is_bg_default() else cell.get_bg_color()
fg = DEFAULT_COLOR if cell.is_fg_default() else cell.get_fg_color()
var draw_in_bright_color = _options_service.options.draw_bold_text_in_bright_colors and cell.is_bold() and fg < 8
fg = Color(fg as int + 8) if draw_in_bright_color else 0
_current_glyph_identifier.chars = cell.get_chars() if cell.get_chars() else Constants.WHITESPACE_CELL_CHAR
_current_glyph_identifier.code = cell.get_code() if cell.get_code() else Constants.WHITESPACE_CELL_CODE
_current_glyph_identifier.bg = bg
_current_glyph_identifier.fg = fg
_current_glyph_identifier.bold = cell.is_bold() as bool
_current_glyph_identifier.dim = cell.is_dim() as bool
_current_glyph_identifier.italic = cell.is_italic() as bool
var atlas_did_draw = _char_atlas and _char_atlas.draw(_ctx,
_current_glyph_identifier, x * _scaled_cell_width + _scaled_char_left,
y * _scaled_cell_width, _scaled_char_top)
if not atlas_did_draw:
_draw_uncached_chars(cell, x, y)
# Draws one or more charaters at one or more cells. The character(s) will be
# clipped to ensure that they fit with the cell(s), including the cell to the
# right if the last character is a wide character.
func _draw_uncached_chars(cell, x: int, y: int, fg_override = null) -> void:
_ctx.save()
_ctx.font = _get_font(cell.is_bold() as bool, cell.is_italic() as bool)
if cell.is_inverse():
if cell.is_bg_default():
_ctx.fill_style = _colors.background
elif cell.is_bg_rgb():
_ctx.fill_style = AttributeData.to_color_rgb(cell.get_bg_color())
else:
var bg = cell.get_bg_color()
if _options_service.options.draw_bold_text_in_bright_colors and cell.is_bold() and bg < 8:
bg += 8
_ctx.fill_style = _colors.ansi[bg]
else:
if cell.is_fg_default():
_ctx.fill_style = _colors.foreground
elif cell.is_fg_rgb():
_ctx.fill_style = AttributeData.to_color_rgb(cell.get_fg_color())
else:
var fg = cell.get_fg_color()
if _options_service.options.draw_bold_text_in_bright_colors and cell.is_bold() and fg < 8:
fg += 8
_ctx.fill_style = _colors.ansi[fg]
#_clip_row(y)
# Apply alpha to dim the character
if cell.is_dim():
pass
#_ctx.global_alpha = DIM_OPACITY
# Draw the character
_ctx.fill_text(cell.get_chars(), x * _scaled_cell_width + _scaled_char_left,
y * _scaled_cell_height + _scaled_char_top + _scaled_char_height / 2)
_ctx.restore()
func _get_font(is_bold: bool, is_italic: bool) -> Font:
var font_family = _options_service.options.font_family
if is_bold and is_italic and font_family.bold_italic:
return font_family.bold_italic
elif is_bold and font_family.bold:
return font_family.bold
elif is_italic and font_family.italic:
return font_family.italic
else:
return font_family.regular
func _get_contrast_color(cell):
if _options_service.options.minimum_contrast_ratio == 1:
return null
var adjusted_color = _colors.contrast_cache.get_color(cell.bg, cell.fg)
if adjusted_color != null:
return adjusted_color
var fg_color = cell.get_fg_color()
var fg_color_mode = cell.get_fg_color_mode()
var bg_color = cell.get_bg_color()
var bg_color_mode = cell.get_bg_color_mode()
var is_inverse = cell.is_inverse() as bool
var is_bold = cell.is_bold() as bool
if is_inverse:
var temp = fg_color
fg_color = bg_color
bg_color = temp
var temp2 = fg_color_mode
fg_color_mode = bg_color_mode
bg_color_mode = temp2
var bg_rgba = _resolve_background_rgba(bg_color_mode, bg_color, is_inverse)
var fg_rgba = _resolve_foreground_rgba(fg_color_mode, fg_color, is_inverse, is_bold)
# TODO
#var result = rgba.ensure_contrast_ratio(bg_rgba, fg_rgba, _options_service.options.minimum_contrast_ratio)
func _resolve_background_rgba(bg_color_mode: int, bg_color: int, inverse: bool) -> int:
match bg_color_mode:
Attributes.CM_P16, Attributes.CM_P256:
return _colors.ansi[bg_color].rgba
Attributes.CM_RGB:
return bg_color << 8
Attributes.CM_DEFAULT, _:
if inverse:
return _colors.foreground.rgba
else:
return _colors.background.rgba
func _resolve_foreground_rgba(fg_color_mode: int, fg_color: int, inverse: bool, bold: bool):
match fg_color_mode:
Attributes.CM_P16, Attributes.CM_P256:
if _options_service.options.draw_bold_text_in_bright_colors and bold and fg_color < 8:
return _colors.ansi[fg_color].rgba
Attributes.CM_RGB:
return fg_color << 8
Attributes.CM_DEFAULT, _:
if inverse:
return _colors.background.rgba
else:
return _colors.foreground.rgba

View file

@ -0,0 +1,74 @@
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
# License MIT
extends Node2D
class_name CanvasRenderingContext2D
# This is a shim for the CavasRenderingContext2D interface of HTML5's Canvas API,
# which the xterm.js renderer code uses heavily. It extends Node2D to take
# advantage of the z_index property and also uses many methods of CanvasItem
# which Node2D inherits.
var fill_style
var font = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
var _saved
var _draw_buffer = []
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
func draw_rect_deferred(rect: Rect2, color: Color):
_draw_buffer.append({"method": "draw_rect", "args": [rect, color]})
update()
func clear_rect(rect: Rect2):
draw_rect_deferred(rect, Color(0, 0, 0, 0))
func fill_rect(rect: Rect2):
draw_rect_deferred(rect, fill_style)
func fill_text(text: String, x: int, y: int):
_draw_buffer.append({"method": "_draw_text", "args": [font, Vector2(x, y), text, fill_style]})
update()
func _draw_text(font: Font, pos: Vector2, text: String, color) -> void:
for i in text.length():
var c = text[i]
var next_char = text[i + 1] if i + 1 < text.length() else ''
var advance = draw_char(font, pos, c, next_char, color)
pos.x += advance
func _draw():
for command in _draw_buffer:
self.callv(command.method, command.args)
_draw_buffer.resize(0)
func save():
_saved = {
'fill_style': fill_style,
'font': font,
}
func restore():
fill_style = _saved['fill_style']
font = _saved['font']
func measure_text(text: String):
var text_metrics = TextMetrics.new()
text_metrics.width = font.get_string_size(text).x
return text_metrics
class TextMetrics:
extends Reference
# https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics
var width

View file

@ -0,0 +1,46 @@
# Copyright (c) 2018 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
class JoinedCellData extends "res://addons/godot_xterm/buffer/attribute_data.gd":
var _width: int = 0
var content: int = 0
var combined_data: String = ''
func _init(first_cell, chars: String, width: int):
fg = first_cell.fg
bg = first_cell.bg
combined_data = chars
_width = width
var _character_joiners: Array = []
var _next_character_joiner_id = 0
var _work_cell = CellData.new()
var _buffer_service
func _init(buffer_service):
_buffer_service = buffer_service
func get_joined_characters(row: int) -> Array:
if _character_joiners.empty():
return []
var line = _buffer_service.buffer.lines.get_el(row)
if not line or line.length == 0:
return []
var ranges = []
var line_str = line.translate_to_string(true)
return ranges

View file

@ -0,0 +1,118 @@
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const CharacterJoinerRegistry = preload("res://addons/godot_xterm/renderer/character_joiner_registry.gd")
const TextRenderLayer = preload("res://addons/godot_xterm/renderer/text_render_layer.gd")
signal redraw_requested
signal options_changed
signal grid_changed(start, end)
var _id: int
var _render_layers: Array
var _device_pixel_ratio: float
var _character_joiner_registry
var _colors
var _container
var _buffer_service
var _options_service
var _char_size_service
var dimensions
func _init(colors, container: Node, buffer_service, options_service):
_id = get_instance_id()
_colors = colors
_container = container
_buffer_service = buffer_service
_options_service = options_service
var allow_transparency = _options_service.options.allow_transparency
_character_joiner_registry = CharacterJoinerRegistry.new(_buffer_service)
_render_layers = [
TextRenderLayer.new(_container, 0, _colors, _character_joiner_registry,
allow_transparency, _id, _buffer_service, _options_service)
]
# Connect render layers to our signals.
for layer in _render_layers:
self.connect("options_changed", layer, "on_options_changed")
self.connect("grid_changed", layer, "on_grid_changed")
dimensions = {
"scaled_char_width": 0,
"scaled_char_height": 0,
"scaled_cell_width": 0,
"scaled_cell_height": 0,
"scaled_char_left": 0,
"scaled_char_top": 0,
"scaled_canvas_width": 0,
"scaled_canvas_height": 0,
"canvas_width": 0,
"canvas_height": 0,
"actual_cell_width": 0,
"actual_cell_height": 0,
}
_device_pixel_ratio = OS.get_screen_dpi()
_update_dimensions()
emit_signal("options_changed")
func on_resize(cols, rows):
# Update character and canvas dimensions
_update_dimensions()
# Resize all render layers
for layer in _render_layers:
layer.resize(dimensions)
func refresh_rows(start: int, end: int) -> void:
emit_signal("grid_changed", start, end)
# Recalculates the character and canvas dimensions.
func _update_dimensions():
var char_width = 0
var char_height = 0
for font in _options_service.options.font_family.values():
var size = font.get_string_size("W")
char_width = max(char_width, size.x)
char_height = max(char_height, size.y)
dimensions.scaled_char_width = char_width
dimensions.scaled_char_height = char_height
# Calculate the scaled cell height, if line_height is not 1 then the value
# will be floored because since line_height can never be lower then 1, there
# is a guarantee that the scaled line height will always be larger than
# scaled char height.
dimensions.scaled_cell_height = floor(dimensions.scaled_char_height * _options_service.options.line_height)
# Calculate the y coordinate within a cell that text should draw from in
# order to draw in the center of a cell.
dimensions.scaled_char_top = 0 if _options_service.options.line_height == 1 else \
round((dimensions.scaled_cell_height - dimensions.scaled_char_height) / 2)
# Calculate the scaled cell width, taking the letter_spacing into account.
dimensions.scaled_cell_width = dimensions.scaled_char_width + round(_options_service.options.letter_spacing)
# Calculate the x coordinate with a cell that text should draw from in
# order to draw in the center of a cell.
dimensions.scaled_char_left = floor(_options_service.options.letter_spacing / 2)

View file

@ -0,0 +1,191 @@
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends "res://addons/godot_xterm/renderer/base_render_layer.gd"
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const CharacterJoinerRegistry = preload("res://addons/godot_xterm/renderer/character_joiner_registry.gd")
const JoinedCellData = CharacterJoinerRegistry.JoinedCellData
const Content = Constants.Content
var _state
var _character_width: int = 0
var _character_font: DynamicFont = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
var _character_overlap_cache: Dictionary = {}
var _character_joiner_registry
var _work_cell = CellData.new()
func _init(container: Node, z_index: int, colors, character_joiner_registry,
alpha: bool, renderer_id: int, buffer_service, options_service).(container,
'text', z_index, alpha, colors, renderer_id, buffer_service, options_service):
_state = null #TODO what?
_character_joiner_registry = character_joiner_registry
func on_grid_changed(first_row: int, last_row: int) -> void:
_clear_cells(0, first_row, _buffer_service.cols, last_row - first_row + 1)
_draw_background(first_row, last_row)
_draw_foreground(first_row, last_row)
# Finally draw everything that has been queued in the draw buffer.
_ctx.update()
func on_options_changed() -> void:
pass
func _cells(first_row: int, last_row: int, joiner_registry = null) -> Array:
var cells = []
for y in range(first_row, last_row + 1):
var row = y + _buffer_service.buffer.ydisp
var line = _buffer_service.buffer.lines.get_el(row)
var joined_ranges = joiner_registry.get_joined_characters(row) if joiner_registry else []
for x in range(_buffer_service.cols):
line.load_cell(x, _work_cell)
var cell = _work_cell
# If true, indicates that the current character(s) to draw were joined.
var is_joined = false
var last_char_x = x
# The character to the left is a wide character, drawing is owned by
# the char at x-1
if cell.get_width() == 0:
continue
# Process any joined character range as needed. Because of how the
# ranges are produced, we know that they are valid for the characters
# and attributes of our input.
if not joined_ranges.empty() and x == joined_ranges[0][0]:
is_joined = true
var r = joined_ranges.pop_front()
# We already know the exact start and end column of the joined
# range, so we get the string and width representing it directly
cell = JoinedCellData.new(_work_cell,
line.trans_late_to_string(true, r[0], r[1]), r[1] - r[0])
# Skip over the cells occupied by this range in the loop
last_char_x = r[1] - 1
# If the character is an overlapping char and the character to the
# right is a space, take ownership of the cell to the right. We skip
# this check for joined characters because their rendering likely won't
# yield the same result as rendering the last character individually.
if not is_joined and _is_overlapping(cell):
if last_char_x < line.length - 1 and line.get_codepoint(last_char_x + 1) == Constants.NULL_CELL_CODE:
# patch width to 2
cell.content &= ~Content.WIDTH_MASK
cell.content |= 2 << Content.WIDTH_SHIFT
# Append a new instance of cell, as we wil reuse the current instance.
cells.append({"cell": CellData.from_char_data(cell.get_as_char_data()),
"x": x, "y": y})
x = last_char_x
return cells
func _draw_background(first_row: int, last_row: int) -> void:
var ctx = _ctx
var cols = _buffer_service.cols
var start_x = 0
var start_y = 0
var prev_fill_style = null
ctx.save()
for c in _cells(first_row, last_row, null):
var cell = c.cell
var x = c.x
var y = c.y
# libvte and xterm draw the background (but not foreground) of invisible characters,
# so we should too.
var next_fill_style = null # null represents the default background color
if cell.is_inverse():
if cell.is_fg_default():
next_fill_style = _colors.foreground
elif cell.is_fg_rgb():
next_fill_style = cell.get_fg_color() # TODO: Figure out how to convert this to Color()
else:
next_fill_style = _colors.ansi[cell.get_fg_color()]
elif cell.is_bg_rgb():
next_fill_style = cell.get_bg_color() # TODO: Figure out how to convert this to Color()
elif cell.is_bg_palette():
next_fill_style = _colors.ansi[cell.get_bg_color()]
if prev_fill_style == null:
# This is either the first iteration, or the default background was set. Either way, we
# don't need to draw anything.
start_x = x
start_y = y
if y != start_y:
# our row changed, draw the previous row
ctx.fill_style = prev_fill_style if prev_fill_style else Color()
_fill_cells(start_x, start_y, cols - start_x, 1)
start_x = x
start_y = y
elif prev_fill_style != next_fill_style:
# our color changed, draw the previous characters in this row
ctx.fill_style = prev_fill_style if prev_fill_style else Color()
start_x = x
start_y = y
prev_fill_style = next_fill_style
# flush the last color we encountered
if prev_fill_style != null:
ctx.fill_style = prev_fill_style
_fill_cells(start_x, start_y, cols - start_x, 1)
ctx.restore()
func _draw_foreground(first_row: int, last_row: int) -> void:
for c in _cells(first_row, last_row, _character_joiner_registry):
var cell = c.cell
var x = c.x
var y = c.y
if cell.is_invisible():
return
_draw_chars(cell, x, y)
func _is_overlapping(cell) -> bool:
# Only single cell characters can be overlapping, rendering issues can
# occur without this check
if cell.get_width() != 1:
return false
var chars = cell.get_chars()
# Deliver from cache if available
if _character_overlap_cache.has(chars):
return _character_overlap_cache[chars]
# Setup the font
_ctx.save()
_ctx.font = _character_font
# Measure the width of the character, but floor it
# because that is what the renderer does when it calculates
# the character dimensions wer are comparing against
var overlaps = floor(_ctx.measure_text(chars).width) > _character_width
# Restore the original context
_ctx.restore()
# Cache and return
_character_overlap_cache[chars] = overlaps
return overlaps

View file

@ -0,0 +1,39 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
signal buffer_activated(active_buffer, inactive_buffer)
signal resized
const BufferSet = preload("res://addons/godot_xterm/buffer/buffer_set.gd")
const MINIMUM_COLS = 2 # Less than 2 can mess with wide chars
const MINIMUM_ROWS = 1
var service_brand
var cols: int
var rows: int
var buffers
# Whether the user is scrolling (locks the scroll position)
var is_user_scrolling: bool = false
var _options_service
var buffer setget ,_get_buffer
func _get_buffer():
return buffers.active if buffers else null
func _init(options_service):
_options_service = options_service
cols = max(_options_service.options.cols, MINIMUM_COLS)
rows = max(_options_service.options.rows, MINIMUM_ROWS)
buffers = BufferSet.new(_options_service, self)
buffers.connect("buffer_activated", self, "_buffer_activated")
func _buffer_activated(active_buffer, inactive_buffer):
emit_signal("buffer_activated", active_buffer, inactive_buffer)

View file

@ -0,0 +1,30 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
var service_brand
var charset = null
var glevel: int = 0
var _charsets = []
func reset() -> void:
charset = null
_charsets = []
glevel = 0
func set_glevel(g: int) -> void:
glevel = g
charset = _charsets[g]
func set_gcharset(g: int, charset = null) -> void:
if _charsets.size() < g + 1:
_charsets.resize(g + 1)
_charsets[g] = charset
if glevel == g:
charset = charset

View file

@ -0,0 +1,27 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
var DEFAULT_MODES = {
"insert_mode": false,
}
var DEFAULT_DEC_PRIVATE_MODES = {
"application_cursor_keys": false,
"application_keypad": false,
"bracketed_paste_mode": false,
"origin": false,
"reverse_wraparound": false, # defaults: xterm -true, vt100 - false
}
var modes = DEFAULT_MODES.duplicate()
var dec_private_modes = DEFAULT_DEC_PRIVATE_MODES.duplicate()
var is_cursor_hidden = false
var is_cursor_initialized = true
func reset():
modes = DEFAULT_MODES.duplicate()
dec_private_modes.duplicate()

View file

@ -0,0 +1,53 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
class TerminalOptions:
var cols: int
var rows: int
var cursor_blink: bool
var cursor_style
var cursor_width: int
var bell_sound
var bell_style
var draw_bold_text_in_bright_colors: bool
var fast_scroll_modifier
var fast_scroll_sensitivity: int
var font_family: Dictionary
var font_size: int
var font_weight: String
var font_weight_bold: String
var line_height: float
var link_tooltip_hover_duration: int
var letter_spacing: float
var log_level
var scrollback: int
var scroll_sensitivity: int
var screen_reader_mode: bool
var mac_option_is_meta: bool
var mac_option_click_forces_selection: bool
var minimum_contrast_ratio: float
var disable_stdin: bool
var allow_proposed_api: bool
var allow_transparency: bool
var tab_stop_width: int
var colors: Dictionary
var right_click_selects_word
var renderer_type
var window_options: Dictionary
var windows_mode: bool
var word_separator: String
var convert_eol: bool
var term_name: String
var cancel_events: bool
signal option_changed
var options
func _init(options):
self.options = options

View file

@ -1,16 +1,42 @@
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
# License MIT
# Copyright (c) 2014-2020 The xterm.js authors. All rights reserved.
# Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
# Ported to GDScript by the GodotXterm authors.
# Licese MIT
#
# Originally forked from (with the author's permission):
# Fabrice Bellard's javascript vt100 for jslinux:
# http://bellard.org/jslinux/
# Copyright (c) 2011 Fabrice Bellard
# The original design remains. The terminal itself
# has been extended to include xterm CSI codes, among
# other features.
#
# Terminal Emulation References:
# http://vt100.net/
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
# http://invisible-island.net/vttest/
# http://www.inwap.com/pdp10/ansicode.txt
# http://linux.die.net/man/4/console_codes
# http://linux.die.net/man/7/urxvt
tool
extends Control
signal data_sent(data)
const BufferService = preload("res://addons/godot_xterm/services/buffer_service.gd")
const CoreService = preload("res://addons/godot_xterm/services/core_service.gd")
const OptionsService = preload("res://addons/godot_xterm/services/options_service.gd")
const CharsetService = preload("res://addons/godot_xterm/services/charset_service.gd")
const InputHandler = preload("res://addons/godot_xterm/input_handler.gd")
const Const = preload("res://addons/godot_xterm/Constants.gd")
const Constants = preload("res://addons/godot_xterm/parser/constants.gd")
const Parser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd")
const Buffer = preload("res://addons/godot_xterm/buffer.gd")
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
const Renderer = preload("res://addons/godot_xterm/renderer/renderer.gd")
const ColorManager = preload("res://addons/godot_xterm/color_manager.gd")
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const SourceCodeProRegular = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
const SourceCodeProBold = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres")
const SourceCodeProItalic = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres")
@ -26,342 +52,200 @@ const LEFT_BRACKET = 91
const ENTER = 10
const BACKSPACE_ALT = 127
export (Font) var normal_font = SourceCodeProRegular setget _set_normal_font
export (Font) var bold_font = SourceCodeProBold setget _set_bold_font
export (Font) var italic_font = SourceCodeProItalic setget _set_italics_font
export (Font) var bold_italic_font = SourceCodeProBoldItalic setget _set_bold_italics_font
var buffer
var alternate_buffer
var parser
# TODO: Move me somewhere else.
enum BellStyle {
NONE
}
signal output(data)
export var cols = 80
export var rows = 24
export var cursor_blink = false
export var cursor_style = 'block'
export var cursor_width = 1
export var bell_sound: AudioStream = null # TODO Bell sound
export(BellStyle) var bell_style = BellStyle.NONE
export var draw_bold_text_in_bright_colors = true
export var fast_scroll_modifier = 'alt' # TODO Use scancode?
export var fast_scroll_sensitivity = 5
export var font_family: Dictionary = {
"regular": SourceCodeProRegular,
"bold": SourceCodeProBold,
"italic": SourceCodeProItalic,
"bold_italic": SourceCodeProBoldItalic,
}
export var font_size: int = 15
export var font_weight = 'normal' # Enum?
export var font_weight_bold = 'bold' # Enum?
export var line_height = 1.0
export var link_tooltip_hover_duration = 500 # Not relevant?
export var letter_spacing = 0
export var log_level = 'info' # Not relevant?
export var scrollback = 1000
export var scroll_sensitivity = 1
export var screen_reader_mode: bool = false
export var mac_option_is_meta = false
export var mac_option_click_forces_selection = false
export var minimum_contrast_ratio = 1
export var disable_stdin = false
export var allow_proposed_api = true
export var allow_transparency = false
export var tab_stop_width = 8
export var colors: Dictionary = {
'black': Color(0, 0, 0)
}
export var right_click_selects_word = 'isMac' # TODO
export var renderer_type = 'canvas' # Relevant?
export var window_options = {
'set_win_lines': false
}
export var windows_mode = false
export var word_separator = " ()[]{}',\"`"
export var convert_eol = true
export var term_name = 'xterm'
export var cancel_events = false
var options_service
var decoder
var cols = 80
var rows = 24
var cell: Vector2
# font flags
export(int, FLAGS,
"Bold",
"Italic", # Not xterm-256color
"Underlined",
"Blink",
"Inverse",
"Invisible",
"Strikethrough" # Not xterm-256color
) var font_flags = Const.FONT_NORMAL
func _init():
pass
func _set_normal_font(font: Font) -> void:
normal_font = font
_calculate_cell_size()
func _set_bold_font(font: Font) -> void:
bold_font = font
_calculate_cell_size()
func _set_italics_font(font: Font) -> void:
italic_font = font
_calculate_cell_size()
func _set_bold_italics_font(font: Font) -> void:
bold_italic_font = font
_calculate_cell_size()
func _calculate_cell_size() -> void:
var x = 0.0
var y = 0.0
var fonts = [normal_font, bold_font, italic_font, bold_italic_font]
for font in fonts:
if not font:
continue
var size = font.get_string_size("W")
x = max(x, size.x)
y = max(y, size.y)
cell.x = x
cell.y = y
var parser
var _buffer_service
var _core_service
var _charset_service
var _input_handler
var _render_service
var _color_manager
var _scaled_char_width
var _scaled_char_height
var _scaled_cell_width
var _scaled_cell_height
var _scaled_char_top
var _scaled_char_left
var _work_cell = CellData.new()
func _ready():
_calculate_cell_size()
var rect = get_rect()
var rs = rect_size
cols = (rect_size.x / cell.x) as int
rows = (rect_size.y / cell.y) as int
var options = OptionsService.TerminalOptions.new()
options.cols = cols
options.rows = rows
options.font_family = font_family
options.line_height = line_height
options.screen_reader_mode = screen_reader_mode
options.window_options = window_options
options.convert_eol = convert_eol
decoder = Decoder.Utf8ToUtf32.new()
options_service = OptionsService.new(options)
options_service.connect("option_changed", self, "_update_options")
buffer = Buffer.new(rows, cols)
alternate_buffer = Buffer.new(rows, cols, true)
_buffer_service = BufferService.new(options_service)
_core_service = CoreService.new()
_charset_service = CharsetService.new()
parser = Parser.new()
# Print handler
parser.set_print_handler(buffer, "insert_at_cursor")
# Register input handler and connect signals.
_input_handler = InputHandler.new(_buffer_service, _core_service, _charset_service, options_service)
_input_handler.connect("bell_requested", self, "bell")
_input_handler.connect("refresh_rows_requested", self, "_refresh_rows")
_input_handler.connect("reset_requested", self, "reset")
_input_handler.connect("scroll_requested", self, "scroll")
_input_handler.connect("windows_options_report_requested", self, "report_windows_options")
# Execute handlers
parser.set_execute_handler(C0.BEL, self, 'bell')
parser.set_execute_handler(C0.LF, buffer, 'line_feed')
parser.set_execute_handler(C0.VT, buffer, 'line_feed')
parser.set_execute_handler(C0.FF, buffer, 'line_feed')
parser.set_execute_handler(C0.CR, buffer, 'carriage_return')
parser.set_execute_handler(C0.BS, buffer, 'backspace')
parser.set_execute_handler(C0.HT, buffer, 'insert_tab');
parser.set_execute_handler(C0.SO, self, 'shift_out')
parser.set_execute_handler(C0.SI, self, 'shift_in')
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')
_color_manager = ColorManager.new()
_color_manager.set_theme(colors)
_render_service = Renderer.new(_color_manager.colors, self, _buffer_service, options_service)
connect("resized", self, "_update_dimensions")
_update_dimensions()
# CSI handlers
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_charAbsolute')
parser.set_csi_handler({'final': 'H'}, buffer, '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')
func print(data, start, end):
print(data.substr(start, end))
func bell():
print("The bell signal was emited!")
func _refresh_rows(start_row = 0, end_row = 0):
# Not optimized, just draw
update()
func line_feed():
pass
func carriage_return():
print("carriage return!")
func backspace():
print("backspace!")
pass
func tab():
pass
func shift_out():
pass
func shift_in():
pass
func index():
pass
func next_line():
pass
func tab_set():
pass
func insert_chars(params):
pass
func scroll_left(params):
pass
func cursor_up(params):
pass
func scroll_right(params):
pass
func cursor_down(params):
pass
func cursor_forward(params):
pass
func cursor_backward(params):
pass
func cursor_next_line(params):
pass
func cursor_preceding_line(params):
pass
func cursor_char_absolute(params):
pass
func cursor_position(params):
pass
func cursor_forward_tab(params):
pass
func erase_in_display(params):
pass
func erase_in_line(params):
pass
func insert_lines(params):
pass
func delete_lines(params):
pass
func delete_chars(params):
pass
func scroll_up(params):
pass
func scroll_down(params):
pass
func erase_chars(params):
pass
func cursor_backward_tab(params):
pass
func char_pos_absolute(params):
pass
func h_position_relative(params):
pass
func repeat_preceding_character(params):
pass
func send_device_attributes_primary(params):
pass
func send_device_attributes_secondary(params):
pass
func line_pos_absolute(params):
pass
func v_position_relative(params):
pass
func h_v_position(params):
pass
func tab_clear(params):
pass
func set_mode(params):
pass
func set_mode_private(params):
pass
func reset_mode(params):
pass
func char_attributes(params):
pass
func device_status(params):
pass
func device_status_private(params):
pass
func soft_reset(params):
pass
func set_cursor_style(params):
pass
func set_scroll_region(params):
pass
func save_cursor(params):
pass
func window_options(params):
pass
func restore_cursor(params):
pass
func insert_columns(params):
pass
func delete_columns(params):
pass
func _input(event):
if event is InputEventKey and event.pressed:
var data = PoolByteArray([])
accept_event()
# TODO: Handle more of these.
if (event.control and event.scancode == KEY_C):
send_data(PoolByteArray([3]))
data.append(3)
elif event.unicode:
send_data(PoolByteArray([event.unicode]))
data.append(event.unicode)
elif event.scancode == KEY_ENTER:
send_data(PoolByteArray([ENTER]))
data.append(ENTER)
elif event.scancode == KEY_BACKSPACE:
send_data(PoolByteArray([BACKSPACE_ALT]))
data.append(BACKSPACE_ALT)
elif event.scancode == KEY_ESCAPE:
send_data(PoolByteArray([27]))
data.append(27)
elif event.scancode == KEY_TAB:
send_data(PoolByteArray([9]))
data.append(9)
elif OS.get_scancode_string(event.scancode) == "Shift":
pass
elif OS.get_scancode_string(event.scancode) == "Control":
pass
else:
push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode)))
pass
#push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode)))
emit_signal("output", data)
func send_data(data: PoolByteArray):
emit_signal("data_sent", data)
func write(data, callback_target = null, callback_method: String = ''):
_input_handler.parse(data)
if callback_target and callback_method:
callback_target.call(callback_method)
func refresh(start = null, end = null) -> void:
pass
# Recalculates the character and canvas dimensions.
func _update_dimensions():
var char_width = 0
var char_height = 0
for font in options_service.options.font_family.values():
var size = font.get_string_size("W")
char_width = max(char_width, size.x)
char_height = max(char_height, size.y)
_scaled_char_width = char_width
_scaled_char_height = char_height
# Calculate the scaled cell height, if line_height is not 1 then the value
# will be floored because since line_height can never be lower then 1, there
# is a guarantee that the scaled line height will always be larger than
# scaled char height.
_scaled_cell_height = floor(_scaled_char_height * options_service.options.line_height)
# Calculate the y coordinate within a cell that text should draw from in
# order to draw in the center of a cell.
_scaled_char_top = 0 if options_service.options.line_height == 1 else \
round((_scaled_cell_height - _scaled_char_height) / 2)
# Calculate the scaled cell width, taking the letter_spacing into account.
_scaled_cell_width = _scaled_char_width + round(options_service.options.letter_spacing)
# Calculate the x coordinate with a cell that text should draw from in
# order to draw in the center of a cell.
_scaled_char_left = floor(options_service.options.letter_spacing / 2)
func _draw():
# Draw the terminal background
draw_rect(get_rect(), Color(0.0, 0.5, 0.0))
# Naive method. Draw the entire buffer starting with row 0.
for row in range(buffer.rows.size()):
#print("Doing the thing for row: ", row)
# Draw each CharacterData.
for col in range(buffer.rows[row].size()):
var data = buffer.rows[row][col]
#print("row: ", ((row + 1) * charHeight), " col: ", (col * charWidth))
_draw_character(col, row, data)
# Draw the cursor.
_draw_cursor()
func _draw_character(col, row, data):
# Draw the background.
draw_rect(Rect2(Vector2(col * cell.x, row * cell.y), Vector2(cell.x, cell.y)), data.bg)
var font
if data.ff & (1 << Const.FONT_BOLD) and data.ff & (1 << Const.FONT_ITALIC):
font = bold_italic_font
elif data.ff & (1 << Const.FONT_BOLD):
font = bold_font
elif data.ff & (1 << Const.FONT_ITALIC):
font = italic_font
else:
font = normal_font
# Draw the character using foreground color.
draw_char(font, Vector2(col * cell.x, (row + 1) * cell.y), data.ch, '', data.fg)
func _draw_cursor():
draw_rect(Rect2(Vector2(buffer.ccol * cell.x, buffer.crow * cell.y), Vector2(cell.x, cell.y)), Color(1.0, 0.0, 1.0))
func receive_data(data: PoolByteArray):
var utf32 = []
var length = decoder.decode(data, utf32)
parser.parse(utf32, length)
update()
# Draw the background and foreground
var buffer = _buffer_service.buffer
for y in range(buffer.ybase, rows):
var line = buffer.lines.get_el(y)
for x in line.length:
line.load_cell(x, _work_cell)
draw_rect(Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
(cols - x) * _scaled_cell_width, 1 * _scaled_cell_height), Color())
var color = _color_manager.colors.ansi[_work_cell.get_fg_color()] if _work_cell.get_fg_color() >= 0 else Color(1, 1, 1)
draw_char(options_service.options.font_family.regular,
Vector2(x * _scaled_cell_width + _scaled_char_left,
y * _scaled_cell_height + _scaled_char_top + _scaled_char_height / 2),
_work_cell.get_chars() if _work_cell.get_chars() else ' ', "", color)
# Draw the cursor
# Draw selection