mirror of
https://github.com/lihop/godot-xterm.git
synced 2025-05-05 04:34:23 +02:00
Remove gdscript version and replace with native
Former-commit-id: f9474fe533
This commit is contained in:
parent
f8412a03f5
commit
a022104230
126 changed files with 116 additions and 14221 deletions
359
addons/godot_xterm/.gitignore
vendored
Normal file
359
addons/godot_xterm/.gitignore
vendored
Normal 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
|
|
@ -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
|
0
addons/godot_xterm/Dockerfile
Normal file
0
addons/godot_xterm/Dockerfile
Normal file
1
addons/godot_xterm/LICENSE
Symbolic link
1
addons/godot_xterm/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
LICENSE
|
118
addons/godot_xterm/SConstruct
Normal file
118
addons/godot_xterm/SConstruct
Normal 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
2
addons/godot_xterm/bin/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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()]
|
|
@ -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
27
addons/godot_xterm/build.sh
Executable 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
|
|
@ -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
|
|
@ -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)
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
@ -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"]
|
BIN
addons/godot_xterm/fonts/Cousine-Bold.ttf
Normal file
BIN
addons/godot_xterm/fonts/Cousine-Bold.ttf
Normal file
Binary file not shown.
BIN
addons/godot_xterm/fonts/Cousine-BoldItalic.ttf
Normal file
BIN
addons/godot_xterm/fonts/Cousine-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
addons/godot_xterm/fonts/Cousine-Italic.ttf
Normal file
BIN
addons/godot_xterm/fonts/Cousine-Italic.ttf
Normal file
Binary file not shown.
BIN
addons/godot_xterm/fonts/Cousine-Regular.ttf
Normal file
BIN
addons/godot_xterm/fonts/Cousine-Regular.ttf
Normal file
Binary file not shown.
202
addons/godot_xterm/fonts/LICENSE.txt
Normal file
202
addons/godot_xterm/fonts/LICENSE.txt
Normal 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.
|
|
@ -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.
|
|
@ -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 )
|
Binary file not shown.
|
@ -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 )
|
Binary file not shown.
|
@ -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 )
|
Binary file not shown.
|
@ -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 )
|
Binary file not shown.
|
@ -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.
|
|
@ -1,3 +0,0 @@
|
|||
[gd_resource type="DynamicFont" format=2]
|
||||
|
||||
[resource]
|
Binary file not shown.
1
addons/godot_xterm/godot-cpp
Submodule
1
addons/godot_xterm/godot-cpp
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 20d57e6cf5d0353de0905f4edd3697b6de32bc74
|
16
addons/godot_xterm/godotxtermnative.gdnlib
Normal file
16
addons/godot_xterm/godotxtermnative.gdnlib
Normal 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=[ ]
|
|
@ -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
1
addons/godot_xterm/libtsm
Submodule
1
addons/godot_xterm/libtsm
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit f70e37982f382b03c6939dac3d5f814450bda253
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
8
addons/godot_xterm/pseudoterminal.gdns
Normal file
8
addons/godot_xterm/pseudoterminal.gdns
Normal 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 )
|
51
addons/godot_xterm/pseudoterminal_icon.svg
Normal file
51
addons/godot_xterm/pseudoterminal_icon.svg
Normal 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 |
34
addons/godot_xterm/pseudoterminal_icon.svg.import
Normal file
34
addons/godot_xterm/pseudoterminal_icon.svg.import
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
21
addons/godot_xterm/src/libgodotxtermnative.cpp
Normal file
21
addons/godot_xterm/src/libgodotxtermnative.cpp
Normal 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>();
|
||||
}
|
156
addons/godot_xterm/src/pseudoterminal.cpp
Normal file
156
addons/godot_xterm/src/pseudoterminal.cpp
Normal 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);
|
||||
}
|
47
addons/godot_xterm/src/pseudoterminal.h
Normal file
47
addons/godot_xterm/src/pseudoterminal.h
Normal 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
|
362
addons/godot_xterm/src/terminal.cpp
Normal file
362
addons/godot_xterm/src/terminal.cpp
Normal 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);
|
||||
}
|
73
addons/godot_xterm/src/terminal.h
Normal file
73
addons/godot_xterm/src/terminal.h
Normal 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
|
|
@ -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
|
8
addons/godot_xterm/terminal.gdns
Normal file
8
addons/godot_xterm/terminal.gdns
Normal 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 )
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -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]
|
||||
|
BIN
addons/godot_xterm/themes/default.theme
Normal file
BIN
addons/godot_xterm/themes/default.theme
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue