Remove gdscript version and replace with native

Former-commit-id: f9474fe533
This commit is contained in:
Leroy Hopson 2020-09-17 15:14:04 +07:00
parent f8412a03f5
commit a022104230
126 changed files with 116 additions and 14221 deletions

359
addons/godot_xterm/.gitignore vendored Normal file
View file

@ -0,0 +1,359 @@
# Godot auto generated files
*.gen.*
.import/
# Documentation generated by doxygen or from classes.xml
doc/_build/
# Javascript specific
*.bc
# CLion
cmake-build-debug
# Android specific
.gradle
local.properties
*.iml
.idea
.gradletasknamecache
project.properties
platform/android/java/app/libs/*
platform/android/java/libs/*
platform/android/java/lib/.cxx/
# General c++ generated files
*.lib
*.o
*.ox
*.a
*.ax
*.d
*.so
*.os
*.Plo
*.lo
# Libs generated files
.deps/*
.dirstamp
# Gprof output
gmon.out
# Vim temp files
*.swo
*.swp
# Qt project files
*.config
*.creator
*.creator.*
*.files
*.includes
*.cflags
*.cxxflags
# Code::Blocks files
*.cbp
*.layout
*.depend
# Eclipse CDT files
.cproject
.settings/
*.pydevproject
*.launch
# Geany/geany-plugins files
*.geany
.geanyprj
# Jetbrains IDEs
.idea/
# Misc
.DS_Store
__MACOSX
logs/
# for projects that use SCons for building: http://http://www.scons.org/
.sconf_temp
.sconsign*.dblite
*.pyc
# https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
*.sln
*.vcxproj*
# Custom SCons configuration override
/custom.py
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
x64/
build/
bld/
[Oo]bj/
*.debug
*.dSYM
# Visual Studio cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# Hints for improving IntelliSense, created together with VS project
cpp.hint
#NUNIT
*.VisualState.xml
TestResult.xml
*.o
*.a
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.bak
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
*.nib
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.opendb
*.VC.VC.opendb
enc_temp_folder/
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# CodeLite project files
*.project
*.workspace
.codelite/
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/*
## TODO: If the tool you use requires repositories.config, also uncomment the next line
#!packages/repositories.config
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
# This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
!packages/build/
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
__pycache__/
# KDE
.directory
#Kdevelop project files
*.kdev4
# Xcode
xcuserdata/
*.xcscmblueprint
*.xccheckout
*.xcodeproj/*
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# =========================
# Windows detritus
# =========================
# Windows image file caches
[Tt]humbs.db
[Tt]humbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Windows stackdumps
*.stackdump
# Windows shortcuts
*.lnk
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
logo.h
*.autosave
# https://github.com/github/gitignore/blob/master/Global/Tags.gitignore
# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
TAGS
!TAGS/
tags
*.tags
!tags/
gtags.files
GTAGS
GRTAGS
GPATH
cscope.files
cscope.out
cscope.in.out
cscope.po.out
godot.creator.*
projects/
platform/windows/godot_res.res
# Visual Studio 2017 and Visual Studio Code workspace folder
/.vs
/.vscode
# Visual Studio Code workspace file
*.code-workspace
# Scons construction environment dump
.scons_env.json
# Scons progress indicator
.scons_node_count
# ccls cache (https://github.com/MaskRay/ccls)
.ccls-cache/
# compile commands (https://clang.llvm.org/docs/JSONCompilationDatabase.html)
compile_commands.json
# Cppcheck
*.cppcheck

View file

@ -1,23 +0,0 @@
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

1
addons/godot_xterm/LICENSE Symbolic link
View file

@ -0,0 +1 @@
LICENSE

View file

@ -0,0 +1,118 @@
#!python
import os, subprocess
opts = Variables([], ARGUMENTS)
# Gets the standard flags CC, CCX, etc.
env = DefaultEnvironment()
# Add PATH to environment so scons can find commands such as g++, etc.
env.AppendENVPath('PATH', os.getenv('PATH'))
# Define our options
opts.Add(EnumVariable('target', "Compilation target", 'debug', ['d', 'debug', 'r', 'release']))
opts.Add(EnumVariable('platform', "Compilation platform", '', ['', 'windows', 'x11', 'linux', 'osx']))
opts.Add(EnumVariable('p', "Compilation target, alias for 'platform'", '', ['', 'windows', 'x11', 'linux', 'osx']))
opts.Add(BoolVariable('use_llvm', "Use the LLVM / Clang compiler", 'no'))
opts.Add(PathVariable('target_path', 'The path where the lib is installed.', 'bin/'))
opts.Add(PathVariable('target_name', 'The library name.', 'libgodotxtermnative', PathVariable.PathAccept))
# Local dependency paths, adapt them to your setup
godot_headers_path = "godot-cpp/godot_headers/"
cpp_bindings_path = "godot-cpp/"
cpp_library = "libgodot-cpp"
libtsm_path = "libtsm/"
# only support 64 at this time..
bits = 64
# Updates the environment with the option variables.
opts.Update(env)
# Process some arguments
if env['use_llvm']:
env['CC'] = 'clang'
env['CXX'] = 'clang++'
else:
env['CC'] = 'gcc'
env['CXX'] = 'g++'
if env['p'] != '':
env['platform'] = env['p']
if env['platform'] == '':
print("No valid target platform selected.")
quit();
# For the reference:
# - CCFLAGS are compilation flags shared between C and C++
# - CFLAGS are for C-specific compilation flags
# - CXXFLAGS are for C++-specific compilation flags
# - CPPFLAGS are for pre-processor flags
# - CPPDEFINES are for pre-processor defines
# - LINKFLAGS are for linking flags
# Check our platform specifics
if env['platform'] == "osx":
env['target_path'] += 'osx/'
cpp_library += '.osx'
env.Append(CCFLAGS=['-arch', 'x86_64'])
env.Append(CXXFLAGS=['-std=c++17'])
env.Append(LINKFLAGS=['-arch', 'x86_64'])
if env['target'] in ('debug', 'd'):
env.Append(CCFLAGS=['-g', '-O2'])
else:
env.Append(CCFLAGS=['-g', '-O3'])
elif env['platform'] in ('x11', 'linux'):
env['target_path'] += 'x11/'
cpp_library += '.linux'
env.Append(CCFLAGS=['-fPIC', '-shared'])
env.Append(CXXFLAGS=['-std=c++17', '-shared', '-pthread'])
if env['target'] in ('debug', 'd'):
env.Append(CCFLAGS=['-g3', '-Og'])
else:
env.Append(CCFLAGS=['-g', '-O3'])
elif env['platform'] == "windows":
env['target_path'] += 'win64/'
cpp_library += '.windows'
# This makes sure to keep the session environment variables on windows,
# that way you can run scons in a vs 2017 prompt and it will find all the required tools
env.Append(ENV=os.environ)
env.Append(CPPDEFINES=['WIN32', '_WIN32', '_WINDOWS', '_CRT_SECURE_NO_WARNINGS'])
env.Append(CCFLAGS=['-W3', '-GR'])
if env['target'] in ('debug', 'd'):
env.Append(CPPDEFINES=['_DEBUG'])
env.Append(CCFLAGS=['-EHsc', '-MDd', '-ZI'])
env.Append(LINKFLAGS=['-DEBUG'])
else:
env.Append(CPPDEFINES=['NDEBUG'])
env.Append(CCFLAGS=['-O2', '-EHsc', '-MD'])
if env['target'] in ('debug', 'd'):
cpp_library += '.debug'
else:
cpp_library += '.release'
cpp_library += '.' + str(bits)
# make sure our binding library is properly includes
env.Append(CPPPATH=['.', godot_headers_path, cpp_bindings_path + 'include/', cpp_bindings_path + 'include/core/', cpp_bindings_path + 'include/gen/', libtsm_path + 'src/tsm'])
env.Append(LIBPATH=[cpp_bindings_path + 'bin/', libtsm_path + 'build/src/tsm'])
env.Append(LIBS=[cpp_library, 'tsm', 'util']) # Note util used by pseudoterminal, tsm used by terminal.
# tweak this if you want to use different folders, or more folders, to store your source code in.
env.Append(CPPPATH=['src/'])
sources = []
sources.append(Glob('src/*.c'))
sources.append(Glob('src/*.cpp'))
library = env.SharedLibrary(target=env['target_path'] + env['target_name'] , source=sources)
Default(library)
# Generates help for the -h scons option.
Help(opts.GenerateHelpText(env))

2
addons/godot_xterm/bin/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -1,168 +0,0 @@
# Copyright (c) 2018 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const Attributes = Constants.Attributes
const FgFlags = Constants.FgFlags
const BgFlags = Constants.BgFlags
const UnderlineStyle = Constants.UnderlineStyle
var fg = 0
var bg = 0
var extended = ExtendedAttrs.new()
static func to_color_rgb(value: int) -> Color:
# Create color from RGB format.
return Color("%02x%02x%02x" % [value >> Attributes.RED_SHIFT & 255,
value >> Attributes.GREEN_SHIFT & 255, value & 255])
# flags
func is_inverse() -> int:
return fg & FgFlags.INVERSE
func is_bold() -> int:
return fg & FgFlags.BOLD
func is_underline() -> int:
return fg & FgFlags.UNDERLINE
func is_blink() -> int:
return fg & FgFlags.BLINK
func is_invisible() -> int:
return fg & FgFlags.INVISIBLE
func is_italic() -> int:
return bg & BgFlags.ITALIC
func is_dim() -> int:
return fg & BgFlags.DIM
# color modes
func get_fg_color_mode() -> int:
return fg & Attributes.CM_MASK
func get_bg_color_mode() -> int:
return bg & Attributes.CM_MASK
func is_fg_rgb() -> bool:
return (fg & Attributes.CM_MASK) == Attributes.CM_RGB
func is_bg_rgb() -> bool:
return (bg & Attributes.CM_MASK) == Attributes.CM_RGB
func is_fg_palette() -> bool:
return (fg & Attributes.CM_MASK) == Attributes.CM_P16 or (fg & Attributes.CM_MASK) == Attributes.CM_P256
func is_bg_palette() -> bool:
return (bg & Attributes.CM_MASK) == Attributes.CM_P16 or (bg & Attributes.CM_MASK) == Attributes.CM_P256
func is_fg_default() -> bool:
return (fg & Attributes.CM_MASK) == 0
func is_bg_default() -> bool:
return (bg & Attributes.CM_MASK) == 0
func is_attribute_default() -> bool:
return fg == 0 && bg == 0
func get_fg_color() -> int:
match fg & Attributes.CM_MASK:
Attributes.CM_P16, Attributes.CM_P256:
return fg & Attributes.PCOLOR_MASK
Attributes.CM_RGB:
return fg & Attributes.RGB_MASK
_:
return -1 # CM_DEFAULT defaults to -1
func get_bg_color() -> int:
match bg & Attributes.CM_MASK:
Attributes.CM_P16, Attributes.CM_P256:
return bg & Attributes.PCOLOR_MASK
Attributes.CM_RGB:
return bg & Attributes.RGB_MASK
_:
return -1 # CM_DEFAULT defaults to -1
func has_extended_attrs() -> int:
return bg & BgFlags.HAS_EXTENDED
func update_extended() -> void:
if extended.is_empty():
bg &= ~BgFlags.HAS_EXTENDED
else:
bg |= BgFlags.HAS_EXTENDED
func get_underline_color() -> int:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
match extended.underline_color & Attributes.CM_MASK:
Attributes.CM_P16, Attributes.CM_P256:
return extended.underline_color & Attributes.PCOLOR_MASK
Attributes.CM_RGB:
return extended.underline_color & Attributes.RGB_MASK
_:
return get_fg_color()
else:
return get_fg_color()
func get_underline_color_mode() -> int:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK
else:
return get_fg_color_mode()
func is_underline_color_rgb() -> bool:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK == Attributes.CM_RGB
else:
return is_fg_rgb()
func is_underline_color_palette() -> bool:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK == Attributes.CM_P16 \
or extended.underline_color & Attributes.CM_MASK == Attributes.CM_P256
else:
return is_fg_palette()
func is_underline_color_default() -> bool:
if bg & BgFlags.HAS_EXTENDED and ~extended.underline_color:
return extended.underline_color & Attributes.CM_MASK == 0
else:
return is_fg_default()
func get_underline_style():
if fg & FgFlags.UNDERLINE:
return extended.underline_style if bg & BgFlags.HAS_EXTENDED else UnderlineStyle.SINGLE
else:
return UnderlineStyle.NONE
class ExtendedAttrs:
extends Reference
# Extended attributes for a cell.
# Holds information about different underline styles and color.
var underline_style = UnderlineStyle.NONE
var underline_color: int = -1
func _init():
underline_style
func duplicate():
# Workaround as per: https://github.com/godotengine/godot/issues/19345#issuecomment-471218401
var AttributeData = load("res://addons/godot_xterm/buffer/attribute_data.gd")
var clone = AttributeData.ExtendedAttrs.new()
clone.underline_style = underline_style
clone.underline_color = underline_color
return clone
# Convenient method to indicate whether the object holds no additional information,
# that needs to be persistant in the buffer.
func is_empty():
return underline_style == UnderlineStyle.NONE

View file

@ -1,430 +0,0 @@
# Copyright (c) 2017 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const BufferLine = preload("res://addons/godot_xterm/buffer/buffer_line.gd")
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const Charsets = preload("res://addons/godot_xterm/data/charsets.gd")
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const CircularList = preload("res://addons/godot_xterm/circular_list.gd")
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const BufferReflow = preload("res://addons/godot_xterm/buffer/buffer_reflow.gd")
const MAX_BUFFER_SIZE = 4294967295 # 2^32 - 1
var lines
var ydisp: int = 0
var ybase: int = 0
var y: int = 0
var x: int = 0
var scroll_bottom: int
var scroll_top: int
var tabs = {}
var saved_y: int = 0
var saved_x: int = 0
var saved_cur_attr_data = AttributeData.new()
var saved_charset = Charsets.DEFAULT_CHARSET
var markers: Array = []
var _null_cell = CellData.from_char_data([0, Constants.NULL_CELL_CHAR,
Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE])
var _whitespace_cell = CellData.from_char_data([0, Constants.WHITESPACE_CELL_CHAR,
Constants.WHITESPACE_CELL_WIDTH, Constants.WHITESPACE_CELL_CODE])
var _cols: int
var _rows: int
var _has_scrollback
var _options_service
var _buffer_service
func _init(has_scrollback: bool, options_service, buffer_service):
_has_scrollback = has_scrollback
_options_service = options_service
_buffer_service = buffer_service
_cols = buffer_service.cols
_rows = buffer_service.rows
lines = CircularList.new(_get_correct_buffer_length(_rows))
scroll_top = 0
scroll_bottom = _rows - 1
setup_tab_stops()
# Resizes the buffer, adjusting its data accordingly.
# @param new_cols The new number of columns.
# @param new_rows The new number of rows.
func resize(new_cols: int, new_rows: int) -> void:
# store reference to null cell with default attrs
var null_cell = get_null_cell(AttributeData.new())
# Increase max length if needed before adjustments to allow space to fill
# as required.
var new_max_length = _get_correct_buffer_length(new_rows)
if new_max_length > lines.max_length:
lines.max_length = new_max_length
# The following adjustments should only happen if the buffer has been
# initialized/filled.
if lines.length > 0:
# Deal with columns increasing (reducing needs to happen after reflow)
if _cols < new_cols:
for i in range(lines.length):
lines.get_line(i).resize(new_cols, null_cell)
# Resize rows in both directions as needed
var add_to_y = 0
if _rows < new_rows:
for y in range(_rows, new_rows):
if lines.length < new_rows + ybase:
if _options_service.options.windows_mode:
# Just add the new missing rows on Windows as conpty reprints the screen with it's
# view of the world. Once a line enters scrollback for conpty it remains there
lines.push(BufferLine.new(new_cols, null_cell))
else:
if ybase > 0 and lines.length <= ybase + y + add_to_y + 1:
# There is room above the buffer and there are no empty elements below the line,
# scroll up
ybase -= 1
add_to_y += 1
if ydisp > 0:
# Viewport is at the top of the buffer, must increase downwards
ydisp -= 1
else:
# Add a blank line if tere is no buffer left at the top to srcoll to, or if there
# are blank lines after the cursor
lines.push(BufferLine.new(new_cols, null_cell))
else: # _rows >= new_rows
for _y in range(_rows, new_rows, -1):
if lines.length > new_rows + ybase:
if lines.length > ybase + y + 1:
# The line is blank line below the cursor, remove it
lines.pop()
else:
# The line is the cursor, scroll down
ybase += 1
ydisp += 1
# Reduce max length if needed after adjustments, this is done after as it
# would otherwise cut data from the bottom of the buffer.
if new_max_length < lines.max_length:
# Trim from the top of th ebuffer and adjust ybase and ydisp.
var amount_to_trim = lines.length - new_max_length
if amount_to_trim > 0:
lines.trim_start(amount_to_trim)
ybase = max(ybase - amount_to_trim, 0)
ydisp = max(ydisp - amount_to_trim, 0)
saved_y = max(saved_y - amount_to_trim, 0)
lines.max_length = new_max_length
# Make sure that the cursor stays on screen
x = min(x, new_cols - 1)
y = min(y, new_rows - 1)
if add_to_y:
y += add_to_y
saved_x = min(saved_x, new_cols -1)
scroll_top = 0
scroll_bottom = new_rows - 1
if _is_reflow_enabled():
_reflow(new_cols, new_rows)
# Trim the end of the line off if cols shrunk
if _cols > new_cols:
for i in range(lines.length):
lines.get_line(i).resize(new_cols, null_cell)
_cols = new_cols
_rows = new_rows
func _is_reflow_enabled() -> bool:
return _has_scrollback and not _options_service.options.windows_mode
func _reflow(new_cols: int, new_rows: int) -> void:
if _cols == new_cols:
return
# Iterate through rows, ignore the last one as it cannot be wrapped
if new_cols > _cols:
_reflow_larger(new_cols, new_rows)
else:
_reflow_smaller(new_cols, new_rows)
func _reflow_larger(new_cols: int, new_rows: int) -> void:
var to_remove: PoolIntArray = BufferReflow.reflow_larger_get_lines_to_remove(lines,
_cols, new_cols, ybase + y, get_null_cell(AttributeData.new()))
if not to_remove.empty():
var new_layout_result = BufferReflow.reflow_larger_create_new_layout(lines, to_remove)
BufferReflow.reflow_larger_apply_new_layout(lines, new_layout_result.layout)
_reflow_larger_adjust_viewport(new_cols, new_rows, new_layout_result.count_removed)
func _reflow_larger_adjust_viewport(new_cols: int, new_rows: int, count_removed: int) -> void:
var null_cell = get_null_cell(AttributeData.new())
# Adjust viewport based on number of items removed
var viewport_adjustments = count_removed
while viewport_adjustments > 0:
viewport_adjustments -= 1
if ybase == 0:
if y > 0:
y -= 1
if lines.length < new_rows:
# Add an extra row at the bottom of the viewport
lines.push(BufferLine.new(new_cols, null_cell))
else:
if ydisp == ybase:
ydisp -= 1
ybase -= 1
saved_y = max(saved_y - count_removed, 0)
func _reflow_smaller(new_cols: int, new_rows: int) -> void:
var null_cell = get_null_cell(AttributeData.new())
# Gather all BufferLines that need to be inserted into the Buffer here so that they can be
# batched up and only commited once
var to_insert = []
var count_to_insert = 0
# Go backwards as many lines may be trimmed and this will avoid considering them
var i = lines.length - 1
while i >= 0:
# Check wether this line is a problem
var next_line = lines.get_line(i)
if not next_line or not next_line.is_wrapped and next_line.get_trimmed_length() <= new_cols:
i -= 1
continue
# Gather wrapped lines and adjust y to be the starting line
var wrapped_lines = [next_line]
while next_line.is_wrapped and i > 0:
i -= 1
next_line = lines.get_line(i)
wrapped_lines.push_front(next_line)
# If these lines contain the cursor don't touch them, the program will handle fixing up
# wrapped lines with the cursor
var absolute_y = ybase + y
if absolute_y >= i and absolute_y < i + wrapped_lines.size():
i -= 1
continue
var last_line_length = wrapped_lines[wrapped_lines.size() - 1].get_trimmed_length()
var dest_line_lengths = BufferReflow.reflow_smaller_get_new_line_lengths(wrapped_lines, _cols, new_cols)
var lines_to_add = dest_line_lengths.size() - wrapped_lines.size()
var trimmed_lines: int
if ybase == 0 and y != lines.length - 1:
# If the top section of the buffer is not yet filled
trimmed_lines = max(0, y - lines.max_length + lines_to_add)
else:
trimmed_lines = max(0, lines.length - lines.max_length + lines_to_add)
# Add the new lines
var new_lines = []
for j in range(lines_to_add):
var new_line = get_blank_line(AttributeData.new(), true)
new_lines.append(new_line)
if not new_lines.empty():
to_insert.append({"start": i + wrapped_lines.size() + count_to_insert,
"new_lines": new_lines})
count_to_insert += new_lines.size()
wrapped_lines += new_lines
# Copy buffer data to new locations, this needs to happen backwards to do in-place
var dest_line_index = dest_line_lengths.size() - 1 # floor(cells_needed / new_cols)
var dest_col = dest_line_lengths[dest_line_index] # cells_needed % new_cols
if dest_col == 0:
dest_line_index -= 1
dest_col = dest_line_lengths[dest_line_index]
var src_line_index = wrapped_lines.size() - lines_to_add - 1
var src_col = last_line_length
while src_line_index >= 0:
var cells_to_copy = min(src_col, dest_col)
wrapped_lines[dest_line_index].copy_cells_from(wrapped_lines[src_line_index],
src_col - cells_to_copy, dest_col - cells_to_copy, cells_to_copy, true)
dest_col -= cells_to_copy
if dest_col == 0:
dest_line_index -= 1
dest_col = dest_line_lengths[dest_line_index]
src_col -= cells_to_copy
if src_col == 0:
src_line_index -= 1
var wrapped_lines_index = max(src_line_index, 0)
src_col = BufferReflow.get_wrapped_line_trimmed_length(wrapped_lines, wrapped_lines_index, _cols)
# Null out the end of the line ends if a wide character wrapped to the following line
for j in range(wrapped_lines.size()):
if dest_line_lengths[j] < new_cols:
wrapped_lines[j].set_cell(dest_line_lengths[j], null_cell)
# Adjust viewport as needed
var viewport_adjustments = lines_to_add - trimmed_lines
while viewport_adjustments > 0:
if ybase == 0:
if y < new_rows - 1:
y += 1
lines.pop()
else:
ybase += 1
ydisp += 1
else:
# Ensure ybase does not exceed its maximum value
if ybase < min(lines.max_length, (lines.length + count_to_insert) - new_rows):
if ybase == ydisp:
ydisp += 1
ybase += 1
viewport_adjustments -= 1
saved_y = min(saved_y + lines_to_add, ybase + new_rows - 1)
i -= 1
# Rearrange lines in the buffer if there are any insertions, this is done at the end rather
# than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
# costly calls to CircularList.splice.
if not to_insert.empty():
# Record buffer insert events and then play them backwards so that the indexes are
# correct
var insert_events = []
# Record original lines so they don't get overridden when we rearrange the list
var original_lines = []
for j in range(lines.length):
original_lines.append(lines.get_line(j))
var original_lines_length = lines.length
var original_line_index = original_lines_length - 1
var next_to_insert_index = 0
var next_to_insert = to_insert[next_to_insert_index]
lines.length = min(lines.max_length, lines.length + count_to_insert)
var count_inserted_so_far = 0
var j = min(lines.max_length - 1, original_lines_length + count_to_insert - 1)
while j >= 0:
if next_to_insert and next_to_insert.start > original_line_index + count_inserted_so_far:
# Insert extra lines here, adjusting i as needed
for next_i in range(next_to_insert.new_lines.size() - 1, -1, -1):
lines.set_line(j, next_to_insert.new_lines[next_i])
j -= 1
j += 1
# Create insert events for later
insert_events.append({"index": original_line_index + 1,
"amount": next_to_insert.new_lines.size()})
count_inserted_so_far += next_to_insert.new_lines.size()
next_to_insert_index += 1
next_to_insert = to_insert[next_to_insert_index] if to_insert.size() > next_to_insert_index else null
else:
lines.set_line(j, original_lines[original_line_index])
original_line_index -= 1
j -= 1
# Update markers
var insert_count_emitted = 0
for k in range(insert_events.size() - 1, -1, -1):
insert_events[k].index += insert_count_emitted
lines.emit_signal("inserted", insert_events[k])
insert_count_emitted += insert_events[k].amount
var amount_to_trim = max(0, original_lines_length + count_to_insert - lines.max_length)
if amount_to_trim > 0:
lines.emit_signal("trimmed", amount_to_trim)
func get_null_cell(attr = null):
if attr:
_null_cell.fg = attr.fg
_null_cell.bg = attr.bg
_null_cell.extended = attr.extended
else:
_null_cell.fg = 0
_null_cell.bg = 0
_null_cell.extended = AttributeData.ExtendedAttrs.new()
return _null_cell
func get_blank_line(attr, is_wrapped: bool = false):
return BufferLine.new(_buffer_service.cols, get_null_cell(attr), is_wrapped)
func _get_correct_buffer_length(rows: int) -> int:
if not _has_scrollback:
return rows
else:
var correct_buffer_length = rows + _options_service.options.scrollback
return correct_buffer_length if correct_buffer_length < MAX_BUFFER_SIZE else MAX_BUFFER_SIZE
# Fills the viewport with blank lines.
func fill_viewport_rows(fill_attr = null) -> void:
if lines.length == 0:
if not fill_attr:
fill_attr = AttributeData.new()
var i = _rows
while i:
lines.push(get_blank_line(fill_attr))
i -= 1
# Clears the buffer to it's initial state, discarding all previous data.
func clear() -> void:
ydisp = 0
ybase = 0
y = 0
x = 0
lines = CircularList.new(_get_correct_buffer_length(_rows))
scroll_top = 0
scroll_bottom = _rows - 1
setup_tab_stops()
func get_wrapped_range_for_line(y: int) -> Dictionary:
var first = y
var last = y
# Scan upwards for wrapped lines
while first > 0 and lines.get_el(first).is_wrapped:
first -= 1
# Scan downwards for wrapped lines
while last + 1 < lines.length and lines.get_el(last + 1).is_wrapped:
last += 1
return {"first": first, "last": last}
# Setup the tab stops.
# @param i The index to start setting up tab stops from.
func setup_tab_stops(i = null) -> void:
if i != null:
if not tabs.get(i):
i = prev_stop(i)
else:
tabs = {}
i = 0
while i < _cols:
tabs[i] = true
i += max(_options_service.options.tab_stop_width, 1)
# Move the cursor to the previous tab stop from the given position (default is current).
# @param x The position to move the cursor to the previous tab stop.
func prev_stop(x: int) -> int:
if x == null:
x = self.x
while not tabs.get(x - 1, false) and x - 1 > 0:
x - 1
return _cols - 1 if x > _cols else 0 if x < 0 else x
# Move the cursor one tab stop forward from the given position (default is current).
# @param x The position to move the cursor one tab stop forward.
func next_stop(x = null) -> int:
if x == null:
x = self.x
x += 1
while not tabs.get(x) and x < _cols:
x += 1
return _cols - 1 if x >= _cols else 0 if x < 0 else x

View file

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

View file

@ -1,198 +0,0 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Object
# Evaluates and returns indexes to be removed after a reflow larger occurs. Lines will be removed
# when a wrapped line unwraps.
# @param lines The buffer lines.
# @param newCols The columns after resize.
static func reflow_larger_get_lines_to_remove(lines, old_cols: int, new_cols: int,
buffer_absolute_y: int, null_cell) -> PoolIntArray:
# Gather all BufferLines that need to be removed from the Buffer here so that they can be
# batched up and only committed once
var to_remove = PoolIntArray([])
var y = 0
while y < lines.length - 1:
# Check if this row is wrapped
var i = y
i += 1
var next_line = lines.get_line(i)
if not next_line.is_wrapped:
y += 1
continue
# Check how many lines it's wrapped for
var wrapped_lines = [lines.get_line(y)]
while i < lines.length and next_line.is_wrapped:
wrapped_lines.append(next_line)
i += 1
next_line = lines.get_line(i)
# If these lines contain the cursor don't touch them, the program will handle fixing up wrapped
# lines with the cursor
if buffer_absolute_y >= y and buffer_absolute_y < i:
y += wrapped_lines.size() - 1
y += 1
continue
# Copy buffer data to new locations
var dest_line_index = 0
var dest_col = get_wrapped_line_trimmed_length(wrapped_lines, dest_line_index, old_cols)
var src_line_index = 1
var src_col = 0
while src_line_index < wrapped_lines.size():
var src_trimmed_line_length = get_wrapped_line_trimmed_length(wrapped_lines, src_line_index, old_cols)
var src_remaining_cells = src_trimmed_line_length - src_col
var dest_remaining_cells = new_cols - dest_col
var cells_to_copy = min(src_remaining_cells, dest_remaining_cells)
wrapped_lines[dest_line_index].copy_cells_from(wrapped_lines[src_line_index], src_col, dest_col, cells_to_copy, false)
dest_col += cells_to_copy
if dest_col == new_cols:
dest_line_index += 1
dest_col = 0
src_col += cells_to_copy
if src_col == src_trimmed_line_length:
src_line_index += 1
src_col = 0
# Make sure the last cell isn't wide, if it is copy it to the current dest
if dest_col == 0 and dest_line_index != 0:
if wrapped_lines[dest_line_index - 1].get_width(new_cols - 1) == 2:
wrapped_lines[dest_line_index].copy_cells_from(wrapped_lines[dest_line_index - 1], new_cols - 1, dest_col, 1, false)
dest_col += 1
# Null out the end of the last row
wrapped_lines[dest_line_index - 1].set_cell(new_cols - 1, null_cell)
# Clear out remaining cells or fragments could remain;
var replaced = wrapped_lines[dest_line_index].translate_to_string()
wrapped_lines[dest_line_index].replace_cells(dest_col, new_cols, null_cell)
# Work backwards and remove any rows at the end that only contain null cells
var count_to_remove = 0
for j in range(wrapped_lines.size() - 1, 0, -1):
if j > dest_line_index or wrapped_lines[j].get_trimmed_length() == 0:
count_to_remove += 1
else:
break
if count_to_remove > 0:
to_remove.append(y + wrapped_lines.size() - count_to_remove) # index
to_remove.append(count_to_remove)
y += wrapped_lines.size() - 1
y += 1
return to_remove
# Creates and return the new layout for lines given an array of indexes to be removed.
# @param lines The buffer lines.
# @param to_remove The indexes to remove.
static func reflow_larger_create_new_layout(lines, to_remove: PoolIntArray):
var layout = PoolIntArray([])
# First iterate through the list and get the actual indexes to use for rows
var next_to_remove_index = 0
var next_to_remove_start = to_remove[next_to_remove_index]
var count_removed_so_far = 0
var i = 0
while i < lines.length:
if next_to_remove_start == i:
next_to_remove_index += 1
var count_to_remove = to_remove[next_to_remove_index]
# Tell markers that there was a deletion
lines.emit_signal("deleted", i - count_removed_so_far, count_to_remove)
i += count_to_remove - 1
count_removed_so_far += count_to_remove
next_to_remove_index += 1
next_to_remove_start = to_remove[next_to_remove_index] if next_to_remove_index < to_remove.size() else null
else:
layout.append(i)
i += 1
return { "layout": layout, "count_removed": count_removed_so_far }
# Applies a new layout to the buffer. This essentially does the same as many splice calls but it's
# done all at once in a single iteration through the list since splice is very expensive.
# @param lines The buffer lines.
# @param new_layout The new layout to apply.
static func reflow_larger_apply_new_layout(lines, new_layout: PoolIntArray) -> void:
# Record original lines so they don't get overridden when we rearrange the list
var new_layout_lines = []
for i in range(new_layout.size()):
new_layout_lines.append(lines.get_line(new_layout[i]))
# Rearrange the list
for i in range(new_layout_lines.size()):
lines.set_line(i, new_layout_lines[i])
lines.length = new_layout.size()
# Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
# compute the wrapping points since wide characters may need to be wrapped onto the following line.
# This function will return an array of numbers of where each line wraps to, the resulting array
# will only contain the values `newCols` (when the line does not end with a wide character) and
# `new_cols - 1` (when the line does end with a wide character), except for the last value which
# will contain the remaining items to fill the line.
#
# Calling this with a `new_cols` value of `1` will lock up.
#
# @param wrapped_lines The wrapped lines to evaluate.
# @param old_cols The columns before resize.
# @param new_cols The columns after resize.
static func reflow_smaller_get_new_line_lengths(wrapped_lines: Array, old_cols: int, new_cols: int) -> PoolIntArray:
var new_line_lengths = PoolIntArray([])
var cells_needed: int
for i in range(wrapped_lines.size()):
cells_needed += get_wrapped_line_trimmed_length(wrapped_lines, i, old_cols)
# Use src_col and scr_line to find the new wrapping point, use that to get the cells_available and
# lines_needed
var src_col = 0
var src_line = 0
var cells_available = 0
while cells_available < cells_needed:
if cells_needed - cells_available < new_cols:
# Add the final line and exit the loop
new_line_lengths.append(cells_needed - cells_available)
break
src_col += new_cols
var old_trimmed_length = get_wrapped_line_trimmed_length(wrapped_lines, src_line, old_cols)
if src_col > old_trimmed_length:
src_col -= old_trimmed_length
src_line += 1
var ends_with_wide = wrapped_lines[src_line].get_width(src_col - 1) == 2
if ends_with_wide:
src_col -= 1
var line_length = new_cols - 1 if ends_with_wide else new_cols
new_line_lengths.append(line_length)
cells_available += line_length
return new_line_lengths
static func get_wrapped_line_trimmed_length(lines: Array, i: int, cols: int) -> int:
# If this is the last row in the wrapped line, get the actual trimmed length
if i == lines.size() - 1:
return lines[i].get_trimmed_length()
# Detect whether the following line starts with a wide character and the end of the current line
# is null, if so then we can be pretty sure the null character should be excluded from the line
# length
var ends_in_null = not (lines[i].has_content(cols - 1)) and lines[i].get_width(cols - 1) == 1
var following_line_starts_with_wide = lines[i + 1].get_width(0) == 2
if ends_in_null and following_line_starts_with_wide:
return cols - 1
return cols

View file

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

View file

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

View file

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

27
addons/godot_xterm/build.sh Executable file
View file

@ -0,0 +1,27 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash --pure -p binutils.bintools cmake scons
# Make sure we are in the addons/godot_xterm directory
cd ${BASH_SOURCE%/*}
# Initialize godot-cpp
if [ ! -d "godot-cpp/bin" ]
then
cd godot-cpp
scons platform=linux generate_bindings=yes -j12
cd ..
fi
# Build libtsm
if [ ! -f "libtsm/build/src/tsm/libtsm.a" ]
then
cd libtsm
mkdir -p build
cd build
cmake -DBUILD_SHARED_LIBS=n ..
make
cd ../..
fi
# Build godotxtermnative
scons platform=linux

View file

@ -1,19 +0,0 @@
# 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

@ -1,185 +0,0 @@
# Copyright (c) 2016 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
# Represents a circular list; a list with a maximum size that wraps around when push is called,
# overriding values at the start of the list.
signal deleted(index, amount)
signal inserted
signal trimmed
var _array
var _start_index: int
var length: int = 0 setget _set_length,_get_length
var max_length: int setget _set_max_length,_get_max_length
var is_full: bool setget ,_get_is_full
func _set_length(new_length: int):
if new_length > length:
for i in range(length, new_length):
_array[i] = null
length = new_length
func _get_length():
return length
func _set_max_length(new_max_length):
if max_length == new_max_length:
return
# Reconstruct array, starting at index 0.
# Only transfer values from the indexes 0 to length.
var new_array = []
new_array.resize(new_max_length)
for i in range(0, min(new_max_length, length)):
new_array[i] = _array[_get_cyclic_index(i)]
_array = new_array
max_length = new_max_length
_start_index = 0
func _get_max_length():
return max_length
# Ringbuffer is at max length.
func _get_is_full() -> bool:
return length == max_length
func _init(max_length = 0):
self.max_length = max_length
_array = []
_array.resize(max_length)
_start_index = 0
func get_el(index: int):
return _array[_get_cyclic_index(index)]
# Alias for `get_al`.
func get_line(index: int):
return get_el(index)
func set_el(index: int, value) -> void:
_array[_get_cyclic_index(index)] = value
# Alias for `set_el`.
func set_line(index: int, value) -> void:
set_el(index, value)
# Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0
# if the maximum length is reached.
# @param value The value to push onto the list.
func push(value) -> void:
_array[_get_cyclic_index(length)] = value
if length == max_length:
_start_index += 1
_start_index %= max_length
emit_signal("trimmed", 1)
else:
length += 1
# Advance ringbuffer index and return current element for recycling.
# Note: The buffer must be full for this method to work.
# @throws When the buffer is not full.
func recycle():
if length != max_length:
push_error("Can only recycle when the buffer is full")
_start_index = (_start_index + 1) % max_length
emit_signal("trimmed", 1)
return _array[_get_cyclic_index(length - 1)]
# Removes and returns the last value on the list.
# @return The popped value.
func pop():
var last = _array[_get_cyclic_index(length - 1)]
length -= 1
return last
# Deletes and/or inserts items at a particular index (in that order). Unlike
# Array.prototype.splice, this operation does not return the deleted items as a new array in
# order to save creating a new array. Note that this operation may shift all values in the list
# in the worst case.
# @param start The index to delete and/or insert.
# @param deleteCount The number of elements to delete.
# @param items The items to insert.
func splice(start: int, delete_count: int, items: Array = []) -> void:
# Delete items
if delete_count:
for i in range(start, length - delete_count):
_array[_get_cyclic_index(i)] = _array[_get_cyclic_index(i + delete_count)]
length -= delete_count
# Add items
var i = length - 1
while i >= start:
_array[_get_cyclic_index(i + items.size())] = _array[_get_cyclic_index(i)]
i -= 1
for j in range(items.size()):
_array[_get_cyclic_index(start + j)] = items[j]
# Adjust length as needed
if length + items.size() > max_length:
var count_to_trim = (length + items.size()) - max_length
_start_index += count_to_trim
length = max_length
emit_signal("trimmed", count_to_trim)
else:
length += items.size()
# Trims a number of items from the start of the list.
# @param count The number of items to remove.
func trim_start(count: int) -> void:
if count > length:
count = length
_start_index += count
length -= count
emit_signal("trimmed", count)
func shift_elements(start: int, count: int, offset: int) -> void:
if count <= 0:
return
if start < 0 or start >= length:
self.push_error("start argument out of range")
if start + offset < 0:
self.push_error("cannot shift elements in list beyond index 0")
if offset > 0:
for i in range(count - 1, -1, -1):
set_el(start + i + offset, get_el(start + i))
var expand_list_by = (start + count + offset) - length
if expand_list_by > 0:
length += expand_list_by
while length > max_length:
length -= 1
_start_index += 1
emit_signal("trimmed", 1)
else:
for i in range(0, count):
set_el(start + i + offset, get_el(start + i))
func _get_cyclic_index(index: int) -> int:
return (_start_index + index) % max_length
# Wrapper for `push_error` so we can test for calls to this built-in function.
func push_error(message):
push_error(message)

View file

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

View file

@ -1,256 +0,0 @@
# Copyrigth (c) 2016 The xterm.js authors. All rights reserved
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
# The character sets supported by the terminal. These enable several languages
# to be represented within the terminal with only 8-bit encoding. See ISO 2022
# for a discussion on character sets. Only VT100 character sets are supported.
const CHARSETS = {
# DEC Special Character and Line Drawing Set.
# Reference: http:#vt100.net/docs/vt102-ug/table5-13.html
# A lot of curses apps use this if they see TERM=xterm.
# testing: echo -e "\e(0a\e(B"
# The xterm output sometimes seems to conflict with the
# reference above. xterm seems in line with the reference
# when running vttest however.
# The table below now uses xterm"s output from vttest.
"0": {
"`": "\u25c6", # "◆"
"a": "\u2592", # "▒"
"b": "\u2409", # "␉" (HT)
"c": "\u240c", # "␌" (FF)
"d": "\u240d", # "␍" (CR)
"e": "\u240a", # "␊" (LF)
"f": "\u00b0", # "°"
"g": "\u00b1", # "±"
"h": "\u2424", # "␤" (NL)
"i": "\u240b", # "␋" (VT)
"j": "\u2518", # "┘"
"k": "\u2510", # "┐"
"l": "\u250c", # "┌"
"m": "\u2514", # "└"
"n": "\u253c", # "┼"
"o": "\u23ba", # "⎺"
"p": "\u23bb", # "⎻"
"q": "\u2500", # "─"
"r": "\u23bc", # "⎼"
"s": "\u23bd", # "⎽"
"t": "\u251c", # "├"
"u": "\u2524", # "┤"
"v": "\u2534", # "┴"
"w": "\u252c", # "┬"
"x": "\u2502", # "│"
"y": "\u2264", # "≤"
"z": "\u2265", # "≥"
"{": "\u03c0", # "π"
"|": "\u2260", # "≠"
"}": "\u00a3", # "£"
"~": "\u00b7" # "·"
},
# British character set
# ESC (A
# Reference: http:#vt100.net/docs/vt220-rm/table2-5.html
"A": {
"#": "£"
},
# United States character set
# ESC (B
"B": null,
# Dutch character set
# ESC (4
# Reference: http:#vt100.net/docs/vt220-rm/table2-6.html
"4": {
"#": "£",
"@": "¾",
"[": "ij",
"\\": "½",
"]": "|",
"{": "¨",
"|": "f",
"}": "¼",
"~": "´"
},
# Finnish character set
# ESC (C or ESC (5
# Reference: http:#vt100.net/docs/vt220-rm/table2-7.html
"C": {
"[": "Ä",
"\\": "Ö",
"]": "Å",
"^": "Ü",
"`": "é",
"{": "ä",
"|": "ö",
"}": "å",
"~": "ü"
},
"5": {
"[": "Ä",
"\\": "Ö",
"]": "Å",
"^": "Ü",
"`": "é",
"{": "ä",
"|": "ö",
"}": "å",
"~": "ü"
},
# French character set
# ESC (R
# Reference: http:#vt100.net/docs/vt220-rm/table2-8.html
"R": {
"#": "£",
"@": "à",
"[": "°",
"\\": "ç",
"]": "§",
"{": "é",
"|": "ù",
"}": "è",
"~": "¨"
},
# French Canadian character set
# ESC (Q
# Reference: http:#vt100.net/docs/vt220-rm/table2-9.html
"Q": {
"@": "à",
"[": "â",
"\\": "ç",
"]": "ê",
"^": "î",
"`": "ô",
"{": "é",
"|": "ù",
"}": "è",
"~": "û"
},
# German character set
# ESC (K
# Reference: http:#vt100.net/docs/vt220-rm/table2-10.html
"K": {
"@": "§",
"[": "Ä",
"\\": "Ö",
"]": "Ü",
"{": "ä",
"|": "ö",
"}": "ü",
"~": "ß"
},
# Italian character set
# ESC (Y
# Reference: http:#vt100.net/docs/vt220-rm/table2-11.html
"Y": {
"#": "£",
"@": "§",
"[": "°",
"\\": "ç",
"]": "é",
"`": "ù",
"{": "à",
"|": "ò",
"}": "è",
"~": "ì"
},
# Norwegian/Danish character set
# ESC (E or ESC (6
# Reference: http:#vt100.net/docs/vt220-rm/table2-12.html
"E": {
"@": "Ä",
"[": "Æ",
"\\": "Ø",
"]": "Å",
"^": "Ü",
"`": "ä",
"{": "æ",
"|": "ø",
"}": "å",
"~": "ü"
},
"6": {
"@": "Ä",
"[": "Æ",
"\\": "Ø",
"]": "Å",
"^": "Ü",
"`": "ä",
"{": "æ",
"|": "ø",
"}": "å",
"~": "ü"
},
# Spanish character set
# ESC (Z
# Reference: http:#vt100.net/docs/vt220-rm/table2-13.html
"Z": {
"#": "£",
"@": "§",
"[": "¡",
"\\": "Ñ",
"]": "¿",
"{": "°",
"|": "ñ",
"}": "ç"
},
# Swedish character set
# ESC (H or ESC (7
# Reference: http:#vt100.net/docs/vt220-rm/table2-14.html
"H": {
"@": "É",
"[": "Ä",
"\\": "Ö",
"]": "Å",
"^": "Ü",
"`": "é",
"{": "ä",
"|": "ö",
"}": "å",
"~": "ü"
},
"7": {
"@": "É",
"[": "Ä",
"\\": "Ö",
"]": "Å",
"^": "Ü",
"`": "é",
"{": "ä",
"|": "ö",
"}": "å",
"~": "ü"
},
# Swiss character set
# ESC (=
# Reference: http:#vt100.net/docs/vt220-rm/table2-15.html
#/
"=": {
"#": "ù",
"@": "à",
"[": "é",
"\\": "ç",
"]": "ê",
"^": "î",
"_": "è",
"`": "ô",
"{": "ä",
"|": "ö",
"}": "ü",
"~": "û"
},
}
# The default character set, US.
const DEFAULT_CHARSET = CHARSETS["B"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,93 +0,0 @@
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

@ -1,6 +0,0 @@
[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

@ -1,6 +0,0 @@
[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

@ -1,6 +0,0 @@
[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

@ -1,6 +0,0 @@
[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

@ -1,93 +0,0 @@
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

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

@ -0,0 +1 @@
Subproject commit 20d57e6cf5d0353de0905f4edd3697b6de32bc74

View file

@ -0,0 +1,16 @@
[general]
singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=true
[entry]
X11.64="res://addons/godot_xterm/bin/x11/libgodotxtermnative.so"
Server.64="res://addons/godot_xterm/bin/x11/libgodotxtermnative.so"
[dependencies]
X11.64=[ ]
Server.64=[ ]

View file

@ -1,266 +0,0 @@
# Copyright (c) 2020 The GodotXterm 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
# Covert UTF32 char codes into a String.
# Basically the same as `char` but for multiple codepoints
# in a loop (which is a lot faster).
static func utf32_to_string(data: Array, start: int = 0, end: int = -1):
if end == -1:
end = data.size()
var result = ''
for i in range(start, end):
result += char(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

File diff suppressed because it is too large Load diff

@ -0,0 +1 @@
Subproject commit f70e37982f382b03c6939dac3d5f814450bda253

View file

@ -1,131 +0,0 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# 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

@ -1,77 +0,0 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# 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

@ -1,337 +0,0 @@
# Copyright (c) 2018 The xterm.js authers. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# 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, arg = null):
_esc_handlers[identifier(id, [0x30, 0x7e])] = [{'target': target,
'method': method, 'arg': arg}]
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:
print("EXEC: ", handler['method'])
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:
print("CSI: ", handler['method'])
# undefined or true means success and to stop bubbling
if handler['target'].call(handler['method'], params):
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
print("ESC: ", handler['method'])
if handler['arg']:
if handler['target'].call(handler['method'], handler['arg']) != false:
continue
else:
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

@ -1,136 +0,0 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# 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)
# Gets param at `index` from param if it exists and is non-zero.
# Otherwise returns `default` (which is zero anyway due to zero default
# mode (ZDM), but allows the caller to specify a non-zero default value).
func get_param(index: int, default = 0) -> int:
if index < params.size() and params[index]:
return params[index]
else:
return default
func add_param(value: int):
digit_is_sub = false
if length >= _max_length:
_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
# Add a sub parameter value.
# The sub parameter is automatically associated with the last parameter value.
# Thus it is not possible to add a subparameter without any parameter added yet.
# `Params` only stores up to `subParamsLength` sub parameters, any later
# sub parameter will be ignored.
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
# Whether parameter at index `idx` has sub parameters.
func has_sub_params(idx: int) -> bool:
return (sub_params_idx[idx] & 0xFF) - (sub_params_idx[idx] >> 8) > 0
func get_sub_params(idx: int):
var start = sub_params_idx[idx] >> 8
var end = sub_params_idx[idx] & 0xFF
if end - start > 0:
return sub_params.slice(start, end - 1)
else:
return null
func add_digit(value: int):
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

@ -1,26 +0,0 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# 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

@ -1,123 +0,0 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# 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

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

View file

@ -1,16 +1,17 @@
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
# License MIT
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
var terminal_script = preload("res://addons/godot_xterm/terminal.gdns")
var terminal_icon = preload("res://addons/godot_xterm/terminal_icon.svg")
add_custom_type("Terminal", "Control", terminal_script, terminal_icon)
var pseudoterminal_script = preload("res://addons/godot_xterm/pseudoterminal.gdns")
var pseudoterminal_icon = preload("res://addons/godot_xterm/pseudoterminal_icon.svg")
add_custom_type("Pseudoterminal", "Node", pseudoterminal_script, pseudoterminal_icon)
func _exit_tree():
remove_custom_type("Terminal")
pass
remove_custom_type("Psuedoterminal")

View file

@ -0,0 +1,8 @@
[gd_resource type="NativeScript" load_steps=2 format=2]
[ext_resource path="res://addons/godot_xterm/godotxtermnative.gdnlib" type="GDNativeLibrary" id=1]
[resource]
resource_name = "Terminal"
class_name = "Pseudoterminal"
library = ExtResource( 1 )

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg4716"
version="1.1"
width="16"
viewBox="0 0 16 16"
height="16">
<metadata
id="metadata4722">
<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></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4720">
<clipPath
id="clipPath826"
clipPathUnits="userSpaceOnUse">
<use
height="100%"
width="100%"
id="use828"
xlink:href="#g822"
y="0"
x="0" />
</clipPath>
</defs>
<g
clip-path="url(#clipPath826)"
id="g824">
<g
id="g822">
<path
style="fill:#e0e0e0;fill-opacity:0.99607999"
d="M 4,1 1,5 H 3 V 8 H 5 V 5 h 2 z m 7,0 V 4 H 9 l 3,4 3,-4 H 13 V 1 Z m -2,9 v 1 h 1 v 4 h 1 v -4 h 1 v -1 z m -4,0 v 2 1 2 H 6 V 13 H 7 8 V 12 10 H 6 Z m 1,1 h 1 v 1 H 6 Z"
id="path4714" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/pseudoterminal_icon.svg-50ba2514dae785a6b48b0da604cf3a09.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_xterm/pseudoterminal_icon.svg"
dest_files=[ "res://.import/pseudoterminal_icon.svg-50ba2514dae785a6b48b0da604cf3a09.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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,104 +0,0 @@
# Copyright (c) 2019 The xterm.js authors. All rights reserved.
# Ported to GDScript by the GodotXterm authors.
# License MIT
extends Reference
const Constants = preload("res://addons/godot_xterm/buffer/constants.gd")
const CursorStyle = Constants.CursorStyle
const UnderlineStyle = Constants.UnderlineStyle
const BellStyle = Constants.BellStyle
class TerminalOptions:
extends Reference
var cols: int = 80
var rows: int = 24
var cursor_blink: bool = false
var cursor_style = CursorStyle.BLOCK
# var cursor_width: int = 1
# var bell_sound: AudioStream = null
# var bell_style = BellStyle.NONE
# var draw_bold_text_in_bright_colors: bool = true
# var fast_scroll_modifier = "alt"
# var fast_scroll_sensitivity: int = 5
var font_family: Dictionary = {
# TODO
}
var font_size: int = 15
# var font_weight: String # TODO: Remove
# var font_weight_bold: String # TODO: Remove
var line_height: float = 1.0
# var link_tooltip_hover_duration: int # TODO: Remove
var letter_spacing: float = 0
# var log_level # TODO: implement
var scrollback: int = 1000
# var scroll_sensitivity: int = 1
var screen_reader_mode: bool = false
# var mac_option_is_meta: bool = false
# var mac_option_click_forces_selection: bool = false
# var minimum_contrast_ratio: float = 1
# var disable_stdin: bool = false
# var allow_proposed_api: bool = true
var allow_transparency: bool = false
var tab_stop_width: int = 8
# var colors: Dictionary = {
# 'black': Color(0, 0, 0)
# }
# var right_click_selects_word = "isMac" # TODO?
# var renderer_type = "canvas" # Remove?
var window_options: Dictionary = {
'set_win_lines': false,
}
var windows_mode: bool = false
# var word_separator: String = " ()[]{}',\""
var convert_eol: bool = true
# var term_name: String = "xterm"
# var cancel_events: bool = false
# Copies options from an `object` to itself.
func copy_from(object: Object):
for property in get_property_list():
if property.usage == PROPERTY_USAGE_SCRIPT_VARIABLE:
var p = object.get(property.name)
if p:
set(property.name, p)
var DEFAULT_OPTIONS = TerminalOptions.new()
signal option_changed
var options
func _init(options):
self.options = options
# Set the font size based on the font_size option
_resize_fonts()
func set_option(key: String, value) -> void:
# TODO: sanitize and validate options.
# Don't fire an option change event if they didn't change
if options[key] == value:
return
options[key] = value
emit_signal("option_changed", key)
# Update other options accordingly.
match key:
"font_size":
_resize_fonts()
func _resize_fonts():
for font in options.font_family.values():
font.size = options.font_size

View file

@ -0,0 +1,21 @@
#include "terminal.h"
#include "pseudoterminal.h"
extern "C" void GDN_EXPORT godot_gdnative_init(godot_gdnative_init_options *o)
{
godot::Godot::gdnative_init(o);
}
extern "C" void GDN_EXPORT godot_gdnative_terminate(godot_gdnative_terminate_options *o)
{
godot::Godot::gdnative_terminate(o);
}
extern "C" void GDN_EXPORT godot_nativescript_init(void *handle)
{
godot::Godot::nativescript_init(handle);
godot::register_class<godot::Terminal>();
godot::register_class<godot::Pseudoterminal>();
}

View file

@ -0,0 +1,156 @@
#include "pseudoterminal.h"
#include <pty.h>
#include <unistd.h>
#include <termios.h>
using namespace godot;
void Pseudoterminal::_register_methods()
{
register_method("_init", &Pseudoterminal::_init);
register_method("_ready", &Pseudoterminal::_ready);
register_method("put_data", &Pseudoterminal::put_data);
register_signal<Pseudoterminal>((char *)"data_received", "data", GODOT_VARIANT_TYPE_POOL_BYTE_ARRAY);
}
Pseudoterminal::Pseudoterminal()
{
}
Pseudoterminal::~Pseudoterminal()
{
}
void Pseudoterminal::_init()
{
pty_thread = std::thread(&Pseudoterminal::process_pty, this);
bytes_to_write = 0;
}
void Pseudoterminal::process_pty()
{
int fd;
char *name;
should_process_pty = true;
struct termios termios = {};
termios.c_iflag = IGNPAR | ICRNL;
termios.c_oflag = 0;
termios.c_cflag = B38400 | CRTSCTS | CS8 | CLOCAL | CREAD;
termios.c_lflag = ICANON;
termios.c_cc[VMIN] = 1;
termios.c_cc[VTIME] = 0;
pid_t pty_pid = forkpty(&fd, NULL, NULL, NULL);
if (pty_pid == -1)
{
ERR_PRINT(String("Error forking pty: {0}").format(Array::make(strerror(errno))));
should_process_pty = false;
return;
}
else if (pty_pid == 0)
{
/* Child */
char termenv[11] = {"TERM=xterm"};
putenv(termenv);
char colortermenv[20] = {"COLORTERM=truecolor"};
putenv(colortermenv);
char *shell = getenv("SHELL");
execvp(shell, NULL);
}
else
{
/* Parent */
while (1)
{
int ready = -1;
fd_set read_fds;
fd_set write_fds;
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
FD_SET(fd, &write_fds);
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
ready = select(fd + 1, &read_fds, &write_fds, NULL, &timeout);
if (ready > 0)
{
if (FD_ISSET(fd, &write_fds))
{
std::lock_guard<std::mutex> guard(write_buffer_mutex);
if (bytes_to_write > 0)
{
write(fd, write_buffer, bytes_to_write);
Godot::print(String("wrote {0} bytes").format(Array::make(bytes_to_write)));
bytes_to_write = 0;
}
}
if (FD_ISSET(fd, &read_fds))
{
std::lock_guard<std::mutex> guard(read_buffer_mutex);
int ret;
int bytes_read = 0;
bytes_read = read(fd, read_buffer, MAX_READ_BUFFER_LENGTH);
// TODO: handle error (-1)
if (bytes_read <= 0)
continue;
//while (1)
//{
// ret = read(fd, read_buffer, 1);
// if (ret == -1 || ret == 0)
// {
// break;
// }
// else
// {
// bytes_read += ret;
// }
//}
PoolByteArray data = PoolByteArray();
data.resize(bytes_read);
memcpy(data.write().ptr(), read_buffer, bytes_read);
emit_signal("data_received", PoolByteArray(data));
if (bytes_read > 0)
{
//Godot::print(String("read {0} bytes").format(Array::make(bytes_read)));
}
}
}
}
}
}
void Pseudoterminal::_ready()
{
}
void Pseudoterminal::put_data(PoolByteArray data)
{
std::lock_guard<std::mutex> guard(write_buffer_mutex);
bytes_to_write = data.size();
memcpy(write_buffer, data.read().ptr(), bytes_to_write);
}

View file

@ -0,0 +1,47 @@
#ifndef PSEUDOTERMINAL_H
#define PSEUDOTERMINAL_H
#include <Godot.hpp>
#include <Node.hpp>
#include <thread>
#include <mutex>
namespace godot
{
class Pseudoterminal : public Node
{
GODOT_CLASS(Pseudoterminal, Node)
public:
static const int MAX_READ_BUFFER_LENGTH = 1024;
static const int MAX_WRITE_BUFFER_LENGTH = 1024;
private:
std::thread pty_thread;
bool should_process_pty;
char write_buffer[MAX_WRITE_BUFFER_LENGTH];
int bytes_to_write;
std::mutex write_buffer_mutex;
char read_buffer[MAX_READ_BUFFER_LENGTH];
int bytes_to_read;
std::mutex read_buffer_mutex;
void process_pty();
public:
static void _register_methods();
Pseudoterminal();
~Pseudoterminal();
void _init();
void _ready();
void put_data(PoolByteArray data);
};
} // namespace godot
#endif // PSEUDOTERMINAL_H

View file

@ -0,0 +1,362 @@
#include "terminal.h"
#include <algorithm>
#include <Theme.hpp>
using namespace godot;
// Use xterm default for default color palette.
const uint8_t Terminal::default_color_palette[TSM_COLOR_NUM][3] = {
[TSM_COLOR_BLACK] = {0x00, 0x00, 0x00},
[TSM_COLOR_RED] = {0x80, 0x00, 0x00},
[TSM_COLOR_GREEN] = {0x00, 0x80, 0x00},
[TSM_COLOR_YELLOW] = {0x80, 0x80, 0x00},
[TSM_COLOR_BLUE] = {0x00, 0x00, 0x80},
[TSM_COLOR_MAGENTA] = {0x80, 0x00, 0x80},
[TSM_COLOR_CYAN] = {0x00, 0x80, 0x80},
[TSM_COLOR_LIGHT_GREY] = {0xc0, 0xc0, 0xc0},
[TSM_COLOR_DARK_GREY] = {0x80, 0x80, 0x80},
[TSM_COLOR_LIGHT_RED] = {0xff, 0x00, 0x00},
[TSM_COLOR_LIGHT_GREEN] = {0x00, 0xff, 0x00},
[TSM_COLOR_LIGHT_YELLOW] = {0xff, 0xff, 0x00},
[TSM_COLOR_LIGHT_BLUE] = {0x00, 0x00, 0xff},
[TSM_COLOR_LIGHT_MAGENTA] = {0xff, 0x00, 0xff},
[TSM_COLOR_LIGHT_CYAN] = {0x00, 0xff, 0xff},
[TSM_COLOR_WHITE] = {0xff, 0xff, 0xff},
[TSM_COLOR_FOREGROUND] = {0xff, 0xff, 0xff},
[TSM_COLOR_BACKGROUND] = {0x00, 0x00, 0x00},
};
static struct
{
Color col;
bool is_set;
} colours[16];
static void term_output(const char *s, size_t len, void *user)
{
}
static void write_cb(struct tsm_vte *vte, const char *u8, size_t len, void *data)
{
Terminal *term = static_cast<Terminal *>(data);
}
static int text_draw_cb(struct tsm_screen *con,
uint64_t id,
const uint32_t *ch,
size_t len,
unsigned int width,
unsigned int posx,
unsigned int posy,
const struct tsm_screen_attr *attr,
tsm_age_t age,
void *data)
{
Terminal *terminal = static_cast<Terminal *>(data);
if (age <= terminal->framebuffer_age)
return 0;
size_t ulen;
char buf[5] = {0};
if (len > 0)
{
char *utf8 = tsm_ucs4_to_utf8_alloc(ch, len, &ulen);
memcpy(terminal->cells[posy][posx].ch, utf8, ulen);
}
else
{
terminal->cells[posy][posx] = {};
}
memcpy(&terminal->cells[posy][posx].attr, attr, sizeof(tsm_screen_attr));
if (!terminal->sleep)
terminal->update();
return 0;
}
void Terminal::_register_methods()
{
register_method("_init", &Terminal::_init);
register_method("_ready", &Terminal::_ready);
register_method("_input", &Terminal::_input);
register_method("_draw", &Terminal::_draw);
register_method("write", &Terminal::write);
register_method("update_size", &Terminal::update_size);
//register_property<Terminal, int>("rows", &Terminal::rows, 24);
//register_property<Terminal, int>("cols", &Terminal::cols, 80);
}
Terminal::Terminal()
{
}
Terminal::~Terminal()
{
}
void Terminal::_init()
{
sleep = true;
if (tsm_screen_new(&screen, NULL, NULL))
{
ERR_PRINT("Error creating new tsm screen");
}
tsm_screen_set_max_sb(screen, 1000); // TODO: Use config var for scrollback size.
if (tsm_vte_new(&vte, screen, write_cb, this, NULL, NULL))
{
ERR_PRINT("Error creating new tsm vte");
}
update_color_palette();
if (tsm_vte_set_custom_palette(vte, color_palette))
{
ERR_PRINT("Error setting custom palette");
}
if (tsm_vte_set_palette(vte, "custom"))
{
ERR_PRINT("Error setting palette");
}
update_size();
}
void Terminal::_ready()
{
connect("resized", this, "update_size");
}
void Terminal::_notification(int what)
{
switch (what)
{
case NOTIFICATION_RESIZED:
Godot::print("resized!");
update_size();
break;
}
}
void Terminal::_input(Variant event)
{
}
void Terminal::_draw()
{
if (sleep)
return;
/* Draw the background */
draw_rect(get_rect(), get_color("Background", "Terminal"));
/* Draw the cell backgrounds */
// Draw the background first so subsequent rows don't overlap
// foreground characters such as y that may extend below the baseline.
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
draw_background(row, col, get_cell_colors(row, col).first);
}
}
/* Draw the cell foregrounds */
for (int row = 0; row < rows; row++)
{
for (int col = 0; col < cols; col++)
{
draw_foreground(row, col, get_cell_colors(row, col).second);
}
}
}
void Terminal::update_color_palette()
{
// Start with a copy of the default color palette
memcpy(color_palette, Terminal::default_color_palette, sizeof(Terminal::default_color_palette));
/* Generate color palette based on theme */
// Converts a color from the Control's theme to one that can
// be used in a tsm color palette.
auto set_pallete_color = [this](tsm_vte_color color, String theme_color) -> void {
Color c = get_color(theme_color, "Terminal");
uint32_t argb32 = c.to_ARGB32();
color_palette[color][0] = (argb32 >> (8 * 0)) & 0xff;
color_palette[color][1] = (argb32 >> (8 * 1)) & 0xff;
color_palette[color][2] = (argb32 >> (8 * 2)) & 0xff;
};
set_pallete_color(TSM_COLOR_BLACK, "Black");
set_pallete_color(TSM_COLOR_RED, "Red");
set_pallete_color(TSM_COLOR_GREEN, "Green");
set_pallete_color(TSM_COLOR_YELLOW, "Yellow");
set_pallete_color(TSM_COLOR_BLUE, "Blue");
set_pallete_color(TSM_COLOR_MAGENTA, "Magenta");
set_pallete_color(TSM_COLOR_CYAN, "Cyan");
set_pallete_color(TSM_COLOR_LIGHT_GREY, "Light Grey");
set_pallete_color(TSM_COLOR_DARK_GREY, "Dark Grey");
set_pallete_color(TSM_COLOR_LIGHT_RED, "Light Red");
set_pallete_color(TSM_COLOR_LIGHT_GREEN, "Light Green");
set_pallete_color(TSM_COLOR_LIGHT_YELLOW, "Light Yellow");
set_pallete_color(TSM_COLOR_LIGHT_BLUE, "Light Blue");
set_pallete_color(TSM_COLOR_LIGHT_MAGENTA, "Light Magenta");
set_pallete_color(TSM_COLOR_WHITE, "White");
set_pallete_color(TSM_COLOR_BACKGROUND, "Background");
set_pallete_color(TSM_COLOR_FOREGROUND, "Foreground");
}
void Terminal::draw_background(int row, int col, Color bgcolor)
{
/* Draw the background */
Vector2 background_pos = Vector2(col * cell_size.x, row * cell_size.y);
Rect2 background_rect = Rect2(background_pos, cell_size);
draw_rect(background_rect, bgcolor);
}
void Terminal::draw_foreground(int row, int col, Color fgcolor)
{
struct cell cell = cells[row][col];
/* Set the font */
Ref<Font> fontref = get_font("");
if (cell.attr.bold && cell.attr.italic)
{
fontref = get_font("Bold Italic", "Terminal");
}
else if (cell.attr.bold)
{
fontref = get_font("Bold", "Terminal");
}
else if (cell.attr.italic)
{
fontref = get_font("Italic", "Terminal");
}
else
{
fontref = get_font("Regular", "Terminal");
}
/* Draw the foreground */
if (cell.ch == nullptr)
return; // No foreground to draw
if (cell.attr.blink)
; // TODO: Handle blink
int font_height = fontref.ptr()->get_height();
Vector2 foreground_pos = Vector2(col * cell_size.x, row * cell_size.y + font_height);
draw_string(fontref, foreground_pos, cell.ch, fgcolor);
if (cell.attr.underline)
draw_string(fontref, foreground_pos, "_", fgcolor);
}
std::pair<Color, Color> Terminal::get_cell_colors(int row, int col)
{
struct cell cell = cells[row][col];
Color fgcol, bgcol;
float fr = 1, fg = 1, fb = 1, br = 0, bg = 0, bb = 0;
Ref<Font> fontref = get_font("");
/* Get foreground color */
if (cell.attr.fccode && palette.count(cell.attr.fccode))
{
fgcol = palette[cell.attr.fccode];
}
else
{
fr = (float)cell.attr.fr / 255.0;
fg = (float)cell.attr.fg / 255.0;
fb = (float)cell.attr.fb / 255.0;
fgcol = Color(fr, fg, fb);
if (cell.attr.fccode)
{
palette.insert(std::pair<int, Color>(cell.attr.fccode, Color(fr, fg, fb)));
}
}
/* Get background color */
if (cell.attr.bccode && palette.count(cell.attr.bccode))
{
bgcol = palette[cell.attr.bccode];
}
else
{
br = (float)cell.attr.br / 255.0;
bg = (float)cell.attr.bg / 255.0;
bb = (float)cell.attr.bb / 255.0;
bgcol = Color(br, bg, bb);
if (cell.attr.bccode)
{
palette.insert(std::pair<int, Color>(cell.attr.bccode, Color(br, bg, bb)));
}
}
if (cell.attr.inverse)
std::swap(bgcol, fgcol);
return std::make_pair(bgcol, fgcol);
}
// Recalculates the cell_size and number of cols/rows based on font size and the Control's rect_size
void Terminal::update_size()
{
sleep = true;
cell_size = get_font("Regular", "Terminal").ptr()->get_string_size("W");
rows = std::max(2, (int)floor(get_rect().size.y / cell_size.y));
cols = std::max(1, (int)floor(get_rect().size.x / cell_size.x));
Godot::print(String("resized_rows: {0}, resized_cols: {1}").format(Array::make(rows, cols)));
cells = {};
for (int x = 0; x < rows; x++)
{
Row row(cols);
for (int y = 0; y < cols; y++)
{
row[y] = empty_cell;
}
cells.push_back(row);
}
tsm_screen_resize(screen, cols, rows);
sleep = false;
update();
}
void Terminal::write(PoolByteArray data)
{
tsm_vte_input(vte, (char *)data.read().ptr(), data.size());
framebuffer_age = tsm_screen_draw(screen, text_draw_cb, this);
}

View file

@ -0,0 +1,73 @@
#ifndef TERMINAL_H
#define TERMINAL_H
#include <libtsm.h>
#include <Control.hpp>
#include <Font.hpp>
#include <Godot.hpp>
#include <map>
#include <vector>
namespace godot
{
class Terminal : public Control
{
GODOT_CLASS(Terminal, Control)
public:
struct cell
{
char ch[5];
struct tsm_screen_attr attr;
} empty_cell = {ch : {0, 0, 0, 0, 0}, attr : {}};
public:
typedef std::vector<std::vector<struct cell>> Cells;
typedef std::vector<struct cell> Row;
Cells cells;
protected:
tsm_screen *screen;
tsm_vte *vte;
private:
static const uint8_t default_color_palette[TSM_COLOR_NUM][3];
Vector2 cell_size;
std::map<int, Color> palette = {};
void update_size();
void update_color_palette();
std::pair<Color, Color> get_cell_colors(int row, int col);
void draw_background(int row, int col, Color bgcol);
void draw_foreground(int row, int col, Color fgcol);
public:
static void _register_methods();
Terminal();
~Terminal();
void _init();
void _ready();
void _notification(int what);
void _input(Variant event);
void _draw();
void write(PoolByteArray bytes);
int rows;
int cols;
bool sleep;
uint8_t color_palette[TSM_COLOR_NUM][3];
tsm_age_t framebuffer_age;
};
} // namespace godot
#endif // TERMINAL_H

