Add all the files

This commit is contained in:
Leroy Hopson 2020-05-10 00:07:31 +07:00
parent d7db117f8b
commit 96e9ddcf79
68 changed files with 9064 additions and 7 deletions

View 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

View 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)

View 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

View 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.

View file

@ -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 )

View file

@ -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 )

View file

@ -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 )

View file

@ -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 )

View 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.

View file

@ -0,0 +1,3 @@
[gd_resource type="DynamicFont" format=2]
[resource]

Binary file not shown.

View 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

View 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

View 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

View 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
}

View 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

View 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

View 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

View 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)

View 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

View file

@ -0,0 +1,7 @@
[plugin]
name="GodotXterm"
description="Xterm.js for Godot"
author="Leroy Hopson"
version="0.1.0"
script="plugin.gd"

View 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

View 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()