mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-05 04:34:23 +02:00
Add all the files
This commit is contained in:
parent
d7db117f8b
commit
96e9ddcf79
68 changed files with 9064 additions and 7 deletions
23
addons/godot_xterm/Constants.gd
Normal file
23
addons/godot_xterm/Constants.gd
Normal file
|
@ -0,0 +1,23 @@
|
|||
extends Reference
|
||||
|
||||
# font flags
|
||||
enum {
|
||||
FONT_NORMAL = 0,
|
||||
FONT_BOLD = 1 << 0
|
||||
FONT_ITALIC = 1 << 1,
|
||||
FONT_UNDERLINED = 1 << 2
|
||||
FONT_BLINK = 1 << 3
|
||||
FONT_INVERSE = 1 << 4
|
||||
FONT_IVSIBILE = 1 << 5
|
||||
FONT_STRIKETHROUGH = 1 << 6
|
||||
}
|
||||
|
||||
# colors
|
||||
const COLOR_BLACK = Color(0.0, 0.0, 0.0) # 0
|
||||
const COLOR_RED = Color(1.0, 0.0, 0.0) # 1
|
||||
const COLOR_GREEN = Color(0.0, 1.0, 0.0) # 2
|
||||
const COLOR_YELLOW = Color(1.0, 1.0, 0.0) # 3
|
||||
const COLOR_BLUE = Color(0.0, 0.0, 1.0) # 4
|
||||
const COLOR_MAGENTA = Color(1.0, 0.0, 1.0) # 5
|
||||
const COLOR_CYAN = Color(0.0, 1.0, 1.0) # 6
|
||||
const COLOR_WHITE = Color(1.0, 1.0, 1.0) # 7
|
271
addons/godot_xterm/buffer.gd
Normal file
271
addons/godot_xterm/buffer.gd
Normal file
|
@ -0,0 +1,271 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const CharData = preload("res://addons/godot_xterm/char_data.gd")
|
||||
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
|
||||
|
||||
const MAX_BUFFER_SIZE = 32768 # 32 KiB
|
||||
|
||||
|
||||
|
||||
# Erase in Line (EL)
|
||||
enum {EL_RIGHT, EL_LEFT, EL_ALL}
|
||||
enum {FONT_NORMAL, FONT_BOLD, FONT_BLINK}
|
||||
|
||||
# Places a tab stop after every 8 columns.
|
||||
var tabWidth = 8
|
||||
|
||||
var rows = [[]] # array of CharData
|
||||
|
||||
var fg = Color(1.0, 1.0, 1.0) # foreground color
|
||||
var bg = Color(0.0, 0.0, 0.0) # background color
|
||||
var font # font
|
||||
var font_flags = FONT_NORMAL
|
||||
|
||||
var crow = 0 setget _set_crow # cursor's row
|
||||
var ccol = 0 setget _set_ccol # cursor's column
|
||||
|
||||
var ccol_saved: int
|
||||
var crow_saved: int
|
||||
|
||||
var num_rows = 20
|
||||
var num_cols = 70
|
||||
|
||||
var savedBuffer
|
||||
var savedCursorRow
|
||||
var savedCursorCol
|
||||
|
||||
func _init(num_rows: int, num_columns: int, alternate: bool = false):
|
||||
rows = []
|
||||
rows.resize(num_rows)
|
||||
for i in range(rows.size()):
|
||||
var cols = []
|
||||
cols.resize(num_columns)
|
||||
for j in range(cols.size()):
|
||||
cols[j] = CharData.new(" ", bg, fg, font_flags)
|
||||
rows[i] = cols
|
||||
|
||||
func _get_buffer_size():
|
||||
# Get the size of the (virtual) buffer.
|
||||
# Count each CharData as one byte even though it might be multiple bytes
|
||||
# in the case of unicode characters.
|
||||
var size = 0
|
||||
for row in rows:
|
||||
size += row.size()
|
||||
return size
|
||||
|
||||
func _set_rows(rows):
|
||||
print("rows: ", rows)
|
||||
|
||||
func _set_crow(row: int):
|
||||
print("setting crow")
|
||||
# Ensure there are enoungh rows in the
|
||||
# buffer for the new cursor position.
|
||||
if row >= rows.size():
|
||||
rows.resize(row + 1)
|
||||
|
||||
# resize() uses null for new elements.
|
||||
# but a row should be an array so we
|
||||
# need to replace null values.
|
||||
for i in range(rows.size()):
|
||||
if rows[i] == null:
|
||||
rows[i] = []
|
||||
|
||||
crow = row
|
||||
|
||||
func _set_ccol(col: int):
|
||||
# Ensure there are enough columns in the
|
||||
# row for the new cursor position.
|
||||
print("da size: ", rows[crow].size())
|
||||
if col >= rows[crow].size():
|
||||
rows[crow].resize(col + 1)
|
||||
|
||||
print("da new size: ", rows[crow].size())
|
||||
|
||||
for i in range(rows[crow].size()):
|
||||
if rows[crow][i] == null:
|
||||
rows[crow][i] = CharData.new(' ', bg, fg)
|
||||
|
||||
ccol = col
|
||||
|
||||
func save_cursor():
|
||||
ccol_saved = ccol
|
||||
crow_saved = crow
|
||||
|
||||
func restore_cursor():
|
||||
ccol = ccol_saved
|
||||
crow = crow_saved
|
||||
|
||||
func insert_at_cursor(d, start: int = 0, end: int = 1):
|
||||
var string
|
||||
if typeof(d) == TYPE_ARRAY:
|
||||
string = Decoder.utf32_to_string(d.slice(start, end - 1))
|
||||
else:
|
||||
string = d
|
||||
|
||||
var row = rows[crow]
|
||||
|
||||
for i in range(string.length()):
|
||||
var data = CharData.new(string[i], bg, fg, font_flags)
|
||||
|
||||
if ccol < row.size():
|
||||
row[ccol] = data
|
||||
else:
|
||||
row.resize(ccol)
|
||||
|
||||
for i in range(row.size()):
|
||||
if row[i] == null:
|
||||
row[i] = CharData.new(' ', bg, fg, font_flags)
|
||||
|
||||
row.append(data)
|
||||
|
||||
# Update the cursor position.
|
||||
ccol += 1
|
||||
|
||||
func insert_tab():
|
||||
print("Insert a tab!")
|
||||
# Insert a space.
|
||||
insert_at_cursor(' ')
|
||||
|
||||
# Keep inserting spaces until cursor is at next Tab stop.
|
||||
while ccol % tabWidth != 0:
|
||||
insert_at_cursor(' ')
|
||||
|
||||
# cr
|
||||
func carriage_return():
|
||||
ccol = 0
|
||||
|
||||
# lf
|
||||
func line_feed():
|
||||
rows.resize(rows.size() + 1)
|
||||
rows[-1] = []
|
||||
crow = crow + 1
|
||||
|
||||
# bs
|
||||
# Deletes the element before the current cursor position.
|
||||
func backspace():
|
||||
rows[crow].remove(ccol - 1)
|
||||
ccol = ccol - 1
|
||||
|
||||
# cup
|
||||
# Move the cursor to the given row and column.
|
||||
# For example cursor_position(0, 0) would move
|
||||
# the cursor to the top left corner of the terminal.
|
||||
func cursor_position(params: Array) -> void:
|
||||
var row = params[0] if params.size() > 0 else 1
|
||||
var col = params[1] if params.size() > 1 else 1
|
||||
|
||||
# Origin is (0,0) so row 1, col 1 would be 0,0.
|
||||
if col != 0:
|
||||
self.ccol = col - 1
|
||||
else:
|
||||
self.ccol = 0
|
||||
if row != 0:
|
||||
self.crow = row - 1
|
||||
else:
|
||||
self.crow = 0
|
||||
|
||||
# ed 3
|
||||
func erase_saved_lines():
|
||||
rows = [[]]
|
||||
print("saved lines erased")
|
||||
|
||||
# el
|
||||
func erase_in_line(section):
|
||||
return
|
||||
match section:
|
||||
EL_LEFT, EL_ALL:
|
||||
for i in range(0, ccol):
|
||||
rows[crow][i] = CharData.new(" ")
|
||||
print("Erased the thing")
|
||||
if section == EL_ALL:
|
||||
continue
|
||||
EL_RIGHT, _:
|
||||
for i in range(ccol, rows[crow].size()):
|
||||
rows[crow][i] = CharData.new(" ")
|
||||
print("Erased the thing")
|
||||
|
||||
# ed 0 (default)
|
||||
func erase_below():
|
||||
# Erase from the cursor through to the end of the display.
|
||||
save_cursor()
|
||||
while crow < num_rows:
|
||||
erase_in_line(EL_RIGHT)
|
||||
_set_ccol(0)
|
||||
_set_crow(crow + 1)
|
||||
restore_cursor()
|
||||
|
||||
func set_scrolling_region(top: int, bottom: int):
|
||||
print("set_scrolling_position")
|
||||
# Not sure what this does yet.
|
||||
# Make default be full window size.
|
||||
pass
|
||||
|
||||
func set_font(fontState: int, set: bool = true):
|
||||
match fontState:
|
||||
FONT_NORMAL:
|
||||
pass
|
||||
|
||||
func set_font_flag(flag: int, set: bool = true):
|
||||
print("setting font flag!")
|
||||
if set: # Set the flag
|
||||
font_flags |= (1 << flag)
|
||||
else: # Clear the flag
|
||||
font_flags &= ~(1 << flag)
|
||||
print("font flag is set!")
|
||||
print(font_flags)
|
||||
|
||||
# Clear all font flags. Returns font to default state.
|
||||
func reset_font_flags():
|
||||
font_flags = FONT_NORMAL
|
||||
|
||||
# setf
|
||||
func set_foreground(color: Color = Color(1.0, 1.0, 1.0)):
|
||||
fg = color
|
||||
|
||||
# setb
|
||||
func set_background(color: Color = Color(0.0, 0.0, 0.0)):
|
||||
bg = color
|
||||
|
||||
# setaf
|
||||
func set_a_foreground(params):
|
||||
pass
|
||||
|
||||
# setab
|
||||
func set_a_background(params):
|
||||
pass
|
||||
|
||||
func reset_sgr():
|
||||
set_foreground()
|
||||
set_background()
|
||||
reset_font_flags()
|
||||
|
||||
func repeat_preceding_character(times: int = 0):
|
||||
|
||||
var preceding_char
|
||||
|
||||
if ccol == 0:
|
||||
preceding_char = rows[crow-1][-1]
|
||||
else:
|
||||
preceding_char = rows[crow][ccol-1]
|
||||
|
||||
print("Repeating preceding char ", preceding_char.ch, " ", times, " times")
|
||||
|
||||
for i in range(times):
|
||||
insert_at_cursor(preceding_char.ch)
|
||||
|
||||
# Save the buffer (useful when switching to the alternate buffer)
|
||||
func save():
|
||||
savedBuffer = rows
|
||||
savedCursorCol = ccol
|
||||
savedCursorRow = crow
|
||||
|
||||
# Full Reset
|
||||
func reset():
|
||||
rows = [[]]
|
||||
crow = 0
|
||||
ccol = 0
|
||||
fg = Color(1.0, 1.0, 1.0)
|
||||
bg = Color(0.0, 0.0, 0.0)
|
19
addons/godot_xterm/char_data.gd
Normal file
19
addons/godot_xterm/char_data.gd
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
var ch # character
|
||||
var fg = Color(1.0, 1.0, 1.0) # foreground color
|
||||
var bg = Color(0.0, 0.0, 0.0) # background color
|
||||
var ff = 0 # font flags
|
||||
|
||||
func _init(
|
||||
character: String,
|
||||
background_color: Color = bg,
|
||||
foreground_color: Color = fg,
|
||||
font_flags = ff # Does this work or will it cause problems (this assignement technique)
|
||||
):
|
||||
ch = character
|
||||
bg = background_color
|
||||
fg = foreground_color
|
||||
ff = font_flags
|
93
addons/godot_xterm/fonts/source_code_pro/OFL.txt
Normal file
93
addons/godot_xterm/fonts/source_code_pro/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
|||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
font_data = ExtResource( 1 )
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold_italic.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
font_data = ExtResource( 1 )
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
font_data = ExtResource( 1 )
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
[gd_resource type="DynamicFont" load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.ttf" type="DynamicFontData" id=1]
|
||||
|
||||
[resource]
|
||||
font_data = ExtResource( 1 )
|
Binary file not shown.
93
addons/godot_xterm/fonts/vt323/OFL.txt
Normal file
93
addons/godot_xterm/fonts/vt323/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
|||
Copyright 2011, The VT323 Project Authors (peter.hull@oikoi.com)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
3
addons/godot_xterm/fonts/vt323/vt323_regular.tres
Normal file
3
addons/godot_xterm/fonts/vt323/vt323_regular.tres
Normal file
|
@ -0,0 +1,3 @@
|
|||
[gd_resource type="DynamicFont" format=2]
|
||||
|
||||
[resource]
|
BIN
addons/godot_xterm/fonts/vt323/vt323_regular.ttf
Normal file
BIN
addons/godot_xterm/fonts/vt323/vt323_regular.ttf
Normal file
Binary file not shown.
14
addons/godot_xterm/icon.svg
Normal file
14
addons/godot_xterm/icon.svg
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<path d="m4.5605 3.9746c-0.074558 0-0.14819 0.029049-0.20508 0.085938l-0.27539 0.27539c-0.11354 0.11358-0.11332 0.29631 0 0.41016l1.8691 1.8789-1.8691 1.8789c-0.11336 0.11385-0.11358 0.29657 0 0.41016l0.27539 0.27539c0.11377 0.11378 0.29833 0.11378 0.41211 0l2.3594-2.3594c0.11378-0.11378 0.11378-0.29834 0-0.41211l-2.3594-2.3574c-0.056882-0.056888-0.13247-0.085938-0.20703-0.085938zm3.2207 4.3984c-0.1609 0-0.29102 0.13012-0.29102 0.29102v0.38867c0 0.1609 0.13012 0.29102 0.29102 0.29102h3.6914c0.1609 0 0.29102-0.13012 0.29102-0.29102v-0.38867c0-0.1609-0.13012-0.29102-0.29102-0.29102z" fill="#a5efac" stroke-width=".012139"/>
|
||||
<path d="m3 1c-1.1046 0-2 0.8954-2 2v10c0 1.1046 0.89543 2 2 2h10c1.1046 0 2-0.8954 2-2v-10c0-1.1046-0.89543-2-2-2zm0 2h10v10h-10z" fill="#a5efac"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
34
addons/godot_xterm/icon.svg.import
Normal file
34
addons/godot_xterm/icon.svg.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="StreamTexture"
|
||||
path="res://.import/icon.svg-b0b594198f5f4040e8f0e39a8a353265.stex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/godot_xterm/icon.svg"
|
||||
dest_files=[ "res://.import/icon.svg-b0b594198f5f4040e8f0e39a8a353265.stex" ]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_mode=0
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
flags/repeat=0
|
||||
flags/filter=true
|
||||
flags/mipmaps=false
|
||||
flags/anisotropic=false
|
||||
flags/srgb=2
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/HDR_as_SRGB=false
|
||||
process/invert_color=false
|
||||
stream=false
|
||||
size_limit=0
|
||||
detect_3d=true
|
||||
svg/scale=1.0
|
269
addons/godot_xterm/input/text_decoder.gd
Normal file
269
addons/godot_xterm/input/text_decoder.gd
Normal file
|
@ -0,0 +1,269 @@
|
|||
# Copyright (c) 2020 The GodotTerm authors.
|
||||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
# Convert a given to a utf8 PoolByteArray.
|
||||
# The code for this function is based on the stackoverflow
|
||||
# answer by user Schwern https://stackoverflow.com/a/42013984.
|
||||
static func utf32_to_utf8(codepoint: int):
|
||||
var utf8 = PoolByteArray([])
|
||||
|
||||
if codepoint <= 0x007F:
|
||||
utf8.append(codepoint)
|
||||
elif codepoint <= 0x07FF:
|
||||
utf8.append(0b11000000 | codepoint >> 6 & 0b00011111)
|
||||
utf8.append(0b10000000 | codepoint & 0b00111111)
|
||||
elif codepoint <= 0xFFFF:
|
||||
utf8.append(0b11100000 | codepoint >> 12 & 0b00001111)
|
||||
utf8.append(0b10000000 | codepoint >> 6 & 0b00111111)
|
||||
utf8.append(0b10000000 | codepoint & 0b00111111)
|
||||
elif codepoint <= 0x10FFFF:
|
||||
utf8.append(0b11110000 | codepoint >> 18 & 0b00000111)
|
||||
utf8.append(0b10000000 | codepoint >> 12 & 0b00111111)
|
||||
utf8.append(0b10000000 | codepoint >> 6 & 0b00111111)
|
||||
utf8.append(0b10000000 | codepoint & 0b00111111)
|
||||
else:
|
||||
push_warning("Codepoint " + String(codepoint) + " is out of UTF-8 range")
|
||||
|
||||
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
|
||||
# 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])
|
||||
return result
|
||||
|
||||
# Utf8Decoder - decodes UTF8 byte sequences into UTF32 codepoints.
|
||||
class Utf8ToUtf32:
|
||||
var interim = PoolByteArray()
|
||||
|
||||
func _init():
|
||||
interim.resize(3)
|
||||
|
||||
# Clears interim bytes and resets decoder to clean state.
|
||||
func clear():
|
||||
for i in interim.size():
|
||||
interim[i] = 0
|
||||
|
||||
# Decodes UTF8 byte sequences in `input` to UTF32 codepoints in `target`.
|
||||
# The methods assumes stream input and will store partly transmitted bytes
|
||||
# and decode them with the next data chunk.
|
||||
# Note: The method does no bound checks for target, therefore make sure
|
||||
# the provided data chunk does not exceed the size of `target`.
|
||||
# Returns the number of written codepoints in `target`.
|
||||
func decode(input: PoolByteArray, target: Array):
|
||||
var length = input.size()
|
||||
|
||||
if !length:
|
||||
return 0
|
||||
|
||||
if length > target.size():
|
||||
target.resize(length)
|
||||
|
||||
var size = 0
|
||||
var byte1: int
|
||||
var byte2: int
|
||||
var byte3: int
|
||||
var byte4: int
|
||||
var codepoint = 0
|
||||
var start_pos = 0
|
||||
|
||||
# handle leftover bytes
|
||||
if interim[0]:
|
||||
var discard_interim = false
|
||||
var cp = interim[0]
|
||||
cp &= 0x1F if (cp & 0xE0) == 0xC0 else 0x0F if (cp & 0xF0) == 0xE0 else 0x07
|
||||
var pos = 1
|
||||
var tmp = interim[pos] & 0x3F
|
||||
while tmp && pos < 4:
|
||||
cp <<= 6
|
||||
cp |= tmp
|
||||
pos += 1
|
||||
tmp = interim[pos] & 0x3F if interim.size() < pos else 0
|
||||
# missing bytes - read from input
|
||||
var type = 2 if (interim[0] & 0xE0) == 0xC0 else 3 if (interim[0] & 0xF0) == 0xE0 else 4
|
||||
var missing = type - pos
|
||||
while start_pos < missing:
|
||||
if start_pos >= length:
|
||||
return 0
|
||||
tmp = input[start_pos]
|
||||
start_pos += 1
|
||||
if (tmp & 0xC0) != 0x80:
|
||||
# wrong continuation, discard interim bytes completely
|
||||
start_pos -= 1
|
||||
discard_interim = true
|
||||
break
|
||||
else:
|
||||
# need to save so we can continue short inputs in next call
|
||||
interim[pos + 1] = tmp
|
||||
pos += 1
|
||||
cp <<= 6
|
||||
cp |= tmp & 0x3F
|
||||
if not discard_interim:
|
||||
# final test is type dependent
|
||||
match type:
|
||||
2:
|
||||
if cp < 0x80:
|
||||
# wrong starter byte
|
||||
start_pos -= 1
|
||||
else:
|
||||
target[size] = cp
|
||||
size += 1
|
||||
3:
|
||||
if cp < 0x0800 or (cp >= 0xD800 and cp <= 0xDFFF):
|
||||
# illegal codepoint
|
||||
pass
|
||||
else:
|
||||
target[size] = cp
|
||||
size += 1
|
||||
_:
|
||||
if cp < 0x10000 or cp > 0x10FFFF:
|
||||
# illegal codepoint
|
||||
pass
|
||||
else:
|
||||
target[size] = cp
|
||||
size += 1
|
||||
clear()
|
||||
|
||||
# loop through input
|
||||
var four_stop = length - 4
|
||||
var i = start_pos
|
||||
while i < length:
|
||||
# ASCII shortcut with loop unrolled to 4 consecutive ASCII chars.
|
||||
# This is a compromise between speed gain for ASCII
|
||||
# and penalty for non ASCII:
|
||||
# For best ASCII performance the char should be stored directly into target,
|
||||
# but even a single attempt to write to target and compare afterwards
|
||||
# penalizes non ASCII really bad (-50%), thus we load the char into byteX first,
|
||||
# which reduces ASCII performance by ~15%.
|
||||
# This trial for ASCII reduces non ASCII performance by ~10% which seems acceptible
|
||||
# compared to the gains.
|
||||
# Note that this optimization only takes place for 4 consecutive ASCII chars,
|
||||
# for any shorter it bails out. Worst case - all 4 bytes being read but
|
||||
# thrown away due to the last being a non ASCII char (-10% performance).
|
||||
while i < four_stop:
|
||||
byte1 = input[i]
|
||||
byte2 = input[i + 1]
|
||||
byte3 = input[i + 2]
|
||||
byte4 = input[i + 3]
|
||||
if not (byte1 & 0x80) | (byte2 & 0x80) | (byte3 & 0x80) | (byte4 & 0x80):
|
||||
target[size] = byte1
|
||||
target[size+1] = byte2
|
||||
target[size+2] = byte3
|
||||
target[size+3] = byte4
|
||||
size += 4
|
||||
i += 4
|
||||
else:
|
||||
break
|
||||
|
||||
# reread byte1
|
||||
byte1 = input[i]
|
||||
i += 1
|
||||
|
||||
# 1 byte
|
||||
if byte1 < 0x80:
|
||||
target[size] = byte1
|
||||
size += 1
|
||||
|
||||
# 2 bytes
|
||||
elif (byte1 & 0xE0) == 0xC0:
|
||||
if i >= length:
|
||||
interim[0] = byte1
|
||||
return size
|
||||
byte2 = input[i]
|
||||
i+=1
|
||||
if (byte2 & 0xC0) != 0x80:
|
||||
# wrong continuation
|
||||
i-=1
|
||||
continue
|
||||
codepoint = (byte1 & 0x1F) << 6 | (byte2 & 0x3F)
|
||||
if (codepoint < 0x80):
|
||||
# wrong starter byte
|
||||
i-=1
|
||||
continue
|
||||
target[size] = codepoint
|
||||
size+=1
|
||||
|
||||
# 3 bytes
|
||||
elif (byte1 & 0xF0) == 0xE0:
|
||||
if i >= length:
|
||||
interim[0] = byte1
|
||||
return size
|
||||
byte2 = input[i]
|
||||
i+=1
|
||||
if (byte2 & 0xC0) != 0x80:
|
||||
# wrong continuation
|
||||
i-=1
|
||||
continue
|
||||
if i >= length:
|
||||
interim[0] = byte1
|
||||
interim[1] = byte2
|
||||
return size
|
||||
byte3 = input[i]
|
||||
i+=1
|
||||
if (byte3 & 0xC0) != 0x80:
|
||||
# wrong continuation
|
||||
i-=1
|
||||
continue
|
||||
codepoint = (byte1 & 0x0F) << 12 | (byte2 & 0x3F) << 6 | (byte3 & 0x3F)
|
||||
if codepoint < 0x0800 or (codepoint >=0xD800 and codepoint <= 0xDFFF):
|
||||
# illegal codepoint, no i-- here
|
||||
continue
|
||||
target[size] = codepoint
|
||||
size+=1
|
||||
|
||||
# 4 bytes
|
||||
elif (byte1 & 0xF8) == 0xF0:
|
||||
if i >= length:
|
||||
interim[0] = byte1
|
||||
return size
|
||||
byte2 = input[i]
|
||||
i += 1
|
||||
if (byte2 & 0xC0) != 0x80:
|
||||
# wrong continuation
|
||||
i -= 1
|
||||
continue
|
||||
if i >= length:
|
||||
interim[0] = byte1
|
||||
interim[1] = byte2
|
||||
return size
|
||||
byte3 = input[i]
|
||||
i += 1
|
||||
if (byte3 & 0xC0) != 0x80:
|
||||
# wrong continuation
|
||||
i -= 1
|
||||
continue
|
||||
if i >= length:
|
||||
interim[0] = byte1
|
||||
interim[1] = byte2
|
||||
interim[2] = byte3
|
||||
return size
|
||||
byte4 = input[i]
|
||||
i += 1
|
||||
if (byte4 & 0xC0) != 0x80:
|
||||
# wrong continuation
|
||||
i -= 1
|
||||
continue
|
||||
codepoint = (byte1 & 0x07) << 18 | (byte2 & 0x3F) << 12 | (byte3 & 0x3F) << 6 | (byte4 & 0x3F)
|
||||
if codepoint < 0x010000 or codepoint > 0x10FFFF:
|
||||
# illegal codepoint, no i-- here
|
||||
continue
|
||||
target[size] = codepoint
|
||||
size += 1
|
||||
else:
|
||||
# illegal byte, just skip
|
||||
pass
|
||||
|
||||
target.resize(size)
|
||||
return size
|
131
addons/godot_xterm/parser/constants.gd
Normal file
131
addons/godot_xterm/parser/constants.gd
Normal file
|
@ -0,0 +1,131 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors.
|
||||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
# Psuedo-character placeholder for non-ascii characters (unicode).
|
||||
const NON_ASCII_PRINTABLE = 0xa0
|
||||
|
||||
# Payload limit for OSC and DCS.
|
||||
const PAYLOAD_LIMIT = 10000000
|
||||
|
||||
# Internal states of EscapeSequenceParser.
|
||||
enum ParserState {
|
||||
GROUND
|
||||
ESCAPE
|
||||
ESCAPE_INTERMEDIATE
|
||||
CSI_ENTRY
|
||||
CSI_PARAM
|
||||
CSI_INTERMEDIATE
|
||||
CSI_IGNORE
|
||||
SOS_PM_APC_STRING
|
||||
OSC_STRING
|
||||
DCS_ENTRY
|
||||
DCS_PARAM
|
||||
DCS_IGNORE
|
||||
DCS_INTERMEDIATE
|
||||
DCS_PASSTHROUGH
|
||||
}
|
||||
|
||||
# Internal actions of EscapeSequenceParser.
|
||||
enum ParserAction {
|
||||
IGNORE
|
||||
ERROR
|
||||
PRINT
|
||||
EXECUTE
|
||||
OSC_START
|
||||
OSC_PUT
|
||||
OSC_END
|
||||
CSI_DISPATCH
|
||||
PARAM
|
||||
COLLECT
|
||||
ESC_DISPATCH
|
||||
CLEAR
|
||||
DCS_HOOK
|
||||
DCS_PUT
|
||||
DCS_UNHOOK
|
||||
}
|
||||
|
||||
# Internal states of OscParser.
|
||||
enum OscState {
|
||||
START
|
||||
ID
|
||||
PAYLOAD
|
||||
ABORT
|
||||
}
|
||||
|
||||
# C0 control codes
|
||||
# See: https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C0_controls
|
||||
enum C0 {
|
||||
NUL
|
||||
SOH
|
||||
STX
|
||||
ETX
|
||||
EOT
|
||||
ENQ
|
||||
ACK
|
||||
BEL
|
||||
BS
|
||||
HT
|
||||
LF
|
||||
VT
|
||||
FF
|
||||
CR
|
||||
SO
|
||||
SI
|
||||
DLE
|
||||
DC1
|
||||
DC2
|
||||
DC3
|
||||
DC4
|
||||
NAK
|
||||
SYN
|
||||
ETB
|
||||
CAN
|
||||
EM
|
||||
SUB
|
||||
ESC
|
||||
FS
|
||||
GS
|
||||
RS
|
||||
US
|
||||
SP
|
||||
DEL = 0x7f
|
||||
}
|
||||
|
||||
# C1 control codes
|
||||
# See: https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_controls
|
||||
enum C1 {
|
||||
PAD = 0x80
|
||||
HOP = 0x81
|
||||
BPH = 0x82
|
||||
NBH = 0x83
|
||||
IND = 0x84
|
||||
NEL = 0x85
|
||||
SSA = 0x86
|
||||
ESA = 0x87
|
||||
HTS = 0x88
|
||||
HTJ = 0x89
|
||||
VTS = 0x8a
|
||||
PLD = 0x8b
|
||||
PLU = 0x8c
|
||||
RI = 0x8d
|
||||
SS2 = 0x8e
|
||||
SS3 = 0x8f
|
||||
DCS = 0x90
|
||||
PU1 = 0x91
|
||||
PU2 = 0x92
|
||||
STS = 0x93
|
||||
CCH = 0x94
|
||||
MW = 0x95
|
||||
SPA = 0x96
|
||||
EPA = 0x97
|
||||
SOS = 0x98
|
||||
SGCI = 0x99
|
||||
SCI = 0x9a
|
||||
CSI = 0x9b
|
||||
ST = 0x9c
|
||||
OSC = 0x9d
|
||||
PM = 0x9e
|
||||
APC = 0x9f
|
||||
}
|
77
addons/godot_xterm/parser/dcs_parser.gd
Normal file
77
addons/godot_xterm/parser/dcs_parser.gd
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors.
|
||||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
|
||||
|
||||
|
||||
const EMPTY_HANDLERS = []
|
||||
|
||||
|
||||
var _handlers: Dictionary = {}
|
||||
var _active: Array = EMPTY_HANDLERS
|
||||
var _ident: int = 0
|
||||
var _handler_fb: Dictionary
|
||||
|
||||
|
||||
func _init():
|
||||
pass
|
||||
|
||||
|
||||
func set_handler(ident: int, handler):
|
||||
_handlers[ident] = [handler]
|
||||
|
||||
|
||||
func clear_handler(ident: int):
|
||||
_handlers.erase(ident)
|
||||
|
||||
|
||||
func set_handler_fallback(target, method):
|
||||
_handler_fb = {'target': target, 'method': method}
|
||||
|
||||
|
||||
func reset():
|
||||
if _active.size():
|
||||
unhook(false)
|
||||
_active = EMPTY_HANDLERS
|
||||
_ident = 0
|
||||
|
||||
|
||||
func hook(ident: int, params):
|
||||
# always reset leftover handlers
|
||||
reset()
|
||||
_ident = ident
|
||||
_active = _handlers[ident] if _handlers.has(ident) else EMPTY_HANDLERS
|
||||
if _active.empty():
|
||||
_handler_fb['target'].call(_handler_fb['method'], _ident, 'HOOK', params)
|
||||
else:
|
||||
_active.invert()
|
||||
for handler in _active:
|
||||
handler.hook(params)
|
||||
_active.invert()
|
||||
|
||||
|
||||
func put(data: Array, start: int, end: int):
|
||||
if _active.empty():
|
||||
_handler_fb['target'].call(_handler_fb['method'], _ident, 'PUT',
|
||||
Decoder.utf32_to_string(data, start, end))
|
||||
else:
|
||||
_active.invert()
|
||||
for handler in _active:
|
||||
handler.put(data, start, end)
|
||||
_active.invert()
|
||||
|
||||
|
||||
func unhook(success: bool):
|
||||
if _active.empty():
|
||||
_handler_fb['target'].call(_handler_fb['method'], _ident, 'UNHOOK', success)
|
||||
else:
|
||||
_active.invert()
|
||||
for handler in _active:
|
||||
if handler.unhook(success) != false:
|
||||
success = false # will cleanup left over handlers
|
||||
_active.invert()
|
||||
_active = EMPTY_HANDLERS
|
||||
_ident = 0
|
329
addons/godot_xterm/parser/escape_sequence_parser.gd
Normal file
329
addons/godot_xterm/parser/escape_sequence_parser.gd
Normal file
|
@ -0,0 +1,329 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
|
||||
# Copyright (c) 2018 The xterm.js authers. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
const Constants = preload("res://addons/godot_xterm/parser/constants.gd")
|
||||
const TransitionTable = preload("res://addons/godot_xterm/parser/transition_table.gd")
|
||||
const VT500TransitionTable = preload("res://addons/godot_xterm/parser/vt500_transition_table.gd")
|
||||
const DcsParser = preload("res://addons/godot_xterm/parser/dcs_parser.gd")
|
||||
const Params = preload("res://addons/godot_xterm/parser/params.gd")
|
||||
|
||||
const TableAccess = TransitionTable.TableAccess
|
||||
const NON_ASCII_PRINTABLE = Constants.NON_ASCII_PRINTABLE
|
||||
const ParserState = Constants.ParserState
|
||||
const ParserAction = Constants.ParserAction
|
||||
|
||||
var initial_state
|
||||
var current_state
|
||||
var preceding_codepoint
|
||||
|
||||
var _transitions
|
||||
|
||||
# buffers over several parse calls
|
||||
var _params
|
||||
var _collect
|
||||
|
||||
# handler lookup containers
|
||||
var _print_handler
|
||||
var _execute_handlers
|
||||
var _csi_handlers
|
||||
var _esc_handlers
|
||||
var _osc_parser
|
||||
var _dcs_parser
|
||||
var _error_handler
|
||||
|
||||
# fallback handlers
|
||||
var _print_handler_fb
|
||||
var _execute_handler_fb
|
||||
var _csi_handler_fb
|
||||
var _esc_handler_fb
|
||||
var _error_handler_fb
|
||||
|
||||
|
||||
# Default do noting fallback handler.
|
||||
# Allows a variable number of arguments from 0 - 7.
|
||||
func noop(a = null, b = null, c = null, d = null, e = null, f = null, g = null):
|
||||
pass
|
||||
|
||||
|
||||
func _init(transitions = VT500TransitionTable.new().table):
|
||||
initial_state = ParserState.GROUND
|
||||
current_state = initial_state
|
||||
_transitions = transitions
|
||||
_params = Params.new() # Defaults to 32 storable params/subparams
|
||||
_params.add_param(0) # ZDM (Zero Default Mode
|
||||
_collect = 0
|
||||
preceding_codepoint = 0
|
||||
|
||||
# set default fallback handlers and handler lookup containers
|
||||
var noop = {'target': self, 'method': 'noop'}
|
||||
_print_handler_fb = noop
|
||||
_execute_handler_fb = noop
|
||||
_csi_handler_fb = noop
|
||||
_esc_handler_fb = noop
|
||||
_error_handler_fb = noop
|
||||
_print_handler = _print_handler_fb
|
||||
_execute_handlers = {}
|
||||
_csi_handlers = {}
|
||||
_esc_handlers = {}
|
||||
_osc_parser = null # TODO OscParser.new()
|
||||
_dcs_parser = DcsParser.new()
|
||||
_error_handler = _error_handler_fb
|
||||
|
||||
# swallow 7bit ST (ESC+\)
|
||||
set_esc_handler({'final': '\\'}, self, 'noop')
|
||||
|
||||
|
||||
static func identifier(id: Dictionary, final_range: Array = [0x40, 0x7e]):
|
||||
var res = 0
|
||||
|
||||
var prefix = id.get('prefix')
|
||||
var intermediates = id.get('intermediates')
|
||||
var final = id.get('final')
|
||||
|
||||
if prefix:
|
||||
if prefix.length() > 1:
|
||||
push_error("only one byte prefix supported")
|
||||
res = prefix.to_ascii()[0]
|
||||
if res and 0x3c > res or res > 0x3f:
|
||||
push_error("prefix must be in the range 0x3c-0x3f")
|
||||
|
||||
if intermediates:
|
||||
if intermediates.length() > 2:
|
||||
push_error("only two bytes as intermediates are supported")
|
||||
for intermediate in intermediates:
|
||||
var im = intermediate.to_ascii()[0]
|
||||
if 0x20 > im or im > 0x2f:
|
||||
push_error("intermediate must be in the range 0x20-0x2f")
|
||||
res = res << 8
|
||||
res = res | im
|
||||
|
||||
if final.length() != 1:
|
||||
push_error("final must be a single byte")
|
||||
var final_code = final.to_ascii()[0]
|
||||
if final_range[0] > final_code or final_code > final_range[1]:
|
||||
push_error("final must be in the range " + String(final_range[0]) + "-" + String(final_range[1]))
|
||||
res = res << 8
|
||||
res = res | final_code
|
||||
|
||||
return res
|
||||
|
||||
static func ident_to_string(ident: int):
|
||||
var res = PoolStringArray([])
|
||||
while ident:
|
||||
res.append(PoolByteArray([ident & 0xFF]).get_string_from_ascii())
|
||||
ident >>= 8
|
||||
res.invert()
|
||||
return res.join('')
|
||||
|
||||
func set_print_handler(target: Object, method: String):
|
||||
_print_handler = { 'target': target, 'method': method }
|
||||
|
||||
|
||||
func add_esc_handler(id, target, method):
|
||||
var ident = identifier(id, [0x30, 0x7e])
|
||||
if not _esc_handlers.has(ident):
|
||||
_esc_handlers[ident] = []
|
||||
var handler_list = _esc_handlers[ident]
|
||||
handler_list.append({'target': target, 'method': method})
|
||||
|
||||
|
||||
func set_csi_handler(id: Dictionary, target: Object, method: String):
|
||||
_csi_handlers[identifier(id)] = [{ 'target': target, 'method': method }]
|
||||
|
||||
|
||||
func set_csi_handler_fallback(target, method):
|
||||
_csi_handler_fb = { 'target': target, 'method': method }
|
||||
|
||||
|
||||
func set_execute_handler(flag: int, target: Object, method: String):
|
||||
_execute_handlers[flag] = { 'target': target, 'method': method }
|
||||
|
||||
|
||||
func set_execute_handler_fallback(target: Object, method: String):
|
||||
_execute_handler_fb = { 'target': target, 'method': method }
|
||||
|
||||
|
||||
func set_esc_handler(id, target, method):
|
||||
_esc_handlers[identifier(id, [0x30, 0x7e])] = [{'target': target, 'method': method}]
|
||||
|
||||
|
||||
func set_esc_handler_fallback(target: Object, method: String):
|
||||
_esc_handler_fb = {'target': target, 'method': method}
|
||||
|
||||
|
||||
func add_dcs_handler(id, target, method):
|
||||
pass
|
||||
# TODO!!!
|
||||
|
||||
func set_dcs_handler(id, target: Object, method: String):
|
||||
_dcs_parser.set_handler(id, {'target': target, 'method': method})
|
||||
|
||||
func set_dcs_handler_fallback(target: Object, method: String):
|
||||
_dcs_parser.set_handler_fallback(target, method)
|
||||
|
||||
func reset():
|
||||
current_state = initial_state
|
||||
_params.reset()
|
||||
_params.add_param(0) # ZDM
|
||||
_collect = 0
|
||||
preceding_codepoint = 0
|
||||
|
||||
func parse(data: Array, length: int):
|
||||
var code = 0
|
||||
var transition = 0
|
||||
var _current_state = current_state
|
||||
var dcs = _dcs_parser
|
||||
var collect = _collect
|
||||
var params = _params
|
||||
|
||||
#print("table", table)
|
||||
|
||||
#print("parse -> data: ", data, " length: ", length)
|
||||
|
||||
# Process input string.
|
||||
var i = 0
|
||||
while i < length:
|
||||
#print("i: ", i)
|
||||
code = data[i]
|
||||
|
||||
#print("code: ", code)
|
||||
|
||||
# Normal transition and action lookup.
|
||||
transition = _transitions[_current_state << TableAccess.INDEX_STATE_SHIFT | code if code < 0xa0 else NON_ASCII_PRINTABLE]
|
||||
|
||||
#print ("transition: ", transition)
|
||||
#print("current state: ", current_state)
|
||||
|
||||
match transition >> TableAccess.TRANSITION_ACTION_SHIFT:
|
||||
ParserAction.PRINT:
|
||||
# read ahead with loop unrolling
|
||||
# # Note: 0x20 (SP) is included, 0x7F (DEL) is excluded
|
||||
var j = i + 1
|
||||
while true:
|
||||
code = data[j] if j < data.size() else 0
|
||||
if j >= length or code < 0x20 or (code > 0x7e and code < NON_ASCII_PRINTABLE):
|
||||
_print_handler['target'].call(_print_handler['method'], data, i, j)
|
||||
i = j - 1
|
||||
break
|
||||
j += 1
|
||||
code = data[j] if j < data.size() else 0
|
||||
if j >= length or code < 0x20 or (code > 0x7e and code < NON_ASCII_PRINTABLE):
|
||||
_print_handler['target'].call(_print_handler['method'], data, i, j)
|
||||
i = j - 1
|
||||
break
|
||||
j += 1
|
||||
code = data[j] if j < data.size() else 0
|
||||
if j >= length or code < 0x20 or (code > 0x7e and code < NON_ASCII_PRINTABLE):
|
||||
_print_handler['target'].call(_print_handler['method'], data, i, j)
|
||||
i = j - 1
|
||||
break
|
||||
j += 1
|
||||
code = data[j] if j < data.size() else 0
|
||||
if j >= length or code < 0x20 or (code > 0x7e and code < NON_ASCII_PRINTABLE):
|
||||
_print_handler['target'].call(_print_handler['method'], data, i, j)
|
||||
i = j - 1
|
||||
break
|
||||
j += 1
|
||||
ParserAction.EXECUTE:
|
||||
var handler = _execute_handlers.get(code)
|
||||
if handler:
|
||||
handler['target'].call(handler['method'])
|
||||
elif _execute_handler_fb:
|
||||
_execute_handler_fb['target'].call(_execute_handler_fb['method'], code)
|
||||
preceding_codepoint = 0
|
||||
ParserAction.IGNORE:
|
||||
pass
|
||||
ParserAction.ERROR:
|
||||
print("Parser error!")
|
||||
|
||||
ParserAction.CSI_DISPATCH:
|
||||
# Trigger CSI Handler
|
||||
var handlers = _csi_handlers.get((collect << 8 | code), [])
|
||||
handlers.invert()
|
||||
for handler in handlers:
|
||||
# undefined or true means success and to stop bubbling
|
||||
if handler['target'].call(handler['method'], params.to_array()):
|
||||
continue
|
||||
handlers.invert()
|
||||
if handlers.empty():
|
||||
_csi_handler_fb['target'].call(_csi_handler_fb['method'], collect << 8 | code, params.to_array())
|
||||
preceding_codepoint = 0
|
||||
|
||||
|
||||
ParserAction.PARAM:
|
||||
# Inner loop digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
|
||||
var do = true
|
||||
while do:
|
||||
match code:
|
||||
0x3b:
|
||||
params.add_param(0)
|
||||
0x3a:
|
||||
params.add_sub_param(-1)
|
||||
_:
|
||||
params.add_digit(code - 48)
|
||||
i += 1
|
||||
code = data[i] if i < data.size() else 0
|
||||
do = i < length and code > 0x2f and code < 0x3c
|
||||
i-=1
|
||||
|
||||
ParserAction.COLLECT:
|
||||
collect <<= 8
|
||||
collect |= code
|
||||
|
||||
ParserAction.ESC_DISPATCH:
|
||||
var handlers = _esc_handlers.get((collect << 8 | code), [])
|
||||
handlers.invert()
|
||||
for handler in handlers:
|
||||
# undefined or true means success and to stop bubbling
|
||||
if handler['target'].call(handler['method']) != false:
|
||||
continue
|
||||
handlers.invert()
|
||||
if handlers.empty():
|
||||
_esc_handler_fb['target'].call(_esc_handler_fb['method'], collect << 8 | code)
|
||||
preceding_codepoint = 0
|
||||
|
||||
ParserAction.CLEAR:
|
||||
params.reset()
|
||||
params.add_param(0) # ZDM
|
||||
collect = 0
|
||||
|
||||
ParserAction.DCS_HOOK:
|
||||
dcs.hook(collect << 8 | code, params.to_array())
|
||||
|
||||
ParserAction.DCS_PUT:
|
||||
# inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f
|
||||
# unhook triggered by: 0x1b, 0x9c (success) and 0x18, 0x1a (abort)
|
||||
for j in range(i + 1, length + 1):
|
||||
code = data[j]
|
||||
if code == 0x18 or code == 0x1a or code == 0x1b or (code > 0x7f and code < NON_ASCII_PRINTABLE):
|
||||
dcs.put(data, i, j)
|
||||
i = j - 1
|
||||
break
|
||||
break
|
||||
ParserAction.DCS_UNHOOK:
|
||||
_dcs_parser.unhook(code != 0x18 and code != 0x1a)
|
||||
if code == 0x1b:
|
||||
transition |= ParserState.ESCAPE
|
||||
params.reset()
|
||||
params.add_param(0) # ZDM
|
||||
collect = 0;
|
||||
preceding_codepoint = 0
|
||||
ParserAction.OSC_START:
|
||||
pass
|
||||
|
||||
ParserAction.OSC_PUT:
|
||||
pass
|
||||
|
||||
ParserAction.OSC_END:
|
||||
pass
|
||||
|
||||
_current_state = transition & TableAccess.TRANSITION_STATE_MASK
|
||||
i += 1
|
||||
|
||||
# save collected intermediates
|
||||
_collect = collect
|
||||
|
||||
# save state
|
||||
current_state = _current_state
|
104
addons/godot_xterm/parser/params.gd
Normal file
104
addons/godot_xterm/parser/params.gd
Normal file
|
@ -0,0 +1,104 @@
|
|||
# Copyright (c) 2020 The GodotTerm authors.
|
||||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
|
||||
# Max value supported for a single param/subparam (clamped to positive int32 range).
|
||||
const MAX_VALUE = 0x7FFFFFFF;
|
||||
# Max allowed subparams for a single sequence (hardcoded limitation).
|
||||
const MAX_SUBPARAMS = 256;
|
||||
|
||||
var params = []
|
||||
var length = 0
|
||||
|
||||
var sub_params = []
|
||||
var sub_params_length = 0
|
||||
var _max_length
|
||||
var _max_sub_params_length
|
||||
var sub_params_idx = []
|
||||
var _reject_digits = false
|
||||
var _reject_sub_digits = false
|
||||
var digit_is_sub = false
|
||||
|
||||
|
||||
static func from_array(values: Array):
|
||||
# Workaround as per: https://github.com/godotengine/godot/issues/19345#issuecomment-471218401
|
||||
var params = load("res://addons/godot_xterm/parser/params.gd").new()
|
||||
if values.empty():
|
||||
return params
|
||||
# skip leading sub params
|
||||
for i in range(values.size()):
|
||||
var value = values[i]
|
||||
if typeof(value) == TYPE_ARRAY:
|
||||
if i == 0:
|
||||
# skip leading sub params
|
||||
continue
|
||||
else:
|
||||
for sub_param in value:
|
||||
params.add_sub_param(sub_param)
|
||||
else:
|
||||
params.add_param(value)
|
||||
return params
|
||||
|
||||
|
||||
func _init(max_length: int = 32, max_sub_params_length: int = 32):
|
||||
_max_length = max_length
|
||||
_max_sub_params_length = max_sub_params_length
|
||||
|
||||
if (max_sub_params_length > MAX_SUBPARAMS):
|
||||
push_error("max_sub_params_length must not be greater than 256")
|
||||
|
||||
params.resize(max_length)
|
||||
sub_params.resize(max_sub_params_length)
|
||||
sub_params_idx.resize(max_length)
|
||||
|
||||
func add_param(value: int):
|
||||
digit_is_sub = false
|
||||
if length >= _max_length:
|
||||
_reject_digits = true
|
||||
return
|
||||
if value < -1:
|
||||
push_error('values lesser than -1 are not allowed')
|
||||
sub_params_idx[length] = sub_params_length << 8 | sub_params_length
|
||||
params[length] = MAX_VALUE if value > MAX_VALUE else value
|
||||
length += 1
|
||||
|
||||
func add_sub_param(value: int):
|
||||
digit_is_sub = true
|
||||
if !length:
|
||||
return
|
||||
if _reject_digits or sub_params_length >= _max_sub_params_length:
|
||||
_reject_sub_digits = true
|
||||
return
|
||||
if value < -1:
|
||||
push_error('values lesser than -1 are not allowed')
|
||||
sub_params[sub_params_length] = MAX_VALUE if value > MAX_VALUE else value
|
||||
sub_params_length += 1
|
||||
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
|
||||
var store = sub_params if digit_is_sub else params
|
||||
var cur = store[_length - 1]
|
||||
store[_length - 1] = min(cur * 10 + value, MAX_VALUE) if ~cur else value
|
||||
|
||||
func to_array():
|
||||
var res = []
|
||||
for i in range(length):
|
||||
res.append(params[i])
|
||||
var start = sub_params_idx[i] >> 8
|
||||
var end = sub_params_idx[i] & 0xff
|
||||
if end - start > 0:
|
||||
res.append(sub_params.slice(start, end - 1))
|
||||
return res
|
||||
|
||||
func reset():
|
||||
length = 0
|
||||
sub_params_length = 0
|
||||
_reject_digits = false
|
||||
_reject_sub_digits = false
|
||||
digit_is_sub = false
|
26
addons/godot_xterm/parser/transition_table.gd
Normal file
26
addons/godot_xterm/parser/transition_table.gd
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors.
|
||||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# License MIT
|
||||
extends Reference
|
||||
|
||||
enum TableAccess {
|
||||
TRANSITION_ACTION_SHIFT = 4,
|
||||
TRANSITION_STATE_MASK = 15,
|
||||
INDEX_STATE_SHIFT = 8
|
||||
}
|
||||
|
||||
var table: PoolByteArray = PoolByteArray()
|
||||
|
||||
func _init(length: int):
|
||||
table.resize(length)
|
||||
|
||||
func setDefault(action: int, next: int):
|
||||
for i in range(table.size()):
|
||||
table[i] = action << TableAccess.TRANSITION_ACTION_SHIFT | next
|
||||
|
||||
func add(code: int, state: int, action: int, next: int):
|
||||
table[state << TableAccess.INDEX_STATE_SHIFT | code] = action << TableAccess.TRANSITION_ACTION_SHIFT | next
|
||||
|
||||
func addMany(codes: Array, state: int, action: int, next: int):
|
||||
for code in codes:
|
||||
add(code, state, action, next)
|
123
addons/godot_xterm/parser/vt500_transition_table.gd
Normal file
123
addons/godot_xterm/parser/vt500_transition_table.gd
Normal file
|
@ -0,0 +1,123 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors.
|
||||
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
# License MIT
|
||||
extends "res://addons/godot_xterm/parser/transition_table.gd"
|
||||
|
||||
const Constants = preload("res://addons/godot_xterm/parser/constants.gd")
|
||||
const ParserState = Constants.ParserState
|
||||
const ParserAction = Constants.ParserAction
|
||||
const NON_ASCII_PRINTABLE = Constants.NON_ASCII_PRINTABLE
|
||||
|
||||
var PRINTABLES = Array(range(0x20, 0x7f)) # 0x20 (SP) included, 0x7f (DEL) excluded.
|
||||
var EXECUTABLES = Array(range(0x00, 0x18)) + [0x19] + Array(range(0x1c, 0x20))
|
||||
|
||||
func _init().(4096):
|
||||
# Set default transition.
|
||||
setDefault(ParserAction.ERROR, ParserState.GROUND)
|
||||
|
||||
# Printables.
|
||||
addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND)
|
||||
|
||||
# Global anywhere rules.
|
||||
for state in ParserState.values():
|
||||
addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND)
|
||||
addMany(range(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND)
|
||||
addMany(range(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND)
|
||||
add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND) # ST as terminator
|
||||
add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE) # ESC
|
||||
add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING) # OSC
|
||||
addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING)
|
||||
add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY) # CSI
|
||||
add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY) # DCS
|
||||
|
||||
# Rules for executables and 7f.
|
||||
addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND)
|
||||
addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE)
|
||||
add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE)
|
||||
addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING)
|
||||
addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY)
|
||||
add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY)
|
||||
addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM)
|
||||
add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM);
|
||||
addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE)
|
||||
addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE)
|
||||
add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE)
|
||||
addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE)
|
||||
add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE);
|
||||
|
||||
# OSC.
|
||||
add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING)
|
||||
addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING)
|
||||
add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING)
|
||||
addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND)
|
||||
addMany(range(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING)
|
||||
|
||||
# SOS/PM/APC does nothing.
|
||||
addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING)
|
||||
addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING)
|
||||
addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING)
|
||||
add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND)
|
||||
add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING)
|
||||
# csi entries
|
||||
add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY)
|
||||
addMany(range(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND)
|
||||
addMany(range(0x30, 0x3c), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM)
|
||||
addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM)
|
||||
addMany(range(0x30, 0x3c), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM)
|
||||
addMany(range(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND)
|
||||
addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE)
|
||||
addMany(range(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE)
|
||||
add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE)
|
||||
addMany(range(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND)
|
||||
addMany(range(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE)
|
||||
addMany(range(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE)
|
||||
addMany(range(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE)
|
||||
addMany(range(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND)
|
||||
addMany(range(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE)
|
||||
# esc_intermediate
|
||||
addMany(range(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE)
|
||||
addMany(range(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE)
|
||||
addMany(range(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND)
|
||||
addMany(range(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND)
|
||||
addMany(range(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND)
|
||||
addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND)
|
||||
addMany(range(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
|
||||
|
||||
# dcs entry
|
||||
add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY)
|
||||
addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY)
|
||||
add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY)
|
||||
addMany(range(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY)
|
||||
addMany(range(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE)
|
||||
addMany(range(0x30, 0x3c), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM)
|
||||
addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM)
|
||||
addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE)
|
||||
addMany(range(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE)
|
||||
addMany(range(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE)
|
||||
addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM)
|
||||
add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM)
|
||||
addMany(range(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM)
|
||||
addMany(range(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM)
|
||||
addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE)
|
||||
addMany(range(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE)
|
||||
addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE)
|
||||
add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE)
|
||||
addMany(range(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE)
|
||||
addMany(range(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE)
|
||||
addMany(range(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE)
|
||||
addMany(range(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH)
|
||||
addMany(range(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH)
|
||||
addMany(range(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH)
|
||||
addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH)
|
||||
addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH)
|
||||
add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH)
|
||||
addMany([0x1b, 0x9c, 0x18, 0x1a], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
|
||||
|
||||
# special handling of unicode chars
|
||||
add(NON_ASCII_PRINTABLE, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND)
|
||||
add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING)
|
||||
add(NON_ASCII_PRINTABLE, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE)
|
||||
add(NON_ASCII_PRINTABLE, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE)
|
||||
add(NON_ASCII_PRINTABLE, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH)
|
||||
|
||||
return table
|
7
addons/godot_xterm/plugin.cfg
Normal file
7
addons/godot_xterm/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="GodotXterm"
|
||||
description="Xterm.js for Godot"
|
||||
author="Leroy Hopson"
|
||||
version="0.1.0"
|
||||
script="plugin.gd"
|
14
addons/godot_xterm/plugin.gd
Normal file
14
addons/godot_xterm/plugin.gd
Normal file
|
@ -0,0 +1,14 @@
|
|||
tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
var script = preload("res://addons/godot_xterm/terminal.gd")
|
||||
var texture = preload("res://addons/godot_xterm/icon.svg")
|
||||
add_custom_type("Terminal", "Control", script, texture)
|
||||
pass
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
remove_custom_type("Terminal")
|
||||
pass
|
367
addons/godot_xterm/terminal.gd
Normal file
367
addons/godot_xterm/terminal.gd
Normal file
|
@ -0,0 +1,367 @@
|
|||
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
|
||||
# License MIT
|
||||
tool
|
||||
extends Control
|
||||
|
||||
|
||||
signal data_sent(data)
|
||||
|
||||
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 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")
|
||||
const SourceCodeProBoldItalic = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold_italic.tres")
|
||||
|
||||
const C0 = Constants.C0
|
||||
const C1 = Constants.C1
|
||||
const ESCAPE = 27
|
||||
const BACKSPACE = 8
|
||||
const BEEP = 7
|
||||
const SPACE = 32
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
decoder = Decoder.Utf8ToUtf32.new()
|
||||
|
||||
buffer = Buffer.new(rows, cols)
|
||||
alternate_buffer = Buffer.new(rows, cols, true)
|
||||
|
||||
parser = Parser.new()
|
||||
|
||||
# Print handler
|
||||
parser.set_print_handler(buffer, "insert_at_cursor")
|
||||
|
||||
# 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')
|
||||
|
||||
# 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 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:
|
||||
accept_event()
|
||||
|
||||
# TODO: Handle more of these.
|
||||
if (event.control and event.scancode == KEY_C):
|
||||
send_data(PoolByteArray([3]))
|
||||
elif event.unicode:
|
||||
send_data(PoolByteArray([event.unicode]))
|
||||
elif event.scancode == KEY_ENTER:
|
||||
send_data(PoolByteArray([ENTER]))
|
||||
elif event.scancode == KEY_BACKSPACE:
|
||||
send_data(PoolByteArray([BACKSPACE_ALT]))
|
||||
elif event.scancode == KEY_ESCAPE:
|
||||
send_data(PoolByteArray([27]))
|
||||
elif event.scancode == KEY_TAB:
|
||||
send_data(PoolByteArray([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)))
|
||||
|
||||
|
||||
func send_data(data: PoolByteArray):
|
||||
emit_signal("data_sent", data)
|
||||
|
||||
|
||||
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()
|
Loading…
Add table
Add a link
Reference in a new issue