View file

@ -1,390 +0,0 @@
# Copyright (c) 2020 The GodotXterm authors. All rights reserved.
# Copyright (c) 2014-2020 The xterm.js authors. All rights reserved.
# Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
# Ported to GDScript by the GodotXterm authors.
# Licese MIT
#
# Originally forked from (with the author's permission):
# Fabrice Bellard's javascript vt100 for jslinux:
# http://bellard.org/jslinux/
# Copyright (c) 2011 Fabrice Bellard
# The original design remains. The terminal itself
# has been extended to include xterm CSI codes, among
# other features.
#
# Terminal Emulation References:
# http://vt100.net/
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
# http://invisible-island.net/vttest/
# http://www.inwap.com/pdp10/ansicode.txt
# http://linux.die.net/man/4/console_codes
# http://linux.die.net/man/7/urxvt
tool
extends Control
const BufferService = preload("res://addons/godot_xterm/services/buffer_service.gd")
const CoreService = preload("res://addons/godot_xterm/services/core_service.gd")
const OptionsService = preload("res://addons/godot_xterm/services/options_service.gd")
const CharsetService = preload("res://addons/godot_xterm/services/charset_service.gd")
const InputHandler = preload("res://addons/godot_xterm/input_handler.gd")
const Const = preload("res://addons/godot_xterm/Constants.gd")
const Constants = preload("res://addons/godot_xterm/parser/constants.gd")
const Parser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd")
const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd")
const ColorManager = preload("res://addons/godot_xterm/color_manager.gd")
const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd")
const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd")
const SourceCodeProRegular = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres")
const SourceCodeProBold = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres")
const SourceCodeProItalic = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres")
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
const BLINK_INTERVAL = 0.6 # 600ms. The time between blinks.
# TODO: Move me somewhere else.
enum BellStyle {
NONE
}
signal output(data)
signal scrolled(ydisp)
export var cols = 80
export var rows = 24
# If set, terminals rows and cols will be automatically calculated based on the
# control's rect size and font_size.
export var auto_resize = false
export var cursor_blink = false
export var cursor_style = 'block'
export var cursor_width = 1
export var bell_sound: AudioStream = null # TODO Bell sound
export(BellStyle) var bell_style = BellStyle.NONE
export var draw_bold_text_in_bright_colors = true
export var fast_scroll_modifier = 'alt' # TODO Use scancode?
export var fast_scroll_sensitivity = 5
export var font_family: Dictionary = {
"regular": SourceCodeProRegular,
"bold": SourceCodeProBold,
"italic": SourceCodeProItalic,
"bold_italic": SourceCodeProBoldItalic,
}
export var font_size: int = 15
export var line_height = 1.0
export var link_tooltip_hover_duration = 500 # Not relevant?
export var letter_spacing = 0
export var log_level = 'info' # Not relevant?
export var scrollback = 1000
export var scroll_sensitivity = 1
export var screen_reader_mode: bool = false
export var mac_option_is_meta = false
export var mac_option_click_forces_selection = false
export var minimum_contrast_ratio = 1
export var disable_stdin = false
export var allow_proposed_api = true
export var allow_transparency = false
export var tab_stop_width = 8
export var colors: Dictionary = {
"black": Color("#2e3436"),
"red": Color("#cc0000"),
"green": Color("#4e9a06"),
"yellow": Color("#c4a000"),
"blue": Color("#3465a4"),
"magenta": Color("#75507b"),
"cyan": Color("#06989a"),
"white": Color("#d3d7cf"),
"bright_black": Color("#555753"),
"bright_red": Color("#ef2929"),
"bright_green": Color("#8ae234"),
"bright_yellow": Color("#fce94f"),
"bright_blue": Color("#729fcf"),
"bright_magenta": Color("#ad7fa8"),
"bright_cyan": Color("#34e2e2"),
"bright_white": Color("#eeeeec"),
}
export var right_click_selects_word = 'isMac' # TODO
export var renderer_type = 'canvas' # Relevant?
export var window_options = {
'set_win_lines': false
}
export var windows_mode = false
export var word_separator = " ()[]{}',\"`"
export var convert_eol = true
export var term_name = 'xterm'
export var cancel_events = false
var options_service
var decoder
var parser
var _buffer_service
var _core_service
var _charset_service
var _input_handler
var _render_service
var _color_manager
var _scaled_char_width
var _scaled_char_height
var _scaled_cell_width
var _scaled_cell_height
var _scaled_char_top
var _scaled_char_left
var _work_cell = CellData.new()
var _blink_on = false
var _time_since_last_blink = 0
func _ready():
var options = OptionsService.TerminalOptions.new()
options.copy_from(self)
options_service = OptionsService.new(options)
_buffer_service = BufferService.new(options_service)
_core_service = CoreService.new()
_charset_service = CharsetService.new()
# Register input handler and connect signals.
_input_handler = InputHandler.new(_buffer_service, _core_service, _charset_service, options_service)
_input_handler.connect("bell_requested", self, "bell")
_input_handler.connect("refresh_rows_requested", self, "_refresh_rows")
_input_handler.connect("reset_requested", self, "reset")
_input_handler.connect("scroll_requested", self, "scroll")
_input_handler.connect("windows_options_report_requested", self, "report_windows_options")
_color_manager = ColorManager.new()
_color_manager.set_theme(colors)
if auto_resize:
connect("resized", self, "_update_dimensions")
_update_dimensions()
func _refresh_rows(start_row = 0, end_row = 0):
# Not optimized, just draw
update()
func _input(event):
if event is InputEventKey and event.pressed:
var data = PoolByteArray([])
accept_event()
# TODO: Handle more of these.
if (event.control and event.scancode == KEY_C):
data.append(3)
elif event.unicode:
data.append(event.unicode)
elif event.scancode == KEY_ENTER:
data.append(ENTER)
elif event.scancode == KEY_BACKSPACE:
data.append(BACKSPACE_ALT)
elif event.scancode == KEY_ESCAPE:
data.append(27)
elif event.scancode == KEY_TAB:
data.append(9)
elif OS.get_scancode_string(event.scancode) == "Shift":
pass
elif OS.get_scancode_string(event.scancode) == "Control":
pass
else:
pass
#push_warning('Unhandled input. scancode: ' + str(OS.get_scancode_string(event.scancode)))
emit_signal("output", data)
func write(data, callback_target = null, callback_method: String = ''):
_input_handler.parse(data)
if callback_target and callback_method:
callback_target.call(callback_method)
func refresh(start = null, end = null) -> void:
pass
# Recalculates the character and canvas dimensions.
func _update_dimensions():
var char_width = 0
var char_height = 0
for font in options_service.options.font_family.values():
var size = font.get_string_size("W")
char_width = max(char_width, size.x)
char_height = max(char_height, size.y)
_scaled_char_width = char_width
_scaled_char_height = char_height
# Calculate the scaled cell height, if line_height is not 1 then the value
# will be floored because since line_height can never be lower then 1, there
# is a guarantee that the scaled line height will always be larger than
# scaled char height.
_scaled_cell_height = floor(_scaled_char_height * options_service.options.line_height)
# Calculate the y coordinate within a cell that text should draw from in
# order to draw in the center of a cell.
_scaled_char_top = 0 if options_service.options.line_height == 1 else \
round((_scaled_cell_height - _scaled_char_height) / 2)
# Calculate the scaled cell width, taking the letter_spacing into account.
_scaled_cell_width = _scaled_char_width + round(options_service.options.letter_spacing)
# Calculate the x coordinate with a cell that text should draw from in
# order to draw in the center of a cell.
_scaled_char_left = floor(options_service.options.letter_spacing / 2)
if auto_resize:
# Calculate cols and rows based on cell size
var rect: Rect2 = get_rect()
var cols = max(2, floor(rect.size.x / _scaled_cell_width))
var rows = max(1, floor(rect.size.y / _scaled_cell_height))
self.cols = cols
self.rows = rows
options_service.set_option("rows", rows)
options_service.set_option("cols", cols)
# Scroll the terminal down 1 row, creating a blank line.
# @param is_wrapped Whether the new line is wrapped from the previous line.
func scroll(erase_attr, is_wrapped: bool = false) -> void:
var buffer = _buffer_service.buffer
var new_line = buffer.get_blank_line(erase_attr, is_wrapped)
var top_row = buffer.ybase + buffer.scroll_top
var bottom_row = buffer.ybase + buffer.scroll_bottom
if buffer.scroll_top == 0:
# Determine whether the buffer is going to be trimmed after insertion.
var will_buffer_be_trimmed = buffer.lines.is_full
# Insert the line using the fastest method
if bottom_row == buffer.lines.length - 1:
if will_buffer_be_trimmed:
buffer.lines.recycle().copy_from(new_line.duplicate())
else:
buffer.lines.push(new_line.duplicate())
else:
buffer.lines.splice(bottom_row + 1, 0, [new_line.duplicate()])
# Only adjust ybase and ydisp when the buffer is not trimmed
if not will_buffer_be_trimmed:
buffer.ybase += 1
# Only scroll the ydisp with ybase if the user has not scrolled up
if not _buffer_service.is_user_scrolling:
buffer.ydisp += 1
else:
# When the buffer is full and the user has scrolled up, keep the text
# stable unless ydisp is right at the top
if _buffer_service.is_user_scrolling:
buffer.ydisp = max(buffer.ydisp - 1, 0)
else:
# scroll_top is non-zero which means no line will be going to the
# scrollback, instead we can just shift them in-place.
var scroll_region_height = bottom_row - top_row + 1 # as it's zero based
buffer.lines.shift_elements(top_row + 1, scroll_region_height - 1, -1)
buffer.lines.set_line(bottom_row, new_line.duplicate())
# Move the viewport to the bottom of the buffer unless the user is scrolling.
if not _buffer_service.is_user_scrolling:
buffer.ydisp = buffer.ybase
# Flag rows that need updating
# TODO
emit_signal("scrolled", buffer.ydisp)
func _process(delta):
_time_since_last_blink += delta
if _time_since_last_blink > BLINK_INTERVAL:
_blink_on = not _blink_on
_time_since_last_blink = 0
update()
func _draw():
# Draw the background and foreground
if _buffer_service == null:
return
var buffer = _buffer_service.buffer
var rows = _buffer_service.rows
for y in range(0, rows):
var row = y + buffer.ydisp
var line = buffer.lines.get_line(row)
for x in line.length:
line.load_cell(x, _work_cell)
# Background
# Get the background color
# TODO: handle inverse
var bg_color
if _work_cell.is_bg_rgb():
bg_color = AttributeData.to_color_rgb(_work_cell.get_bg_color())
elif _work_cell.is_bg_palette():
bg_color = _color_manager.colors.ansi[_work_cell.get_bg_color()]
else:
bg_color = _color_manager.colors.background
draw_rect(Rect2(x * _scaled_cell_width, y * _scaled_cell_height,
(cols - x) * _scaled_cell_width, 1 * _scaled_cell_height),
bg_color)
# Foreground
# Don't draw if cell is invisible
if _work_cell.is_invisible():
continue
# Don't draw if cell is blink and blink is off
if _work_cell.is_blink() and not _blink_on:
continue
# Get the foreground color
# TODO: handle inverse min contrast and draw bold in bright colors
# dim and maybe more!
var fg_color
if _work_cell.is_fg_default():
fg_color = _color_manager.colors.foreground
if _work_cell.is_fg_rgb():
fg_color = AttributeData.to_color_rgb(_work_cell.get_fg_color())
else:
fg_color = _color_manager.colors.ansi[_work_cell.get_fg_color()]
# Get font
var font: DynamicFont = options_service.options.font_family.regular
var is_bold = _work_cell.is_bold()
var is_italic = _work_cell.is_italic()
if is_bold and is_italic:
font = options_service.options.font_family.bold_italic
elif is_bold:
font = options_service.options.font_family.bold
elif is_italic:
font = options_service.options.font_family.italic
# TODO: set this once initially
font.size = options_service.options.font_size
draw_char(font,
Vector2(x * _scaled_cell_width + _scaled_char_left,
y * _scaled_cell_height + _scaled_char_top + _scaled_char_height / 2),
_work_cell.get_chars() if _work_cell.get_chars() else ' ', "", fg_color)
# Draw the cursor
# Draw selection

View file

@ -0,0 +1,8 @@
[gd_resource type="NativeScript" load_steps=2 format=2]
[ext_resource path="res://addons/godot_xterm/godotxtermnative.gdnlib" type="GDNativeLibrary" id=1]
[resource]
resource_name = "Terminal"
class_name = "Terminal"
library = ExtResource( 1 )

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

@ -2,15 +2,15 @@
importer="texture"
type="StreamTexture"
path="res://.import/icon.svg-b0b594198f5f4040e8f0e39a8a353265.stex"
path="res://.import/terminal_icon.svg-33ee6ad8b86db2f37e5d8d61a6b1b8db.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_xterm/icon.svg"
dest_files=[ "res://.import/icon.svg-b0b594198f5f4040e8f0e39a8a353265.stex" ]
source_file="res://addons/godot_xterm/terminal_icon.svg"
dest_files=[ "res://.import/terminal_icon.svg-33ee6ad8b86db2f37e5d8d61a6b1b8db.stex" ]
[params]

Binary file not shown.