From f9474fe533daed999208eb66d2fcaf4de4761741 Mon Sep 17 00:00:00 2001 From: Leroy Hopson Date: Thu, 17 Sep 2020 15:14:04 +0700 Subject: [PATCH] Remove gdscript version and replace with native --- .gitmodules | 8 +- .travis.yml | 8 +- README.md | 6 +- .../.gitignore | 0 addons/godot_xterm/Constants.gd | 23 - .../Dockerfile | 0 .../LICENSE | 0 .../SConstruct | 0 .../bin/.gitignore | 0 addons/godot_xterm/buffer/attribute_data.gd | 168 -- addons/godot_xterm/buffer/buffer.gd | 430 ----- addons/godot_xterm/buffer/buffer_line.gd | 291 ---- addons/godot_xterm/buffer/buffer_reflow.gd | 198 --- addons/godot_xterm/buffer/buffer_set.gd | 69 - addons/godot_xterm/buffer/cell_data.gd | 68 - addons/godot_xterm/buffer/constants.gd | 106 -- .../build.sh | 2 +- addons/godot_xterm/char_data.gd | 19 - addons/godot_xterm/circular_list.gd | 185 -- addons/godot_xterm/color_manager.gd | 108 -- addons/godot_xterm/data/charsets.gd | 256 --- .../fonts/Cousine-Bold.ttf | Bin .../fonts/Cousine-BoldItalic.ttf | Bin .../fonts/Cousine-Italic.ttf | Bin .../fonts/Cousine-Regular.ttf | Bin .../fonts/LICENSE.txt | 0 .../godot_xterm/fonts/source_code_pro/OFL.txt | 93 - .../source_code_pro/source_code_pro_bold.tres | 6 - .../source_code_pro/source_code_pro_bold.ttf | Bin 191568 -> 0 bytes .../source_code_pro_bold_italic.tres | 6 - .../source_code_pro_bold_italic.ttf | Bin 155288 -> 0 bytes .../source_code_pro_italic.tres | 6 - .../source_code_pro_italic.ttf | Bin 161376 -> 0 bytes .../source_code_pro_regular.tres | 6 - .../source_code_pro_regular.ttf | Bin 192740 -> 0 bytes addons/godot_xterm/fonts/vt323/OFL.txt | 93 - .../fonts/vt323/vt323_regular.tres | 3 - .../godot_xterm/fonts/vt323/vt323_regular.ttf | Bin 149464 -> 0 bytes .../godot-cpp | 0 addons/godot_xterm/godotxtermnative.gdnlib | 16 + addons/godot_xterm/icon.svg | 14 - addons/godot_xterm/input/text_decoder.gd | 266 --- addons/godot_xterm/input_handler.gd | 1491 ----------------- .../libtsm | 0 addons/godot_xterm/parser/constants.gd | 131 -- addons/godot_xterm/parser/dcs_parser.gd | 77 - .../parser/escape_sequence_parser.gd | 337 ---- addons/godot_xterm/parser/params.gd | 136 -- addons/godot_xterm/parser/transition_table.gd | 26 - .../parser/vt500_transition_table.gd | 123 -- addons/godot_xterm/plugin.cfg | 4 +- addons/godot_xterm/plugin.gd | 15 +- .../pseudoterminal.gdns | 2 +- .../pseudoterminal_icon.svg | 0 ....import => pseudoterminal_icon.svg.import} | 6 +- .../renderer/canvas_rendering_context_2d.gd | 74 - .../renderer/character_joiner_registry.gd | 46 - addons/godot_xterm/services/buffer_service.gd | 53 - .../godot_xterm/services/charset_service.gd | 30 - addons/godot_xterm/services/core_service.gd | 27 - .../godot_xterm/services/options_service.gd | 104 -- .../src/libgodotxtermnative.cpp | 0 .../src/pseudoterminal.cpp | 0 .../src/pseudoterminal.h | 0 .../src/terminal.cpp | 0 .../src/terminal.h | 0 addons/godot_xterm/terminal.gd | 390 ----- .../terminal.gdns | 2 +- .../terminal_icon.svg | 0 .../terminal_icon.svg.import} | 6 +- addons/godot_xterm/themes/default.theme | Bin 0 -> 703 bytes .../godotxtermnative.gdnlib | 16 - addons/godot_xterm_native/plugin.cfg | 7 - addons/godot_xterm_native/plugin.gd | 17 - .../pseudoterminal_icon.svg.import | 34 - .../terminal_icon.svg.import | 34 - .../godot_xterm_native/themes/default.theme | Bin 709 -> 0 bytes addons/gut/GutScene.gd | 347 ---- addons/gut/GutScene.tscn | 299 ---- addons/gut/LICENSE.md | 22 - .../gut/double_templates/function_template.gd | 6 - .../gut/double_templates/script_template.gd | 36 - addons/gut/doubler.gd | 525 ------ addons/gut/gut.gd | 1343 --------------- addons/gut/gut_cmdln.gd | 366 ---- addons/gut/gut_plugin.gd | 12 - addons/gut/hook_script.gd | 35 - addons/gut/icon.png | Bin 320 -> 0 bytes addons/gut/logger.gd | 105 -- addons/gut/method_maker.gd | 211 --- addons/gut/one_to_many.gd | 38 - addons/gut/optparse.gd | 250 --- addons/gut/plugin.cfg | 7 - addons/gut/signal_watcher.gd | 166 -- addons/gut/source_code_pro.fnt | Bin 26499 -> 0 bytes addons/gut/spy.gd | 96 -- addons/gut/stub_params.gd | 43 - addons/gut/stubber.gd | 162 -- addons/gut/summary.gd | 152 -- addons/gut/test.gd | 1173 ------------- addons/gut/test_collector.gd | 241 --- addons/gut/thing_counter.gd | 43 - addons/gut/utils.gd | 160 -- demo.gif | Bin 32652 -> 0 bytes docker-compose.yml | 13 +- examples/asciicast/demo.cast | 61 + examples/terminal/Terminal.tscn | 6 +- project.godot | 5 +- scenes/demo.gd | 85 - scenes/demo.tscn | 55 - scenes/showcase.tscn | 39 - test/integration/test_terminal.gd | 102 -- test/test.tscn | 17 - test/test_utils.gd | 66 - test/unit/.test_input_handler.gd.swp | Bin 16384 -> 0 bytes test/unit/buffer/test_buffer.gd | 362 ---- test/unit/buffer/test_buffer_line.gd | 436 ----- test/unit/input/test_text_decoder.gd | 113 -- test/unit/parser/test_dcs_parser.gd | 100 -- .../parser/test_escape_sequence_parser.gd | 411 ----- test/unit/parser/test_parser.gd | 202 --- .../test_canvas_rendering_context_2d.gd | 35 - .../test_character_joiner_registry.gd | 38 - test/unit/test_circular_list.gd | 201 --- test/unit/test_input_handler.gd | 288 ---- test/unit/test_params.gd | 222 --- 126 files changed, 116 insertions(+), 14221 deletions(-) rename addons/{godot_xterm_native => godot_xterm}/.gitignore (100%) delete mode 100644 addons/godot_xterm/Constants.gd rename addons/{godot_xterm_native => godot_xterm}/Dockerfile (100%) rename addons/{godot_xterm_native => godot_xterm}/LICENSE (100%) rename addons/{godot_xterm_native => godot_xterm}/SConstruct (100%) rename addons/{godot_xterm_native => godot_xterm}/bin/.gitignore (100%) delete mode 100644 addons/godot_xterm/buffer/attribute_data.gd delete mode 100644 addons/godot_xterm/buffer/buffer.gd delete mode 100644 addons/godot_xterm/buffer/buffer_line.gd delete mode 100644 addons/godot_xterm/buffer/buffer_reflow.gd delete mode 100644 addons/godot_xterm/buffer/buffer_set.gd delete mode 100644 addons/godot_xterm/buffer/cell_data.gd delete mode 100644 addons/godot_xterm/buffer/constants.gd rename addons/{godot_xterm_native => godot_xterm}/build.sh (87%) delete mode 100644 addons/godot_xterm/char_data.gd delete mode 100644 addons/godot_xterm/circular_list.gd delete mode 100644 addons/godot_xterm/color_manager.gd delete mode 100644 addons/godot_xterm/data/charsets.gd rename addons/{godot_xterm_native => godot_xterm}/fonts/Cousine-Bold.ttf (100%) rename addons/{godot_xterm_native => godot_xterm}/fonts/Cousine-BoldItalic.ttf (100%) rename addons/{godot_xterm_native => godot_xterm}/fonts/Cousine-Italic.ttf (100%) rename addons/{godot_xterm_native => godot_xterm}/fonts/Cousine-Regular.ttf (100%) rename addons/{godot_xterm_native => godot_xterm}/fonts/LICENSE.txt (100%) delete mode 100644 addons/godot_xterm/fonts/source_code_pro/OFL.txt delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.ttf delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold_italic.tres delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold_italic.ttf delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.ttf delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres delete mode 100644 addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.ttf delete mode 100644 addons/godot_xterm/fonts/vt323/OFL.txt delete mode 100644 addons/godot_xterm/fonts/vt323/vt323_regular.tres delete mode 100644 addons/godot_xterm/fonts/vt323/vt323_regular.ttf rename addons/{godot_xterm_native => godot_xterm}/godot-cpp (100%) create mode 100644 addons/godot_xterm/godotxtermnative.gdnlib delete mode 100644 addons/godot_xterm/icon.svg delete mode 100644 addons/godot_xterm/input/text_decoder.gd delete mode 100644 addons/godot_xterm/input_handler.gd rename addons/{godot_xterm_native => godot_xterm}/libtsm (100%) delete mode 100644 addons/godot_xterm/parser/constants.gd delete mode 100644 addons/godot_xterm/parser/dcs_parser.gd delete mode 100644 addons/godot_xterm/parser/escape_sequence_parser.gd delete mode 100644 addons/godot_xterm/parser/params.gd delete mode 100644 addons/godot_xterm/parser/transition_table.gd delete mode 100644 addons/godot_xterm/parser/vt500_transition_table.gd rename addons/{godot_xterm_native => godot_xterm}/pseudoterminal.gdns (59%) rename addons/{godot_xterm_native => godot_xterm}/pseudoterminal_icon.svg (100%) rename addons/godot_xterm/{icon.svg.import => pseudoterminal_icon.svg.import} (66%) delete mode 100644 addons/godot_xterm/renderer/canvas_rendering_context_2d.gd delete mode 100644 addons/godot_xterm/renderer/character_joiner_registry.gd delete mode 100644 addons/godot_xterm/services/buffer_service.gd delete mode 100644 addons/godot_xterm/services/charset_service.gd delete mode 100644 addons/godot_xterm/services/core_service.gd delete mode 100644 addons/godot_xterm/services/options_service.gd rename addons/{godot_xterm_native => godot_xterm}/src/libgodotxtermnative.cpp (100%) rename addons/{godot_xterm_native => godot_xterm}/src/pseudoterminal.cpp (100%) rename addons/{godot_xterm_native => godot_xterm}/src/pseudoterminal.h (100%) rename addons/{godot_xterm_native => godot_xterm}/src/terminal.cpp (100%) rename addons/{godot_xterm_native => godot_xterm}/src/terminal.h (100%) delete mode 100644 addons/godot_xterm/terminal.gd rename addons/{godot_xterm_native => godot_xterm}/terminal.gdns (58%) rename addons/{godot_xterm_native => godot_xterm}/terminal_icon.svg (100%) rename addons/{gut/icon.png.import => godot_xterm/terminal_icon.svg.import} (67%) create mode 100644 addons/godot_xterm/themes/default.theme delete mode 100644 addons/godot_xterm_native/godotxtermnative.gdnlib delete mode 100644 addons/godot_xterm_native/plugin.cfg delete mode 100644 addons/godot_xterm_native/plugin.gd delete mode 100644 addons/godot_xterm_native/pseudoterminal_icon.svg.import delete mode 100644 addons/godot_xterm_native/terminal_icon.svg.import delete mode 100644 addons/godot_xterm_native/themes/default.theme delete mode 100644 addons/gut/GutScene.gd delete mode 100644 addons/gut/GutScene.tscn delete mode 100644 addons/gut/LICENSE.md delete mode 100644 addons/gut/double_templates/function_template.gd delete mode 100644 addons/gut/double_templates/script_template.gd delete mode 100644 addons/gut/doubler.gd delete mode 100644 addons/gut/gut.gd delete mode 100644 addons/gut/gut_cmdln.gd delete mode 100644 addons/gut/gut_plugin.gd delete mode 100644 addons/gut/hook_script.gd delete mode 100644 addons/gut/icon.png delete mode 100644 addons/gut/logger.gd delete mode 100644 addons/gut/method_maker.gd delete mode 100644 addons/gut/one_to_many.gd delete mode 100644 addons/gut/optparse.gd delete mode 100644 addons/gut/plugin.cfg delete mode 100644 addons/gut/signal_watcher.gd delete mode 100644 addons/gut/source_code_pro.fnt delete mode 100644 addons/gut/spy.gd delete mode 100644 addons/gut/stub_params.gd delete mode 100644 addons/gut/stubber.gd delete mode 100644 addons/gut/summary.gd delete mode 100644 addons/gut/test.gd delete mode 100644 addons/gut/test_collector.gd delete mode 100644 addons/gut/thing_counter.gd delete mode 100644 addons/gut/utils.gd delete mode 100644 demo.gif create mode 100644 examples/asciicast/demo.cast delete mode 100644 scenes/demo.gd delete mode 100644 scenes/demo.tscn delete mode 100644 scenes/showcase.tscn delete mode 100644 test/integration/test_terminal.gd delete mode 100644 test/test.tscn delete mode 100644 test/test_utils.gd delete mode 100644 test/unit/.test_input_handler.gd.swp delete mode 100644 test/unit/buffer/test_buffer.gd delete mode 100644 test/unit/buffer/test_buffer_line.gd delete mode 100644 test/unit/input/test_text_decoder.gd delete mode 100644 test/unit/parser/test_dcs_parser.gd delete mode 100644 test/unit/parser/test_escape_sequence_parser.gd delete mode 100644 test/unit/parser/test_parser.gd delete mode 100644 test/unit/renderer/test_canvas_rendering_context_2d.gd delete mode 100644 test/unit/renderer/test_character_joiner_registry.gd delete mode 100644 test/unit/test_circular_list.gd delete mode 100644 test/unit/test_input_handler.gd delete mode 100644 test/unit/test_params.gd diff --git a/.gitmodules b/.gitmodules index d633757..87938ad 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "addons/godot_xterm_native/godot-cpp"] - path = addons/godot_xterm_native/godot-cpp +[submodule "addons/godot_xterm/godot-cpp"] + path = addons/godot_xterm/godot-cpp url = https://github.com/lihop/godot-cpp -[submodule "addons/godot_xterm_native/libtsm"] - path = addons/godot_xterm_native/libtsm +[submodule "addons/godot_xterm/libtsm"] + path = addons/godot_xterm/libtsm url = https://github.com/Aetf/libtsm diff --git a/.travis.yml b/.travis.yml index 5527a51..722f934 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,13 +5,11 @@ services: jobs: include: - - name: "Run tests" - env: SERVICE=tests - - name: "Build native on Arch Linux" + - name: "Build on Arch Linux" env: SERVICE=build-archlinux - - name: "Build native on NixOS" + - name: "Build on NixOS" env: SERVICE=build-nixos - - name: "Build native on Ubuntu" + - name: "Build on Ubuntu" env: SERVICE=build-ubuntu script: docker-compose run $SERVICE diff --git a/README.md b/README.md index 87e17d5..77fd191 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ Native implementation of Godot Xterm using GDNative with [libtsm](https://github.com/Aetf/libtsm). +Terminal emulator for Godot using GDNative and [libtsm](https://github.com/Aetf/libtsm). + +**Note**: If you are looking for the purely gdscript version of this plugin it was too buggy and slow so is no longer being developed or maintained but can found [here](https://github.com/lihop/godot-xterm/tree/gdscript-unmaintained). + ## Demo Click the thumbnail to watch a demo video on youtube: @@ -22,4 +26,4 @@ You are also implicitly verifying that all code is your original work, or unorig Copyright (c) 2020 Leroy Hopson (MIT License) The fonts used in this project are published under a seperate license. -See: [addons/godot_xterm_native/fonts/LICENSE.txt](addons/godot_xterm_native/fonts/LICENSE.txt). +See: [addons/godot_xterm/fonts/LICENSE.txt](addons/godot_xterm/fonts/LICENSE.txt). diff --git a/addons/godot_xterm_native/.gitignore b/addons/godot_xterm/.gitignore similarity index 100% rename from addons/godot_xterm_native/.gitignore rename to addons/godot_xterm/.gitignore diff --git a/addons/godot_xterm/Constants.gd b/addons/godot_xterm/Constants.gd deleted file mode 100644 index 210c4b8..0000000 --- a/addons/godot_xterm/Constants.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm_native/Dockerfile b/addons/godot_xterm/Dockerfile similarity index 100% rename from addons/godot_xterm_native/Dockerfile rename to addons/godot_xterm/Dockerfile diff --git a/addons/godot_xterm_native/LICENSE b/addons/godot_xterm/LICENSE similarity index 100% rename from addons/godot_xterm_native/LICENSE rename to addons/godot_xterm/LICENSE diff --git a/addons/godot_xterm_native/SConstruct b/addons/godot_xterm/SConstruct similarity index 100% rename from addons/godot_xterm_native/SConstruct rename to addons/godot_xterm/SConstruct diff --git a/addons/godot_xterm_native/bin/.gitignore b/addons/godot_xterm/bin/.gitignore similarity index 100% rename from addons/godot_xterm_native/bin/.gitignore rename to addons/godot_xterm/bin/.gitignore diff --git a/addons/godot_xterm/buffer/attribute_data.gd b/addons/godot_xterm/buffer/attribute_data.gd deleted file mode 100644 index 5198725..0000000 --- a/addons/godot_xterm/buffer/attribute_data.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/buffer/buffer.gd b/addons/godot_xterm/buffer/buffer.gd deleted file mode 100644 index 3c7ce4f..0000000 --- a/addons/godot_xterm/buffer/buffer.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/buffer/buffer_line.gd b/addons/godot_xterm/buffer/buffer_line.gd deleted file mode 100644 index b7d49ac..0000000 --- a/addons/godot_xterm/buffer/buffer_line.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/buffer/buffer_reflow.gd b/addons/godot_xterm/buffer/buffer_reflow.gd deleted file mode 100644 index 1a55143..0000000 --- a/addons/godot_xterm/buffer/buffer_reflow.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/buffer/buffer_set.gd b/addons/godot_xterm/buffer/buffer_set.gd deleted file mode 100644 index 63f0cdf..0000000 --- a/addons/godot_xterm/buffer/buffer_set.gd +++ /dev/null @@ -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) diff --git a/addons/godot_xterm/buffer/cell_data.gd b/addons/godot_xterm/buffer/cell_data.gd deleted file mode 100644 index b2ec880..0000000 --- a/addons/godot_xterm/buffer/cell_data.gd +++ /dev/null @@ -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()] diff --git a/addons/godot_xterm/buffer/constants.gd b/addons/godot_xterm/buffer/constants.gd deleted file mode 100644 index 00fc648..0000000 --- a/addons/godot_xterm/buffer/constants.gd +++ /dev/null @@ -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 -} diff --git a/addons/godot_xterm_native/build.sh b/addons/godot_xterm/build.sh similarity index 87% rename from addons/godot_xterm_native/build.sh rename to addons/godot_xterm/build.sh index 07275d3..d51ba09 100755 --- a/addons/godot_xterm_native/build.sh +++ b/addons/godot_xterm/build.sh @@ -1,7 +1,7 @@ #! /usr/bin/env nix-shell #! nix-shell -i bash --pure -p binutils.bintools cmake scons -# Make sure we are in the addons/godot_xterm_native directory +# Make sure we are in the addons/godot_xterm directory cd ${BASH_SOURCE%/*} # Initialize godot-cpp diff --git a/addons/godot_xterm/char_data.gd b/addons/godot_xterm/char_data.gd deleted file mode 100644 index d65d8a7..0000000 --- a/addons/godot_xterm/char_data.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/circular_list.gd b/addons/godot_xterm/circular_list.gd deleted file mode 100644 index 4f6f191..0000000 --- a/addons/godot_xterm/circular_list.gd +++ /dev/null @@ -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) diff --git a/addons/godot_xterm/color_manager.gd b/addons/godot_xterm/color_manager.gd deleted file mode 100644 index 8e2ceb9..0000000 --- a/addons/godot_xterm/color_manager.gd +++ /dev/null @@ -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() - - - diff --git a/addons/godot_xterm/data/charsets.gd b/addons/godot_xterm/data/charsets.gd deleted file mode 100644 index a429cb7..0000000 --- a/addons/godot_xterm/data/charsets.gd +++ /dev/null @@ -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"] diff --git a/addons/godot_xterm_native/fonts/Cousine-Bold.ttf b/addons/godot_xterm/fonts/Cousine-Bold.ttf similarity index 100% rename from addons/godot_xterm_native/fonts/Cousine-Bold.ttf rename to addons/godot_xterm/fonts/Cousine-Bold.ttf diff --git a/addons/godot_xterm_native/fonts/Cousine-BoldItalic.ttf b/addons/godot_xterm/fonts/Cousine-BoldItalic.ttf similarity index 100% rename from addons/godot_xterm_native/fonts/Cousine-BoldItalic.ttf rename to addons/godot_xterm/fonts/Cousine-BoldItalic.ttf diff --git a/addons/godot_xterm_native/fonts/Cousine-Italic.ttf b/addons/godot_xterm/fonts/Cousine-Italic.ttf similarity index 100% rename from addons/godot_xterm_native/fonts/Cousine-Italic.ttf rename to addons/godot_xterm/fonts/Cousine-Italic.ttf diff --git a/addons/godot_xterm_native/fonts/Cousine-Regular.ttf b/addons/godot_xterm/fonts/Cousine-Regular.ttf similarity index 100% rename from addons/godot_xterm_native/fonts/Cousine-Regular.ttf rename to addons/godot_xterm/fonts/Cousine-Regular.ttf diff --git a/addons/godot_xterm_native/fonts/LICENSE.txt b/addons/godot_xterm/fonts/LICENSE.txt similarity index 100% rename from addons/godot_xterm_native/fonts/LICENSE.txt rename to addons/godot_xterm/fonts/LICENSE.txt diff --git a/addons/godot_xterm/fonts/source_code_pro/OFL.txt b/addons/godot_xterm/fonts/source_code_pro/OFL.txt deleted file mode 100644 index 6f4c937..0000000 --- a/addons/godot_xterm/fonts/source_code_pro/OFL.txt +++ /dev/null @@ -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. diff --git a/addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres b/addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres deleted file mode 100644 index 6a95e02..0000000 --- a/addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres +++ /dev/null @@ -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 ) diff --git a/addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.ttf b/addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.ttf deleted file mode 100644 index c790e045bfef5a65717c0a794781478f7e3b138b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191568 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%0AHdD*D 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�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 diff --git a/addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres b/addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres deleted file mode 100644 index 033a34e..0000000 --- a/addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres +++ /dev/null @@ -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 ) diff --git a/addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.ttf b/addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.ttf deleted file mode 100644 index 91a2a44fd31e05a9e439b630cff6b253ec9cb033..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161376 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.<-*=3?2 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 j}y%)NR=oMF; zK4%kMUEs#~(zdbKfWI&t3KarfrLt%9HsZzS0t8c7$>rMf%5#4k?YTC0VncgAvbx)` z_v0VBczCq1>dI^G+S|~cZGf|+JvX&OSs7fZx5u$2sA$I7b348dLBt_T#gHPxg`NK^ zdh<5u&B5wy8hW#55_+?n^yVD&=A1hx+U(K7DCy0fY%NfmCcW978cNLW4ygQtiS&5R zX^q!nBcwNbA~|1Xy5wdjyXFd&KBGV5btR!{eXT2y3wX29pvga1_2>LtZ%%@jy8#h%pR z)NwNNso=XOHA3pl;Ns*n_bLqgYBa67Su4SEC=GAP??CsU+-B$*`J;ZtQ_qXQm zl6dp|$k|?{xDhr;XM;zyUPlHe&de4$k$$`Aya4^;vjB)1>|Us_&59c!B}E!Ov>`zB_4^3}yL~;&W#BF2~4XA`FnTV;&>kC=K znMf#}E>FTonTnWA{!rZ28aklWyD-3Rm17v2V_z1XBu@s=YQ#cNY68t7L@mF`f`8Ki0)tN}|XWj<(_-)22% z$3FVW;ln?{F8UlhfT{^!Kvc--A40^Ua5@eXWDx$(fuD^`G7h}KK>G^j3)&)D%#Fx| z>}w|fyGGs%Y&DKS?p;^Z56pFRbUFM*c1}8%oE^*=GC2QO6~y_+L^XbdcYhUUK1=VW zI-K-w!ZnFtpf1{SLh-9(&stvTOyv);bA&CzW0=dCd@g66MRei_T8pR;9}~{vt!MGp zn-+T5`HJ@dKhIit{OHqk-$IX2L`xhkA3FN90>3EE!j&oBVCS(LF=7XYh7rab8nF>0 zc5r+JG#Y_Sau8Fz&1%E(tLKc(Q5gGfL#l+!fIIX z6|mz2+nz=?9Icp=!yEX6!^a&O2P>SX|FZwaE#03P`9%2MdkBU%DJtyU>}&G<@Ne-I zMr0(8K+!ymd(-FYE4bJHnGwuvTG1^S*wcW(`5Xf0a|qy+EMz@?4Xy9yt?%ZoTUizv z_6LPFa}TNmGem3+-qZ+nb@I7J z7jXxJ9Csmm9PF&UHXG=ki1n=slqdYoe8gUix#;txnf~NN(K9nZvEzA*SPUcz^qHh6 z$9oi)um{-(0aY41^+-FoY6_X|VTMbXzWztzA2%z`W>2!4aF+&{DdjUrV?&e?aD%S{ zo{5($;>|4fmuH_nn?>rcr2h@Seo%24d!F6fq!Drt6~vd=K6s~a=VbyD>pv3E%Xy0P z*iYG4aA!MTW7MbN5Wsl&-O+OVyoZE`>OXqr)bpTj*T1SvK2JD$ui^lD_!OSAHRm9m z8^gF6w`wY@ZR&dk7OwyJ02{qic_+>3HpK?Sqe;D$S9p%9#^dc=w%0VjA!dfR5sc`q z)oT1UfyNSl?zs)@@I%T6r%yeV&-z4+>3#B;1U#jLF^~2SRj(i}R|(maEPW>%x${IO z3PSxy90KbAfsZyJ09hFjC+JG|$EzYsKZI`BsjsYKV@hTHtLvWUy&Yipa|kTi)zM0{ zY=Zsv$1E%yz_ja6qK|iB-d~aDoy5E)8X(P9DuhY1mA=tz=^Ogz?}}G6?6LY+*_e=g zZo`Ahhw3*>zj7+x*}@M8sI4?lnlEhfO?+zUkCKB7cygVu6tP+(C7*odNTWxbiO*N0 zAEYbZqEI{|Y-6Qmd%pr83frDJ35IgMIJqWUqF$ zangMfE>ji>f*cW24rYX&2-@@v4CNY+p1rECYv}X6{r&G67{DDDAgXASaQ3o(EV%yy zEqhTXjF!t7kGS9vKCtivMnmHv)4cTVrq8{HSlU8ZtrM{)l%3+sde&Ijz`dy}sP#-^4y06h#f+`+a@&Sy8DKNq>>gCk@24?lX^j=SD} zPA z-~adP_uV-fm)?3dz3t+KlZ1ZZ8!{cB9ZLB5IB@1GDZ8{U-~5u689#SZRw-ovemC`f8v35b z(`8hHd>pX^RB?^^HsD(){{}r0vN)N|V`!zAL^)DavX_ZuFQae_9u-6G1;`ZmzZ_`- zaKi7zdiq98Hj~!Zo!002BSxi8*J0W`dO*7|Go))bw0GpQVXx6*%%;}sZc69flT#yB zz0=ZBGT42F!g1yKzHF|L-kB>_+g0j>+c_~kYWMpPp`g%W_CIZozcqU&esl5h$MG$} z$FGM*AxDYP$pg}$8;35Cn;F5PQfe})DU3|Nut!)Wd=u;3)am4kLOkzDNtzccGd=s$OugeS<$=H7f>mQ$?6>lo2ysn{=750WA<$bC$ax)kX{>RTt& zG)d-xWJ{8gCVox$imUB6=W@=^>hD{VI{sYW$7d3iFMYY1T79qccIxS~3-{yv@Mkfy zsLU~?3_!|8YlAdL*M=RGY9Ko-cr(3T6IXua{P%ZW=^2Z>uk*^&)(Io(G4)+{;p6C2 zc<)ZUSBdx9AZ9@HaMc_SM;os>XV8!cLudgdRz zdku|2^OSg%{;EKd52W$+D)+iO)njcBKdg~Pqt`)fe-FVP!%ocSsb)431!l z7W}nQyE_=_iCJ0N;O^>7#%w{aL$7ys8ZwbcjwFp8Sg{~%nR>`{P3>Eb#%O7$a#bJR zKsnc)q}MfM$TI4VpxksdyKYd-t=U(pY}H;j_mQ7DGGpnHJwt)|#`+7wt4D*Rh1`k# z_L%S+NZWVZKzi04N2s+A8nRco6Rjt|ms(H07~DwVMC&2w%{~-SorcGvSzTe7^|Mur znyMy9x-;21TXpOxvzf--jVl!(?Z=g(+-Gp5h@yGgLqlpFBGX2Pp2^`NswLX>JKwQ| zx#QWGI~wq$EMebd%#)8;J;_4S+f=^)`i^(}GBHp}XNmz|!r>_RLPjJ_#|KJ?gN!t_ z+dsbZEd;^zA_(T;@oMH$F0+yr$ZT4WY8&(bf+>O`V3PrCXjhWwM}lSnEmSY42Uj{P zJo=7MTfs_m>N^6V{;MTsm45~%$J%-%3`)uvMDuUj&zH@AB@9|VccQk0NrX8CTnTe} z{wd5^4uf~b6z2N>4Fc@%JAeaPz#s_Qce5(dGV7D<2ISVKxV(cJ)+cZqMBu3*p@q22 zLY1JTBRF{s#6Q?8e-L$J2CFR-aTH6rNnct#FeDCST>e~7F`}M!2gyEH_~ol}bK9+A zEaQt5xgp9T7X0!4d@|cDHj$mE)LPh~9)lff$t>hSx2$%{?NCZS@{nA|JB0P3*#(Ju zX<*5dveuerDQ?17Ej3E*|7Ye=|L5kx6kxtwLRE9Qn=~i^e26LlkX(BEH6)c@E_@?< zl**$kzK9Q!NTs`5Qf!DPtT1*3385bnZpCM=#*gu86yzXx>e|kpv*gxTxi;efg4s_jhN7D~`?+{I@nG>kh7CH%2b$4w8=D zkgekgE8lt_tG0jomV4X(J9S$|ma!o#<4q)G|B*}C(wmSQf)SIXymSndl0$Fm(ti5p zd$p|ZzO9*C(z3iwZ(@pH`$mi<{cRInBG5&ZSHm!`#4@o3iXuyc&|AYvE7A zr0^l=qjcs_GtPS%-)*@i7DrZ4v?E!EQMCiuOw=d`FSeA^Np(J`a12&b=Fbi}@^Pt- zyC_k?flLlLd!cOSir=pb^F98`u8UR`=BxhhT^EfH9$A-lWJgnjhu3CzysK|A+&2@C z~bAIwIr8_wTsjKej#p=)fZ7jxh)Ns^{@ltPChQ^VB6@gJ&-xoZc9nMq|y%FO%taf9*5Ofk8 z?qDN=59ggy)=CCkYZMYI!T`C3{y2pJf)kS#S%V%)7FY^Z%VCZr`4CQILGvp3tp;i> z@h8@h)RtgEW6e?EMw%hjNrk{F6-^a2tQzi3+Qp$wrBH9eL4nZmiIR7`Cq7a3ji2B* zcwAoN1&P(3xYr#{`P4xJ8*!xiBjwFQq9aY$K{4#>o=y%=`76`O!KpV_IgpBW-}A1Z zE84zhdYb6UG&>D3Zg)dZ_wZey$v=+WAyLK0K_eOB188kY-=V{XDP)lJA0@7o6-QUn z9(4FeE-b2dNO?^*zC)bg%@*u#q&h%7D?3nZ6)`&?`zT=;7d4u*y7zSaLASf|fuS*b zhbl8ZK3Mj8RHr7xDub7u#x%KH{r|Aa+3VMzU4QQClb(F?q^oJn8Fs300knxIR%R#G zhV*(1r(g|j;&F;hhqghh)QNQ?pECu^S!4}^>QE#w(j5MgOfiaU9{(szuEs}RrI9y6 z0mVNxB87Xv&rPL zm=j_u!vK;^n+PN-zZg(I^`z1-231!BGVX=bL6I*2_6_q$e+yPK3P=#Xw(`~=r^IM* z5n5MDtfnynWD;#Qq0u5b5tBSaK+57%_>!YR;}_)rE_B+xus3r87M`nCvT~EyJs@-QNJs+ zsmnhc@tl7+`QiBLQ>IUQ@ZyQB^A^!s|IdDZvb((HIFnm6c(XzCz-f2vrO+6+J>v>6 zZ&Yo&CCmo(ZG!S+gz=|?pi~Jj%^>SgNv6GGeCu9hT_ISap>TctICrx zt+p?xR{G|V6L9@arpoHR)3#LhtS_ty*;Bhc!4c6tw58%Sg(prJ-Fw?9wLo<)SAVtN zm#lPe+F@P2p>+I}n@>8^VavK*zY2ve*SbP!H%?Jii~vkgYhie*EK)K+-viyfu zgB;bxMb^b70nyNsN~FMQ!av!!23(`Mxkg;c%0N0H<(yJwRH7~9`gY(hbpk8yF#bp<%Bg!%w3mBqx0!?L_t@bB{z04G>~2 z+||`I`#cjq>mu_9lNs9$owtt0*Rv`2hlk?XyFQ+ck6-7wie!>=6+dJzFh6MQQad#H zsm?X9Rh>T^A194mz7Kw-r?_8f$=`T-VBs2As(uS!h#BV*uLcz)YipYCbF%snJtXTr z!qqwL>q1MK^CO~tPHH2##*F3I7$@^$ZFdOp}Z0uN~dz5JkLWn#G*iK*vcF0x! zi4?-vg$773ak=3CE2-4D-I^b&49}IatKf9*bavD-1Mz+<61D@YeMQk4=$=k=c?><- zS}u zQ-04&a?6+S)T_MZWx3^1-17!+`R<~7{>b0+J-OutJoOiT&wt7-e_mghmOmeJ;@b%iLGe4f8&xx2f<2ME)$dxh%pV%^%+r2u! zWh}pIb-oxIoQ>oLrBA}=y$fsEM-(r^dKCZ;wyf5qRcQIHr=3jMu9EE@=5&{P{rF)>v)N!@RRKE`vKE(mN@mkEo&EM!k3uH2IFT!d>gMyAw?5>^Yvm) zXe>BMM#ck3mXK*^RIrC(WL#%%%GV%9G?eBbB+Y`i!GZI4DjXfI&cp{|VQ;c9+hHpC zO1+L)$=2nub%uS`yfyoTE#51R>>CHo^RAmSyZdwDYRpzNI7M4PbT~DAh?!!FPhmYC z=6LVo97K|uNspE&DV?6^0J1}~r7X?+T-#=!)-km@HQ*}3fqvNeeqv9s;h7U+yf`;#^ zN|YRM0SGj*-JFYJzwQ|~dctOAa#+Q@J3mP1XY)DY-JbrS+ZT^HL*-7p&nQL{PxR$% zQNOcY)fIENl0IW4V+nce_%ji3r-P<9A}L=$^m?M-j0eq5b62}5=L|$-s`OEgx6(S2 z9z|;=SsUE^8_iMnV?#*fBju?n8ajN13$6}tfYs?;6%(%^#pcNjYexIu^uzz7ejEVeD4KJ!(4zl4z z{gc&hiuT51Bj$=ivi+`mt5QrqBSs5FRvzDLd*0+aK zj>6!t;%cM5{%wNmh4{D#b8%sRtuPa+6-OvZ(p{qz3b7ZdWJ1%#%GVGTcrMiBr}NIN zD{Trm#uKTMJz0y~stbp@Gf7+8Z4SD{RMMTxWL*X7`zpj%UWL9BQeNJ%P;9Xn4{j!6 zD~$q#9qzBtKV&xYeqP3G{yW`c_M759Um}&)WJ@lGHDPwyN5b)PI_8g9!#RD(5wbab z9+xu`a$7BCW6g_kQBw2NXbda$S(0GEn*I7M4r zTE7NEk_)N*zEaEtOjcVMcD?Sj)8?6L%NE^{fFYO;z;)r#XG3DjAasVUjy#NZe=le#&8p=#67FtD_L}qyxsWiR?_7HvbC?U&L77586+hh@#6V){e*XaFZ}V zPd#tRqC;=vHT<~jnyw-)}&B8p9K%YA-pvIb!j3bb2I&Ib3nXW@F5$8)9@PA(1= zZ$;lrXoN~Z+L@lzxX8(icEJoMY^=iHQQ3g4YtpiP`an{LaUJfs@ z1+T^9SZQfH-hOE%`#fy9$1JVCQuCL`qrQ|`;~YRzICJ)D9XVIf6tz0Dq7_R!I+|b8 za(gRqY`z4dZ3N^AefIc;c=N)1cmBiYFz zkYbzyG;p$L+95{5yNT1&2@M+p0+n%8GE;S(Zf#rVs#-8>@*4csSW>5YulBvYFg@9l zO8@A9Ss&BslR0~){%cz8>lH_d5c5k=_EIwI|!a?$tUD+LtP!XubOFDKNU-`xken(ytelm z-Ls>H)$hk?MGmtq{C8Vtvcs6jF;Bk!==7M-Wr1P|O%P*Zif6GVS*(d3G+ExFavmVo zgj{>D{W49We#tu`8U3LZ=%+;?AdNi6`I}$0F;&_a240|Gh z=s|gWEu4`XS|F4g7DuI0VldpD*s?BcR|=;h48ze$v|VN1!7)wJHVEfr#Y1!aa9*Wy z_4r}|wXe^c65A4eaaF=xaAkYrf-+?)x-)>nXwDH3UG8u?ZgD;L4XY{d_x|`B9me(| zt~7(kFas@SV1=D)h4FE_<7BX7m>n0VfFxiHZ8PV0=R2OkqPdqNUVQ6;!az%wzFN?ur(NQWqm<&gsIrG zQL|^fR$ANRvzU8NzMu5`Xa-!(Y{1wbz}SfzldRILbI>Bb%WzK`J1J*UCR|H+f$Wc2 zm>|4(I1NUA+#-9IBy7bO6JB=MVQ}{OyZcGj@+GX0wRWDCEd|YB#UQsB`Wd|@*XTfD zZPjnH1na%`r|xM=SxoU8V4N2tH@0MkWiVfMu*PB(L{I?mp}9{uR|8CcGn&__eADrO z+iq}}#zrE9dTZi2LrJY>J0HO+kqq!#^o-c`2Cgq=qz(GEB)WY+?vgV04 zve&i6@musw@{tf_mHP&plK8nSt}|;T*_rH5wA+J$vM)O@ftbXBd6 zvim_~8!^QV=w~zfv9;5Z)aTn2X41|Z9G=ekYPzf{- zc=|e&%Gl;gms%Cw&~yECr&5Tmp>aKY+;I=nzxtD(+Iie3@!C&gY#NNs4U34}vz!y% zf_)1H=#UN*hBvef`H8c{SWK#i{OPMFhXQK7T4}O(PyWlAp>Uf~C8#>|ovJ?efzRGB z(djdId=sCzW6}`Qs*NUN+Z1}goH4<}dvC71Y`19jE}zZFaj~&^FplPysV+SUu&wPY zW}nvHX6f{#%=(!@QPr+gbvX3<2EXl@U`(e;tJ^{$I~)7FHScufZJ)2-RzSr-rnnD~ zU>J+GwQpNk*m4QMRETXLNYsI(Bph#6*4)?rJ2*Zpn%{j($377HeLnm6*Q}r9{59~KG)=)GaFb_GWToh{trL3S8+_62o z=DdHb87SE);<@#c{;_(1@k%4V`hR-l=eSM}fAVcct`44*st4a@+3s-7M*XFz zEm)Zd#zqVew?nPwDwn!loypjY39HKzE(b^x%Xa5;6`;=d6n{nD^Yd^nL|T+*83B;f z5BQK9{$a|7AaQpo%JTp%jE$HFbXr8y!~&s!Z*^Z>t!opEZo`58FKczhh|!`^E6vWo z20h+df_d(-W}L3H^`83AT}ey2H{n4A5u*pLB-#ys!fx<#{gAvg4X-r71J!7G7K(-? zqKSedRTGQjHqn|FbCqN%$v*H%aJ&$z#Nh}Dx;@>=cz@vAn7?e@ftD<`_L^KZkx!RG z-Drgt7fIxng&tWMf^7|=D@lb79bSsLGDdP|UZ}Apf_+YlXFfZW(Z};j!9CU$?DiP9 zZFI5AVzK_=&S1`FNE+SK!(UI^`~|0Vy)Gk~&A|2Ru`+J(?mXf`=M+a86j841hq1}~ zRXzxh4|WKHC5cD!XHM{M1++!*jciO4a=mmDS_)hzrBm_90wtCGLzEirYe|BmwUtim zkfH2=7R}ACVmQv{SjNPqwU#wh4PSV%@jH9N-!qpRnT)^}?ew}*JwCVdT+Kw66Y{9f zskb=2$7f47i#;K?Dd_47==*0q{w>xnw>{6!;xcW-4cl{<*BLRmlYydWZcihUFBdd* zwA(uk2JfYrq&L**K*jaI-_kylUS;s1*gtG^zr(&eTX-6n{bNNLu^JS`u^aX~#ySKW z`+_h5_%4kGV&EXGLwAc#u;ocN{vMsrDvT_6*vsUc=bpMXr&NCsfMEqKMBF)`w@b@eKvCDyc&h?8AdoHGntC)J=(++p3m<`&@FvbdpeQHO9m(bQIN2_ME3_3T z)}e|BCW^!LBlgyed;J`rs3m|ZVj0Y?%3J7O${0b26DB~#J3$CVqU(Zap{S4$W-T4=-W`W)ZY5$x zI|>COkn$SR*tMJI*||DYU@!$E?8G` zA&^6h>QEb+zf%4pMG!U$=g=KgFq8yAH?FkO9zJJ^93+#-;$x99ReqYvw*rsozGQer z_lfh@^mo{GN_)X^>4yKlNBd>NW4cEIcP6KjZ2JDtR3tnTy1)J?{=Ads6h=<#iIC|W)s7;vBFZuG7eQIcQ-z% z0-J1DM9eX&vm?zHb88x>SZU{>IqjsNQjKcI&4cUqboZ}88P94;9PP_>F{`%zOUUPM z>3%UWmYt8qvxoGTorPd5bJ!GD4IGbho{5AQ_mrkH8B^Gt*|s+oEoRH$R3|LF0=vc4 zFeXQNrYXgYOHsz8w@6Z&8DqEdSxVMWnhV8uQlufpd=eBQ@--?)9$E}ai;SBjM$6HK zN=(*)nQ87Xq^CtxEUelmHzy{KE8Fw7URqPYta`~p3JKP+#wu~*x(pqC}jpFpRa;M%a&yOI0z*c zoo}@;22BB0AzUf&osLf-meq7k)55?42b^29HW)fq1xC4vK-u6mL|w5S`-Vfh>6F(H z$@ht&k}22`a^)&U@3s0HxBGVNwY2wWI~nVlV(n%1j@($nVmIX4jYHkrd+Vi-p4)rQKnPF{?vby*Q)~iM-Gf;&_-97Lwhi z?oi+3_I}!2|9$)>95Uja2>LvcNHUYp&L18MMgyvVJrs?3P&B#2Ww<9X5lp7`4F!Zb zcC>C(3MO^^FG4Car~pB+~~O!u0-!B!<91A3F8Qk#JwzXvl8%qEgLpO|#{W@L39E{*M~j-*ykntj)ZVfBTg5e+IiYi2Gp8)B&74&jD6VN= z(>dQhU!2dJRb+vV{rYbGe238czyo#Nm;UmXka{Ad{tNyWn~33)`6lo`iqo3`+%^Ml zcJMN>b@GO65pTh4F^?vdiVPHsGw=x?$lTnCf5fH2xEk<}&H-8|Hy38g=h%O;YW$qR`8{2)v3oP>dyJ93 zWLGgWnz8*&Sbb)Df4=^+p#dllkl_`=PI>(Un90W)t1p|9S~FR^`ZzC2KF3Q7X|Vn z0gXW%5v@FB3gQcQI+S@FRO%ZP!rQpi`}?)nBtswvSj>2S+x}n8!Cx?e5nO z@7wAe*yB;n_X^IZo~lRxRR1?t3M*N>{)2j*#p*wZQA7RT2LD9s+ynS*6mA8*NCQ?U zHlQQtEc3V#inJj$h7zR26m9_?j+0!Hm*=dqQHffSh3ae@^IJ`Fv zYQX(V9H-?B5z?(ePa8@NME=)@rX#hvWUAZR*?yb$R>PQKzJ2Z1l2YZH>GBN(L-tI_ ziuYnaK##|Fm&4*n4Mel$yE7TW3ZOV-nu}oX z3W6%66z^-y`)#B(THQx+KTXz)=_m1zc5oC|qLnF3R)uR4S2eCAkGIQL!gy*YCQA3w z*)vgG30QOu0gNOYQEo1s$V?kTqK&lGp@Hx{0k~jGfFSf#M9sw9oMw%Fu5I3V&HMq~ zD$R(X8E791T)qLD!|)#+6TRmSUYP&Ee`$ZBXYUEAhC}LiRYzl@H##SvK?Ruhebd5vu zA#H@#A7rhme{iMBVaM7_?mUg=$F|K|v+K{8u=U!q!}hYRJJqvuEIK|MpR|V0*MGGV z-;%SQzF&9wkuBGrJl1x?iESk@5sFrKUo^92A7c*VeM9v(eSXz`n%@{C*f@CMU`v7x zlAsV=@%$z_xru1MM5C!}C84zi*C6I3Nw7rSiQCaHgabs)^SIKMDBw!SUghg4RTAdn zE1X!InDZc|W~e0{6vD+=F3dutZ8=++ndveZT9df8Xxu>q*axp4dUXS*(bmian{ehPqNJdnFdk1bu~2#wr&5v6>@i z37T@&axk7wczaN(Tu^L-d^Iin3RExJ$`4*9<;(s6;6THXNHV?T3n2NIq5z2AFPD3v zVI!jJ9pv3l^;QmN=ler**7ds!8&4mxlq}^DN7+j}|*9l$i13S;H zAAf@G(j(iiJ9(sS&mMI(5)VdHN8eJXf~9y_eFCleF6{9c?D0Tr)#bt>Et=n|#6XE=w|RL$wH-6$&lqV1%ud|47t|z7w?~K1ORs6pE-8 z&6q@fqFf};(BC8@)A|zMBg~cp22rI4wG!zcF9d|bIaH_tra6kg)3mi6sGVrM(0H)x z;@Fzt6UOg#ebxA%g}yzL2^jm?DC+j8zN=@!RJ3O<9j$)l;~S#KuihBl{YU1?PvtdT zZF;p+e+BBZ!0(N9T#flU6qh#UE4w0E>)4V)C{q#VJ@Wa|E+sZZ^QG^N`Of~Q()^^-wSW_&bPb>N)NyrJzIn?O$Z868(v_DWD zsTKXgE5ZB4Uw{a~li(-8u|XRNk_}EtRv6-EB*;jKGT>x%tQ!6#L$xSg5WG@%W#{{J zR}J62=}>HP@4#jQ+%pS`;K=;u0}~T#K9N*Uq*Sbba;CDO*Ef;-1RLM5zB>cFAPr|7 z=3{D|^Yu2;DQP%r88138n@-FIK23QxB$d%@NGgRp0Ih@vuy+$jCZdfh5A>fTd2e*B z({9+wF6yj*JBfO03$BlK+~vL`r)JNMy)b$~`4S9l;pYmUuwOWZ>m7?@T&Y?p{cDfB z;=14UV$_%Zs_%C1b7{mkHl9JeV?V_^F8>>RY(FyC#L}|jH^P8 z-?xI#b@A-hJo1=Vp$gBrjkT5q+!lVxOc3;3rXw}tiZv-(m`tovG7i9m*OUB5(Lso_ z19*rPN@`*Ppc@0|3b@YVTETTKDKJebkHQkTty+@mH1VjT6(G3iM{vGYfcet)FQ!Wr zJC!Ne;yq5WyI>AFyL>wKy0PAoL5yuBkio&J6_;2kL@KOzdD9iWSvp{>l8f}Goyo!K zh|!<+jNIaTqFNuU;-|Eu2S25p$${SCp~^^dg*Lt(3c&v&!2hQUXD@>P&k6o-^ZRQG zr1Cd^r}g{U%FpxlUH<-`HJ)#-|7%O0U;6v<=YR1I&oBRd>G+oa{_`dMFa3Ra|F19U zpML+vlINShW86Qe`Cq`mjAxA|6fu6pe}UY&sO1L zNKtxhlPL75=H8QID_VO`RzF*NPj+ylVR2Ck3Jf*yz1M~v*APk?q$`X4mW8ka#T$E) z4t>O~1aNb;Tu3`-)jbLNu3x-a4vo+bB3y6c>QY+8aTOEUsyGBcdm2>P2Tw<%3P&TK zP|}yF7GSg@R?3yKt!xu*tn=ZAhaP_T&^Kz|!2d}$kSitQ`K3N33#y@RMfOOxh6)#o zgNlpU?*%LRqAIU#u-%enrvq21+9BDnNDiVaaW6Icm1`XG0*BY8Zo4gY>#h9r;>?|Q zra$(v^qqHRK1TZUR>eNdqlmXTcs_|#p@S-K!tR6j@nZbY9X$9>)*~v)FF7$eLu^|d zP#MRlS;p@7+ZX-&HKy?`uhZf6I-K76O^t8Q)oSpz-N)M56#FbI^+e=8I(fpgGRL!S$je|f&5$lekTf`{~DH4xT-|7ZrJ zvuOYk4Ui1*29gPAAx^163k4GrJaIqdEvywPk75(kbScV1iUBKQ zaj0*bMq_h0fL!%0>@zQ4dF9Lf{Wsq-b<6nbtSZa12^Wu}c`DrEalnX#chb~b@yg{#4HA|di<6tfI)@@?XE7^ zI%MuHpZCpSL9s;A5>o*iNs%E`B~?HzUuoe90Vua>@ix>Dno3*okL)uSK2&?^soFyq z_LKke4CZ5R2zNkoYUXUoh67&AQHeJzVCyZ=iixYPI}+2*IE0T8vj!N>zeV4`)}#Ln17%@Sf2$QyAJz1juFt} zNq&C9X*!q=J>%^}H^>S=S^`x-TBP5T6%!5}cDX;FjN2SZbICOwzqasrRC2MjikLTRVS|EPEIB3*B`2boHE5e#UW&R^x@r&Ja1a4)*LCv9x2OmYO+lv z0UmkQwtfDd*-Qo}+78W)jLaSSZvz3v6??)KiEI1;QgY_YIjN zUb8b<4aNsk4x=|}tBksET4Qp&;GWN?%b9#C)uSHH6h}&ZL*#M%5o(zKK)4UPHvqg_ zxdQ&NGPGQpfSc0Tc)H(}topNk=oIVN$NV(x@qbF+fM~&Wy z%?C#bV`26vdsui7HXYJg$hS#(c@&|cVc}J)!=voczwNmNvaI4c_NZVG9t3_K`ZTJj zDi3X==RJ7-3(e;nXDYBbq6OJSqv6%7*rT`X`5W$B`18V~5Mi&w_R4LJWFV$|VVPRA zsw}onDrsuH!1WFg5kH)i9h_wPLBdE^gLeVHc|b0ac@NGM#SEm10nj&tR^K$Q(9c5L4`l2p2cno?CB$PL@X_&|3#Ks2}j2`G${W`amc%#ilx-N+xpEDxRH^>+arpL4RblnlrkFGwF%EtEk_!q35{q zXf`x5vT@pOD|&s7?hXBWK7GmTNDw}{Sk_+}i|rHIzJ#+xJ7`}@yOt79 zX@q1P;({kHf62|<8cs!UEH-VvHv>kk#?+B^b{ac7e3g{J)uz=ulCI9KPMxp&4oyuE zHm&Ws1R!`DZKVA8?)(i2}ZJ5gj7?p~@AAwbYCr1g_f{u2e9-O>qaH_dyUX(mdD(o8_aFg(=P!y{SXF^9& z`cl-`v~2U1z1sRFc4F48Ix{Prc=WdHDC>UtW!kMj2SN|=x?<}ay~;MYm3x&G6^c-4 zxPYkk8u>v9ZzCLUNwmm-qfo?3BS1&O0UkfV4P`!_3vTsL~-l+GljXbCr}y; zuy^UdtqYHp1Ez3yxUzLfjQKl;Rl(C{=C3|H6dB!789Qa$q+2!I>zdeh>SO}t1bdEq z_xP&A!CpXMFJ^QaDp6~|-&Qx~BuiEdB^5HeaGbQGr0Zg@cqa@HU z1|JRlw2amj-dVd{IIVu$_Vw$mZyu_@sPdLQwSVnU>x2&zxa;}lmNr`0a8Z9#l|rACXANl0ktsn zF@Op=)tpBs02c`wB$t!Xkh}`S&w?CAq-Dz`Cjy9#%wrL8ECSKygW0 zscE>-Skhkz8q@|~m))7S_v$10s=rVXC-#jf+Xa)~94^|t{yA%A(_XvPSJqooBbjzn zd%4rw#VmaryIn)o>u+)m$mvLgH z9VvxQHRR>Sdm5i>11WM7)ze_G>^sprg?c!51pAAg~XX;n;{ zd2)m@SV9YLAeN>J&(i5aDsVEgqfqq)73U{vlO~Q+FEnuz(?6M!pLDS&UPv%w{db8M z>fuL-^OmmJNB~^&*C^DkNOxqkwy-$ z4<9*cBWgZ!HAb$($n|mya8JS<@a;wT(|Rf4(3xys{UBg*=>^rh- z2O|ph1Yt64M8IX7j8cJb5Ew0-_+a|d`s}6Y2XSQ&Uz)C~UW$C=BaxS+3!dAu@UMa( z6!0cGe+xLqM@&16Jhv1l-1tUgmfENW27SZmMX(O+Jp_nh?*aXDOWxo2#q=fY3sUd( z^_Qfv-v$=Whopfc7@#pPwVXP@hvUb*Z-Bk}ndJ0=UCmUNexpNbUzN~CO2(qIE$wgOSLDmjY~VBYO01zkk|2s8hd}aeiPePzxO0|eW8A4Pn_+-eIdmh zDD*ygtdR5hSfg5jW$J%o9js%HKI(}4qEiG9AtPp! z?MFW)uLoOr1J8^teP(%|!pPB23EPFnSe2-%bwDr+Ibfm#k%c0+CJ{E0%qJPzsn#0l zfQA?G59LT`M@iCdL+kSk)nj(}6np;l;zpt#8;hll1EJ8s#!_@R>vqGKCSMP*XY0MH zMdXE(85DdRjmJmx?#2~k0j=GMTF@Igjp=XYpp{6-FDYPyT3ibMpC>!V4w_}kJ0=o| zi5=y}H9p-N3iVFM8&?WoB!OJM?i5FN_1AWdiQ?F_kX|t=lB2rWtp70 z_f9=C=ggTiGiSc={*1Pzn~ej`uKI;>vB*Y@M2OuqC&TrAK9*vxk{gPf56cP7UxmxF zikk1K%9P8{>v+05l_W^=R%w_<2kL62UkdggB~U3v-Cd2Tl7D?cBN5tCPOFs>T=b{y zH?+aVjHNMX0!Tx|wIbkiahL}Lp@^6Yn1Vy%Le8ah$S!eJ8+7)G?OM|0gbvf3E%#T+ zED0xavfRaE8g8m}RxGTU`tgY7J+gWD=hx0fRnn@H3*5QQD@jM?J?s5O9wtA9HuqUx z2`s6YG@G-NEUKC|vUiH1&t2^u)i<>pxcRYrD)P&3 zdt@8ucs;#eVjS3lIZ`tEys%70#tp@e!cJ6$LfNe3yefM}WdeT;X*q*Of6+p!nm@~In`umUnRr>*Lz7CE-Us&PE$%{YreuK!c{$m`F)q*wv^rb}O)zxD!k@ z(;|m2ojbm?uxpXIq+Pobb5YmA((!Yb0#}f_c$qr-=X$F0*?;2GPflVnej9dENnFRg z4JQkP?|G>MMgUI<-H$-JWxt}+XG~mGbD8|brC@FcdV+CWYI%+=EX2^sWi52-6Wkta zT&hWDw#EdRGD>W=k_=N&jMc0&rN&u3?gYKF(5iQ(TEauZY&qqr?x4hiZf%NO37O?- zj$%iAScEPi+npMfl9!5$YVuN|aQ}9KE+Q-*AL+i0Fhi=jO}B!?AUE|E+CS7+eh(6i zZ`|~seOFt>j{!nGCXF7<*Yb2Ne`hWob49W#IySlGZjyckKHo!HzR~k{npE4|KxS$D zxDy+NK_S>9OIrFs44K+zmUm66pE5w#e`mO( zrx&XgC96;wa)UgP^L>w01DhuOv4^Y?^Wh7n%Vvm~NEYz*fQw71T;D?nnNdd%5u54{p;MIo6KXVAGO*U+s2y0M!X%T~Y=XN{g`?}S%OkobH@7A_HMIAlsHmtXqoN`+ zvKz+N)QoS)_CaUe;Igv8bxsX3=1fU(2D%qJw2gART<)m09nQq4{~+O7r7F2XZUR&)NvgOAo{ zan~VQjrI}KUdw*)*T7#xQXZ!U>f+FRkzx-#k`8^SYEH4D44o*2g9eboMJr&GWt={Q zc8t_!zJ2oQauR9HeyttTC1dI0*|$Wu=?DXU>5ggjRil!#YBCKKRkN?0J~yqVu_V7! zu`?>>%7Fu?$3!}dJ3SH}CRe3>ne0 zTSU9|>D4)j6{Q)4Z4#V?$$7P%dkyG5XklOXnaBj2A-}v$p23z7+1*lZF{ap4pR^?4 zqRbzdEdLM`IW6a&Ql_F3^qN(!y6h)wcJqe5Q~Xnj*wdtnaD# z@1SCsWDDG}$476nrA|JKJiv4TFkJwwlfMjpTCKp|4Dc#?Q;j#(_*w~n7avveOGWXA z_^qytpM0m*0sT-DQTwEVl=yg+K<77rY*VWzmecdfu8!;iXH`LHl-}yjNRM6;5nfQ` zEXa0ry|QOb*Wi{D!CeQY;L7j#csYfo+U@prDKZ}7@Pvod=*ZOa+=7b8q}tNTu&@N9 z*%BKD;l|z5V2TK_5}_zHOcZ4dlZ0apZRTPwldfW3jc zPVo0vafcpB)*3`lC3FSD{_us<7(ZM8JuHId``ACr$?4s`G}dl2niG~zp1d@{Y_!?q zO4|3%@eHhWrBx3qD<4>$=Bgd&898M1;K8GZoU_ZZ@$s>;T~4#x)1GMcq>q7}Dif@a zCS=uQg|w@4r3Z}|GGs(hx~r~T2wo**+v`SkXgi?HY%Uwnw!^48`-ZI2zCC*MEzRmG zr&=tj0@a%Dmx!p);NZ|GY_wOTeD5|nQ)$AgM=EBhB_z+`wgxvaNt2CjXp2-n@1q@X zY2abB$%@wLh5nyT)<7PH+6qVh_{eAUK$qo$Dh}lDW9lY_p9sXlZDU48USqFbjd>Xv zEoX8JIZ2k}LI-9@mTm_;#l@bEE?MVV(KK{u(+XGfIoCqH{>g~UWM_W9GdVMYIqzUY*NTTaXOv@WD%#*v z`Ds1PQ|tMBwdHTuR*ngBx9yo**q?M+{R?w@wsi-Msl1lvW@n@b`3kIESybB=bOO^q z)@GpW8pjo*L|I4)6xWV2A7RttlE3&TsU%b)S6#~5XIV536@)xsu{mq$O@4B6K9!ze zZ3NxaR*1{j;^S6@mMcoj56?Yl>zP;7ui849-~81qvW6w!qK5<5lI|Oprsa=(g&+^;#&zC ziY8oiy_xI0hBCQf_`y+<&ae7$V^dfuZOVt$6^{`N4#q!B*dB(v=~#}{<1`wb9;*t! z9*E5j1u&5f5zUc2QYh&5i~@*VgA6%KHbz!(`ZQJ zxEEPZr$fv5emWBieeAK$k3E*It8Lc~I{kJq6wsITX?eP3eccVW-Fm|fx86o{Ko%>~ zdA?D9I$y|h$k!8xeB+>%(afh-} zK17`huj*D@5Z0-$wXBDnlqa_>$j!)8>gv*~vLL%K8*IvauT&uw->hHV4GB%2*xCwW z4yNHxHqqi6)>GN@9VxXup0b>*CdNeCDA^g5`_I@$s?c9x8_C2@4!yuO(k!&fN>qIf zc1Mvmxlp4Q^>mA$Jp6MpNQNK;TG!Blo6<>fbcB$V6T>~pMSq)k`|WJt?bxDD_I?Md z>7IXYy+{?286vVQ#M0utDV<_Fac2uzZ4~P=#9?S&1L`R&^DoP?dDaYha@4)D5*8C4 zVGNCoPCJ!Vnq5L00gVR${l*t7xF;%5BUzH z@czYeY+WMY=ytD;nJ_EV^!*Ch4n`;>e0%?r%yU@rdmX9nkn7|u`E8_nn3waI+=DgC z&*AwPe?Bi)=~L{!PLe;u_m6lfg5@08dH)vAr}%RSzIT%Uh3C`!xd7>Hkl(~JOs68h zv0f{)V;@Ga4jdlObG$ZIBq?~Nb*)G#19SHT+*wTf&>dX*5x1C+#~Fl0(h})4try)) zT=(VxuIukb+i5o~(jrM{8hjJDFAF{)@zef`D$aU|<&pwX?0Kal-gFL(>=+Q^B?**H zIhO)f0d@wg2W$YG05}V97UZZwfP=7S1+xaEG>o3`bu{2;e4PgWJos-$Ue^f@7a$&8 zYF;NSqlwvcYC!lu`93f>SBa^U^1y7FHQHc^jx`wO5R5Szb_u9&c}slIvtiLjV|0um zzGVb|gC+fYV+@9v=mbN{73YO%*(PH9Qkg3P7$WuYae5e=&?nPh@@udUo(%@^mBUE} z60hUq3>utlpy&8_Tz*1nxDW$IsPKm&1hzQF8Ira5pe>a))t;}d9+9m2k#* zg`0>5Bjo4Ui^}MWg}dv9vN`f|3yLtdFs9P>#lm%VJdrQA%UwY_S=LG3A;-aDvY@zD z`Hr$a>XLWl%%E_R${WBFEzqrsa5VhWUhi#qi#J>)h>HqoCDd`+c1z!Z?jbt4Jnfd- zZ8MO91kn7v>>vEma z1(GS{8ms0S8<1;=nk${hk0;p_GiXPewgXh`F=B5drKKdrWf)2$>vNN!tq@-M^@LaCu-U3`a0*-8dpgC^6@SVmOM*qzL?% zk09Jdc(57|-0cIT%rx$Z+L=&0Oe;7}=_m#rMu<~uqYgIJGAZTR2zRkHTq$9&fu5L{ zVZ~)%A(q@4xiTrcG$mB;vc|_-UHZ_J((EM4;RlQ(lKdjti*gVv>~v05=O1M{o$!O$)QTQVsSe2!`lQWWtPY_3C`REdw#w>A=e25W+a`wD!b61E=)BT<);1e z)Cwqf0^hoXRA)O1EK#twkdc#J7F`%>$u5^)HA0~fnqK5_h0?xqf!7_fMJc;Tx`dC5 zm3FSPFeO}RtE4&|1rcpS%o(Nf4r5w&LRxmNJs~@dr>c%Az;ED*)WFk7Y+Pb$W=fJh zQ|^(RWXT|hR_-I?_IAbdd-1L_SR5uedA}(kF)cB{YzH-}VgL%6W#S(4M z_CL{6{nOr7I=TpbqCF9At$?X=50laCNHm($G>BZ9<$d5DS-xCoKajT0_=j?F!2mpr zz*`=TPq5&oF^jyOX=W#QG(*bO;xmprtA$l`S*? zIN@$~DIvXM+Si8$nH|}3RR)C0te{dkKR3Zj&n;T*ebMOKWDoBA z+(_B!&+Ix!aa@W!!BN~c+XMrjCWlFWF+CGkcE-9hvf4P)%*G^#*=R;Ss(c7eOShL3 zEn&wo#vP(1(x}LQ!NhMcTAOIZF36l&Y|6T-SgtfboOVAPl{Qh{DG$fYOG_ni483S7 zHSSqORycX5d5QBnIO2M$Ox|h~r5$2o{1s)PGn7;o4<^&Bl1AYrwp-*H48t?hZb2H& zC`q2u|Bt89pN8QZQWwcPXPbu&PniV@D!~borxobyI6o70L9TRd1}iD-9AAv@k-h26 z(|Wm#ZI^%hxAlhPz_Itwj(&MtE!cc2=xx-Oljl{U* z&dDj0U*Q9hHY@?t*|M8GDW68Fdeofupi$qWE#AQzHI>pH<`7j(W|y0iORJ(Fe(1vD z%y!xBOv-7mPs3eh%oLx5eZZJTyV-hQioxpoIE@Grr3l?$>ua-MgEP$`QZt!SN@X`s zEHN$#B%0HXYJROZgq3>VK#!t%Dy=|bKOW88aQ>O>rX7o(6gDZk(DiHQ{LZwSbv(k` zT7{=`eH0#N17Yc5ysHo~CP)3c0})W-K5|>!KD8Gb8lMh9-3No2r&zv3!6~y4wmyDR z*h9&3+adY4BW5S|EY2U#MjZ)zfnx_BH%f>h(6|BRG3zuwNLLjc(wjY-5!EZg5Rnj* z9ACxjcYVt*EZ$4!5NLlG?KTveKK~wGJt;6sVF;h=FTTmHYN!cG&#@)9{3d5-6;(QM zZ5!cU8sp7n*}Ud?+N@BW1O{m}5sZb-Ytj}S+Okc|1*qZ3857pQSZM4J_HO?8@i4H) zq#3|v8}Dl1-i#X3jFkEZ9dE_%H_Yvb8}N1jULQ4f40|!Gqw&}A`T64!cM{?jp)}$o zxYlmqYRH&mS}Fh?LDkxk+UC*dj$s|63!^)Pb%>$%`*mA%$`PH2ufxb?M&6JnHY+_lF{jdzRbkf4mJnM?q9YL_rOs@M zi_;PIRowv6acb@9H)bbvq#@Lja70|Nqya$%J&x>ep|gx*Rpm0Xr`ML|I+JAGa1=Hr zQE$)}FG}Bbu}tv!O4c{jSD8mpU-Q!@>fo+>-mP$J^v3XVJ7 ziFxkS2wOXw$rKvuj)suIq!lecOTT&PT)fyRDl|tlXIhb4Y;F1Zwbv+AH}s?d-ltVQ zMY}`&dof=ox_KtFLv~%uxq@#(HFn+UUBXdys7uv**xzKy!lA& zuUXy*^kCe;r+kk$?;?!To5t?&ZpNF#-cjd{<4rejN7m1K4c@$ubiPHtL#3zKF;&>981n)~KAJeEbkdj*Ic7tyQ53?tQfSa|Pb;{|MB(syVGs0zew5lJMv{Tkk@x-dS zr{wCF>T^?$-!W|19pFV>ys@m&`wUv>(z|qtTab;#F1GLOFWeKi^dApDoI&f_7Hcn6T`mstk8VTQmKYvr|2uH2y!; zLv#4^HJ++Q0(1EHfORHq@|LkQ?@n+?Ivqkg)`fhktxrIWRz`HI-qQl+_D^?mXjrup ztSB)NL80LhhCKzjc~O>#xM*0^(BUjV%PH?bd58Co3pjcZGz=}L!j*#&k3+%0yc^^u zB~PKVo^&fTjUnPzXwo2XQB##WX0y@C>;I{|Q%S=I_lOFIrOwEc_u-Ar5N|;K0#3C|!l5>` z-aY(z#1C2&$uYL?232 zaY?-W;yMvgAF2awy$#d@1!`ePPPm{Cza8Ejc41qtBI~N64_lQR|6}<>n|OYEei#ZX zaThk|ohX2)zfq-M8&);7`d^BEX^#RZoA02Z-DeijlD@4Nt+1iZY0vN7bNi(>>8>)* zxDYGXU2f^x)q0tR8HYlvXeMYz{+mj6KCTvaclFdwv`)SdUDpgYckGxnh;#|Zuy=11 zG`S%tn+q5dGCZB)p>tn?5=hGq4IV#5Me|glBPVUcujU!;`=t)hU1OV=Emt@O54Cj* zvQHshPzyBfQs> z#~SoxJhWR{f;4TUE)E@1@i2~0h87mf$Y&MdHEd|u7VTX1N zwvtb2esC^vdiwz&&aMgeBy)63d}vlgwj%)-mBb`PXGLTJSB-;hojSnagdsuaE&|Nb zrIQsF>bu3tQz)bDKTt*ucNdhA&oNlCWm4xJ={7{=}F#V`xrT zE^yh{WUlWT8y}mP5R^^~AJ9!VN%6I1=zPd-4@oN*uAe}&1ZUtN3sx70PN ze|e{i)pg-?$2@;sg@pSbt7}+Oo37{Ab<#}RT(z#r3f0wxZB4)5t|s7@q!OzS$F15| zo$-sc3Et95q1w`RYH4d;O|ERRDLQ**T-&NpU4s3hTK*|c!BC3ij0}Ut07W2C$*Zzc z!73f7gi85)J%Freo8jmT%y}zgZiwexE5UUVyiY{R1CVm?NrF1-llZI$%bcY0|wAi=j2^an{k9v~E3tCR$hvp*3hDW?H`&pH{Lti{h)< z6KV2<73~o;UBayM2n&)Iu)av4wZ4&TnYiW0SWp8_gLscfM-htVI6^B#^WuU+vh|2C zM_1BTFBXLJzI#!1LkEyC-n{H^?O#i7K^pDa;UCMY}C~cy4O`EN)LtQFm z*Tqss?dcyd!h*aeV4x0pP|AUrA}lDXQ;UK!gtz<*Z20~n-ch75$Cm=F&bXoL=<0@@#9a!r-n!BElPm% z)k2CgF->FWd1rhK&WUXZ+?AWnD9AWoIUeI#5 zfz4q-%@gF4zR+{fBdDhdT@|#Ofnkp}->vX_*Fkl^pYqw2*i)&dOf<@={SqltSS@Am z5u$qZFMpYZB@~7dm!z`34K0V+9Hh4ly#joMhxV75)_=iI&kH-5_z4d?=h7+9SqUBN znpP>#L4VWA+$v>XSe`NqBAy2xvD-&Q_=Q3Ya|PvDjjW=u9&OR334qYPgGz6(TKC2Z+Vgd!*L$gx8(+w z(}0dEFXMPaDMV;0ua>SWFznHl@?^pLIWHFJYFcb)87bK~ScpM+k!UmSIHD0i4I9YU zFaoF{Dams`BYzqPIKIRhCJrM9_F{pv!wnq4%@cyI28vt7q0~dT-c{|(0Wr|&5aDjZ zotg((@$uiHt?@JTD53%fp^{ULnst14fF|L z{NJhL*_CRhY8`!X!uSfRCSi&(N3Cn?5~I5EWM9wn)tJ^_wb)vDIn}o=FQ-s>vf3|w zZE^|x^3wY7E9ewGPSlS$Q6N=QpCh`?!LSa(Mz>Qbo5!FWf)L(M*{#A)R{NeR2Z$Eb zauEA2$ohof31|U_$SY{2Tg?pJQR5EC@BH|*XR%d$598FI)8LCwJx*|!->V^y6$F_i zu!bbLi1rgT{CDN0%5tjk<<~m)@0XTh^Y-(_rg7~e*!7pyk6q61LpXhCGkj#l|v?yTdNaVW4R;$VA2LdGX1j5cOGD?FF|NBNQ3= zRdWZgokM+nSp%Kmrn>U@A)HPGFYuRKi|s2pr;?Svczw}li6{JSozG(I_?>9@2F@;6&QUkzPh+Joe(QGDN-oAL;@@h?X@DW#FJ>&9a$C2v7E_FhYD^l!E?H-7 z*9q-|3ghi0#xqphC`P}Qvpm}gYR_ziF>RNJf4qV%) zbt;@8@wWGsiBb`|Ize;P@BK7>epv*W`+9&c6`JXYR4%NSM)4O*s}+a*FJnn`l8a-B zZg%*?IOmit$m~Cr+NH}D#ZM3fwjiB)5qpj#(7uZ#Agx2DvN>+*5R@D`HAxnnj{O`P zeL|#2HreQqB5^^o`K7gpCZlMboO58e*3l`&0NPetR-`CK4uMBe#tX+iKAvbJHI)%5 zh}I1lRW2x_#%YBnom;I^si4F!4N(zEf)kz39WbVVJZ}Bz9 zd1?J23KgS?Dr>5(s*OlKI{&v6>PzK6K%uSD(kN7xx?!w}QZ*4oRoZ+*>tC8i2eKdL z$$XwaYk5oca{j^nlKI4Y2EXX^a^_9OVHjrc?tMS*M=CnpEFhKH@-~hsopcrS61QJ3iFyr2Rl;o4X%@(EbtL7Kfxh&Tg zXm!yzl1MRdMnXJE%oEUVT;I@%_ZcotLRQo2Lb3^313XRW__Vq>FyK*Lj>7PosLQej znrEOjsXc(rz`9V*qAnyEYjYI^bwMfCG(c3^y%I^~AgPlL=MKu92&>Q|pkJV6MZ0M7 zso3cwcF(!co+NwV_KP6kg13rN*!%)j1+AjWdO}{)bPKdYmG+oIPmI|7XWWViq=|r{ zf!c-B&Q>*dtsZ_{YAF#e5Z@TYO95;gZ7C;xQ(vUZF#6tC;{>}9Zq8?i8)}ueQ z2gwjZH$fF?phC%=g#5;PxPN{@eFJsy%dBcepx!AzifF0;hc3aXi^u-mdVqPvdGw+L zK`n=mHmqzZXLA}*C%MzPgAGuh1?U_^AJxWqQ4i6s>a3bK38xgTQJP=Cq#n(p^>t3K z5nO{#=?ED}4?JQPOGN6~3PhqNAQU1}Y80UrPz0^;_+ijk61Bu2g>c@(w9!qpEv4r% z3X0(^O!T5ANO}ph5hokb=Sc(Pr#{hRwKms!tkyDRL&ZK zbvi#k6|_iL7t#;O0nlL?jjAjFE(_HupXL&y1(rBPsx!^Lz!Qx}#2aYuvRIX3ht$<6 zAHPEZRgQNQYaTu}!Fe99v0652N4jN48{Y)y^_PzJ+>zcUS}P^>kyPp_upEt)sa0ZyUV-2f^A{l%ryDC3($sbh`YsriMkh^P4GQs>Vt zXkAf8ISGon1(&DL-fw}RpaV@T= zp(Kr|ycaN(mGd3U7SW?fEg_hXAX~wGfOz7fdRUwiw{rML7Q%&JnxGp}DJQA}?OYXm zF0|24<4}U(a_S}u;8dWpUfxfL7&xg1$W(g1B1JZ;1IH3^mLvo@(V3DMKpJECxliie zf?TM!sIM3~K{i06zbg?Y$x)Zm`DSW_Puj6h^~L(*zrm=;-GJQbJWc}dy#W{w=xcsV z6L0c&I?hTs#)+MXzNcz+F^-CT{zSv-*vGFVL5G{!%EXTc-L-!R{o!ua2YsG>F@Z(T^@Npt|HiTns3M+{Er|nd&cxB%yOQ zDT{eu_e+vSh7~b~oSWVJZuWb+fsJP$Cuyp6SWpVrH0YTTmIt$WU}f>y#~TqoFf^U~ z)9g=+K27u(jy9#SK1_C$;Ov-(QI5D+Nyby22)7x0_Vt% z1SQ`pLtif82ZrM7%;=Mc2mh&*t``?@g7Pyf%)HfKW7oG__3Ku)!bZ!1-Z#N~rdbmbJc6xZ9gB z8<3J3N{(v&r!N$v-L4l66<8|v_e+FQCY+;HFHjlEj(dYIo|Z1@>?vO;rOl;6?Kul; z!D?EUz?Qa0L4=CBc&MmWq1eughQeiWVpD|3k(it*sbhd_AuMPwzD_cyX;f; zHTxGNq378d^l0kUH0t7H3Of_~Jo^&x6B_?CW29t!^%DM|OS}&Z|5q5*fbAu4^*;?i z?2s`g#z=1W1s+x&9UdM{ZubQqcKU+gz6Ab?_vi7cEEcVxVburwix$utzy}BY6`teh zQuvXwydSr8m$MXFGx>|3{!imO|F4#Zy^M3hHgdZ!@UUu)s(q}|=V2!<2<}VZuXukR zpUPs<3K~{@u*aqN=3n7CjxL2C@YB656}aK_y}$S=WaR%>`Pcunda;M|)6(+E?Y_Xn zI%!nxW0gJ+J90sAUjl!{`}6oz7K>KUuLm*Sg$h37cB6n;pbp|rV(ULcNZl9V=F zlO#h)a*;5!CL~AsGz_HIAyvXfG&WrMAaq1R4{<>#zaGM;;i9xI&{vQij6V26sU;&j z*3iAGx&@|c%>25I=JupFgAFZTO=xcyRvoy0O~llRSY}y76PYx(=UMqRP5zu@#HBb` zAIA@Rx5K1rW;xC6gRTzLO^Fz!^I&j67sNo3UWBPgXhNxCm)6l}wZ3>Q=zcRQktEX3 zl(A5S^r%quS2@j5aDF|4)KZa-QL6`SsSqtjrOLvI*;Jgbn)=@{;>t6r)yVevXtEPL zTJqg=#|Gd98$d4t?F7?4K(UJSGkpKxD{5cC_Zjz1hi@YH9fj{h?wb$a)!g?EeEYeN zEW$13zBk}|h5Ja;{VaSa4>b2=W0hlxo^V5J-l5>D436lYl zGo$lV$cfRq8NM@GPonR1E|0$3)$i%*bB_94q&}Cc&(-R4d-b_1;4sAR1vnA#GQjD8 zBLJ@k91A!fa55mu&1L|i9&C>Kyik3dJz;6KK0e>VtV}RMm z!26K?h#z8?&r-vs{2>8hF69q2^L(jv0xdyYDxLqG_xgZx2G%|BeP9_b<^Qu~xKuiU zc=)qv{*mx3Cp0oLG%O+_Oof$~c-G#Z54Cvb!@=jJr^UPdy!6kH_wSx>4=5{z%~GE) z1q16AnD)d=#Czzx^e+|vQt#zXks_PO=8rU=oUZvU_D=hDJ}lB+{EkNpaz6Z>D1YSB z|8)h1y?yN3kh{neu=9|1A~|qZTsiI$6gSI_!<{GdVRL_(v`TtP+A6(@QSW`}xb&^` zD^plBOJojMxvhkq$lmO7HV&&bbJ>l!z~BM)ID3Y#*BoSjXJ4>?!g|PdiN?x1`3U(I zeD81{?WY%`a4~%O^o}_pzhv>R>+o(fe^-Tf_1w1{K9LIT#o#+(7q8K1R6J@SjjqJB@dz@opX7eFy($z@Gr02mB3i zXYBJ#LRgZL$*;q^4*SRD7|-JQS>$px{LjNraezM_XD8rIcqZ8)5pX}A z_aiK&a|nJ)=Y2qm0}S&xCjjX?u*<)H$K{8TVo#CL5R@jMTZLr-?T3B|!!1IQtyb0R_t8s*-%}^XO-9LbuPNYdYroMT!kTju^X8iqxQd*jXL#+)6 zQ&c&MADB~_+HQfeLtyTK@mY?+U}*Uge-yYVbzn~;Xb8u6Y%oORiwD+*KjG#L18!w9 zl&E8@Cp_GM09E{Bop`9HZ*X48Krl&t)L@o@Vt5SU;S`Yv=`J2v2Q?@knc+|5d7J2O zc(|Phb&7{{HROu(6X!9usBd#rSjDr9M7dE=UN#&22Gu`Mg;Zv8+XKE82E^%fUO2fO z1w2T9D(O#Aln@)DvZIiUAuP%o6^2Zr;?>8NU&M3mMPpFy0%GvT7NeFVi%OuHB~)uK zDm70r+6dWY^Y0N6RuL{^Tyrw&?z4SOO)kQVn~m3IBiI z2mKd*g5*ozm)-gf_6$u!K=A-wkY)Y4evTT49coqI~y%8|Z~WeE|!e4EDz3@Iu_`>2l-+X_I7r zV+Kczj^*hRyY%T>pJgfOoKxSZ*XbMUv%8d7vg-Tx=@Ob>Q|xI!taCv(PjY3~u9eB2 zZUvo&xAzp+;4VV%aVbN-S)K@+M`R(ky)-}?0gb(wOo=w6bFEI^a%TAS+(#Pr&fM1z zz8v0~ozZF$XuSw%N&CSsG~U|X>->@ry50%r?1Je6xH5hldAbAlSK~%|9GoMgtm^d? z(Fz29p;H4s`S%1EvF5kklnR5XqVa#N9n&Ra>EhY9M7QZ^^|VWOOslUNm7GeX1 zOUuX`cJ~!SM)d3!(XM@ZbxvYMX+~k21YAy;SKGPQfbN49_I00$Ot2a9%iH7`YzdLw zE#lAAH z6uOppJM7(RG}J-GFzHNkqz=%PY=aF_;d@@{08AGE>jl6%`ODys!t}S2Yvt)pHQrR? zYbE?$d{k9h#&wbWmL**oAJM3FKtI$()IO;oB|h)MW$?TaOs$?+PR}d5Ivqcs|tGq_6F`c;az_fcj%F%#TD1q@C(au*QJ~HW-`2vW30S0Qy{^Feh-U*N?w+e z)4P3XtlefbCoG*jd1->#XtT$awC|nc8CdH|s~%KVKCn8?RXfl#a>(exgGUcJXP0B+ z<6~vJoMyMDJ<;k(ACnMm!c9#X30XB+A?@m1=|Llg3>guW?y74Sf>#OI_PS9W+72i) zo682Y?J%m&z9Fl$Z;u{*OS8JlsTNDBK(*%kB_b*`I5;#4hF}ya-@8rDRGM%DZz^`; z6aw4vz@nd9xH)Fi@GhBSHVxQQfJuUMAFRn8SLRXCWAvEfLR`2a5v1>9=q3f9Io&sA zWaKsW>eZN+kg`NiK9a3X?6}4tR=-Jsn-L&b6Xx=+LGWuI6*Dg?jyy5t+%( z{CsC}W(0HI-L_r3wp3!;X4M5Z0wrKHO2=*BSQVrFJvt&dUBEry>Xl)*%uWsAzZe{$ zK~^}jjVU3Sa)y}&_;jd}9#x~G+O#}HhsMc>wgi|2Xa-CM8&8EHW{=)cBa3ds@81cb zEA|u*E>+jl^j+;O?fX?n)Wt@3tFz?jlOwG9T7B%qs7d_}j_5wTQN5U!J<%qpSJ!?~ z5oICaL;6&=G_g}HNddRj%2IppPqK&CHw*iW{%~zggb$OX@aj`7(DnaFt+B+VE{c-b zZLv&xsrm*{UCU7EV1v=%DJr7d2`*7*mS9f}p4;OZIW{OHltm<7Q18f8YiyD{q4}=R zP$gce)Zr#LSsK-HS{|WuqNT0iqTBFSXqw3`m6dbP7@sdi^}#|0j*wRemy|flOb&3s zU}IcJoM&^7>> z6?ZOXOA2wVCCbp>J4jZP`*6Nn51xm3g%jII!z11wmaE=> zwk3_-@Wv~2SCvYoxSMJnY69QNC2M3y{YMljTv(56ILMOXZ~``}~ZQ zhx$_1!UZvA!j{<1+WJ| z$Dc=n+iyS;W7s3?GdV?`D{qs3R0@@=m4|d;y1u%#y03!z1w9#T4(QQ zofi9MY;#;_oIcJPml;4M{d@Xj`mgmr8<-)|kZ5ok@(pE%TMf$%YYgiQTMau6Zy7!`oHTYb zHX275CmF9cUT0ilTw#2|_>A#o;~wK-N`7iEkvn zllV#E>BN5~wwS}r2D9CqV=gn-n|qj>%p=W{&9lvm&37b)BAGJxg2wQ?J z)s|x`x3#zRv<Db}e=Xl?7-0_X$UryN>=S*?Bokh;J&h9S7 z740&+9(HYTZF9ZuI^_Dh>s0!L^jYcGr7v;Ex|7}M?m~B!yOX<*`*QcE?yuZGXRwS& zTzQ<9k(*JT(J7-(#^o7fGp@{-pK)`>(u`FZPiAb%*qL!4?-M zOEOnvK9ad1b6e)?nTIn!%}UG4%_`4opVcdCY1XQ&r?R$W?aX>R>yzxP?9%MI?C#l3 z*(0+jXV1=FoP9_3{n?LYZ_dfjsmSS&(<^6i&gh(JIoIUenA4mamTSnhC)0)%C0IqT|TvZdz-p8?^Rr0@oL4M%ACq2Rj#U)ZA05$ z)ApU}@zv9-_t%uyyjE+gy}iy**Q;)D-92>=)qPv{YyFh^P3>~p^=bEB`_Ubg4%0h) z*3r}PzD|*y?(Fno=d{kZcK)$TR+rgb_I4fEb#1rcZiBjQ?w;FydG|9ta(gW6@m)h< z!<`M@p2K?;_Ikc|uindgAL*0dXK|nR`&RaSuwPO?SHFUOmHj&Q>)r3Neq;Jg@1NM; z(LcX`MgI={d-WgOe{}zu{TKGXwg2+|Yx-{-(6ceQF}<;{v8u6CW1q&$8^<g1G1N8mvZ0R*iyziB?8RZb zhW#+?*WqJ^zdyWX#F!B)N1PhjIP%deBCfdQiXEeRjM_K4V)XdYca7dP`q1c)#}tkk zJ7&!o@7T-7UO9ID*mYx%jQwov_hWw<7c{PPT*J6g?Ll zC;l|4-=ry%7EM|?Y0IRqCz~dZnEa0^?kPo6+D&?-kbK#bY*(R z^d8ekO`kV?>GY?rEV=T98Tm7Ao|!On^;LyeJ$cm^SDl@u%!-(`eAcR2Pt4jhYul{X zX6>K#-mIguzL@ooS-)N_UmbC^;p(1OUw!qa*~;wF*>h*_oBhL_p>sCOd1ualr4fBiV_nLq0{9ESVGk@j$N9R91f9w2L=I@#R z&iud6|7`xZ^M9W2T@bP$W`SwJ%msTFMlBq<@cCR_uex#fjX&Si?xy26eRs2bbHdG8 zH!r;T%q_!i*?ViZTc5ry^0r5B&%FKhC7DZJxTE-v?RRG1`Qlyacinw=_}w$^e)^t- zdq&-};GP@rx#ONyOM5OIx^&9Y1xxQ(x@zf$r8|}$T>9zK@0WU)#VoTe%U@Qrtmm?! z%RXOrW_jZB?BxTO-?x0t@^#B!T>jm?)_X_X`^de=?hCuG>wVYXcjtXi-nZkvZ&t`F z7O!|^#d|A`tvGZ4==(o?AoYO`4~%=@&Ik6dELpi=6w5IEt%h$|XvwF>zHT%|_UMsJ4 ztnIjV#@aj9Zd|)}?dK1dJ^awadmkx$WcVXbJo3|{)sOCY^weXX$BsUJ)#I-`G3{^m zzkT#%$&>dz`OQ=Fo_gV_AD;Sk9a|T+E@$0nk6WL$zG!{L z`ug?#*I&MV)cQ&5XRe>O{`&Q|uV1$Q!KZgT{qECWJbiXU%!YyueKy>_;q?t4Zun-y zPaDo{%-z_uaq-3t8~1JeVpGzl9-FS(v})7I&1stlZoYl<#?9|;KJ$#}nVe@Do|*Q{ znrHSubLLs=v!%~AJUijpCC@(d?8ndk_MGjxqUS1~YyVu2=Vm?kz;pYbYu=K(W$2da zTW;BM&z6;29^LZvmaSWU+?u$xU~9wHHCqpCeShoG=R==QdA{BA*FL}F`Q{fYUwHV1 zliM8Irfz#_+n3vo+v~PZ-o9!3nHM{~_`r)tUW$9E=Sw%f^wG=dFW>O;XFC#hlLagifA#HGPrur- z)37shXYJ1ZJ16X1xbyyuV!koBP^5uRZhHyRUt-D`HpHuKHcW zb}ihsde@7)KHBx$>q)PdzTWTk8LuyWedFr~UjO!u$Tu?H==sLz4_Fehu{2dw|#fJ-J^HkwtM~V{ku=?k@nd3)b1I+=h{7w?Rj(07keZ3=IrgZ zck13d_iosGc<;aVnfBG~yJFv>ee3tVx9{x!-2DUgU$cM1{%_xMzBS>k+uwTntwV49 zcp&tE?LhAXR~(pi;MM~V9oTr_wFB=RIDL>EG#@NF*!AG$2d5ldba2_hCk}2uc;Mji zgFhV#Ih1rL_fYMjK8LP2bk(6--Yt6XqYoE-xb(xvKiu}=fe(*=`0J5`Bl$-ZEOP(-gCai~8dG&fiohux{Cju)p3wqUz0q>2r9#Pr zdBQc3H<4ZA|19u{gkZPqlQmOZx zGzmJlVraeE;cty}C1$!#+%Wwe=DN2dQKyvWJ&P~Hp~vfrJ+6(|=^E}mETwtB{8PW3 zUi!80vh_gu=m$ZM{}M4zt@;DMwy00*Up(p;?|qK`qE;9GQSIqR^0W36_=w>+F#|9D zUnWZUCw|@^y+2~+-#TX(@azBSfot*W&aqw#%>U6JT1fp(LOTWi;?Qc^FA*BDGR(fR z(VE$4t3UDcenmgDGPS?<%TiM)K!5o6zgYB`z#pZBpLZAjx}>oe|MC7+h(1QW%>NrH zjpJRJ`dzD^_h0CDf9mI@fB!d)I(pI{NpY`4KJfpZ9Ra zDI+ixNi#$58hrI2e+4v@2JKX;bQxlGg8nDT`yEnpqGs{Pp)Jgvy${Xocq)_kAKrf; z&qnD6=`N|4Gzj18P#Vvl`*Ga1`h!*`?RcekH~L{EY&yE2i!gxm)JfCPhjRG4GQ77+ z(U`C6@qP^6o6#O_U-(2G{wlnu8_?X+MrjB+??17Jey#L~RL-o@I!ui}k(Njkr2F8$ zkX``n1pd*;f7Pfrxx)ytSb9`iA>9V6Nl!>CIlK+}p%uW~XWsqNT(3iV9XMJ59xw$w zG?Il$JK#oP$NpfHZV>kFx52LWY`BNONgu?H`g_nCjR^l9{1nd&%6|yC=Rp6Gg_gY! z+BW)CK})zv+9ExN(mf}=CH)J#$hWg(=@S+ueTPyH$2q%EXva~o`t>;QGXb`oZkJv{ zTJvyLV4if7bPdwKL)swSD$Paw`=q_7%L`I7+%9NXwj=!4(wnd?8zvoQ!8lp4NqQML z+622$2N3I3=@)4!(t8(a?Pf{nIot4cHNJm`Z=axKf0vd^e*=crL_`VJjZOn*-BvgO~#l@+9RD{>CzX>#TeRh zhja{m^HUZK+gt^pjX6>VLPg+fCxrYA-`_@9iMvmwAEa~8wHB~Gh}FzWSzGC2oD>Ph zA_DUEZ`I0i zasP$+!T!0Z8?PzVl73{}P)(Q8KB}VOl7QuSPkTT1?uBFqeJoB~`~C&^t>7cn>)?0~ z)Oue`tKQchbT>-skA5h5e}$v7x03Q<%l9Ppp)IqNJjjSUeHoAkH;a%hPV{9-z6G`l ze~`Knl-jc)=*4!tjR*=;bV``4!^13DDyRFe_`kaL^*s^a?)A#wd4nk@7B@^{_s~an zjFg;`9pjEfX>&+*g9bI6N`QA+9q|?=1dmW?tipR;_;SP@gqI=Ut@+@swSdFWSLd^Z z>=vAUx(BwyH^M;t$Lu7|K7Gy3Ft3~}m&xtq%j7ZgSb4I16?C5S<;60tzmZqStK_xv zBl6?&M)?`}1svesfqO~!%Wum^<PvW?FVySz70`;c25BvLn`!>RRPm z<5~xe-7eP~uKliqt|QRe#ibk5tI})J>(hs&k4zt(KEbVr*6s@T7kPCuCBU8REp~#PVqdYJ*e`M-$89fp2yi=Io+4i@ z&yyF*H$sVhpS)6D1Kd6a+-{b);#TCBf!jCaL-L36F_>hHQ4$pgaGM9*wrPdiPQYzX z;C6ryx53s(Yl78oby|z8)z&`NChJ7&d}u=-vaaU1jknu@+kATwaNEXS1>APBPqojm z&$BPI-_CLSu>CRM_9@_Yll@uy^D1r=e7Nle+};V?9s+JhaI6lAQ zZ~l(If0sVO+4;lLA?YAV5iJWphZ%s+u|)Mb^8NgU&qsZJ#pjpv&=>i00<`#9?ujY% z=|l%fI$3tI^h7QE|Ke}@!Pnm5N_ut` z+(NjU*b+)d>8f;BV8&W$P$nvqm8r^fWrhNL;>}|I_dfco{Zp1I;4k7|pdX>{R8}gF zC@7Ia-06aU{L9Pu3Mc+4+msiTH*cBPy`b(jN?*|YJaE+q<#F25GVlcv4%;`TIa}90GY{I8MkekQPddq_dEs)Z{w8fyO6TKgIshkOfoAB_mx5(3S(wwWf?4!WkELT!n(30ILZm7dD%G+`sV}6qe#|ZnfLz(1rAm#=Aq{43 zX#~raMnbZ>0@B(jmM;xs+0s;2DNSQl(samUSF&p9YF01JW1XdIS$F9=)*#&sYsfdS zUeaRLPx7(@(k-mNbSoRc21-lNpYLRsNq4Zp(lRz&TFQn=E7&OMK{if$0PA;?q!zYc zdW>BuJ;CNnkFyz=9o!_n$Zp0A`9+N&~k4s0`6Vl(=lhW60oAf<<88$i2uwBx>*c;N%>~(e@OHr~g2igqU zI>9zcHISmOXFa9Av6<3G>~GS?>?ugu>mb>zhrIf<)D!cR8LUQn67)EQRY>=<(NZ-F zml`mS*v4*zw7XP#3n!J|W=GgNuq%6*y~o~XAFvN`V)CWr#iVRj-UMCkR`x5qls%xd6eV1VQxX)5azHtxyrUde-Urovq#RX_ zDJPUu%4y{*`qMAeJsoW^JUI44N+1UYgC?Npo38=^9ARx3Na)cGe`_#V(ibhCFl+8!9blBcyxT zNa;Ryh4c^`FRfw|FnUge#oPtb%j`Dk6?VJyDtOaQb_Zrv%cMPQxpaW7ln$~7rPFLH zWOLZRU@u7Du=o%3_9mqG-O_Jt59`1llIk$~t7p+tI~D^OELQ3OS-c~QmpU=M z)R`HiF3c!(WeJd7O;UHr{i9i-G=>#PW0^-9$BH5Imq5ZVl_s(>X%Z`!Ze+csn=og1 zip|2T_-biAn=L)f=3tF@uCx(nyEn0U(lhK@=~=c&dX8NuZDETc4__}GV;iL7Y@>9N zZI(V`&q$xMXQfl@IY{7Q(;^b>ncUIdBYTKPIi0!y*;=t@XnSIaZx zner^iUJuLH>poCwAb~vvd2G4-g#4uZH+h#5A|H?sLISIRgixSlDCJ6tQYzQU&nw-O zUP^DJozfoCSVyIk(pl-kxqJ`s_@2r&$~xmj7LT&pZnu2Uu{GnH9NKc!Kb zri@WWE5nq5@_PAcd4s%BnW9XEv~rVtw{n+qH}>`2qTHh_m2Z`AQ&uR;l;!g6@)G4< z!+myHDyOal%$CSsF4a!>O5ol8I&&J0S$iu7fU{@UjOchR^2$&|E zJPptxoJ=EzQ#i#1s5*r>bh>bIJs^#laPm~Z47g1A?*+^fPF@3;Eu7L95S*TzJQgrl zIC&mmo^bLQK+FWl$qxb+z_o$D9UzSgmGF}ujYcatWe^~?29Z;m0I7U%%3#16;bf9z zYK0@CLy#XO$md81j>5so(49cX#2v@gW<14#ISlP3YL6;1(8F@kWX5bhrlPNDXBR5*p& z;W6Rl)qsx+CsWubgi~Gz{F`v{R=_8Plg|M@C7euUUniV05^%k6GL`pf;p7(pHwdS^ z0k}~(h1ztJa0=mUGu$(%3x#=BIFd-9H{wpAzPCj;t6y;AATQ zHsRzJK*BqmOgN@=Uq<*^z#YP2e3qnF;C8{096|BPp|40(CUQOC-c(&5z};|=@~G{h zi4u<5Xg}NmIKsn0)hz-%q`I?!Z>w%4;5%^d!%=&FARNYj39_$nRHq|we}|)ZAFJ*? zz)w{79pF*bodf(-ICd}KF}TklQBoiNTsUaVBxo_XV`jiFgkx5~({NuQd}pZR`g&jPz3Dxq@udH?}M#B*^4H!S4B|}P(e*O>@Kjl z?BZVVlKG=DQ?r}-D>YN|Qf6gnT54X%%Ab{*nptWVrefaB%uA{K-_JaAb`NOy>-T#7 zcVC}*X68HJxju7yzVpm^MPa2*UR7ABi@zzXwC`)+4Z?Rr-&Dj$LElo?v!HJS>cd_R zm3#wx74%(&y&d|V!b%-)QLvsd7OOz|xy9m94yByFtRIEO0mGn9Vdg-~0xF>?1u`?k z8cDG`RNetdbdA)PM;bapA$u2A5sJxBi3=c~FwAj^NzhRW*&Q+GDW*V2D`ZSz4pdBs z?xaA0GT1;DUARU8hTsX)eOSjQ@)O{IvP32Xp=(Z{jB{I!VfJ9|yz5p0@BMNCFsdr$029-J$Nc~H_ z1DOL@4=cV5JzgPm0PADLSD?!jGEcC(QJfE5u8?_vb+qDQXuCq@3U)yXX_plWnIEjw zwcs+aN`VB#uwznO30O_*P(8n=7QAKdzAZfwj6q z>iH82KYs|X2&A6>E8s%t`hW>gX>TCwGWI1NGKa8d^kDDj<+ki2k?~@`Tp{ZYeyLF* z@%=!-*9ODhKyf|vE=9B(^ge~G8QJeDDxkkn@Z|s%o`Ct#2NklOvJVAF{ry&fl*?FY zV}XSEokHe$D{U%Z9-%4D9?*nN+AZr0DaS5&iQZ|6B-PorB z#zQwN_?BYqvjI}Z=M}OBw9@thY4g_=d^a>!+EuV0cuQd}hQ1vjJpNCGnGO9{zzFC& z3ch$5D{Uqi2i{Z2I?iqh_$+IFLuQ0gU;dip!` z6!3BE|AKx3oPnJ(MeD(tzyc}T`5*TtLEs9v)`w6&> zc;vl1z@6A{g8ozyOB{Ct#>Mz{=mUVdk0ri8C}L?-X#)`d3M%CU@jXy!0}%6m^hZU^ z7!p09h<^$FlOnzqx=9gFfIg{+;kO9h7sNY4;eSCq7W%Xzo`@SZ^0*&L+jx-QI0|?O zN&t_Cp)P>F8kYr7CaF^oDMNVxd6fF~Adm6z0Lc^i^Vkfn2>2_sGT?dW4goJgX&Vpn zF6Hrf1xgzu2Qu;Bz^)(-{tl*qY2Y0&UE!pBdn%liX)iDX>8zAvrou_yXMrsKlJ{zb zlYG^K1MnXSZB#f(zeVB3Ku=IO_$MwX9CaRVR77)(nb@R==ob?YQji@p@nA5QaMb<8 z!xU1-6Xz?W{w6L^NS#ewsEEnW#G@4PDCp5(3GtA(iN`8p@;31jg|zX+%N5d|6X9op z)L%k73ea{n33VZmHF84v1%`MN(i6x!Hz7TNSqi1S1hVE$XfJ`t6cXA?AnV_Rx)7Ld zDE&$xYsMt4Fayv@3OffXX#kNUBoZ%>^<5%y0ls{jWV%AufC=p@5P3sF`wC>;m(b1v zb2@Zyg{%RSoWh(3ty0L^Dxuv4eA_okULotOq(;GM8k5v2WDS+nDa`p$DG!kKRI;Cf zvm_?jUm3Em+MtlNRRX^WI2&RTDJPJ1Rw8u)I7?!Z7KN;v z5-Bs_REtTZoIuu42|Ok+mqXz-fvl$zcu64gg=CRJ)*y+L8HgMqS*k!bY7%KzU^YUJ zRUj`l$#Du!nV3ZC1<3cEMCt;_H|s>o4`lt4EK`V#At@-3wVFig0&sf5BvO8W{M96q zS0HkSWQ_uutVx8QfXE?|9tCn*lboTDy?!F)0myDmvR+}HgMLzhJl7T&pm`Yu{DKo<8}W!oYLM_Z70gPp(s#gP}iA$X-9$ zpfGcx*DGY-pWL7@hd_U*5E(#nqk?lECb>x=@_^(=3NsHXZ45*_Qc6;3bP7&yF&KI$sG#Q3B6Mxd*wvh8<^G5pDJYEoZO}0ypu_OrjR{! z@^giOkCR_0WIvtUtuVsZ!YjbmL4`kn`7Bg;0?0lxk+uisZ0N5QvZqY$RfzmDxlbYc zmE?Yf$TE}PC}iJ~JfIM{X7ZrIo(g?PAu`V7w+h+QBo8Y@-kJPPA^V%;5rxP;liw?3 zuao>iA#%{aIk^1MQ1 zj>!uOzOR_%MTN*9la~~Hb1})w3Xw-9uPFEqW0F@DBAZP9rr_I*NnTTMkFZJpuHbu( zNnTfo+%frwf^Rq`c|&0&?3)VN&n0gujJ)@@!orW~a)qmgj#fmBkLjHh5p|y48H{1Q zxE}fuMMRya$0}rfo!&(uYx(r9U^l||LdPj&Z;&3Zko`e=0+>jcerTeIPKKtzB>b;{ zPF6&;S9+R4_8jSb74G-Yj3Pb|ngdm=Vd$IbydswH^Cb|=7D}H?-+(;}y%F4moq9~)tcdHNw}4ymmwLTjA#34DWr~<`PJ&Ma z@o7+aLl8AU;SGW8r6$q70@*)JqJ0Gs@lK+B1#usAmLmQ*bb%s16nX?$NEq>31dhSp z1U(iUk9{7Lx)j8OoAf2{73`-&smn>UZG1NLtKcH+>!H^u;&Y)7fZyVOrZHs8BkqSr zig*n)R>T9)lp^kh?x2X_?a4bT;*+5}DdNS@k0@ezaPn@7_-N=jMSK#Jz95KKLU&ih zE1-KR;tnW$Ac(u6wThVjHThsgyd1hr5eqFS;-%1bKpgRLeA1*`g7|vqXhqEPDH8x` zN8g90!6d@;gioE9vG*a(sh5Lq;t$VGr9VuC$7D}G75);)-hS#0;D?0yG8Dd=dK3P% z&D0-(ALCEkOob<=-h%(-pHk*0}xRWrF)<*DC{J#dh3;YcKFG7E= zh%biTt%zy=slQajmq34|h&Mogt%%Qq-m8f3gZ@SlOMd>Kh$R1yD&nt0H!GsE_~c8y zP1}+Dd;?1R3gYiT_XGQ5{}%M4iuec6If|INoz|#`>4(#nDdMZ4ZHo9xC~YN(uYj&l z#8*LAgA?(m9j5QBh-sVY3l%YSGM%;*@Vmlh`eyJfbwyjx+Eo$JPO~P1srb`Qn}}-@ z>A07n)X^sV+}{j8HVh){FYoh5G|ypPK;f>K;c__8kusUyGQGn=^nx zN8i3Z*2Bc#HN+yjKVyAlSd5|K%#PHh`!kj3{z?tHXQ)EAnYwXLQz`B*)P#GIYH&|c ze)j|w;QmDE-6o3ZxH|tP_XL@6e=+-a9_w_xOu0>%t{dmZr~5pF6kx+;nSt zRiS-@o9ZbKI?i3XZ+e4Sw4@Vr@sdd!<}4jQXzf_KbU)&85)ZEs{M@C)usXy5^^RH9;KI zM9a-~{Q@F`Oqn!ZJf=*VGKqXHZ6@vEGY?&~q=lSLTDp&HA@@d>$q{!G?ad*jNG*3_ znPX}=1u@Btp}~jk&4&!j$L2XEXZA3=sMoECqsmRW{6>M6lGak9sFanN9xsJcP!h+1bdm8SzaCNDU&H*7VPC_R{58LeVECW z-x2J?Oc$$!buSMq4%N}SY`=fr7rtvZ+3uX@5bJ!Y8 zJ7Jfb4(y9~qmQs1>}>jQ&!zvj@wAt?#4Pw{?L67j$Jvkner8|(Pxk+_2vLk}mVa{% z&-U^uo=i^qc`MCH!wzEaAii~&5?d>=cl$gZP7G`CO|w2-gzo^M+Dr#4)mf!(-jNcv zOGqT=z1ZjSeu14@C*iY%&X_#dpYk=sG4rvN#5ef1zPpcqTf@#Lbjwy@-=CJ$%us4a z`LgtpvXmmdRq6|fspymO`B_D%eqT4;lwY3JkXu0>`~H-^z&^CYhgyNJlnCt>|6Ic5 zu}e#SSZPY1r-?(_vjF;iJ4($-N}}VGCao2i|2IzP^ktTom9{t%m$b9C>mpxE;?mT+ zX)(D+rPrsa?}%M^Y`}AX@T-KL^?%CE0!lPMjiqVxS(pchScT&k6K{{F1JE_3(~VE} zf56B6^p3T>vzT|ZK8N^LYI_BxJ=xc()P#mmew1{C4aLJU%p58^lBcqwBsXIrOR%uNA~D-d57=5kEQ=TN^C?r5dnkAO-~-w!Gy z$51ctBPY^AYw?pd5}xmZN{gJOV5#Z?1Z1zQWL_v?Y{hruu1Ly=PjIu zk2KceyM3N3L<4sPXd_l>ziw)L0J@TNB%i{Wy`HZnXF3)Q^*VV+aw%nzwjFxDlGJ1z z?j=U)6Dx`9RQzP*6>b&2EshY9qYvZ!S=;c;_GX4+kJ^{qb&#@*-}?R!f~v3$+@81GBH^^rgAiKjtj=HNNw>Bp!Q z>EAu%LuR_QzN`|{D(^3ENzXi%lb`=Sti&(;E4|#svbb!)I%<@!PM8+@4Pj}b2A-HEgOJKHh#BX%sk?p?bJr~P+D6JeYkZztG^HZi}ZhVHSun_t#nLQryd_`u#ai z{!z}0A7E$O1MM8!U>j|dZRYfNE58+XuxdPTihmwj4~N_Nb^*U1wvcn;i|k^1q&*6q zi3d0*`2$XkA8nV|rD)q7Ymeg>#*Viq*k!h0m)kbmZaeG>bP!hAPENnC=A`(Ew#%*= z(x9+?oC@!^19qJ~*`8ugwWsmxWT)Ft*fThH^RW3HUx8MkvG+;)Df?-Aru_{17N13D z=N$CRK4;IfpSNE?m+wpV%l3RU`Yy2lZNJJ{%ops1=r~+tFGh3V628N1wwKz=L{Ee7 zEia*~aV6RsSEJ9c8V)@X{_EnG(5~V1{&(!P_Ph3b_WSla^w&1n>+KEvLfVb?Ci^44 z+FZ_kaJSf7`R>w#M#XJxFmAVZ*gNe;`%`PpN%XB=2_uJpt2RH-q_BMJ9kD|5Ubs6Xh{jB!?WKOn!wwvse=*~T5pGNQE8Fc8LwSPso z;d!(jM85%z2hP)@^I%`Ie@DCSANCFOMc%Y;*|+UKIr|_QxgR&5K=uTt%|;X1 zD2n39MP*ScDvyRm!=n*VMN}D$j7FJnMmw16qtVfh(N59M(U|BX(b#C0Xjk)0v|BU| zt;h-HoM>W{M7whz=A>wHG$j&E_A|`-XsY>SG%cDQ?HTPA&4~7nW=8u&v!Z>u#V#A= zxF@kX%11S5{?$eG+-JOh^wH=5^CJ7DTcZP`IZ*>TLQUq`s5xqhTBC#b>h*GTFtNS{fb0t*^&%o!#-#3DL5s5G{||qV}jG zTH!S}(J_inL=R~VIz~O{Af4oO2}F-z9hwBEM5jilq1kyl8cAnF|K&9fK84P~nbBvW zv-}Bv&i2c>emT)Er}?8VqXGIAG!jJXK=hd|iY`W5=@PV=E=4Q!a (JK-v{k-mj? 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 z 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 - - - - - image/svg+xml - - - - - - - - diff --git a/addons/godot_xterm/input/text_decoder.gd b/addons/godot_xterm/input/text_decoder.gd deleted file mode 100644 index 6ea7155..0000000 --- a/addons/godot_xterm/input/text_decoder.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/input_handler.gd b/addons/godot_xterm/input_handler.gd deleted file mode 100644 index 11caaeb..0000000 --- a/addons/godot_xterm/input_handler.gd +++ /dev/null @@ -1,1491 +0,0 @@ -# Copyright (c) 2014 The xterm.js authors. All rights reserved. -# Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends Reference -# The terminal's InputHandler, this handles all input from the Parser. -# -# Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand -# each function's header comment. - - -const Constants = preload("res://addons/godot_xterm/parser/constants.gd") -const BufferConstants = preload("res://addons/godot_xterm/buffer/constants.gd") -const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd") -const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd") -const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd") -const EscapeSequenceParser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd") -const Charsets = preload("res://addons/godot_xterm/data/charsets.gd") - -const Attributes = BufferConstants.Attributes -const C0 = Constants.C0 -const C1 = Constants.C1 -const FgFlags = BufferConstants.FgFlags -const BgFlags = BufferConstants.BgFlags -const UnderlineStyle = BufferConstants.UnderlineStyle -const CursorStyle = BufferConstants.CursorStyle -const CHARSETS = Charsets.CHARSETS -const Content = BufferConstants.Content - -const GLEVEL = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2} -const MAX_PARSEBUFFER_LENGTH = 131072 -const STACK_LIMIT = 10 -var DEFAULT_ATTRIBUTE_DATA = AttributeData.new() - -signal line_fed -signal cursor_moved -signal bell_requested -signal refresh_rows_requested(start_row, end_row) -signal reset_requested -signal scroll_requested -signal windows_options_report_requested -signal scrollbar_sync_requested - -var _buffer_service -var _core_service -var _charset_service -var _options_service -var _parser - -var _parse_buffer: Array = [] -var _utf8_decoder = Decoder.Utf8ToUtf32.new() -var _cur_attr_data = AttributeData.new() -var _erase_attr_data_internal = AttributeData.new() -var _work_cell = CellData.new() -var _parse_thread: Thread = Thread.new() - -var _buffer setget ,_get_buffer -var buffer setget _set_buffer,_get_buffer - - -func _set_buffer(buffer) -> void: - buffer = buffer - - -func _get_buffer(): - return _buffer_service.buffer - - -func _init(buffer_service, core_service, charset_service, options_service, - parser = EscapeSequenceParser.new()): - _buffer_service = buffer_service - _core_service = core_service - _charset_service = charset_service - _options_service = options_service - _parser = parser - - buffer = _buffer_service.buffer - _buffer_service.connect("buffer_activated", self, "_set_buffer") - - # Print handler - _parser.set_print_handler(self, "print") - - # CSI handler - _parser.set_csi_handler({"final": "@"}, self, "insert_chars") - _parser.set_csi_handler({"intermediates": " ", "final": "@"}, self, "scroll_left") - _parser.set_csi_handler({"final": "A"}, self, "cursor_up") - _parser.set_csi_handler({"intermediates": " ", "final": "A"}, self, "scroll_right") - _parser.set_csi_handler({"final": "B"}, self, "cursor_down") - _parser.set_csi_handler({"final": "C"}, self, "cursor_forward") - _parser.set_csi_handler({"final": "D"}, self, "cursor_backward") - _parser.set_csi_handler({"final": "E"}, self, "cursor_nextLine") - _parser.set_csi_handler({"final": "F"}, self, "cursor_precedingLine") - _parser.set_csi_handler({"final": "G"}, self, "cursor_char_absolute") - _parser.set_csi_handler({"final": "H"}, self, "cursor_position") - _parser.set_csi_handler({"final": "I"}, self, "cursor_forward_tab") - _parser.set_csi_handler({"final": "J"}, self, "erase_in_display") - _parser.set_csi_handler({"prefix": "?", "final": "J"}, self, "erase_in_display") - _parser.set_csi_handler({"final": "K"}, self, "erase_in_line") - _parser.set_csi_handler({"prefix": "?", "final": "K"}, self, "erase_in_line") - _parser.set_csi_handler({"final": "L"}, self, "insert_lines") - _parser.set_csi_handler({"final": "M"}, self, "delete_lines") - _parser.set_csi_handler({"final": "P"}, self, "delete_chars") - _parser.set_csi_handler({"final": "S"}, self, "scroll_up") - _parser.set_csi_handler({"final": "T"}, self, "scroll_down") - _parser.set_csi_handler({"final": "X"}, self, "erase_chars") - _parser.set_csi_handler({"final": "Z"}, self, "cursor_backward_tab") - _parser.set_csi_handler({"final": "`"}, self, "char_pos_absolute") - _parser.set_csi_handler({"final": "a"}, self, "h_position_relative") - _parser.set_csi_handler({"final": "b"}, self, "repeat_preceding_character") - _parser.set_csi_handler({"final": "c"}, self, "send_device_attributes_primary") - _parser.set_csi_handler({"prefix": ">", "final": "c"}, self, "send_device_attributes_secondary") - _parser.set_csi_handler({"final": "d"}, self, "line_pos_absolute") - _parser.set_csi_handler({"final": "e"}, self, "v_position_relative") - _parser.set_csi_handler({"final": "f"}, self, "h_v_position") - _parser.set_csi_handler({"final": "g"}, self, "tab_clear") - _parser.set_csi_handler({"final": "h"}, self, "set_mode") - _parser.set_csi_handler({"prefix": "?", "final": "h"}, self, "set_mode_private") - _parser.set_csi_handler({"final": "l"}, self, "reset_mode") - _parser.set_csi_handler({"prefix": "?", "final": "l"}, self, "reset_mode_private") - _parser.set_csi_handler({"final": "m"}, self, "char_attributes") - _parser.set_csi_handler({"final": "n"}, self, "device_status") - _parser.set_csi_handler({"prefix": "?", "final": "n"}, self, "device_status_private") - _parser.set_csi_handler({"intermediates": "!", "final": "p"}, self, "soft_reset") - _parser.set_csi_handler({"intermediates": " ", "final": "q"}, self, "set_cursor_style") - _parser.set_csi_handler({"final": "r"}, self, "set_scroll_region") - _parser.set_csi_handler({"final": "s"}, self, "save_cursor") - _parser.set_csi_handler({"final": "t"}, self, "window_options") - _parser.set_csi_handler({"final": "u"}, self, "restore_cursor") - _parser.set_csi_handler({"intermediates": "\"", "final": "}"}, self, "insert_columns") - _parser.set_csi_handler({"intermediates": "\"", "final": "~"}, self, "delete_columns") - - # execute handler - _parser.set_execute_handler(C0.BEL, self, "bell") - _parser.set_execute_handler(C0.LF, self, "line_feed") - _parser.set_execute_handler(C0.VT, self, "line_feed") - _parser.set_execute_handler(C0.FF, self, "line_feed") - _parser.set_execute_handler(C0.CR, self, "carriage_return") - _parser.set_execute_handler(C0.BS, self, "backspace") - _parser.set_execute_handler(C0.HT, self, "tab"); - _parser.set_execute_handler(C0.SO, self, "shift_out") - _parser.set_execute_handler(C0.SI, self, "shift_in") - # FIXME: What to do with missing? Old code just added those to print - - _parser.set_execute_handler(C1.IND, self, "index") - _parser.set_execute_handler(C1.NEL, self, "next_line") - _parser.set_execute_handler(C1.HTS, self, "tab_set") - - # ESC handlers - _parser.set_esc_handler({"final": "7"}, self, "save_cursor") - _parser.set_esc_handler({"final": "8"}, self, "restore_cursor") - _parser.set_esc_handler({"final": "D"}, self, "index") - _parser.set_esc_handler({"final": "E"}, self, "next_line") - _parser.set_esc_handler({"final": "H"}, self, "tab_set") - _parser.set_esc_handler({"final": "M"}, self, "reverse_index") - _parser.set_esc_handler({"final": "="}, self, "keypad_application_mode") - _parser.set_esc_handler({"final": ">"}, self, "keypad_numeric_mode") - _parser.set_esc_handler({"final": "c"}, self, "full_reset") - _parser.set_esc_handler({"final": "n"}, self, "set_glevel", 2) - _parser.set_esc_handler({"final": "o"}, self, "set_glevel", 3) - _parser.set_esc_handler({"final": "|"}, self, "set_glevel", 3) - _parser.set_esc_handler({"final": "}"}, self, "set_glevel", 2) - _parser.set_esc_handler({"final": "~"}, self, "set_glevel", 1) - _parser.set_esc_handler({"intermediates": "%", "final": "@"}, self, "select_default_charset") - _parser.set_esc_handler({"intermediates": "%", "final": "G"}, self, "select_default_charset") - for flag in CHARSETS.keys(): - _parser.set_esc_handler({"intermediates": "(", "final": flag}, self, "select_charset", "(" + flag) - _parser.set_esc_handler({"intermediates": ")", "final": flag}, self, "select_charset", ")" + flag) - _parser.set_esc_handler({"intermediates": "*", "final": flag}, self, "select_charset", "*" + flag) - _parser.set_esc_handler({"intermediates": "+", "final": flag}, self, "select_charset", "+" + flag) - _parser.set_esc_handler({"intermediates": "-", "final": flag}, self, "select_charset", "-" + flag) - _parser.set_esc_handler({"intermediates": ".", "final": flag}, self, "select_charset", "." + flag) - _parser.set_esc_handler({"intermediates": "/", "final": flag}, self, "select_charset", "/" + flag) # TODO: supported? - _parser.set_esc_handler({"intermediates": "#", "final": "8"}, self, "screen_alignment_pattern") - - -#func parse(data) -> void: -# if _parse_thread.is_active(): -# _parse_thread.wait_to_finish() -# _parse_thread.start(self, "_parse_async", data) - - -func parse(data) -> void: - var buffer = _buffer_service.buffer - var cursor_start_x = buffer.x - var cursor_start_y = buffer.y - - var data_length = data.length() if typeof(data) == TYPE_STRING else data.size() - - # resize input buffer if needed - if _parse_buffer.size() < data_length and _parse_buffer.size() < MAX_PARSEBUFFER_LENGTH: - _parse_buffer.resize(min(data_length, MAX_PARSEBUFFER_LENGTH)) - - # process big data in smaller chunks - if data_length > MAX_PARSEBUFFER_LENGTH: - var i = 0 - while i < data_length: - var end = i + MAX_PARSEBUFFER_LENGTH if i + MAX_PARSEBUFFER_LENGTH < data_length else data_length - var length - match typeof(data): - TYPE_STRING: - length = _utf8_decoder.decode(data.to_utf8(), _parse_buffer) - TYPE_RAW_ARRAY: - length = _utf8_decoder.decode(data, _parse_buffer) - TYPE_ARRAY: - length = data.size() - _parse_buffer = data.duplicate() - _parser.parse(_parse_buffer, length) - i += MAX_PARSEBUFFER_LENGTH - else: - var length - match typeof(data): - TYPE_STRING: - length = _utf8_decoder.decode(data.to_utf8(), _parse_buffer) - TYPE_RAW_ARRAY: - length = _utf8_decoder.decode(data, _parse_buffer) - TYPE_ARRAY: - length = data.size() - _parse_buffer = data.duplicate() - _parser.parse(_parse_buffer, length) - - buffer = _buffer_service.buffer - if (buffer.x != cursor_start_x or buffer.y != cursor_start_y): - emit_signal("cursor_moved") - - # Refresh all rows. - emit_signal("refresh_rows_requested") - # TODO: Refresh only dirty rows accumulated as part of parsing. - - -func _exit_tree(): - _parse_thread.wait_to_finish() - - -func print(data: Array, start: int, end: int) -> void: - var code: int - var ch_width: int - var buffer = _buffer_service.buffer - var charset = _charset_service.charset - var screen_reader_mode = _options_service.options.screen_reader_mode - var cols = _buffer_service.cols - var wraparound_mode = true #TODO _core_service.modes.wraparound - var insert_mode = false # TODO FIXME! _core_service.modes.insert_mode - var cur_attr = _cur_attr_data - var buffer_row = buffer.lines.get_el(buffer.ybase + buffer.y) - - # TODO: dirtyRowService stuff - - # handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char - if buffer.x and end - start > 0 and buffer_row.get_width(buffer.x - 1) == 2: - buffer_row.set_cell_from_codepoint(buffer.x - 1, 0, 1, cur_attr.fg, cur_attr.bg, cur_attr.extended) - - for pos in range(start, end): - code = data[pos] - - # calculate print space - # expensive call, therefore we save width in line buffer - ch_width = char(code).length() # FIXME - - # get charset replacement character - # charset is only defined for ASCII, therefore we only - # search for an replacement char if code < 127 - if code < 127 and charset: - var ch = charset.get(char(code)) - if ch: - code = ch.ord_at(0) - - if screen_reader_mode: - pass - # TODO: Handle A11y - - # insert combining char at last cursor position - # buffer.x should never be 0 for a combining char - # since they always follow a cell consuming char - # therefore we can test for buffer.x to avoid oveflow left - if (not ch_width) and buffer.x: - if not buffer_row.get_width(buffer.x - 1): - # found empty cell after full_width, need to go 2 cells back - # it is save to step 2 cells back here - # since an empty cell is only set by full_width chars - buffer_row.add_codepoint_to_cell(buffer.x - 2, code) - else: - buffer_row.add_codepoint_to_cell(buffer.x - 1, code) - continue - - # goto next line if ch would overflow - # NOTE: To avoid costly width checks here, - # the terminal does not allow a cols < 2 - if buffer.x + ch_width - 1 >= cols: - # autowrap - DECAWM - # automatically wraps to the beginning of the next line - if wraparound_mode: - while buffer.x < cols: - buffer_row.set_cell_from_codepoint(buffer.x, 0, 1, cur_attr.fg, cur_attr.bg, cur_attr.extended) - buffer.x += 1 - buffer.x = 0 - buffer.y += 1 - if buffer.y == buffer.scroll_bottom + 1: - buffer.y -= 1 - emit_signal("scroll_requested", _erase_attr_data(), true) - else: - if buffer.y >= _buffer_service.rows: - buffer.y = _buffer_service.rows - 1 - # The line already exists (e.g. the initial viewport), mark it as a - # wrapped line - buffer.lines.get_el(buffer.ybase + buffer.y).is_wrapped = true - # row changed, get it again - buffer_row = buffer.lines.get_el(buffer.ybase + buffer.y) - else: - buffer.x = cols - 1 - if ch_width == 2: - # FIXME: check for xterm behavior - # What to do here? We got a wide char that does not fit into last cell - continue - - # insert mode: move characters to right - if insert_mode: - # right shift cells according to the width - buffer_row.insert_cells(buffer.x, ch_width, buffer.get_null_cell(cur_attr), cur_attr) - # test last cell - since the last cell has only room for - # a halfwidth char any fullwidth shifted there is lost - # and will be set to empty cell - if buffer_row.get_width(cols - 1) == 2: - buffer_row.set_cell_from_codepoint(cols - 1, Constants.NULL_CELL_CODE, Constants.NULL_CELL_WIDTH, cur_attr.fg, cur_attr.bg, cur_attr.extended) - - # write current char to buffer and advance cursor - buffer_row.set_cell_from_codepoint(buffer.x, code, ch_width, cur_attr.fg, cur_attr.bg, cur_attr.extended) - buffer.x += 1 - - # fullwidth char - also set next cell to placeholder stub and advance cursor - # for graphemes bigger than fullwidth we can simply loop to zero - # we already made sure above, that buffer.x + ch_width will not overflow right - if ch_width > 0: - ch_width -= 1 - while ch_width: - # other than a regular empty cell a cell following a wide char has no width - buffer_row.set_cell_from_codepoint(buffer.x, 0, 0, cur_attr.fg, cur_attr.bg, cur_attr.extended) - buffer.x += 1 - ch_width -= 1 - - # Store last char in Parser.preceding_codepoint for REP to work correctly - # This needs to check whether: - # - fullwidth + surrogates: reset - # - combining: only base char gets carried on (bug in xterm?) - if end - start > 0: - buffer_row.load_cell(buffer.x - 1, _work_cell) - if _work_cell.get_width() == 2 or _work_cell.get_code() > 0xFFFF: - _parser.preceding_codepoint = 0 - elif _work_cell.is_combined(): - _parser.preceding_codepoint = _work_cell.get_chars().ord_at(0) - else: - _parser.preceding_codepoint = _work_cell.content - - # handle wide chars: reset cell to the right if it is second cell of a wide char - if buffer.x < cols and end - start > 0 and buffer_row.get_width(buffer.x) == 0 and not buffer_row.has_content(buffer.x): - buffer_row.set_cell_from_codepoint(buffer.x, 0, 1, cur_attr.fg, cur_attr.bg, cur_attr.extended) - - # TODO dirty row stuff - # _dirty_row_service.mark_dirty(buffer.y) - - -func bell(): - emit_signal("bell_requested") - - -func line_feed(): - var buffer = _buffer_service.buffer - - if _options_service.options.convert_eol: - buffer.x = 0 - buffer.y += 1 - if buffer.y == buffer.scroll_bottom + 1: - buffer.y -= 1 - emit_signal("scroll_requested", _erase_attr_data()) - elif buffer.y >= _buffer_service.rows: - buffer.y = _buffer_service.rows - 1 - # If the end of the line is hit, prevent this action from wrapping around to the next line. - if buffer.x >= _buffer_service.cols: - buffer.x -= 1 - - emit_signal("line_fed") - - -func carriage_return(): - _buffer_service.buffer.x = 0 - - -func backspace(): - var buffer = _buffer_service.buffer - - # reverse wrap-around is disabled - if not _core_service.dec_private_modes.reverse_wraparound: - _restrict_cursor() - if buffer.x > 0: - buffer.x -= 1 - return - - # reverse wrap-around is enabled - # other than for normal operation mode, reverse wrap-around allows the cursor - # to be at x=cols to be able to address the last cell of a row by BS - _restrict_cursor(_buffer_service.cols) - - if buffer.x > 0: - buffer.x -= 1 - else: - # reverse wrap-around handling: - # Our implementation deviates from xterm on purpose. Details: - # - only previous soft NLs can be reversed (is_wrapped=true) - # - only works within scrollborders (top/bottom, left/right not yet supported) - # - cannot peek into scrollbuffer - # - any cursor movement sequence keeps working as expected - if buffer.x == 0 \ - and buffer.y > buffer.scroll_top \ - and buffer.y <= buffer.scroll_bottom \ - and buffer.lines.get_el(buffer.ybase + buffer.y).is_wrapped: - buffer.lines.get_el(buffer.ybase + buffer.y).is_wrapped = false - buffer.y -= 1 - buffer.x = _buffer_service.cols - 1 - # find last taken cell - last can have 3 different states: - # - has_content(true) + has_width(1): narrow char - we are done - # - has_width(0): second part of a wide char - we are done - # - has_content(false) + has_width(1): empty cell due to early wrapping wide char, go one cell further back - var line = buffer.lines.get_el(buffer.ybase + buffer.y) - if line.has_width(buffer.x) and line.has_content(buffer.x): - buffer.x -= 1 - # We do this only once, since width=1 + has_content= false currently happens only once before - # early wrapping of a wide char. - # This needs to be fixed once we support graphemes taking more than 2 cells. - _restrict_cursor() - - -func tab(): - if _buffer_service.buffer.x >= _buffer_service.cols: - return - var original_x = _buffer_service.buffer.x - _buffer_service.buffer.x = _buffer_service.buffer.next_stop() - # TODO A11y - - -# SO -# Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the -# G1 character set. -# -# @vt: #P[Only limited ISO-2022 charset support.] C0 SO "Shift Out" "\x0E" "Switch to an alternative character set." -func shift_out(): - _charset_service.set_glevel(1) - - -# SI -# Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0 -# character set (the default). -# -# @vt: #Y C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out." -func shift_in(): - _charset_service.set_glevel(0) - - -func _restrict_cursor(max_col: int = _buffer_service.cols - 1) -> void: - var buffer = _buffer_service.buffer - - self._buffer.x = min(max_col, max(0, self._buffer.x)) - if _core_service.dec_private_modes.origin: - self._buffer.y = min(self._buffer.scroll_bottom, max(self._buffer.scroll_top, self._buffer.y)) - else: - self._buffer.y = min(_buffer_service.rows - 1, max(0, self._buffer.y)) - - # _dirty_row_service.mark_dirty(_buffer_service.buffer.y) - - -# Set absolute cursor position. -func _set_cursor(x: int, y: int) -> void: - # _dirty_row_service.mark_dirty(self._buffer.y) - if _core_service.dec_private_modes.origin: - self._buffer.x = x - self._buffer.y = self._buffer.scroll_top + y - else: - self._buffer.x = x - self._buffer.y = y - - -# Set relative cursor position. -func _move_cursor(x: int, y: int) -> void: - # for relative changes we have to make sure we are within 0 .. cols/rows - 1 - # before calculating the new position - _restrict_cursor() - _set_cursor(self._buffer.y + x, self._buffer.y + y) - - -# ESC D -# C1.IND -# DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html) -# Moves the cursor down one line in the same column. -# -# @vt: #Y C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed." -# @vt: #Y ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed." -func index() -> void: - _restrict_cursor() - buffer.y += 1 - if buffer.y == buffer.scroll_bottom + 1: - buffer.y -= 1 - emit_signal("scroll_requested", _erase_attr_data()) - elif buffer.y >= _buffer_service.rows: - buffer.y = _buffer_service.rows - 1 - _restrict_cursor() - - -# ESC E -# C1.NEL -# DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL) -# Moves cursor to first position on next line. -# -# @vt: #Y C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row." -# @vt: #Y ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row." -func next_line() -> void: - buffer.x = 0 - index() - - -# ESC = -# DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html) -# Enables the numeric keypad to send application sequences to the host. -func keypad_application_mode() -> void: - _core_service.dec_private_modes.application_keypad = true - emit_signal("scrollbar_sync_requested") - - -# ESC > -# DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html) -# Enables the keypad to send numeric characters to the host. -func keypad_numeric_mode() -> void: - _core_service.dec_private_modes.application_keypad = false - emit_signal("scrollbar_sync_requested") - - -# ESC % @ -# ESC % G -# Select default character set. UTF-8 is not supported (string are unicode anyways) -# therefore ESC % G does the same. -func select_default_charset() -> void: - _charset_service.set_glevel(0) - _charset_service.set_gcharset(0, Charsets.DEFAULT_CHARSET) # US (default) - - -# ESC ( C -# Designate G0 Character Set, VT100, ISO 2022. -# ESC ) C -# Designate G1 Character Set (ISO 2022, VT100). -# ESC * C -# Designate G2 Character Set (ISO 2022, VT220). -# ESC + C -# Designate G3 Character Set (ISO 2022, VT220). -# ESC - C -# Designate G1 Character Set (VT300). -# ESC . C -# Designate G2 Character Set (VT300). -# ESC / C -# Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported? -func select_charset(collect_and_flag: String) -> void: - if collect_and_flag.length() != 2: - select_default_charset() - return - - if collect_and_flag.substr(0, 1) == "/": - return # TODO: Is this supported? - - var g = GLEVEL[collect_and_flag.substr(0, 1)] - var charset = CHARSETS[collect_and_flag.substr(1, 1)] - _charset_service.set_gcharset(g, charset) - - -# ESC H -# C1.HTS -# DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html) -# Sets a horizontal tab stop at the column position indicated by -# the value of the active column when the terminal receives an HTS. -# -# @vt: #Y C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position." -# @vt: #Y ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position." -func tab_set(): - buffer.tabs[buffer.x] = true - - -# CSI Ps @ -# Insert Ps (Blank) Character(s) (default = 1) (ICH). -# -# @vt: #Y CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)." -# The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters. -# Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost. -# -# -# FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) -func insert_chars(params) -> void: - _restrict_cursor() - var line = buffer.lines.get_el(buffer.ybase + buffer.y) - if line: - line.insert_cells(buffer.x, params.get_param(0, 1), - buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) - - -# CSI Ps SP @ Scroll left Ps columns (default = 1) (SL) ECMA-48 -# -# Notation: (Pn) -# Representation: CSI Pn 02/00 04/00 -# Parameter default value: Pn = 1 -# SL causes the data in the presentation component to be moved by n character positions -# if the line orientation is horizontal, or by n line positions if the line orientation -# is vertical, such that the data appear to move to the left; where n equals the value of Pn. -# The active presentation position is not affected by this control function. -# -# Supported: -# - always left shift (no line orientation setting respected) -# -# @vt: #Y CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left." -# SL moves the content of all lines within the scroll margins `Ps` times to the left. -# SL has no effect outside of the scroll margins. -func scroll_left(params) -> void: - if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: - return - var param = params.get_param(0, 1) - for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): - var line = buffer.lines.get_el(buffer.ybase + y) - line.delete_cells(0, param, buffer.get_null_cell(_erase_attr_data()), - _erase_attr_data()) - line.is_wrapped = false - - -func cursor_up(params) -> void: - # stop at scroll_top - var diff_to_top = self._buffer.y - self._buffer.scroll_top - if diff_to_top >= 0: - _move_cursor(0, -min(diff_to_top, params.get_param(0, 1))) - else: - _move_cursor(0, -params.get_param(0, 1)) - - -# CSI Ps SP A Scroll right Ps columns (default = 1) (SR) ECMA-48 -# -# Notation: (Pn) -# Representation: CSI Pn 02/00 04/01 -# Parameter default value: Pn = 1 -# SR causes the data in the presentation component to be moved by n character positions -# if the line orientation is horizontal, or by n line positions if the line orientation -# is vertical, such that the data appear to move to the right; where n equals the value of Pn. -# The active presentation position is not affected by this control function. -# -# Supported: -# - always right shift (no line orientation setting respected) -# -# @vt: #Y CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right." -# SL moves the content of all lines within the scroll margins `Ps` times to the right. -# Content at the right margin is lost. -# SL has no effect outside of the scroll margins. -func scroll_right(params) -> void: - if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: - return - var param = params.get_param(0, 1) - for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): - var line = buffer.lines.get_el(buffer.ybase + y) - line.insert_cells(0, param, buffer.get_null_cell(_erase_attr_data()), - _erase_attr_data()) - line.is_wrapped = false - - -func cursor_down(params): - # stop at scroll_bottom - var diff_to_bottom = self._buffer.scroll_bottom - self._buffer.y - if diff_to_bottom >= 0: - _move_cursor(0, min(diff_to_bottom, params.get_param(0,1))) - else: - _move_cursor(0, params.get_param(0, 1)) - - -func cursor_forward(params): - _move_cursor(params.get_param(0, 1), 0) - - -func cursor_backward(params): - _move_cursor(-params.get_param(0, 1), 0) - - -func cursor_next_line(params): - cursor_down(params) - self._buffer.x = 0 - - -func cursor_preceding_line(params): - cursor_up(params) - self._buffer.x = 0 - - -func cursor_char_absolute(params): - _set_cursor(params.get_param(0, 1) - 1, self._buffer.y) - - -func cursor_position(params): - _set_cursor( - # col - (params.get_param(1, 1)) - 1 if params.length >= 2 else 0, - # row - (params.get_param(0, 1)) - 1 - ) - - -func char_pos_absolute(params) -> void: - _set_cursor(params.get_param(0, 1) - 1, self._buffer.y) - - -func h_position_relative(params): - _move_cursor(params.get_param(0, 1), 0) - - -func line_pos_absolute(params): - _set_cursor(self._buffer.x, params.get_param(0, 1) - 1) - - -func v_position_relative(params): - _move_cursor(0, params.get_param(0, 1)) - - -func h_v_position(params): - cursor_position(params) - - -# CSI Ps g Tab Clear (TBC). -# Ps = 0 -> Clear Current Column (default). -# Ps = 3 -> Clear All. -# Potentially: -# Ps = 2 -> Clear Stops on Line. -# http://vt100.net/annarbor/aaa-ug/section6.html -# -# @vt: #Y CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)." -# Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported. -func tab_clear(params) -> void: - match params.get_param(0): - 3: - self._buffer.tabs = {} - 0, _: - self._buffer.tabs.erase(self._buffer.x) - - -# CSI Ps I -# Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). -# -# @vt: #Y CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)." -func cursor_forward_tab(params) -> void: - if self._buffer.x >= self._buffer.cols: - return - var param = params.get_param(0, 1) - while param: - self._buffer.x = self._buffer.next_stop() - param -= 1 - - -func cursor_backward_tab(params) -> void: - if self._buffer.x >= _buffer_service.cols: - return - var param = params.get_param(0, 1) - while param: - self._buffer.x = self._buffer.buffer.prev_stop() - param -= 1 - - -# Helper method to erase cells in a terminal row. -# The cell gets replaced with the eraseChar of the terminal. -# params: -# - `y` row index -# - `start` first cell index to be erased -# - `end` end - 1 is last erased cell -func _erase_in_buffer_line(y: int, start: int, end: int, clear_wrap: bool = false) -> void: - var line = self._buffer.lines.get_el(self._buffer.ybase + y) - line.replace_cells(start, end, self._buffer.get_null_cell(_erase_attr_data()), - _erase_attr_data()) - if clear_wrap: - line.is_wrapped = false - - -# Helper method to reset cells in a terminal row. -# The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false. -# @param y row index -func _reset_buffer_line(y: int) -> void: - var line = buffer.lines.get_line(buffer.ybase + y) - line.fill(buffer.get_null_cell(_erase_attr_data())) - line.is_wrapped = false - - -func erase_in_display(params) -> void: - _restrict_cursor() - var j - match params.get_param(0): - 0: - j = buffer.y - # _dirty_row_service.mark_dirty(j) - _erase_in_buffer_line(j, buffer.x, _buffer_service.cols, buffer.x == 0) - j += 1 - while j < _buffer_service.rows: - _reset_buffer_line(j) - j += 1 - # _dirty_row_service.mark_dirty(j) - 1: - j = buffer.y - # _dirty_row_service.mark_dirty(j) - # Deleted front part of line and everything before. This line will no longer be wrapped. - _erase_in_buffer_line(j, 0, buffer.x + 1, true) - if buffer.x + 1 >= _buffer_service.cols: - # Deleted entire previous line. This next line can no longer be wrapped. - buffer.lines.get_el(j + 1).is_wrapped = false - while j > 0: - j -= 1 - _reset_buffer_line(j) - # _dirty_row_service.mark_dirty(0) - 2: - j = _buffer_service.rows - # _dirty_row_service.mark_dirty(j - 1) - while j > 0: - j -= 1 - _reset_buffer_line(j) - # _dirty_row_sevice.mark_dirty(0) - 3: - # Clear scrollback (everything not in viewport) - var scrollback_size = self._buffer.lines.length - _buffer_service.rows - if scrollback_size > 0: - self._buffer.lines.trim_start(scrollback_size) - self._buffer.ybase = max(self._buffer.ybase - scrollback_size, 0) - self._buffer.ydisp = max(self._buffer.ydisp - scrollback_size, 0) - # Force a scroll to refresh viewport - emit_signal("scroll_requested", 0) - - -func erase_in_line(params): - _restrict_cursor() - match params.get_param(0): - 0: - _erase_in_buffer_line(buffer.y, buffer.x, _buffer_service.cols) - 1: - _erase_in_buffer_line(buffer.y, 0, buffer.x + 1) - 2: - _erase_in_buffer_line(buffer.y, 0, _buffer_service.cols) - - -# CSI Ps L -# Insert Ps Line(s) (default = 1) (IL). -# -# @vt: #Y CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)." -# For every inserted line at the scroll top one line at the scroll bottom gets removed. -# The cursor is set to the first column. -# IL has no effect if the cursor is outside the scroll margins. -func insert_lines(params) -> void: - _restrict_cursor() - var param = params.get_param(0, 1) - - if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: - return - - var row: int = buffer.ybase + buffer.y - var scroll_bottom_row_offset = _buffer_service.rows - 1 - buffer.scroll_bottom - var scroll_bottom_absolute = _buffer_service.rows - 1 + buffer.ybase - scroll_bottom_row_offset + 1 - - while param: - # test: echo -e '\e[44m\e[1L\e[0m' - # blank_line(true) - xterm/linux behavior - buffer.lines.splice(scroll_bottom_absolute - 1, 1) - buffer.lines.splice(row, 0, [buffer.get_blank_line(_erase_attr_data())]) - param -= 1 - - buffer.x = 0 # see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only? - - -# CSI Ps M -# Delete Ps Line(s) (default = 1) (DL). -# -# @vt: #Y CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)." -# For every deleted line at the scroll top one blank line at the scroll bottom gets appended. -# The cursor is set to the first column. -# DL has no effect if the cursor is outside the scroll margins. -func delete_lines(params) -> void: - _restrict_cursor() - var param = params.get_param(0, 1) - - if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: - return - - var row: int = buffer.ybase + buffer.y - - var j: int - j = _buffer_service.rows - 1 - buffer.scroll_bottom - j = _buffer_service.rows - 1 + buffer.ybase - j - - while param: - # test echo -e '\e[44m\e[1M\e[0m' - # blank_line(true) - xterm/linux behavior - buffer.lines.splice(row, 1) - buffer.lines.splice(j, 0, [buffer.get_blank_line(_erase_attr_data())]) - param -= 1 - - -# CSI Ps P -# Delete Ps Character(s) (default = 1) (DCH). -# -# @vt: #Y CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)." -# As characters are deleted, the remaining characters between the cursor and right margin move to the left. -# Character attributes move with the characters. The terminal adds blank characters at the right margin. -# -# -# FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual) -func delete_chars(params) -> void: - _restrict_cursor() - var line = buffer.lines.get_el(buffer.ybase + buffer.y) - if line: - line.delete_cells(buffer.x, params.get_param(0, 1), - buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) - #_dirty_row_service.markDirty(buffer.y) - - -# CSI Ps S Scroll up Ps lines (default = 1) (SU). -# -# @vt: #Y CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)." -# -# -# FIXME: scrolled out lines at top = 1 should add to scrollback (xterm) -func scroll_up(params) -> void: - var param = params.get_param(0, 1) - while param: - buffer.lines.splice(buffer.ybase + buffer.scroll_top, 1) - buffer.lines.splice(buffer.ybase + buffer.scroll_bottom, 0, - [buffer.get_blank_line(_erase_attr_data())]) - param -= 1 - - -# CSI Ps T Scroll down Ps lines (default = 1) (SD). -# -# @vt: #Y CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)." -func scroll_down(params) -> void: - var param = params.get_param(0, 1) - while param: - buffer.lines.splice(buffer.ybase + buffer.scroll_bottom, 1) - buffer.lines.splice(buffer.ybase + buffer.scroll_top, 0, - buffer.get_blank_line(DEFAULT_ATTRIBUTE_DATA)) - param -= 1 - - -func erase_chars(params) -> void: - _restrict_cursor() - var line = buffer.lines.get_el(buffer.ybase + buffer.y) - if line: - line.replace_cells(buffer.x, buffer.x + params.get_param(0, 1), - buffer.get_null_cell(_erase_attr_data()), _erase_attr_data()) - #this._dirtyRowService.markDirty(this._bufferService.buffer.y) - - -func repeat_preceding_character(params) -> void: - if not _parser.preceding_codepoint: - return - # call print to insert the chars and handle correct wrapping - var length = params.get_param(0, 1) - var data = [] - for _i in range(length): - data.append(_parser.preceding_codepoint) - self.print(data, 0, length) - - -func send_device_attributes_primary(params): - # TODO - pass - - -func send_device_attributes_secondary(params): - # TODO - pass - - -func set_mode(params): - # TODO - pass - - -func reset_mode(params) -> void: - for param in params.params: - match param: - 4: - _core_service.modes.insert_mode = false - 20: - #this._t.convertEol = false - pass - - -func char_attributes(params): - # Optimize a single SGR0 - if params.length == 1 and params.get_param(0) == 0: - _cur_attr_data.fg = AttributeData.new().fg - _cur_attr_data.bg = AttributeData.new().bg - return - - var attr = _cur_attr_data - - for i in range(params.length): - var p = params.get_param(i) - if p >= 30 and p <= 37: - # fg color 8 - attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) - attr.fg |= Attributes.CM_P16 | (p - 30) - elif p >= 40 and p <= 47: - # bg color 8 - attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) - attr.bg |= Attributes.CM_P16 | (p - 40) - elif p >= 90 and p <= 97: - # fg color 16 - attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) - attr.fg |= Attributes.CM_P16 | (p - 90) | 8 - elif p >= 100 and p <= 107: - # bg color 16 - attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) - attr.bg |= Attributes.CM_P16 | (p - 100) | 8 - elif p == 0: - # default - attr.fg = DEFAULT_ATTRIBUTE_DATA.fg - attr.bg = DEFAULT_ATTRIBUTE_DATA.bg - elif p == 1: - # bold text - attr.fg |= FgFlags.BOLD - elif p == 3: - # italic text - attr.bg |= BgFlags.ITALIC - elif p == 4: - # underlined text - attr.fg |= FgFlags.UNDERLINE - _process_underline(params.get_sub_params(i)[0] if params.has_sub_params(i) else UnderlineStyle.SINGLE, attr) - elif p == 5: - # blink - # test with: echo -e '\e[5mblink\e[m' - attr.fg |= FgFlags.BLINK - elif p == 7: - # inverse and positive - # test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' - attr.fg |= FgFlags.INVERSE - elif p == 8: - # invisible - attr.fg |= FgFlags.INVISIBLE - elif p == 2: - # dimmed text - attr.bg |= BgFlags.DIM - elif p == 21: - # double underline - _process_underline(UnderlineStyle.DOUBLE, attr) - elif p == 22: - # not bold nor faint - attr.fg &= ~FgFlags.BOLD - attr.bg &= ~BgFlags.DIM - elif p == 23: - # not italic - attr.bg &= ~BgFlags.ITALIC - elif p == 24: - # not underlined - attr.fg &= ~FgFlags.UNDERLINE - elif p == 25: - # not blink - attr.fg &= ~FgFlags.BLINK - elif p == 27: - # not inverse - attr.fg &= ~FgFlags.INVERSE - elif p == 28: - # not invisible - attr.fg &= ~FgFlags.INVISIBLE - elif p == 39: - # reset fg - attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK) - attr.fg |= AttributeData.new().fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK) - elif p == 49: - # reset bg - attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK) - attr.bg |= AttributeData.new().bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK) - elif p == 38 or p == 48 or p == 58: - # fg color 256 and RGB - i += _extract_color(params, i, attr) - elif p == 59: - attr.extended = attr.extended.duplicate() - attr.extended.underline_color = -1 - attr.update_extended() - elif p == 100: # FIXME: dead branch, p=100 already handled above! - # TODO reset fg/bg - pass - - -func device_status(params): - # TODO - pass - - -func device_status_private(params): - # TODO - pass - - -# CSI ! p Soft terminal reset (DECSTR). -# http://vt100.net/docs/vt220-rm/table4-10.html -# -# @vt: #Y CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state." -# There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap, -# DECSTR only resets certain attributes. For most needs DECSTR should be sufficient. -# -# The following terminal attributes are reset to default values: -# - IRM is reset (dafault = false) -# - scroll margins are reset (default = viewport size) -# - erase attributes are reset to default -# - charsets are reset -# - DECSC data is reset to initial values -# - DECOM is reset to absolute mode -# -# -# FIXME: there are several more attributes missing (see VT520 manual) -func soft_reset(params): - _core_service.is_cursor_hidden = false - emit_signal("scrollbar_sync_requested") - buffer.scroll_top = 0 - buffer.scroll_bottom = _buffer_service.rows - 1 - _cur_attr_data = DEFAULT_ATTRIBUTE_DATA - _core_service.reset() - _charset_service.reset() - - # reset DECSC data - buffer.saved_x = 0 - buffer.saved_y = buffer.ybase - buffer.saved_cur_attr_data.fg = _cur_attr_data.fg - buffer.saved_cur_attr_data.bg = _cur_attr_data.bg - buffer.saved_charset = _charset_service.charset - - # reset DECOM - _core_service.dec_private_modes.origin = false - - -# CSI Ps SP q Set cursor style (DECSCUSR, VT520). -# Ps = 0 -> blinking block. -# Ps = 1 -> blinking block (default). -# Ps = 2 -> steady block. -# Ps = 3 -> blinking underline. -# Ps = 4 -> steady underline. -# Ps = 5 -> blinking bar (xterm). -# Ps = 6 -> steady bar (xterm). -# -# @vt: #Y CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style." -# Supported cursor styles: -# - empty, 0 or 1: steady block -# - 2: blink block -# - 3: steady underline -# - 4: blink underline -# - 5: steady bar -# - 6: blink bar -func set_cursor_style(params) -> void: - var param = params.get_param(0, 1) - - match param: - 1, 2: - _options_service.options.cursor_style = CursorStyle.BLOCK - 3, 4: - _options_service.options.cursor_style = CursorStyle.UNDERLINE - 5, 6: - _options_service.options.cursor_style = CursorStyle.BAR - - var is_blinking = param % 2 == 1 - _options_service.options.cursor_blink = is_blinking - - -func set_scroll_region(params) -> void: - var top = params.get_param(0, 1) - var bottom = params.get_param(1, 0) - - if bottom > _buffer_service.rows or bottom == 0: - bottom = _buffer_service.rows - - if bottom > top: - buffer.scroll_top = top - 1 - buffer.scroll_bottom = bottom - 1 - _set_cursor(0, 0) - - -func save_cursor(params = null): - self._buffer.saved_x = self._buffer.x - self._buffer.saved_y = self._buffer.ybase + self._buffer.y - self._buffer.saved_cur_attr_data.fg = _cur_attr_data.fg - self._buffer.saved_cur_attr_data.bg = _cur_attr_data.bg - self._buffer.saved_charset = _charset_service.charset - - -func window_options(params): - var second = params.get_param(1, 0) - match params.get_param(0): - 14: - pass - 16: - pass - 18: - pass - 22: - pass - 23: - pass - - -# CSI Pm ' } -# Insert Ps Column(s) (default = 1) (DECIC), VT420 and up. -# -# @vt: #Y CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position." -# DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll margins, -# moving content to the right. Content at the right margin is lost. -# DECIC has no effect outside the scrolling margins. -func insert_columns(params): - if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: - return - - var param = params.get_param(0, 1) - - for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): - var line = buffer.lines.get_el(buffer.ybase + y) - line.insert_cells(buffer.x, param, buffer.get_null_cells(_erase_attr_data()), - _erase_attr_data()) - line.is_wrapped = false - - -# CSI Pm ' ~ -# Delete Ps Column(s) (default = 1) (DECDC), VT420 and up. -# -# @vt: #Y CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position." -# DECDC deletes `Ps` times columns at the cursor position for all lines with the scroll margins, -# moving content to the left. Blank columns are added at the right margin. -# DECDC has no effect outside the scrolling margins. -func delete_columns(params): - if buffer.y > buffer.scroll_bottom or buffer.y < buffer.scroll_top: - return - - var param = params.get_param(0, 1) - - for y in range(buffer.scroll_top, buffer.scroll_bottom + 1): - var line = buffer.lines.get_el(buffer.ybase + y) - line.delete_cells(buffer.x, param, buffer.get_null_cells(_erase_attr_data()), - _erase_attr_data()) - line.is_wrapped = false - - -func set_mode_private(params) -> void: - for param in params.params: - match param: - 1: - _core_service.dec_private_modes.application_cursor_keys = true - 2: - _charset_service.set_gcharset(0, Charsets.DEFAULT_CHARSET) - _charset_service.set_gcharset(1, Charsets.DEFAULT_CHARSET) - _charset_service.set_gcharset(2, Charsets.DEFAULT_CHARSET) - _charset_service.set_gcharset(3, Charsets.DEFAULT_CHARSET) - # set VT100 mode here - 3: - # DECCOLM - 132 column mode. - # This is only active if 'set_win_lines' (24) is enabled - # through `options.window_options`. - if _options_service.options.window_options.set_win_lines: - _buffer_service.resize(132, _buffer_service.rows) - emit_signal("reset_requested") - 6: - _core_service.dec_private_modes.origin = true - _set_cursor(0, 0) - 7: - _core_service.dec_private_modes.wraparound = true - 12: - # cursor_blink = true - # TODO handle cursor blink - pass - 45: - _core_service.dec_private_modes.reverse_wraparound = true - 66: - _core_service.dec_private_modes.application_keypad = true - emit_signal("scrollbar_sync_requested") - 9: # X10 Mouse - # no release, no motion, no wheel, no modifiers. - # _core_mouse_service.active_protocal = 'X10' - # TODO - pass - 1000: # vt200 mouse - pass - 1002: # button event mouse - pass - 1003: # any event mouse - pass - 1004: # send focusin/focusout events - # focusin: ^[[I - # focusout: ^[[O - _core_service.dec_private_modes.send_focus = true - 1005: # utf8 ext mode mouse - removed in # 2507 - pass - 1006: # sgr ext mode mouse - pass - 1015: - pass - 25: # show cursor - _core_service.is_cursor_hidden = false - 1048: # alt screen cursor - save_cursor() - 1049: # alt screen buffer cursor - save_cursor() - continue - 47, 1047, 1049: # alt screen buffer - _buffer_service.buffers.activate_alt_buffer(_erase_attr_data()) - _core_service.is_cursor_initialized = true - emit_signal("refresh_rows_requested", 0, _buffer_service.rows - 1) - emit_signal("scrollbar_sync_requested") - 2004: # bracketed paste mode (https://cirw.in/blog/bracketed-paste) - _core_service.dec_private_modes.bracketed_paste_mode = true - - -func reset_mode_private(params): - for param in params.to_array(): - match param: - 1: - _core_service.dec_private_modes.application_cursor_keys = false - 3: - # DECCOLM - 80 column mode. - # This is only active if 'set_win_lines' (24) is enabled - # through `options.windows_options`. - if _options_service.options.window_options.get("set_win_lines", false): - _buffer_service.resize(80, _buffer_service.rows) - emit_signal("reset_requested") - 6: - _core_service.dec_private_modes.origin = false - _set_cursor(0, 0) - 7: - _core_service.dec_private_modes.wraparound = false - 12: - # cursor_blink = false - # TODO: Handle cursor_blink - pass - 45: - _core_service.dec_private_modes.reverse_wraparound = false - 66: - _core_service.dec_private_modes.application_keypad = false - emit_signal("scrollbar_sync_requested") - 9, 1000, 1002, 1003: - # X10 Mouse, vt200 mouse, button event mouse and any event mouse respectively. - # TODO: Core mouse service - # _core_mouse_service.active_protocal = "NONE" - pass - 1004: # send focusin/focusout events - _core_service.dec_private_modes.send_focus = false - 1005: # utf8 ext mode mouse - removed in #2507 - pass - 1006: # sgr ext mode mouse - # TODO - pass - 1015: # urxvt ext mode mouse - removed in #2507 - pass - 25: # hide cursor - _core_service.is_cursor_hidden = true - pass - 1048: # alt screen cursor - restore_cursor() - 1049, 47, 1047: - # Ensure the selection manager has the correct buffer. - _buffer_service.buffers.activate_normal_buffer() - if param == 1049: - restore_cursor() - _core_service.is_cursor_initialized = true - emit_signal("refresh_rows_requested", 0, _buffer_service.rows - 1) - emit_signal("scrollbar_sync_requested") - 2004: # bracketed paste mode (https://cirw.in/blog/bracketed-paste) - _core_service.dec_private_modes.bracketed_paste_mode = false - - -# Helper to write color information packed with color mode. -func _update_attr_color(color: int, mode: int, c1: int, c2: int, c3: int) -> int: - if mode == 2: - color |= Attributes.CM_RGB - color &= ~Attributes.RGB_MASK - color |= AttributeData.from_color_rgb([c1, c2, c3]) - elif mode == 5: - color &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK) - color |= Attributes.CM_P256 | (c1 & 0xff) - return color - - -# Helper to extract and apply color params/subparams. -# Returns advance for params index. -func _extract_color(params, pos: int, attr) -> int: - # normalize params - # meaning: [target, CM, ign, val, val, val] - # RGB : [ 38/34, 2, ign, r, g, b] - # P256 : [ 38/34, 5, ign, v, ign, ign] - var accu = [0, 0, -1, 0, 0, 0] - - # alignment placeholder for non color space sequences - var c_space = 0 - - # return advance we took in params - var advance = -1 - - while advance + pos < params.length and advance + c_space < accu.size(): - accu[advance + c_space] = params.get_param(pos + advance) - advance += 1 - # TODO FIX and FINISH me - return advance - - -func restore_cursor(params = null) -> void: - self._buffer.x = self._buffer.saved_x if self._buffer.saved_x else 0 - self._buffer.y = max(self._buffer.saved_y - self._buffer.ybase, 0) - _cur_attr_data.fg = self._buffer.saved_cur_attr_data.fg - _cur_attr_data.bg = self._buffer.saved_cur_attr_data.bg - # FIXME _charset_service.charset = _saved_charset - if self._buffer.saved_charset: - _charset_service.charset = self._buffer.saved_charset - _restrict_cursor() - - -# ESC M -# C1.RI -# DEC mnemonic: HTS -# Moves the cursor up one line in the same column. If the cursor is at the top margin, -# the page scrolls down. -# -# @vt: #Y ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed." -# -func reverse_index() -> void: - _restrict_cursor() - if buffer.y == buffer.scroll_top: - # possibly move the code below to term.reverse_srcoll() - # test: echo -ne '\e[1;1H\e[44m\eM\e[0m' - # blank_line(true) is xterm/linux behavior - var scroll_region_height = buffer.scroll_bottom - buffer.scroll_top - buffer.lines.shift_elements(buffer.ybase + buffer.y, scroll_region_height, 1) - buffer.lines.set_line(buffer.ybase + buffer.y, buffer.get_blank_line(_erase_attr_data())) - else: - buffer.y -= 1 - _restrict_cursor() # quickfix to not run out of bounds - - -# ESC c -# DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html) -# Reset to initial state. -func full_reset() -> void: - _parser.reset() - emit_signal("reset_requested") - - -func reset() -> void: - _cur_attr_data = DEFAULT_ATTRIBUTE_DATA - _erase_attr_data_internal = DEFAULT_ATTRIBUTE_DATA - - -func _process_underline(style: int, attr) -> void: - # treat extended attrs as immutable, thus always clone from old one - # this is needed since the buffer only holds references to it - attr.extended = attr.extended.duplicate() - - # default to 1 == single underline - if not ~style or style > 5: - style = 1 - attr.extended.underline_style = style - attr.fg |= FgFlags.UNDERLINE - - # 0 deactivates underline - if style == 0: - attr.fg &= ~FgFlags.UNDERLINE - - # update HAS_EXTENDED in BG - attr.update_extended() - - - -# back_color_erase feature for xterm. -func _erase_attr_data(): - _erase_attr_data_internal.bg &= ~(Attributes.CM_MASK | 0xFFFFFF) - _erase_attr_data_internal.bg |= _cur_attr_data.bg & ~0xFC000000 - return _erase_attr_data_internal - - -# ESC # 8 -# DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html) -# This control function fills the complete screen area with -# a test pattern (E) used for adjusting screen alignment. -# -# @vt: #Y ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)." -func screen_alignment_pattern() -> void: - # prepare cell data - var cell = CellData.new() - cell.content = 1 << Content.WIDTH_SHIFT | 'E'.ord_at(0) - cell.fg = _cur_attr_data.fg - cell.bg = _cur_attr_data.bg - - _set_cursor(0, 0) - - for y_offset in range(0, _buffer_service.rows): - var row = buffer.ybase + buffer.y + y_offset - var line = buffer.lines.get_line(row) - if line: - line.fill(cell) - line.is_wrapped = false - _set_cursor(0, 0) diff --git a/addons/godot_xterm_native/libtsm b/addons/godot_xterm/libtsm similarity index 100% rename from addons/godot_xterm_native/libtsm rename to addons/godot_xterm/libtsm diff --git a/addons/godot_xterm/parser/constants.gd b/addons/godot_xterm/parser/constants.gd deleted file mode 100644 index 6ef558c..0000000 --- a/addons/godot_xterm/parser/constants.gd +++ /dev/null @@ -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 -} diff --git a/addons/godot_xterm/parser/dcs_parser.gd b/addons/godot_xterm/parser/dcs_parser.gd deleted file mode 100644 index 5746d55..0000000 --- a/addons/godot_xterm/parser/dcs_parser.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/parser/escape_sequence_parser.gd b/addons/godot_xterm/parser/escape_sequence_parser.gd deleted file mode 100644 index ee5c155..0000000 --- a/addons/godot_xterm/parser/escape_sequence_parser.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/parser/params.gd b/addons/godot_xterm/parser/params.gd deleted file mode 100644 index 49bf95a..0000000 --- a/addons/godot_xterm/parser/params.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/parser/transition_table.gd b/addons/godot_xterm/parser/transition_table.gd deleted file mode 100644 index 860c452..0000000 --- a/addons/godot_xterm/parser/transition_table.gd +++ /dev/null @@ -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) diff --git a/addons/godot_xterm/parser/vt500_transition_table.gd b/addons/godot_xterm/parser/vt500_transition_table.gd deleted file mode 100644 index 09993a1..0000000 --- a/addons/godot_xterm/parser/vt500_transition_table.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/plugin.cfg b/addons/godot_xterm/plugin.cfg index ff9fc59..4c733ed 100644 --- a/addons/godot_xterm/plugin.cfg +++ b/addons/godot_xterm/plugin.cfg @@ -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" diff --git a/addons/godot_xterm/plugin.gd b/addons/godot_xterm/plugin.gd index 173102b..48f5e24 100644 --- a/addons/godot_xterm/plugin.gd +++ b/addons/godot_xterm/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") diff --git a/addons/godot_xterm_native/pseudoterminal.gdns b/addons/godot_xterm/pseudoterminal.gdns similarity index 59% rename from addons/godot_xterm_native/pseudoterminal.gdns rename to addons/godot_xterm/pseudoterminal.gdns index db30774..19c7ba2 100644 --- a/addons/godot_xterm_native/pseudoterminal.gdns +++ b/addons/godot_xterm/pseudoterminal.gdns @@ -1,6 +1,6 @@ [gd_resource type="NativeScript" load_steps=2 format=2] -[ext_resource path="res://addons/godot_xterm_native/godotxtermnative.gdnlib" type="GDNativeLibrary" id=1] +[ext_resource path="res://addons/godot_xterm/godotxtermnative.gdnlib" type="GDNativeLibrary" id=1] [resource] resource_name = "Terminal" diff --git a/addons/godot_xterm_native/pseudoterminal_icon.svg b/addons/godot_xterm/pseudoterminal_icon.svg similarity index 100% rename from addons/godot_xterm_native/pseudoterminal_icon.svg rename to addons/godot_xterm/pseudoterminal_icon.svg diff --git a/addons/godot_xterm/icon.svg.import b/addons/godot_xterm/pseudoterminal_icon.svg.import similarity index 66% rename from addons/godot_xterm/icon.svg.import rename to addons/godot_xterm/pseudoterminal_icon.svg.import index a4fb527..d351d90 100644 --- a/addons/godot_xterm/icon.svg.import +++ b/addons/godot_xterm/pseudoterminal_icon.svg.import @@ -2,15 +2,15 @@ importer="texture" type="StreamTexture" -path="res://.import/icon.svg-b0b594198f5f4040e8f0e39a8a353265.stex" +path="res://.import/pseudoterminal_icon.svg-50ba2514dae785a6b48b0da604cf3a09.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/pseudoterminal_icon.svg" +dest_files=[ "res://.import/pseudoterminal_icon.svg-50ba2514dae785a6b48b0da604cf3a09.stex" ] [params] diff --git a/addons/godot_xterm/renderer/canvas_rendering_context_2d.gd b/addons/godot_xterm/renderer/canvas_rendering_context_2d.gd deleted file mode 100644 index 3e14e6e..0000000 --- a/addons/godot_xterm/renderer/canvas_rendering_context_2d.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/renderer/character_joiner_registry.gd b/addons/godot_xterm/renderer/character_joiner_registry.gd deleted file mode 100644 index c6d86a7..0000000 --- a/addons/godot_xterm/renderer/character_joiner_registry.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/services/buffer_service.gd b/addons/godot_xterm/services/buffer_service.gd deleted file mode 100644 index 62d7124..0000000 --- a/addons/godot_xterm/services/buffer_service.gd +++ /dev/null @@ -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) diff --git a/addons/godot_xterm/services/charset_service.gd b/addons/godot_xterm/services/charset_service.gd deleted file mode 100644 index 8e098f0..0000000 --- a/addons/godot_xterm/services/charset_service.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm/services/core_service.gd b/addons/godot_xterm/services/core_service.gd deleted file mode 100644 index 9d7b0df..0000000 --- a/addons/godot_xterm/services/core_service.gd +++ /dev/null @@ -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() diff --git a/addons/godot_xterm/services/options_service.gd b/addons/godot_xterm/services/options_service.gd deleted file mode 100644 index f056729..0000000 --- a/addons/godot_xterm/services/options_service.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm_native/src/libgodotxtermnative.cpp b/addons/godot_xterm/src/libgodotxtermnative.cpp similarity index 100% rename from addons/godot_xterm_native/src/libgodotxtermnative.cpp rename to addons/godot_xterm/src/libgodotxtermnative.cpp diff --git a/addons/godot_xterm_native/src/pseudoterminal.cpp b/addons/godot_xterm/src/pseudoterminal.cpp similarity index 100% rename from addons/godot_xterm_native/src/pseudoterminal.cpp rename to addons/godot_xterm/src/pseudoterminal.cpp diff --git a/addons/godot_xterm_native/src/pseudoterminal.h b/addons/godot_xterm/src/pseudoterminal.h similarity index 100% rename from addons/godot_xterm_native/src/pseudoterminal.h rename to addons/godot_xterm/src/pseudoterminal.h diff --git a/addons/godot_xterm_native/src/terminal.cpp b/addons/godot_xterm/src/terminal.cpp similarity index 100% rename from addons/godot_xterm_native/src/terminal.cpp rename to addons/godot_xterm/src/terminal.cpp diff --git a/addons/godot_xterm_native/src/terminal.h b/addons/godot_xterm/src/terminal.h similarity index 100% rename from addons/godot_xterm_native/src/terminal.h rename to addons/godot_xterm/src/terminal.h diff --git a/addons/godot_xterm/terminal.gd b/addons/godot_xterm/terminal.gd deleted file mode 100644 index fd7fbd8..0000000 --- a/addons/godot_xterm/terminal.gd +++ /dev/null @@ -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 diff --git a/addons/godot_xterm_native/terminal.gdns b/addons/godot_xterm/terminal.gdns similarity index 58% rename from addons/godot_xterm_native/terminal.gdns rename to addons/godot_xterm/terminal.gdns index 4a21b9f..334e2d6 100644 --- a/addons/godot_xterm_native/terminal.gdns +++ b/addons/godot_xterm/terminal.gdns @@ -1,6 +1,6 @@ [gd_resource type="NativeScript" load_steps=2 format=2] -[ext_resource path="res://addons/godot_xterm_native/godotxtermnative.gdnlib" type="GDNativeLibrary" id=1] +[ext_resource path="res://addons/godot_xterm/godotxtermnative.gdnlib" type="GDNativeLibrary" id=1] [resource] resource_name = "Terminal" diff --git a/addons/godot_xterm_native/terminal_icon.svg b/addons/godot_xterm/terminal_icon.svg similarity index 100% rename from addons/godot_xterm_native/terminal_icon.svg rename to addons/godot_xterm/terminal_icon.svg diff --git a/addons/gut/icon.png.import b/addons/godot_xterm/terminal_icon.svg.import similarity index 67% rename from addons/gut/icon.png.import rename to addons/godot_xterm/terminal_icon.svg.import index 848473e..d8ea732 100644 --- a/addons/gut/icon.png.import +++ b/addons/godot_xterm/terminal_icon.svg.import @@ -2,15 +2,15 @@ importer="texture" type="StreamTexture" -path="res://.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex" +path="res://.import/terminal_icon.svg-33ee6ad8b86db2f37e5d8d61a6b1b8db.stex" metadata={ "vram_texture": false } [deps] -source_file="res://addons/gut/icon.png" -dest_files=[ "res://.import/icon.png-91b084043b8aaf2f1c906e7b9fa92969.stex" ] +source_file="res://addons/godot_xterm/terminal_icon.svg" +dest_files=[ "res://.import/terminal_icon.svg-33ee6ad8b86db2f37e5d8d61a6b1b8db.stex" ] [params] diff --git a/addons/godot_xterm/themes/default.theme b/addons/godot_xterm/themes/default.theme new file mode 100644 index 0000000000000000000000000000000000000000..6f200eaae70e7dafc74bb8115eb709baa1c8efe6 GIT binary patch literal 703 zcmV;w0zmyzQ$s@n000005C8x^2mk=50ssIgwJ-f(J_qd-07e^JJwS^?06Z{H5xs^M z*#q;y1IVVad_RNq>F-GLfB(r(;*wiq2*J&SXLmXC2TUN@mX>+Zw)V{cIwcR4!}bg z$SDehN@?rVIx_V?z_)U#AGdLfyBUS!A?{Yi=QK2%%1+rok*7%+BMIqrWE1x+jiS!~ zjQ(P!|X$26EjHW-;qY!uH$x1d6 ztvu0yq;@(U100V9&wq*q3KAfYMw$a4A0E;u(;-Rl@WBA)V&RZR|AvP25r+a9(5 z5%GyX;ujIVVY4?Dm_G>d5fB&Ya0iT$jEF#>x;G6rzIidnlG`b*dE$!AfRU(i!9g`7 z0|W#h0RXT9fg-AH5+KtsPD9EefkHrm1QkFq0Z2d^F$yDzuQ4Mxb=DcE#CM_x-wWJ4 z`~F(^!4r5P!gJss1fF=4;0gi!OKDg66467&F7uK0ae(C6gVFVoKju4Ouittx(^XY%@YxB$Hnz zB8v&(qCmB7%zJI*fTpKyb1~pYR|OeFm_XRYH@t2YH#QQSRuUBJ_27@6F+1;efnFH{NI1#p9C2z-I^_y=PuC-(GrZ2fs9=lk_flFF@Qtfu%rh< z07w8%0RK5Ix8a)zd|FyruWCE5{~4DI*=e+m5BA0X3w%ku`9Hw7(x~x`>t^F_P#wdz zPF_4${{Q&z;9ITd!+#8m{{a3RkVge=_JSX$&~hnWEUAyv5;hss_$s-Ww}zKaE4B5c z6L1LZ;}iu#owQYI9eMgc;9I#=kL!5F-HhUKi@QPck2=0h^A8&i{)B3>GMeLYV|3A|X)-0}^R4aRR}|#o`f#{tk}lp%Mo+ zqR?-^qT;W3Mmq_cT>-->U|c>S#U-G41jaO)3mK+vk_Q{#ym+%BUTbWX+)im7fRm_^ zy`dVC0RjR@5TF8qqN;5YAk!>QL&_n6LO>Y^Du7@DB9_<;V~8&}Q#WK58K~p?um_(D zqbcyNECQFNWW6Bfqu)sLx@+1h2IqURz~QkX?%?k^I(r=)zwurivWG0~;jmYZ&l%!P zOo>w!u(nKH+V#`<2uxD%WQ`sm%@O8jUT(y4^GV7vTd#4FrN1Jnv$Q1HXPVo`I7_fz zXG8!XE=6dHQ%PanLa}ePup{A~q6Wz?CQaq^kMIJrYrl=AAOu<@2wEE=*6o1-Jo_u6 zFIemk>K(RPKb%EY2I!)==6|#YxirI!iZW(XaF9>}FnRA7hZwytB0LU{qNl+}$T@RR rVXc^nS}JE!=zV`=V%jpfg)&>!N%STX&pb~9czNk}j{1rLQd2`i4D~~S diff --git a/addons/gut/GutScene.gd b/addons/gut/GutScene.gd deleted file mode 100644 index 4139dd4..0000000 --- a/addons/gut/GutScene.gd +++ /dev/null @@ -1,347 +0,0 @@ -extends Panel - -onready var _script_list = $ScriptsList -onready var _nav = { - prev = $Navigation/Previous, - next = $Navigation/Next, - run = $Navigation/Run, - current_script = $Navigation/CurrentScript, - show_scripts = $Navigation/ShowScripts -} -onready var _progress = { - script = $ScriptProgress, - test = $TestProgress -} -onready var _summary = { - failing = $Summary/Failing, - passing = $Summary/Passing -} - -onready var _extras = $ExtraOptions -onready var _ignore_pauses = $ExtraOptions/IgnorePause -onready var _continue_button = $Continue/Continue -onready var _text_box = $TextDisplay/RichTextLabel - -onready var _titlebar = { - bar = $TitleBar, - time = $TitleBar/Time, - label = $TitleBar/Title -} - -var _mouse = { - down = false, - in_title = false, - down_pos = null, - in_handle = false -} -var _is_running = false -var _start_time = 0.0 -var _time = 0.0 - -const DEFAULT_TITLE = 'Gut: The Godot Unit Testing tool.' -var _utils = load('res://addons/gut/utils.gd').new() -var _text_box_blocker_enabled = true -var _pre_maximize_size = null - -signal end_pause -signal ignore_pause -signal log_level_changed -signal run_script -signal run_single_script - -func _ready(): - _pre_maximize_size = rect_size - _hide_scripts() - _update_controls() - _nav.current_script.set_text("No scripts available") - set_title() - clear_summary() - $TitleBar/Time.set_text("") - $ExtraOptions/DisableBlocker.pressed = !_text_box_blocker_enabled - _extras.visible = false - update() - -func _process(_delta): - if(_is_running): - _time = OS.get_unix_time() - _start_time - var disp_time = round(_time * 100)/100 - $TitleBar/Time.set_text(str(disp_time)) - -func _draw(): # needs get_size() - # Draw the lines in the corner to show where you can - # drag to resize the dialog - var grab_margin = 3 - var line_space = 3 - var grab_line_color = Color(.4, .4, .4) - for i in range(1, 10): - var x = rect_size - Vector2(i * line_space, grab_margin) - var y = rect_size - Vector2(grab_margin, i * line_space) - draw_line(x, y, grab_line_color, 1, true) - -func _on_Maximize_draw(): - # draw the maximize square thing. - var btn = $TitleBar/Maximize - btn.set_text('') - var w = btn.get_size().x - var h = btn.get_size().y - btn.draw_rect(Rect2(0, 0, w, h), Color(0, 0, 0, 1)) - btn.draw_rect(Rect2(2, 4, w - 4, h - 6), Color(1,1,1,1)) - -func _on_ShowExtras_draw(): - var btn = $Continue/ShowExtras - btn.set_text('') - var start_x = 20 - var start_y = 15 - var pad = 5 - var color = Color(.1, .1, .1, 1) - var width = 2 - for i in range(3): - var y = start_y + pad * i - btn.draw_line(Vector2(start_x, y), Vector2(btn.get_size().x - start_x, y), color, width, true) - -# #################### -# GUI Events -# #################### -func _on_Run_pressed(): - _run_mode() - emit_signal('run_script', get_selected_index()) - -func _on_CurrentScript_pressed(): - _run_mode() - emit_signal('run_single_script', get_selected_index()) - -func _on_Previous_pressed(): - _select_script(get_selected_index() - 1) - -func _on_Next_pressed(): - _select_script(get_selected_index() + 1) - -func _on_LogLevelSlider_value_changed(_value): - emit_signal('log_level_changed', $LogLevelSlider.value) - -func _on_Continue_pressed(): - _continue_button.disabled = true - emit_signal('end_pause') - -func _on_IgnorePause_pressed(): - var checked = _ignore_pauses.is_pressed() - emit_signal('ignore_pause', checked) - if(checked): - emit_signal('end_pause') - _continue_button.disabled = true - -func _on_ShowScripts_pressed(): - _toggle_scripts() - -func _on_ScriptsList_item_selected(index): - _select_script(index) - -func _on_TitleBar_mouse_entered(): - _mouse.in_title = true - -func _on_TitleBar_mouse_exited(): - _mouse.in_title = false - -func _input(event): - if(event is InputEventMouseButton): - if(event.button_index == 1): - _mouse.down = event.pressed - if(_mouse.down): - _mouse.down_pos = event.position - - if(_mouse.in_title): - if(event is InputEventMouseMotion and _mouse.down): - set_position(get_position() + (event.position - _mouse.down_pos)) - _mouse.down_pos = event.position - - if(_mouse.in_handle): - if(event is InputEventMouseMotion and _mouse.down): - var new_size = rect_size + event.position - _mouse.down_pos - var new_mouse_down_pos = event.position - rect_size = new_size - _mouse.down_pos = new_mouse_down_pos - _pre_maximize_size = rect_size - -func _on_ResizeHandle_mouse_entered(): - _mouse.in_handle = true - -func _on_ResizeHandle_mouse_exited(): - _mouse.in_handle = false - -# Send scroll type events through to the text box -func _on_FocusBlocker_gui_input(ev): - if(_text_box_blocker_enabled): - if(ev is InputEventPanGesture): - get_text_box()._gui_input(ev) - # convert a drag into a pan gesture so it scrolls. - elif(ev is InputEventScreenDrag): - var converted = InputEventPanGesture.new() - converted.delta = Vector2(0, ev.relative.y) - converted.position = Vector2(0, 0) - get_text_box()._gui_input(converted) - elif(ev is InputEventMouseButton and (ev.button_index == BUTTON_WHEEL_DOWN or ev.button_index == BUTTON_WHEEL_UP)): - get_text_box()._gui_input(ev) - else: - get_text_box()._gui_input(ev) - print(ev) - -func _on_RichTextLabel_gui_input(ev): - pass - # leaving this b/c it is wired up and might have to send - # more signals through - print(ev) - -func _on_Copy_pressed(): - _text_box.select_all() - _text_box.copy() - _text_box.deselect() - -func _on_DisableBlocker_toggled(button_pressed): - _text_box_blocker_enabled = !button_pressed - -func _on_ShowExtras_toggled(button_pressed): - _extras.visible = button_pressed - -func _on_Maximize_pressed(): - if(rect_size == _pre_maximize_size): - maximize() - else: - rect_size = _pre_maximize_size -# #################### -# Private -# #################### -func _run_mode(is_running=true): - if(is_running): - _start_time = OS.get_unix_time() - _time = _start_time - _summary.failing.set_text("0") - _summary.passing.set_text("0") - _is_running = is_running - - _hide_scripts() - var ctrls = $Navigation.get_children() - for i in range(ctrls.size()): - ctrls[i].disabled = is_running - -func _select_script(index): - $Navigation/CurrentScript.set_text(_script_list.get_item_text(index)) - _script_list.select(index) - _update_controls() - -func _toggle_scripts(): - if(_script_list.visible): - _hide_scripts() - else: - _show_scripts() - -func _show_scripts(): - _script_list.show() - -func _hide_scripts(): - _script_list.hide() - -func _update_controls(): - var is_empty = _script_list.get_selected_items().size() == 0 - if(is_empty): - _nav.next.disabled = true - _nav.prev.disabled = true - else: - var index = get_selected_index() - _nav.prev.disabled = index <= 0 - _nav.next.disabled = index >= _script_list.get_item_count() - 1 - - _nav.run.disabled = is_empty - _nav.current_script.disabled = is_empty - _nav.show_scripts.disabled = is_empty - - -# #################### -# Public -# #################### -func run_mode(is_running=true): - _run_mode(is_running) - -func set_scripts(scripts): - _script_list.clear() - for i in range(scripts.size()): - _script_list.add_item(scripts[i]) - _select_script(0) - _update_controls() - -func select_script(index): - _select_script(index) - -func get_selected_index(): - return _script_list.get_selected_items()[0] - -func get_log_level(): - return $LogLevelSlider.value - -func set_log_level(value): - $LogLevelSlider.value = _utils.nvl(value, 0) - -func set_ignore_pause(should): - _ignore_pauses.pressed = should - -func get_ignore_pause(): - return _ignore_pauses.pressed - -func get_text_box(): - return $TextDisplay/RichTextLabel - -func end_run(): - _run_mode(false) - _update_controls() - -func set_progress_script_max(value): - _progress.script.set_max(max(value, 1)) - -func set_progress_script_value(value): - _progress.script.set_value(value) - -func set_progress_test_max(value): - _progress.test.set_max(max(value, 1)) - -func set_progress_test_value(value): - _progress.test.set_value(value) - -func clear_progress(): - _progress.test.set_value(0) - _progress.script.set_value(0) - -func pause(): - print('we got here') - _continue_button.disabled = false - -func set_title(title=null): - if(title == null): - $TitleBar/Title.set_text(DEFAULT_TITLE) - else: - $TitleBar/Title.set_text(title) - -func get_run_duration(): - return $TitleBar/Time.text.to_float() - -func add_passing(amount=1): - if(!_summary): - return - _summary.passing.set_text(str(_summary.passing.get_text().to_int() + amount)) - $Summary.show() - -func add_failing(amount=1): - if(!_summary): - return - _summary.failing.set_text(str(_summary.failing.get_text().to_int() + amount)) - $Summary.show() - -func clear_summary(): - _summary.passing.set_text("0") - _summary.failing.set_text("0") - $Summary.hide() - -func maximize(): - if(is_inside_tree()): - var vp_size_offset = get_viewport().size - rect_size = vp_size_offset / get_scale() - set_position(Vector2(0, 0)) - diff --git a/addons/gut/GutScene.tscn b/addons/gut/GutScene.tscn deleted file mode 100644 index 8d56a4b..0000000 --- a/addons/gut/GutScene.tscn +++ /dev/null @@ -1,299 +0,0 @@ -[gd_scene load_steps=5 format=2] - -[ext_resource path="res://addons/gut/GutScene.gd" type="Script" id=1] - -[sub_resource type="StyleBoxFlat" id=1] -bg_color = Color( 0.193863, 0.205501, 0.214844, 1 ) -corner_radius_top_left = 20 -corner_radius_top_right = 20 - -[sub_resource type="StyleBoxFlat" id=2] -bg_color = Color( 1, 1, 1, 1 ) -border_color = Color( 0, 0, 0, 1 ) -corner_radius_top_left = 5 -corner_radius_top_right = 5 - -[sub_resource type="Theme" id=3] -resource_local_to_scene = true -Panel/styles/panel = SubResource( 2 ) -Panel/styles/panelf = null -Panel/styles/panelnc = null - -[node name="Gut" type="Panel"] -margin_right = 740.0 -margin_bottom = 320.0 -rect_min_size = Vector2( 740, 250 ) -custom_styles/panel = SubResource( 1 ) -script = ExtResource( 1 ) - -[node name="TitleBar" type="Panel" parent="."] -anchor_right = 1.0 -margin_bottom = 40.0 -theme = SubResource( 3 ) - -[node name="Title" type="Label" parent="TitleBar"] -anchor_right = 1.0 -margin_bottom = 40.0 -custom_colors/font_color = Color( 0, 0, 0, 1 ) -text = "Gut" -align = 1 -valign = 1 - -[node name="Time" type="Label" parent="TitleBar"] -anchor_left = 1.0 -anchor_right = 1.0 -margin_left = -114.0 -margin_right = -53.0 -margin_bottom = 40.0 -custom_colors/font_color = Color( 0, 0, 0, 1 ) -text = "9999.99" -valign = 1 - -[node name="Maximize" type="Button" parent="TitleBar"] -anchor_left = 1.0 -anchor_right = 1.0 -margin_left = -30.0 -margin_top = 10.0 -margin_right = -6.0 -margin_bottom = 30.0 -custom_colors/font_color = Color( 0, 0, 0, 1 ) -text = "M" -flat = true - -[node name="ScriptProgress" type="ProgressBar" parent="."] -anchor_top = 1.0 -anchor_bottom = 1.0 -margin_left = 70.0 -margin_top = -100.0 -margin_right = 180.0 -margin_bottom = -70.0 -step = 1.0 - -[node name="Label" type="Label" parent="ScriptProgress"] -margin_left = -70.0 -margin_right = -10.0 -margin_bottom = 24.0 -text = "Scripts" -align = 1 -valign = 1 - -[node name="TestProgress" type="ProgressBar" parent="."] -anchor_top = 1.0 -anchor_bottom = 1.0 -margin_left = 70.0 -margin_top = -70.0 -margin_right = 180.0 -margin_bottom = -40.0 -step = 1.0 - -[node name="Label" type="Label" parent="TestProgress"] -margin_left = -70.0 -margin_right = -10.0 -margin_bottom = 24.0 -text = "Tests" -align = 1 -valign = 1 - -[node name="TextDisplay" type="Panel" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_top = 40.0 -margin_bottom = -107.0 -__meta__ = { -"_edit_group_": true -} - -[node name="RichTextLabel" type="TextEdit" parent="TextDisplay"] -anchor_right = 1.0 -anchor_bottom = 1.0 -mouse_default_cursor_shape = 0 -readonly = true -syntax_highlighting = true -smooth_scrolling = true - -[node name="FocusBlocker" type="Panel" parent="TextDisplay"] -self_modulate = Color( 1, 1, 1, 0 ) -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_right = -10.0 - -[node name="Navigation" type="Panel" parent="."] -self_modulate = Color( 1, 1, 1, 0 ) -anchor_top = 1.0 -anchor_bottom = 1.0 -margin_left = 220.0 -margin_top = -100.0 -margin_right = 580.0 - -[node name="Previous" type="Button" parent="Navigation"] -margin_left = -30.0 -margin_right = 50.0 -margin_bottom = 40.0 -text = "<" - -[node name="Next" type="Button" parent="Navigation"] -margin_left = 230.0 -margin_right = 310.0 -margin_bottom = 40.0 -text = ">" - -[node name="Run" type="Button" parent="Navigation"] -margin_left = 60.0 -margin_right = 220.0 -margin_bottom = 40.0 -text = "Run" - -[node name="CurrentScript" type="Button" parent="Navigation"] -margin_left = -30.0 -margin_top = 50.0 -margin_right = 310.0 -margin_bottom = 90.0 -text = "res://test/unit/test_gut.gd" -clip_text = true - -[node name="ShowScripts" type="Button" parent="Navigation"] -margin_left = 320.0 -margin_top = 50.0 -margin_right = 360.0 -margin_bottom = 90.0 -text = "..." - -[node name="LogLevelSlider" type="HSlider" parent="."] -anchor_top = 1.0 -anchor_bottom = 1.0 -margin_left = 80.0 -margin_top = -40.0 -margin_right = 130.0 -margin_bottom = -20.0 -rect_scale = Vector2( 2, 2 ) -max_value = 2.0 -tick_count = 3 -ticks_on_borders = true - -[node name="Label" type="Label" parent="LogLevelSlider"] -margin_left = -35.0 -margin_top = 5.0 -margin_right = 25.0 -margin_bottom = 25.0 -rect_scale = Vector2( 0.5, 0.5 ) -text = "Log Level" -align = 1 -valign = 1 - -[node name="ScriptsList" type="ItemList" parent="."] -anchor_bottom = 1.0 -margin_left = 180.0 -margin_top = 40.0 -margin_right = 620.0 -margin_bottom = -108.0 -allow_reselect = true - -[node name="ExtraOptions" type="Panel" parent="."] -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_left = -210.0 -margin_top = -246.0 -margin_bottom = -106.0 -custom_styles/panel = SubResource( 1 ) - -[node name="IgnorePause" type="CheckBox" parent="ExtraOptions"] -margin_left = 10.0 -margin_top = 10.0 -margin_right = 128.0 -margin_bottom = 34.0 -rect_scale = Vector2( 1.5, 1.5 ) -text = "Ignore Pauses" - -[node name="DisableBlocker" type="CheckBox" parent="ExtraOptions"] -margin_left = 10.0 -margin_top = 50.0 -margin_right = 130.0 -margin_bottom = 74.0 -rect_scale = Vector2( 1.5, 1.5 ) -text = "Selectable" - -[node name="Copy" type="Button" parent="ExtraOptions"] -margin_left = 20.0 -margin_top = 90.0 -margin_right = 200.0 -margin_bottom = 130.0 -text = "Copy" - -[node name="ResizeHandle" type="Control" parent="."] -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_left = -40.0 -margin_top = -40.0 - -[node name="Continue" type="Panel" parent="."] -self_modulate = Color( 1, 1, 1, 0 ) -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_left = -150.0 -margin_top = -100.0 -margin_right = -30.0 -margin_bottom = -10.0 - -[node name="Continue" type="Button" parent="Continue"] -margin_top = 50.0 -margin_right = 119.0 -margin_bottom = 90.0 -disabled = true -text = "Continue" - -[node name="ShowExtras" type="Button" parent="Continue"] -margin_left = 50.0 -margin_right = 120.0 -margin_bottom = 40.0 -rect_pivot_offset = Vector2( 35, 20 ) -toggle_mode = true -text = "_" - -[node name="Summary" type="Node2D" parent="."] -position = Vector2( 0, 3 ) - -[node name="Passing" type="Label" parent="Summary"] -margin_top = 10.0 -margin_right = 40.0 -margin_bottom = 24.0 -custom_colors/font_color = Color( 0, 0, 0, 1 ) -text = "0" -align = 1 -valign = 1 - -[node name="Failing" type="Label" parent="Summary"] -margin_left = 40.0 -margin_top = 10.0 -margin_right = 80.0 -margin_bottom = 24.0 -custom_colors/font_color = Color( 0, 0, 0, 1 ) -text = "0" -align = 1 -valign = 1 -[connection signal="mouse_entered" from="TitleBar" to="." method="_on_TitleBar_mouse_entered"] -[connection signal="mouse_exited" from="TitleBar" to="." method="_on_TitleBar_mouse_exited"] -[connection signal="draw" from="TitleBar/Maximize" to="." method="_on_Maximize_draw"] -[connection signal="pressed" from="TitleBar/Maximize" to="." method="_on_Maximize_pressed"] -[connection signal="gui_input" from="TextDisplay/RichTextLabel" to="." method="_on_RichTextLabel_gui_input"] -[connection signal="gui_input" from="TextDisplay/FocusBlocker" to="." method="_on_FocusBlocker_gui_input"] -[connection signal="pressed" from="Navigation/Previous" to="." method="_on_Previous_pressed"] -[connection signal="pressed" from="Navigation/Next" to="." method="_on_Next_pressed"] -[connection signal="pressed" from="Navigation/Run" to="." method="_on_Run_pressed"] -[connection signal="pressed" from="Navigation/CurrentScript" to="." method="_on_CurrentScript_pressed"] -[connection signal="pressed" from="Navigation/ShowScripts" to="." method="_on_ShowScripts_pressed"] -[connection signal="value_changed" from="LogLevelSlider" to="." method="_on_LogLevelSlider_value_changed"] -[connection signal="item_selected" from="ScriptsList" to="." method="_on_ScriptsList_item_selected"] -[connection signal="pressed" from="ExtraOptions/IgnorePause" to="." method="_on_IgnorePause_pressed"] -[connection signal="toggled" from="ExtraOptions/DisableBlocker" to="." method="_on_DisableBlocker_toggled"] -[connection signal="pressed" from="ExtraOptions/Copy" to="." method="_on_Copy_pressed"] -[connection signal="mouse_entered" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_entered"] -[connection signal="mouse_exited" from="ResizeHandle" to="." method="_on_ResizeHandle_mouse_exited"] -[connection signal="pressed" from="Continue/Continue" to="." method="_on_Continue_pressed"] -[connection signal="draw" from="Continue/ShowExtras" to="." method="_on_ShowExtras_draw"] -[connection signal="toggled" from="Continue/ShowExtras" to="." method="_on_ShowExtras_toggled"] diff --git a/addons/gut/LICENSE.md b/addons/gut/LICENSE.md deleted file mode 100644 index a38ac23..0000000 --- a/addons/gut/LICENSE.md +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) -===================== - -Copyright (c) 2018 Tom "Butch" Wesley - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/addons/gut/double_templates/function_template.gd b/addons/gut/double_templates/function_template.gd deleted file mode 100644 index 666952e..0000000 --- a/addons/gut/double_templates/function_template.gd +++ /dev/null @@ -1,6 +0,0 @@ -{func_decleration} - __gut_spy('{method_name}', {param_array}) - if(__gut_should_call_super('{method_name}', {param_array})): - return {super_call} - else: - return __gut_get_stubbed_return('{method_name}', {param_array}) diff --git a/addons/gut/double_templates/script_template.gd b/addons/gut/double_templates/script_template.gd deleted file mode 100644 index 4766a30..0000000 --- a/addons/gut/double_templates/script_template.gd +++ /dev/null @@ -1,36 +0,0 @@ -{extends} - -var __gut_metadata_ = { - path = '{path}', - subpath = '{subpath}', - stubber = __gut_instance_from_id({stubber_id}), - spy = __gut_instance_from_id({spy_id}), -} - -func __gut_instance_from_id(inst_id): - if(inst_id == -1): - return null - else: - return instance_from_id(inst_id) - -func __gut_should_call_super(method_name, called_with): - if(__gut_metadata_.stubber != null): - return __gut_metadata_.stubber.should_call_super(self, method_name, called_with) - else: - return false - -var __gut_utils_ = load('res://addons/gut/utils.gd').new() - -func __gut_spy(method_name, called_with): - if(__gut_metadata_.spy != null): - __gut_metadata_.spy.add_call(self, method_name, called_with) - -func __gut_get_stubbed_return(method_name, called_with): - if(__gut_metadata_.stubber != null): - return __gut_metadata_.stubber.get_return(self, method_name, called_with) - else: - return null - -# ------------------------------------------------------------------------------ -# Methods start here -# ------------------------------------------------------------------------------ diff --git a/addons/gut/doubler.gd b/addons/gut/doubler.gd deleted file mode 100644 index 790e566..0000000 --- a/addons/gut/doubler.gd +++ /dev/null @@ -1,525 +0,0 @@ -# ------------------------------------------------------------------------------ -# Utility class to hold the local and built in methods separately. Add all local -# methods FIRST, then add built ins. -# ------------------------------------------------------------------------------ -class ScriptMethods: - # List of methods that should not be overloaded when they are not defined - # in the class being doubled. These either break things if they are - # overloaded or do not have a "super" equivalent so we can't just pass - # through. - var _blacklist = [ - 'has_method', - 'get_script', - 'get', - '_notification', - 'get_path', - '_enter_tree', - '_exit_tree', - '_process', - '_draw', - '_physics_process', - '_input', - '_unhandled_input', - '_unhandled_key_input', - '_set', - '_get', # probably - 'emit_signal', # can't handle extra parameters to be sent with signal. - 'draw_mesh', # issue with one parameter, value is `Null((..), (..), (..))`` - '_to_string', # nonexistant function ._to_string - '_get_minimum_size', # Nonexistent function _get_minimum_size - ] - - var built_ins = [] - var local_methods = [] - var _method_names = [] - - func is_blacklisted(method_meta): - return _blacklist.find(method_meta.name) != -1 - - func _add_name_if_does_not_have(method_name): - var should_add = _method_names.find(method_name) == -1 - if(should_add): - _method_names.append(method_name) - return should_add - - func add_built_in_method(method_meta): - var did_add = _add_name_if_does_not_have(method_meta.name) - if(did_add and !is_blacklisted(method_meta)): - built_ins.append(method_meta) - - func add_local_method(method_meta): - var did_add = _add_name_if_does_not_have(method_meta.name) - if(did_add): - local_methods.append(method_meta) - - func to_s(): - var text = "Locals\n" - for i in range(local_methods.size()): - text += str(" ", local_methods[i].name, "\n") - text += "Built-Ins\n" - for i in range(built_ins.size()): - text += str(" ", built_ins[i].name, "\n") - return text - -# ------------------------------------------------------------------------------ -# Helper class to deal with objects and inner classes. -# ------------------------------------------------------------------------------ -class ObjectInfo: - var _path = null - var _subpaths = [] - var _utils = load('res://addons/gut/utils.gd').new() - var _method_strategy = null - var make_partial_double = false - var scene_path = null - var _native_class = null - var _native_class_instance = null - - func _init(path, subpath=null): - _path = path - if(subpath != null): - _subpaths = _utils.split_string(subpath, '/') - - # Returns an instance of the class/inner class - func instantiate(): - var to_return = null - if(is_native()): - to_return = _native_class.new() - else: - to_return = get_loaded_class().new() - return to_return - - # Can't call it get_class because that is reserved so it gets this ugly name. - # Loads up the class and then any inner classes to give back a reference to - # the desired Inner class (if there is any) - func get_loaded_class(): - var LoadedClass = load(_path) - for i in range(_subpaths.size()): - LoadedClass = LoadedClass.get(_subpaths[i]) - return LoadedClass - - func to_s(): - return str(_path, '[', get_subpath(), ']') - - func get_path(): - return _path - - func get_subpath(): - return _utils.join_array(_subpaths, '/') - - func has_subpath(): - return _subpaths.size() != 0 - - func get_extends_text(): - var extend = null - if(is_native()): - extend = str("extends ", get_native_class_name()) - else: - extend = str("extends '", get_path(), "'") - - if(has_subpath()): - extend += str('.', get_subpath().replace('/', '.')) - - return extend - - func get_method_strategy(): - return _method_strategy - - func set_method_strategy(method_strategy): - _method_strategy = method_strategy - - func is_native(): - return _native_class != null - - func set_native_class(native_class): - _native_class = native_class - _native_class_instance = native_class.new() - _path = _native_class_instance.get_class() - - func get_native_class_name(): - return _native_class_instance.get_class() - -# ------------------------------------------------------------------------------ -# Allows for interacting with a file but only creating a string. This was done -# to ease the transition from files being created for doubles to loading -# doubles from a string. This allows the files to be created for debugging -# purposes since reading a file is easier than reading a dumped out string. -# ------------------------------------------------------------------------------ -class FileOrString: - extends File - - var _do_file = false - var _contents = '' - var _path = null - - func open(path, mode): - _path = path - if(_do_file): - return .open(path, mode) - else: - return OK - - func close(): - if(_do_file): - return .close() - - func store_string(s): - if(_do_file): - .store_string(s) - _contents += s - - func get_contents(): - return _contents - - func get_path(): - return _path - - func load_it(): - if(_contents != ''): - var script = GDScript.new() - script.set_source_code(get_contents()) - script.reload() - return script - else: - return load(_path) - -# ------------------------------------------------------------------------------ -# A stroke of genius if I do say so. This allows for doubling a scene without -# having to write any files. By overloading instance we can make whatever -# we want. -# ------------------------------------------------------------------------------ -class PackedSceneDouble: - extends PackedScene - var _script = null - var _scene = null - - func set_script_obj(obj): - _script = obj - - func instance(edit_state=0): - var inst = _scene.instance(edit_state) - if(_script != null): - inst.set_script(_script) - return inst - - func load_scene(path): - _scene = load(path) - - - - -# ------------------------------------------------------------------------------ -# START Doubler -# ------------------------------------------------------------------------------ -var _utils = load('res://addons/gut/utils.gd').new() - -var _ignored_methods = _utils.OneToMany.new() -var _stubber = _utils.Stubber.new() -var _lgr = _utils.get_logger() -var _method_maker = _utils.MethodMaker.new() - -var _output_dir = 'user://gut_temp_directory' -var _double_count = 0 # used in making files names unique -var _spy = null -var _strategy = null -var _base_script_text = _utils.get_file_as_text('res://addons/gut/double_templates/script_template.gd') -var _make_files = false - -# These methods all call super implicitly. Stubbing them to call super causes -# super to be called twice. -var _non_super_methods = [ - "_init", - "_ready", - "_notification", - "_enter_world", - "_exit_world", - "_process", - "_physics_process", - "_exit_tree", - "_gui_input ", -] - -func _init(strategy=_utils.DOUBLE_STRATEGY.PARTIAL): - set_logger(_utils.get_logger()) - _strategy = strategy - -# ############### -# Private -# ############### -func _get_indented_line(indents, text): - var to_return = '' - for _i in range(indents): - to_return += "\t" - return str(to_return, text, "\n") - - -func _stub_to_call_super(obj_info, method_name): - if(_non_super_methods.has(method_name)): - return - var path = obj_info.get_path() - if(obj_info.scene_path != null): - path = obj_info.scene_path - var params = _utils.StubParams.new(path, method_name, obj_info.get_subpath()) - params.to_call_super() - _stubber.add_stub(params) - -func _get_base_script_text(obj_info, override_path): - var path = obj_info.get_path() - if(override_path != null): - path = override_path - - var stubber_id = -1 - if(_stubber != null): - stubber_id = _stubber.get_instance_id() - - var spy_id = -1 - if(_spy != null): - spy_id = _spy.get_instance_id() - - var values = { - "path":path, - "subpath":obj_info.get_subpath(), - "stubber_id":stubber_id, - "spy_id":spy_id, - "extends":obj_info.get_extends_text() - } - return _base_script_text.format(values) - -func _write_file(obj_info, dest_path, override_path=null): - var base_script = _get_base_script_text(obj_info, override_path) - var script_methods = _get_methods(obj_info) - - var f = FileOrString.new() - f._do_file = _make_files - var f_result = f.open(dest_path, f.WRITE) - - if(f_result != OK): - _lgr.error(str('Error creating file ', dest_path)) - _lgr.error(str('Could not create double for :', obj_info.to_s())) - return - - f.store_string(base_script) - - for i in range(script_methods.local_methods.size()): - if(obj_info.make_partial_double): - _stub_to_call_super(obj_info, script_methods.local_methods[i].name) - f.store_string(_get_func_text(script_methods.local_methods[i])) - - for i in range(script_methods.built_ins.size()): - _stub_to_call_super(obj_info, script_methods.built_ins[i].name) - f.store_string(_get_func_text(script_methods.built_ins[i])) - - f.close() - return f - -func _double_scene_and_script(scene_info): - var to_return = PackedSceneDouble.new() - to_return.load_scene(scene_info.get_path()) - - var inst = load(scene_info.get_path()).instance() - var script_path = null - if(inst.get_script()): - script_path = inst.get_script().get_path() - inst.free() - - if(script_path): - var oi = ObjectInfo.new(script_path) - oi.set_method_strategy(scene_info.get_method_strategy()) - oi.make_partial_double = scene_info.make_partial_double - oi.scene_path = scene_info.get_path() - to_return.set_script_obj(_double(oi, scene_info.get_path()).load_it()) - - return to_return - -func _get_methods(object_info): - var obj = object_info.instantiate() - # any method in the script or super script - var script_methods = ScriptMethods.new() - var methods = obj.get_method_list() - - # first pass is for local methods only - for i in range(methods.size()): - # 65 is a magic number for methods in script, though documentation - # says 64. This picks up local overloads of base class methods too. - if(methods[i].flags == 65 and !_ignored_methods.has(object_info.get_path(), methods[i]['name'])): - script_methods.add_local_method(methods[i]) - - - if(object_info.get_method_strategy() == _utils.DOUBLE_STRATEGY.FULL): - # second pass is for anything not local - for i in range(methods.size()): - # 65 is a magic number for methods in script, though documentation - # says 64. This picks up local overloads of base class methods too. - if(methods[i].flags != 65 and !_ignored_methods.has(object_info.get_path(), methods[i]['name'])): - script_methods.add_built_in_method(methods[i]) - - return script_methods - -func _get_inst_id_ref_str(inst): - var ref_str = 'null' - if(inst): - ref_str = str('instance_from_id(', inst.get_instance_id(),')') - return ref_str - -func _get_func_text(method_hash): - return _method_maker.get_function_text(method_hash) + "\n" - -# returns the path to write the double file to -func _get_temp_path(object_info): - var file_name = null - var extension = null - if(object_info.is_native()): - file_name = object_info.get_native_class_name() - extension = 'gd' - else: - file_name = object_info.get_path().get_file().get_basename() - extension = object_info.get_path().get_extension() - - if(object_info.has_subpath()): - file_name += '__' + object_info.get_subpath().replace('/', '__') - - file_name += str('__dbl', _double_count, '__.', extension) - - var to_return = _output_dir.plus_file(file_name) - return to_return - -func _load_double(fileOrString): - return fileOrString.load_it() - -func _double(obj_info, override_path=null): - var temp_path = _get_temp_path(obj_info) - var result = _write_file(obj_info, temp_path, override_path) - _double_count += 1 - return result - -func _double_script(path, make_partial, strategy): - var oi = ObjectInfo.new(path) - oi.make_partial_double = make_partial - oi.set_method_strategy(strategy) - return _double(oi).load_it() - -func _double_inner(path, subpath, make_partial, strategy): - var oi = ObjectInfo.new(path, subpath) - oi.set_method_strategy(strategy) - oi.make_partial_double = make_partial - return _double(oi).load_it() - -func _double_scene(path, make_partial, strategy): - var oi = ObjectInfo.new(path) - oi.set_method_strategy(strategy) - oi.make_partial_double = make_partial - return _double_scene_and_script(oi) - -func _double_gdnative(native_class, make_partial, strategy): - var oi = ObjectInfo.new(null) - oi.set_native_class(native_class) - oi.set_method_strategy(strategy) - oi.make_partial_double = make_partial - return _double(oi).load_it() - -# ############### -# Public -# ############### -func get_output_dir(): - return _output_dir - -func set_output_dir(output_dir): - if(output_dir != null): - _output_dir = output_dir - if(_make_files): - var d = Directory.new() - d.make_dir_recursive(output_dir) - -func get_spy(): - return _spy - -func set_spy(spy): - _spy = spy - -func get_stubber(): - return _stubber - -func set_stubber(stubber): - _stubber = stubber - -func get_logger(): - return _lgr - -func set_logger(logger): - _lgr = logger - _method_maker.set_logger(logger) - -func get_strategy(): - return _strategy - -func set_strategy(strategy): - _strategy = strategy - -func partial_double_scene(path, strategy=_strategy): - return _double_scene(path, true, strategy) - -# double a scene -func double_scene(path, strategy=_strategy): - return _double_scene(path, false, strategy) - -# double a script/object -func double(path, strategy=_strategy): - return _double_script(path, false, strategy) - -func partial_double(path, strategy=_strategy): - return _double_script(path, true, strategy) - -func partial_double_inner(path, subpath, strategy=_strategy): - return _double_inner(path, subpath, true, strategy) - -# double an inner class in a script -func double_inner(path, subpath, strategy=_strategy): - return _double_inner(path, subpath, false, strategy) - -# must always use FULL strategy since this is a native class and you won't get -# any methods if you don't use FULL -func double_gdnative(native_class): - return _double_gdnative(native_class, false, _utils.DOUBLE_STRATEGY.FULL) - -# must always use FULL strategy since this is a native class and you won't get -# any methods if you don't use FULL -func partial_double_gdnative(native_class): - return _double_gdnative(native_class, true, _utils.DOUBLE_STRATEGY.FULL) - -func clear_output_directory(): - if(!_make_files): - return false - - var did = false - if(_output_dir.find('user://') == 0): - var d = Directory.new() - var result = d.open(_output_dir) - # BIG GOTCHA HERE. If it cannot open the dir w/ erro 31, then the - # directory becomes res:// and things go on normally and gut clears out - # out res:// which is SUPER BAD. - if(result == OK): - d.list_dir_begin(true) - var f = d.get_next() - while(f != ''): - d.remove(f) - f = d.get_next() - did = true - return did - -func delete_output_directory(): - var did = clear_output_directory() - if(did): - var d = Directory.new() - d.remove(_output_dir) - -func add_ignored_method(path, method_name): - _ignored_methods.add(path, method_name) - -func get_ignored_methods(): - return _ignored_methods - -func get_make_files(): - return _make_files - -func set_make_files(make_files): - _make_files = make_files - set_output_dir(_output_dir) diff --git a/addons/gut/gut.gd b/addons/gut/gut.gd deleted file mode 100644 index 34f18c7..0000000 --- a/addons/gut/gut.gd +++ /dev/null @@ -1,1343 +0,0 @@ -################################################################################ -#(G)odot (U)nit (T)est class -# -################################################################################ -#The MIT License (MIT) -#===================== -# -#Copyright (c) 2019 Tom "Butch" Wesley -# -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: -# -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. -# -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# -################################################################################ -# View readme for usage details. -# -# Version 6.8.2 -################################################################################ -#extends "res://addons/gut/gut_gui.gd" -tool -extends Control -var _version = '6.8.2' - -var _utils = load('res://addons/gut/utils.gd').new() -var _lgr = _utils.get_logger() -# Used to prevent multiple messages for deprecated setup/teardown messages -var _deprecated_tracker = _utils.ThingCounter.new() - -# ########################### -# Editor Variables -# ########################### -export(String) var _select_script = '' -export(String) var _tests_like = '' -export(String) var _inner_class_name = '' - -export var _run_on_load = false -export var _should_maximize = false setget set_should_maximize, get_should_maximize - -export var _should_print_to_console = true setget set_should_print_to_console, get_should_print_to_console -export(int, 'Failures only', 'Tests and failures', 'Everything') var _log_level = 1 setget set_log_level, get_log_level -# This var is JUST used to expose this setting in the editor -# the var that is used is in the _yield_between hash. -export var _yield_between_tests = true setget set_yield_between_tests, get_yield_between_tests -export var _disable_strict_datatype_checks = false setget disable_strict_datatype_checks, is_strict_datatype_checks_disabled -# The prefix used to get tests. -export var _test_prefix = 'test_' -export var _file_prefix = 'test_' -export var _file_extension = '.gd' -export var _inner_class_prefix = 'Test' - -export(String) var _temp_directory = 'user://gut_temp_directory' -export(String) var _export_path = '' setget set_export_path, get_export_path - -export var _include_subdirectories = false setget set_include_subdirectories, get_include_subdirectories -# Allow user to add test directories via editor. This is done with strings -# instead of an array because the interface for editing arrays is really -# cumbersome and complicates testing because arrays set through the editor -# apply to ALL instances. This also allows the user to use the built in -# dialog to pick a directory. -export(String, DIR) var _directory1 = '' -export(String, DIR) var _directory2 = '' -export(String, DIR) var _directory3 = '' -export(String, DIR) var _directory4 = '' -export(String, DIR) var _directory5 = '' -export(String, DIR) var _directory6 = '' -export(int, 'FULL', 'PARTIAL') var _double_strategy = _utils.DOUBLE_STRATEGY.PARTIAL setget set_double_strategy, get_double_strategy -export(String, FILE) var _pre_run_script = '' setget set_pre_run_script, get_pre_run_script -export(String, FILE) var _post_run_script = '' setget set_post_run_script, get_post_run_script -export(bool) var _color_output = false setget set_color_output, get_color_output - -# The instance that is created from _pre_run_script. Accessible from -# get_pre_run_script_instance. -var _pre_run_script_instance = null -var _post_run_script_instance = null # This is not used except in tests. - -# ########################### -# Other Vars -# ########################### -const LOG_LEVEL_FAIL_ONLY = 0 -const LOG_LEVEL_TEST_AND_FAILURES = 1 -const LOG_LEVEL_ALL_ASSERTS = 2 -const WAITING_MESSAGE = '/# waiting #/' -const PAUSE_MESSAGE = '/# Pausing. Press continue button...#/' - -var _script_name = null -var _test_collector = _utils.TestCollector.new() - -# The instanced scripts. This is populated as the scripts are run. -var _test_script_objects = [] - -var _waiting = false -var _done = false -var _is_running = false - -var _current_test = null -var _log_text = "" - -var _pause_before_teardown = false -# when true _pause_before_teardown will be ignored. useful -# when batch processing and you don't want to watch. -var _ignore_pause_before_teardown = false -var _wait_timer = Timer.new() - -var _yield_between = { - should = false, - timer = Timer.new(), - after_x_tests = 5, - tests_since_last_yield = 0 -} - -var _was_yield_method_called = false -# used when yielding to gut instead of some other -# signal. Start with set_yield_time() -var _yield_timer = Timer.new() - -var _unit_test_name = '' -var _new_summary = null - -var _yielding_to = { - obj = null, - signal_name = '' -} - -var _stubber = _utils.Stubber.new() -var _doubler = _utils.Doubler.new() -var _spy = _utils.Spy.new() -var _gui = null - -const SIGNAL_TESTS_FINISHED = 'tests_finished' -const SIGNAL_STOP_YIELD_BEFORE_TEARDOWN = 'stop_yield_before_teardown' - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _init(): - # This min size has to be what the min size of the GutScene's min size is - # but it has to be set here and not inferred i think. - rect_min_size =Vector2(740, 250) - - add_user_signal(SIGNAL_TESTS_FINISHED) - add_user_signal(SIGNAL_STOP_YIELD_BEFORE_TEARDOWN) - add_user_signal('timeout') - add_user_signal('done_waiting') - _doubler.set_output_dir(_temp_directory) - _doubler.set_stubber(_stubber) - _doubler.set_spy(_spy) - - _lgr.set_gut(self) - _doubler.set_logger(_lgr) - _spy.set_logger(_lgr) - - - _stubber.set_logger(_lgr) - _test_collector.set_logger(_lgr) - _gui = load('res://addons/gut/GutScene.tscn').instance() - -# ------------------------------------------------------------------------------ -# Initialize controls -# ------------------------------------------------------------------------------ -func _ready(): - _lgr.info(str('using [', OS.get_user_data_dir(), '] for temporary output.')) - - set_process_input(true) - - add_child(_wait_timer) - _wait_timer.set_wait_time(1) - _wait_timer.set_one_shot(true) - - add_child(_yield_between.timer) - _wait_timer.set_one_shot(true) - - add_child(_yield_timer) - _yield_timer.set_one_shot(true) - _yield_timer.connect('timeout', self, '_yielding_callback') - - _setup_gui() - - add_directory(_directory1) - add_directory(_directory2) - add_directory(_directory3) - add_directory(_directory4) - add_directory(_directory5) - add_directory(_directory6) - - if(_select_script != null): - select_script(_select_script) - - if(_tests_like != null): - set_unit_test_name(_tests_like) - - if(_run_on_load): - test_scripts(_select_script == null) - - if(_should_maximize): - maximize() - - # hide the panel that IS gut so that only the GUI is seen - self.self_modulate = Color(1,1,1,0) - show() - var v_info = Engine.get_version_info() - p(str('Godot version: ', v_info.major, '.', v_info.minor, '.', v_info.patch)) - p(str('GUT version: ', get_version())) - - -################################################################################ -# -# GUI Events and setup -# -################################################################################ -func _setup_gui(): - # This is how we get the size of the control to translate to the gui when - # the scene is run. This is also another reason why the min_rect_size - # must match between both gut and the gui. - _gui.rect_size = self.rect_size - add_child(_gui) - _gui.set_anchor(MARGIN_RIGHT, ANCHOR_END) - _gui.set_anchor(MARGIN_BOTTOM, ANCHOR_END) - _gui.connect('run_single_script', self, '_on_run_one') - _gui.connect('run_script', self, '_on_new_gui_run_script') - _gui.connect('end_pause', self, '_on_new_gui_end_pause') - _gui.connect('ignore_pause', self, '_on_new_gui_ignore_pause') - _gui.connect('log_level_changed', self, '_on_log_level_changed') - var _foo = connect('tests_finished', _gui, 'end_run') - -func _add_scripts_to_gui(): - var scripts = [] - for i in range(_test_collector.scripts.size()): - var s = _test_collector.scripts[i] - var txt = '' - if(s.has_inner_class()): - txt = str(' - ', s.inner_class_name, ' (', s.tests.size(), ')') - else: - txt = str(s.get_full_name(), ' (', s.tests.size(), ')') - scripts.append(txt) - _gui.set_scripts(scripts) - -func _on_run_one(index): - clear_text() - var indexes = [index] - if(!_test_collector.scripts[index].has_inner_class()): - indexes = _get_indexes_matching_path(_test_collector.scripts[index].path) - _test_the_scripts(indexes) - -func _on_new_gui_run_script(index): - var indexes = [] - clear_text() - for i in range(index, _test_collector.scripts.size()): - indexes.append(i) - _test_the_scripts(indexes) - -func _on_new_gui_end_pause(): - _pause_before_teardown = false - emit_signal(SIGNAL_STOP_YIELD_BEFORE_TEARDOWN) - -func _on_new_gui_ignore_pause(should): - _ignore_pause_before_teardown = should - -func _on_log_level_changed(value): - _log_level = value - -##################### -# -# Events -# -##################### - -# ------------------------------------------------------------------------------ -# Timeout for the built in timer. emits the timeout signal. Start timer -# with set_yield_time() -# ------------------------------------------------------------------------------ -func _yielding_callback(from_obj=false): - if(_yielding_to.obj): - _yielding_to.obj.call_deferred( - "disconnect", - _yielding_to.signal_name, self, - '_yielding_callback') - _yielding_to.obj = null - _yielding_to.signal_name = '' - - if(from_obj): - # we must yiled for a little longer after the signal is emitted so that - # the signal can propagate to other objects. This was discovered trying - # to assert that obj/signal_name was emitted. Without this extra delay - # the yield returns and processing finishes before the rest of the - # objects can get the signal. This works b/c the timer will timeout - # and come back into this method but from_obj will be false. - _yield_timer.set_wait_time(.1) - _yield_timer.start() - else: - emit_signal('timeout') - -# ------------------------------------------------------------------------------ -# completed signal for GDScriptFucntionState returned from a test script that -# has yielded -# ------------------------------------------------------------------------------ -func _on_test_script_yield_completed(): - _waiting = false - -##################### -# -# Private -# -##################### - -# ------------------------------------------------------------------------------ -# Convert the _summary dictionary into text -# ------------------------------------------------------------------------------ -func _get_summary_text(): - var to_return = "\n\n*****************\nRun Summary\n*****************" - - to_return += "\n" + _new_summary.get_summary_text() + "\n" - - var logger_text = '' - if(_lgr.get_errors().size() > 0): - logger_text += str("\n * ", _lgr.get_errors().size(), ' Errors.') - if(_lgr.get_warnings().size() > 0): - logger_text += str("\n * ", _lgr.get_warnings().size(), ' Warnings.') - if(_lgr.get_deprecated().size() > 0): - logger_text += str("\n * ", _lgr.get_deprecated().size(), ' Deprecated calls.') - if(logger_text != ''): - logger_text = "\nWarnings/Errors:" + logger_text + "\n\n" - to_return += logger_text - - if(_new_summary.get_totals().tests > 0): - to_return += '+++ ' + str(_new_summary.get_totals().passing) + ' passed ' + str(_new_summary.get_totals().failing) + ' failed. ' + \ - "Tests finished in: " + str(_gui.get_run_duration()) + ' +++' - var c = Color(0, 1, 0) - if(_new_summary.get_totals().failing > 0): - c = Color(1, 0, 0) - elif(_new_summary.get_totals().pending > 0): - c = Color(1, 1, .8) - - _gui.get_text_box().add_color_region('+++', '+++', c) - else: - to_return += '+++ No tests ran +++' - _gui.get_text_box().add_color_region('+++', '+++', Color(1, 0, 0)) - - return to_return - -func _validate_hook_script(path): - var result = { - valid = true, - instance = null - } - - # empty path is valid but will have a null instance - if(path == ''): - return result - - var f = File.new() - if(f.file_exists(path)): - var inst = load(path).new() - if(inst and inst is _utils.HookScript): - result.instance = inst - result.valid = true - else: - result.valid = false - _lgr.error('The hook script [' + path + '] does not extend res://addons/gut/hook_script.gd') - else: - result.valid = false - _lgr.error('The hook script [' + path + '] does not exist.') - - return result - - -# ------------------------------------------------------------------------------ -# Runs a hook script. Script must exist, and must extend -# res://addons/gut/hook_script.gd -# ------------------------------------------------------------------------------ -func _run_hook_script(inst): - if(inst != null): - inst.gut = self - inst.run() - return inst - -# ------------------------------------------------------------------------------ -# Initialize variables for each run of a single test script. -# ------------------------------------------------------------------------------ -func _init_run(): - var valid = true - _test_collector.set_test_class_prefix(_inner_class_prefix) - _test_script_objects = [] - _new_summary = _utils.Summary.new() - - _log_text = "" - - _current_test = null - - _is_running = true - - _yield_between.tests_since_last_yield = 0 - - _gui.get_text_box().clear_colors() - _gui.get_text_box().add_keyword_color("PASSED", Color(0, 1, 0)) - _gui.get_text_box().add_keyword_color("FAILED", Color(1, 0, 0)) - _gui.get_text_box().add_color_region('/#', '#/', Color(.9, .6, 0)) - _gui.get_text_box().add_color_region('/-', '-/', Color(1, 1, 0)) - _gui.get_text_box().add_color_region('/*', '*/', Color(.5, .5, 1)) - - var pre_hook_result = _validate_hook_script(_pre_run_script) - _pre_run_script_instance = pre_hook_result.instance - var post_hook_result = _validate_hook_script(_post_run_script) - _post_run_script_instance = post_hook_result.instance - - valid = pre_hook_result.valid and post_hook_result.valid - - return valid - - - - -# ------------------------------------------------------------------------------ -# Print out run information and close out the run. -# ------------------------------------------------------------------------------ -func _end_run(): - p(_get_summary_text(), 0) - p("\n") - if(!_utils.is_null_or_empty(_select_script)): - p('Ran Scripts matching ' + _select_script) - if(!_utils.is_null_or_empty(_unit_test_name)): - p('Ran Tests matching ' + _unit_test_name) - if(!_utils.is_null_or_empty(_inner_class_name)): - p('Ran Inner Classes matching ' + _inner_class_name) - - # For some reason the text edit control isn't scrolling to the bottom after - # the summary is printed. As a workaround, yield for a short time and - # then move the cursor. I found this workaround through trial and error. - _yield_between.timer.set_wait_time(0.1) - _yield_between.timer.start() - yield(_yield_between.timer, 'timeout') - _gui.get_text_box().cursor_set_line(_gui.get_text_box().get_line_count()) - - _is_running = false - update() - _run_hook_script(_post_run_script_instance) - emit_signal(SIGNAL_TESTS_FINISHED) - _gui.set_title("Finished. " + str(get_fail_count()) + " failures.") - - -# ------------------------------------------------------------------------------ -# Checks the passed in thing to see if it is a "function state" object that gets -# returned when a function yields. -# ------------------------------------------------------------------------------ -func _is_function_state(script_result): - return script_result != null and \ - typeof(script_result) == TYPE_OBJECT and \ - script_result is GDScriptFunctionState - -# ------------------------------------------------------------------------------ -# Print out the heading for a new script -# ------------------------------------------------------------------------------ -func _print_script_heading(script): - if(_does_class_name_match(_inner_class_name, script.inner_class_name)): - p("\n/-----------------------------------------") - if(script.inner_class_name == null): - p("Running Script " + script.path, 0) - else: - p("Running Class [" + script.inner_class_name + "] in " + script.path, 0) - - if(!_utils.is_null_or_empty(_inner_class_name) and _does_class_name_match(_inner_class_name, script.inner_class_name)): - p(str(' [',script.inner_class_name, '] matches [', _inner_class_name, ']')) - - if(!_utils.is_null_or_empty(_unit_test_name)): - p(' Only running tests like: "' + _unit_test_name + '"') - - p("-----------------------------------------/") - -# ------------------------------------------------------------------------------ -# Just gets more logic out of _test_the_scripts. Decides if we should yield after -# this test based on flags and counters. -# ------------------------------------------------------------------------------ -func _should_yield_now(): - var should = _yield_between.should and \ - _yield_between.tests_since_last_yield == _yield_between.after_x_tests - if(should): - _yield_between.tests_since_last_yield = 0 - else: - _yield_between.tests_since_last_yield += 1 - return should - -# ------------------------------------------------------------------------------ -# Yes if the class name is null or the script's class name includes class_name -# ------------------------------------------------------------------------------ -func _does_class_name_match(the_class_name, script_class_name): - return (the_class_name == null or the_class_name == '') or (script_class_name != null and script_class_name.find(the_class_name) != -1) - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _setup_script(test_script): - test_script.gut = self - test_script.set_logger(_lgr) - add_child(test_script) - _test_script_objects.append(test_script) - - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _do_yield_between(time): - _yield_between.timer.set_wait_time(time) - _yield_between.timer.start() - return _yield_between.timer - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _wait_for_done(result): - var iter_counter = 0 - var print_after = 3 - - # sets waiting to false. - result.connect('completed', self, '_on_test_script_yield_completed') - - if(!_was_yield_method_called): - p('/# Yield detected, waiting #/') - - _was_yield_method_called = false - _waiting = true - _wait_timer.set_wait_time(0.25) - - while(_waiting): - iter_counter += 1 - if(iter_counter > print_after): - p(WAITING_MESSAGE, 2) - iter_counter = 0 - _wait_timer.start() - yield(_wait_timer, 'timeout') - - emit_signal('done_waiting') - -# ------------------------------------------------------------------------------ -# returns self so it can be integrated into the yield call. -# ------------------------------------------------------------------------------ -func _wait_for_continue_button(): - p(PAUSE_MESSAGE, 0) - _waiting = true - return self - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _call_deprecated_script_method(script, method, alt): - if(script.has_method(method)): - var txt = str(script, '-', method) - if(!_deprecated_tracker.has(txt)): - # Removing the deprecated line. I think it's still too early to - # start bothering people with this. Left everything here though - # because I don't want to remember how I did this last time. - #_lgr.deprecated(str('The method ', method, ' has been deprecated, use ', alt, ' instead.')) - _deprecated_tracker.add(txt) - script.call(method) - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _get_indexes_matching_script_name(name): - var indexes = [] # empty runs all - for i in range(_test_collector.scripts.size()): - if(_test_collector.scripts[i].get_filename().find(name) != -1): - indexes.append(i) - return indexes - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _get_indexes_matching_path(path): - var indexes = [] - for i in range(_test_collector.scripts.size()): - if(_test_collector.scripts[i].path == path): - indexes.append(i) - return indexes - -# ------------------------------------------------------------------------------ -# Run all tests in a script. This is the core logic for running tests. -# -# Note, this has to stay as a giant monstrosity of a method because of the -# yields. -# ------------------------------------------------------------------------------ -func _test_the_scripts(indexes=[]): - var is_valid = _init_run() - if(!is_valid): - _lgr.error('Something went wrong and the run was aborted.') - return - - _run_hook_script(_pre_run_script_instance) - if(_pre_run_script_instance!= null and _pre_run_script_instance.should_abort()): - _lgr.error('pre-run abort') - emit_signal(SIGNAL_TESTS_FINISHED) - return - - _gui.run_mode() - - var indexes_to_run = [] - if(indexes.size()==0): - for i in range(_test_collector.scripts.size()): - indexes_to_run.append(i) - else: - indexes_to_run = indexes - - _gui.set_progress_script_max(indexes_to_run.size()) # New way - _gui.set_progress_script_value(0) - - if(_doubler.get_strategy() == _utils.DOUBLE_STRATEGY.FULL): - _lgr.info("Using Double Strategy FULL as default strategy. Keep an eye out for weirdness, this is still experimental.") - - # loop through scripts - for test_indexes in range(indexes_to_run.size()): - var the_script = _test_collector.scripts[indexes_to_run[test_indexes]] - - if(the_script.tests.size() > 0): - _gui.set_title('Running: ' + the_script.get_full_name()) - _print_script_heading(the_script) - _new_summary.add_script(the_script.get_full_name()) - - var test_script = the_script.get_new() - var script_result = null - _setup_script(test_script) - _doubler.set_strategy(_double_strategy) - - # yield between test scripts so things paint - if(_yield_between.should): - yield(_do_yield_between(0.01), 'timeout') - - # !!! - # Hack so there isn't another indent to this monster of a method. if - # inner class is set and we do not have a match then empty the tests - # for the current test. - # !!! - if(!_does_class_name_match(_inner_class_name, the_script.inner_class_name)): - the_script.tests = [] - else: - # call both pre-all-tests methods until prerun_setup is removed - _call_deprecated_script_method(test_script, 'prerun_setup', 'before_all') - test_script.before_all() - - _gui.set_progress_test_max(the_script.tests.size()) # New way - - # Each test in the script - for i in range(the_script.tests.size()): - _stubber.clear() - _spy.clear() - _doubler.clear_output_directory() - _current_test = the_script.tests[i] - - if((_unit_test_name != '' and _current_test.name.find(_unit_test_name) > -1) or - (_unit_test_name == '')): - p(_current_test.name, 1) - _new_summary.add_test(_current_test.name) - - # yield so things paint - if(_should_yield_now()): - yield(_do_yield_between(0.001), 'timeout') - - _call_deprecated_script_method(test_script, 'setup', 'before_each') - test_script.before_each() - - - #When the script yields it will return a GDScriptFunctionState object - script_result = test_script.call(_current_test.name) - if(_is_function_state(script_result)): - _wait_for_done(script_result) - yield(self, 'done_waiting') - - #if the test called pause_before_teardown then yield until - #the continue button is pressed. - if(_pause_before_teardown and !_ignore_pause_before_teardown): - _gui.pause() - yield(_wait_for_continue_button(), SIGNAL_STOP_YIELD_BEFORE_TEARDOWN) - - test_script.clear_signal_watcher() - - # call each post-each-test method until teardown is removed. - _call_deprecated_script_method(test_script, 'teardown', 'after_each') - test_script.after_each() - - if(_current_test.passed): - _gui.get_text_box().add_keyword_color(_current_test.name, Color(0, 1, 0)) - else: - _gui.get_text_box().add_keyword_color(_current_test.name, Color(1, 0, 0)) - - _gui.set_progress_test_value(i + 1) - _doubler.get_ignored_methods().clear() - - # call both post-all-tests methods until postrun_teardown is removed. - if(_does_class_name_match(_inner_class_name, the_script.inner_class_name)): - _call_deprecated_script_method(test_script, 'postrun_teardown', 'after_all') - test_script.after_all() - - # This might end up being very resource intensive if the scripts - # don't clean up after themselves. Might have to consolidate output - # into some other structure and kill the script objects with - # test_script.free() instead of remove child. - remove_child(test_script) - #END TESTS IN SCRIPT LOOP - _current_test = null - _gui.set_progress_script_value(test_indexes + 1) # new way - #END TEST SCRIPT LOOP - - _end_run() - -func _pass(text=''): - _gui.add_passing() - if(_current_test): - _new_summary.add_pass(_current_test.name, text) - -func _fail(text=''): - _gui.add_failing() - if(_current_test != null): - var line_text = ' at line ' + str(_extractLineNumber( _current_test)) - p(line_text, LOG_LEVEL_FAIL_ONLY) - # format for summary - line_text = "\n " + line_text - - _new_summary.add_fail(_current_test.name, text + line_text) - _current_test.passed = false - -# Extracts the line number from curren stacktrace by matching the test case name -func _extractLineNumber(current_test): - var line_number = current_test.line_number - # if stack trace available than extraxt the test case line number - var stackTrace = get_stack() - if(stackTrace!=null): - for index in stackTrace.size(): - var line = stackTrace[index] - var function = line.get("function") - if function == current_test.name: - line_number = line.get("line") - return line_number - -func _pending(text=''): - if(_current_test): - _new_summary.add_pending(_current_test.name, text) - -# Gets all the files in a directory and all subdirectories if get_include_subdirectories -# is true. The files returned are all sorted by name. -func _get_files(path, prefix, suffix): - var files = [] - var directories = [] - - var d = Directory.new() - d.open(path) - # true parameter tells list_dir_begin not to include "." and ".." directories. - d.list_dir_begin(true) - - # Traversing a directory is kinda odd. You have to start the process of listing - # the contents of a directory with list_dir_begin then use get_next until it - # returns an empty string. Then I guess you should end it. - var fs_item = d.get_next() - var full_path = '' - while(fs_item != ''): - full_path = path.plus_file(fs_item) - - #file_exists returns fasle for directories - if(d.file_exists(full_path)): - if(fs_item.begins_with(prefix) and fs_item.ends_with(suffix)): - files.append(full_path) - elif(get_include_subdirectories() and d.dir_exists(full_path)): - directories.append(full_path) - - fs_item = d.get_next() - d.list_dir_end() - - for dir in range(directories.size()): - var dir_files = _get_files(directories[dir], prefix, suffix) - for i in range(dir_files.size()): - files.append(dir_files[i]) - - files.sort() - return files -######################### -# -# public -# -######################### - -# ------------------------------------------------------------------------------ -# Conditionally prints the text to the console/results variable based on the -# current log level and what level is passed in. Whenever currently in a test, -# the text will be indented under the test. It can be further indented if -# desired. -# -# The first time output is generated when in a test, the test name will be -# printed. -# ------------------------------------------------------------------------------ -func p(text, level=0, indent=0): - var str_text = str(text) - var to_print = "" - var printing_test_name = false - - if(level <= _utils.nvl(_log_level, 0)): - if(_current_test != null): - # make sure everything printed during the execution - # of a test is at least indented once under the test - if(indent == 0): - indent = 1 - - # Print the name of the current test if we haven't - # printed it already. - if(!_current_test.has_printed_name): - to_print = "* " + _current_test.name - _current_test.has_printed_name = true - printing_test_name = str_text == _current_test.name - - if(!printing_test_name): - if(to_print != ""): - to_print += "\n" - # Make the indent - var pad = "" - for _i in range(0, indent): - pad += " " - to_print += pad + str_text - to_print = to_print.replace("\n", "\n" + pad) - - if(_should_print_to_console): - var formatted = to_print - if(_color_output): - formatted = _utils.colorize_text(to_print) - print(formatted) - - _log_text += to_print + "\n" - - _gui.get_text_box().insert_text_at_cursor(to_print + "\n") - -################ -# -# RUN TESTS/ADD SCRIPTS -# -################ -func get_minimum_size(): - return Vector2(810, 380) - -# ------------------------------------------------------------------------------ -# Runs all the scripts that were added using add_script -# ------------------------------------------------------------------------------ -func test_scripts(run_rest=false): - clear_text() - - if(_script_name != null and _script_name != ''): - var indexes = _get_indexes_matching_script_name(_script_name) - if(indexes == []): - _lgr.error('Could not find script matching ' + _script_name) - else: - _test_the_scripts(indexes) - else: - _test_the_scripts([]) - - -# ------------------------------------------------------------------------------ -# Runs a single script passed in. -# ------------------------------------------------------------------------------ -func test_script(script): - _test_collector.set_test_class_prefix(_inner_class_prefix) - _test_collector.clear() - _test_collector.add_script(script) - _test_the_scripts() - -# ------------------------------------------------------------------------------ -# Adds a script to be run when test_scripts called -# -# No longer supports selecting a script via this method. -# ------------------------------------------------------------------------------ -func add_script(script, was_select_this_one=null): - if(was_select_this_one != null): - _lgr.error('The option to select a script when using add_script has been removed. Calling add_script with 2 parameters will be removed in a later release.') - - if(!Engine.is_editor_hint()): - _test_collector.set_test_class_prefix(_inner_class_prefix) - _test_collector.add_script(script) - _add_scripts_to_gui() - -# ------------------------------------------------------------------------------ -# Add all scripts in the specified directory that start with the prefix and end -# with the suffix. Does not look in sub directories. Can be called multiple -# times. -# ------------------------------------------------------------------------------ -func add_directory(path, prefix=_file_prefix, suffix=_file_extension): - var d = Directory.new() - # check for '' b/c the calls to addin the exported directories 1-6 will pass - # '' if the field has not been populated. This will cause res:// to be - # processed which will include all files if include_subdirectories is true. - if(path == '' or !d.dir_exists(path)): - if(path != ''): - _lgr.error(str('The path [', path, '] does not exist.')) - return - - var files = _get_files(path, prefix, suffix) - for i in range(files.size()): - add_script(files[i]) - -# ------------------------------------------------------------------------------ -# This will try to find a script in the list of scripts to test that contains -# the specified script name. It does not have to be a full match. It will -# select the first matching occurrence so that this script will run when run_tests -# is called. Works the same as the select_this_one option of add_script. -# -# returns whether it found a match or not -# ------------------------------------------------------------------------------ -func select_script(script_name): - _script_name = script_name - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func export_tests(path=_export_path): - if(path == null): - _lgr.error('You must pass a path or set the export_path before calling export_tests') - else: - var result = _test_collector.export_tests(path) - if(result): - p(_test_collector.to_s()) - p("Exported to " + path) - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func import_tests(path=_export_path): - if(!_utils.file_exists(path)): - _lgr.error(str('Cannot import tests: the path [', path, '] does not exist.')) - else: - _test_collector.clear() - var result = _test_collector.import_tests(path) - if(result): - p(_test_collector.to_s()) - p("Imported from " + path) - _add_scripts_to_gui() - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func import_tests_if_none_found(): - if(_test_collector.scripts.size() == 0): - import_tests() - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func export_if_tests_found(): - if(_test_collector.scripts.size() > 0): - export_tests() -################ -# -# MISC -# -################ - -# ------------------------------------------------------------------------------ -# Maximize test runner window to fit the viewport. -# ------------------------------------------------------------------------------ -func set_should_maximize(should): - _should_maximize = should - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_should_maximize(): - return _should_maximize - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func maximize(): - _gui.maximize() - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func disable_strict_datatype_checks(should): - _disable_strict_datatype_checks = should - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func is_strict_datatype_checks_disabled(): - return _disable_strict_datatype_checks - -# ------------------------------------------------------------------------------ -# Pauses the test and waits for you to press a confirmation button. Useful when -# you want to watch a test play out onscreen or inspect results. -# ------------------------------------------------------------------------------ -func end_yielded_test(): - _lgr.deprecated('end_yielded_test is no longer necessary, you can remove it.') - -# ------------------------------------------------------------------------------ -# Clears the text of the text box. This resets all counters. -# ------------------------------------------------------------------------------ -func clear_text(): - _gui.get_text_box().set_text("") - _gui.get_text_box().clear_colors() - update() - -# ------------------------------------------------------------------------------ -# Get the number of tests that were ran -# ------------------------------------------------------------------------------ -func get_test_count(): - return _new_summary.get_totals().tests - -# ------------------------------------------------------------------------------ -# Get the number of assertions that were made -# ------------------------------------------------------------------------------ -func get_assert_count(): - var t = _new_summary.get_totals() - return t.passing + t.failing - -# ------------------------------------------------------------------------------ -# Get the number of assertions that passed -# ------------------------------------------------------------------------------ -func get_pass_count(): - return _new_summary.get_totals().passing - -# ------------------------------------------------------------------------------ -# Get the number of assertions that failed -# ------------------------------------------------------------------------------ -func get_fail_count(): - return _new_summary.get_totals().failing - -# ------------------------------------------------------------------------------ -# Get the number of tests flagged as pending -# ------------------------------------------------------------------------------ -func get_pending_count(): - return _new_summary.get_totals().pending - -# ------------------------------------------------------------------------------ -# Set whether it should print to console or not. Default is yes. -# ------------------------------------------------------------------------------ -func set_should_print_to_console(should): - _should_print_to_console = should - -# ------------------------------------------------------------------------------ -# Get whether it is printing to the console -# ------------------------------------------------------------------------------ -func get_should_print_to_console(): - return _should_print_to_console - -# ------------------------------------------------------------------------------ -# Get the results of all tests ran as text. This string is the same as is -# displayed in the text box, and similar to what is printed to the console. -# ------------------------------------------------------------------------------ -func get_result_text(): - return _log_text - -# ------------------------------------------------------------------------------ -# Set the log level. Use one of the various LOG_LEVEL_* constants. -# ------------------------------------------------------------------------------ -func set_log_level(level): - _log_level = level - if(!Engine.is_editor_hint()): - _gui.set_log_level(level) - -# ------------------------------------------------------------------------------ -# Get the current log level. -# ------------------------------------------------------------------------------ -func get_log_level(): - return _log_level - -# ------------------------------------------------------------------------------ -# Call this method to make the test pause before teardown so that you can inspect -# anything that you have rendered to the screen. -# ------------------------------------------------------------------------------ -func pause_before_teardown(): - _pause_before_teardown = true; - -# ------------------------------------------------------------------------------ -# For batch processing purposes, you may want to ignore any calls to -# pause_before_teardown that you forgot to remove. -# ------------------------------------------------------------------------------ -func set_ignore_pause_before_teardown(should_ignore): - _ignore_pause_before_teardown = should_ignore - _gui.set_ignore_pause(should_ignore) - -func get_ignore_pause_before_teardown(): - return _ignore_pause_before_teardown - -# ------------------------------------------------------------------------------ -# Set to true so that painting of the screen will occur between tests. Allows you -# to see the output as tests occur. Especially useful with long running tests that -# make it appear as though it has humg. -# -# NOTE: not compatible with 1.0 so this is disabled by default. This will -# change in future releases. -# ------------------------------------------------------------------------------ -func set_yield_between_tests(should): - _yield_between.should = should - -func get_yield_between_tests(): - return _yield_between.should - -# ------------------------------------------------------------------------------ -# Call _process or _fixed_process, if they exist, on obj and all it's children -# and their children and so and so forth. Delta will be passed through to all -# the _process or _fixed_process methods. -# ------------------------------------------------------------------------------ -func simulate(obj, times, delta): - for _i in range(times): - if(obj.has_method("_process")): - obj._process(delta) - if(obj.has_method("_physics_process")): - obj._physics_process(delta) - - for kid in obj.get_children(): - simulate(kid, 1, delta) - -# ------------------------------------------------------------------------------ -# Starts an internal timer with a timeout of the passed in time. A 'timeout' -# signal will be sent when the timer ends. Returns itself so that it can be -# used in a call to yield...cutting down on lines of code. -# -# Example, yield to the Gut object for 10 seconds: -# yield(gut.set_yield_time(10), 'timeout') -# ------------------------------------------------------------------------------ -func set_yield_time(time, text=''): - _yield_timer.set_wait_time(time) - _yield_timer.start() - var msg = '/# Yielding (' + str(time) + 's)' - if(text == ''): - msg += ' #/' - else: - msg += ': ' + text + ' #/' - p(msg, 1) - _was_yield_method_called = true - return self - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_yield_signal_or_time(obj, signal_name, max_wait, text=''): - obj.connect(signal_name, self, '_yielding_callback', [true]) - _yielding_to.obj = obj - _yielding_to.signal_name = signal_name - - _yield_timer.set_wait_time(max_wait) - _yield_timer.start() - _was_yield_method_called = true - p(str('/# Yielding to signal "', signal_name, '" or for ', max_wait, ' seconds #/ ', text)) - return self - -# ------------------------------------------------------------------------------ -# get the specific unit test that should be run -# ------------------------------------------------------------------------------ -func get_unit_test_name(): - return _unit_test_name - -# ------------------------------------------------------------------------------ -# set the specific unit test that should be run. -# ------------------------------------------------------------------------------ -func set_unit_test_name(test_name): - _unit_test_name = test_name - -# ------------------------------------------------------------------------------ -# Creates an empty file at the specified path -# ------------------------------------------------------------------------------ -func file_touch(path): - var f = File.new() - f.open(path, f.WRITE) - f.close() - -# ------------------------------------------------------------------------------ -# deletes the file at the specified path -# ------------------------------------------------------------------------------ -func file_delete(path): - var d = Directory.new() - var result = d.open(path.get_base_dir()) - if(result == OK): - d.remove(path) - -# ------------------------------------------------------------------------------ -# Checks to see if the passed in file has any data in it. -# ------------------------------------------------------------------------------ -func is_file_empty(path): - var f = File.new() - f.open(path, f.READ) - var empty = f.get_len() == 0 - f.close() - return empty - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_file_as_text(path): - return _utils.get_file_as_text(path) - -# ------------------------------------------------------------------------------ -# deletes all files in a given directory -# ------------------------------------------------------------------------------ -func directory_delete_files(path): - var d = Directory.new() - var result = d.open(path) - - # SHORTCIRCUIT - if(result != OK): - return - - # Traversing a directory is kinda odd. You have to start the process of listing - # the contents of a directory with list_dir_begin then use get_next until it - # returns an empty string. Then I guess you should end it. - d.list_dir_begin() - var thing = d.get_next() # could be a dir or a file or something else maybe? - var full_path = '' - while(thing != ''): - full_path = path + "/" + thing - #file_exists returns fasle for directories - if(d.file_exists(full_path)): - d.remove(full_path) - thing = d.get_next() - d.list_dir_end() - -# ------------------------------------------------------------------------------ -# Returns the instantiated script object that is currently being run. -# ------------------------------------------------------------------------------ -func get_current_script_object(): - var to_return = null - if(_test_script_objects.size() > 0): - to_return = _test_script_objects[-1] - return to_return - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_current_test_object(): - return _current_test - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_stubber(): - return _stubber - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_doubler(): - return _doubler - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_spy(): - return _spy - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_temp_directory(): - return _temp_directory - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_temp_directory(temp_directory): - _temp_directory = temp_directory - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_inner_class_name(): - return _inner_class_name - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_inner_class_name(inner_class_name): - _inner_class_name = inner_class_name - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_summary(): - return _new_summary - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_double_strategy(): - return _double_strategy - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_double_strategy(double_strategy): - _double_strategy = double_strategy - _doubler.set_strategy(double_strategy) - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_include_subdirectories(): - return _include_subdirectories - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_logger(): - return _lgr - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_logger(logger): - _lgr = logger - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_include_subdirectories(include_subdirectories): - _include_subdirectories = include_subdirectories - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_test_collector(): - return _test_collector - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_export_path(): - return _export_path - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_export_path(export_path): - _export_path = export_path - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_version(): - return _version - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_pre_run_script(): - return _pre_run_script - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_pre_run_script(pre_run_script): - _pre_run_script = pre_run_script - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_post_run_script(): - return _post_run_script - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func set_post_run_script(post_run_script): - _post_run_script = post_run_script - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_pre_run_script_instance(): - return _pre_run_script_instance - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_post_run_script_instance(): - return _post_run_script_instance - -func get_color_output(): - return _color_output - -func set_color_output(color_output): - _color_output = color_output diff --git a/addons/gut/gut_cmdln.gd b/addons/gut/gut_cmdln.gd deleted file mode 100644 index ef89144..0000000 --- a/addons/gut/gut_cmdln.gd +++ /dev/null @@ -1,366 +0,0 @@ -################################################################################ -#(G)odot (U)nit (T)est class -# -################################################################################ -#The MIT License (MIT) -#===================== -# -#Copyright (c) 2019 Tom "Butch" Wesley -# -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: -# -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. -# -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# -################################################################################ -# Description -# ----------- -# Command line interface for the GUT unit testing tool. Allows you to run tests -# from the command line instead of running a scene. Place this script along with -# gut.gd into your scripts directory at the root of your project. Once there you -# can run this script (from the root of your project) using the following command: -# godot -s -d test/gut/gut_cmdln.gd -# -# See the readme for a list of options and examples. You can also use the -gh -# option to get more information about how to use the command line interface. -# -# Version 6.8.2 -################################################################################ -extends SceneTree - - -var Optparse = load('res://addons/gut/optparse.gd') -var Gut = load('res://addons/gut/gut.gd') - -#------------------------------------------------------------------------------- -# Helper class to resolve the various different places where an option can -# be set. Using the get_value method will enforce the order of precedence of: -# 1. command line value -# 2. config file value -# 3. default value -# -# The idea is that you set the base_opts. That will get you a copies of the -# hash with null values for the other types of values. Lower precedented hashes -# will punch through null values of higher precedented hashes. -#------------------------------------------------------------------------------- -class OptionResolver: - var base_opts = null - var cmd_opts = null - var config_opts = null - - - func get_value(key): - return _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key])) - - func set_base_opts(opts): - base_opts = opts - cmd_opts = _null_copy(opts) - config_opts = _null_copy(opts) - - # creates a copy of a hash with all values null. - func _null_copy(h): - var new_hash = {} - for key in h: - new_hash[key] = null - return new_hash - - func _nvl(a, b): - if(a == null): - return b - else: - return a - func _string_it(h): - var to_return = '' - for key in h: - to_return += str('(',key, ':', _nvl(h[key], 'NULL'), ')') - return to_return - - func to_s(): - return str("base:\n", _string_it(base_opts), "\n", \ - "config:\n", _string_it(config_opts), "\n", \ - "cmd:\n", _string_it(cmd_opts), "\n", \ - "resolved:\n", _string_it(get_resolved_values())) - - func get_resolved_values(): - var to_return = {} - for key in base_opts: - to_return[key] = get_value(key) - return to_return - - func to_s_verbose(): - var to_return = '' - var resolved = get_resolved_values() - for key in base_opts: - to_return += str(key, "\n") - to_return += str(' default: ', _nvl(base_opts[key], 'NULL'), "\n") - to_return += str(' config: ', _nvl(config_opts[key], ' --'), "\n") - to_return += str(' cmd: ', _nvl(cmd_opts[key], ' --'), "\n") - to_return += str(' final: ', _nvl(resolved[key], 'NULL'), "\n") - - return to_return - -#------------------------------------------------------------------------------- -# Here starts the actual script that uses the Options class to kick off Gut -# and run your tests. -#------------------------------------------------------------------------------- -var _utils = load('res://addons/gut/utils.gd').new() -# instance of gut -var _tester = null -# array of command line options specified -var _final_opts = [] -# Hash for easier access to the options in the code. Options will be -# extracted into this hash and then the hash will be used afterwards so -# that I don't make any dumb typos and get the neat code-sense when I -# type a dot. -var options = { - config_file = 'res://.gutconfig.json', - dirs = [], - disable_colors = false, - double_strategy = 'partial', - ignore_pause = false, - include_subdirs = false, - inner_class = '', - log_level = 1, - opacity = 100, - post_run_script = '', - pre_run_script = '', - prefix = 'test_', - selected = '', - should_exit = false, - should_exit_on_success = false, - should_maximize = false, - show_help = false, - suffix = '.gd', - tests = [], - unit_test_name = '', -} - -# flag to indicate if only a single script should be run. -var _run_single = false - -func setup_options(): - var opts = Optparse.new() - opts.set_banner(('This is the command line interface for the unit testing tool Gut. With this ' + - 'interface you can run one or more test scripts from the command line. In order ' + - 'for the Gut options to not clash with any other godot options, each option starts ' + - 'with a "g". Also, any option that requires a value will take the form of ' + - '"-g=". There cannot be any spaces between the option, the "=", or ' + - 'inside a specified value or godot will think you are trying to run a scene.')) - opts.add('-gtest', [], 'Comma delimited list of full paths to test scripts to run.') - opts.add('-gdir', [], 'Comma delimited list of directories to add tests from.') - opts.add('-gprefix', 'test_', 'Prefix used to find tests when specifying -gdir. Default "[default]"') - opts.add('-gsuffix', '.gd', 'Suffix used to find tests when specifying -gdir. Default "[default]"') - opts.add('-gmaximize', false, 'Maximizes test runner window to fit the viewport.') - opts.add('-gexit', false, 'Exit after running tests. If not specified you have to manually close the window.') - opts.add('-gexit_on_success', false, 'Only exit if all tests pass.') - opts.add('-glog', 1, 'Log level. Default [default]') - opts.add('-gignore_pause', false, 'Ignores any calls to gut.pause_before_teardown.') - opts.add('-gselect', '', ('Select a script to run initially. The first script that ' + - 'was loaded using -gtest or -gdir that contains the specified ' + - 'string will be executed. You may run others by interacting ' + - 'with the GUI.')) - opts.add('-gunit_test_name', '', ('Name of a test to run. Any test that contains the specified ' + - 'text will be run, all others will be skipped.')) - opts.add('-gh', false, 'Print this help, then quit') - opts.add('-gconfig', 'res://.gutconfig.json', 'A config file that contains configuration information. Default is res://.gutconfig.json') - opts.add('-ginner_class', '', 'Only run inner classes that contain this string') - opts.add('-gopacity', 100, 'Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque.') - opts.add('-gpo', false, 'Print option values from all sources and the value used, then quit.') - opts.add('-ginclude_subdirs', false, 'Include subdirectories of -gdir.') - opts.add('-gdouble_strategy', 'partial', 'Default strategy to use when doubling. Valid values are [partial, full]. Default "[default]"') - opts.add('-gdisable_colors', false, 'Disable command line colors.') - opts.add('-gpre_run_script', '', 'pre-run hook script path') - opts.add('-gpost_run_script', '', 'post-run hook script path') - opts.add('-gprint_gutconfig_sample', false, 'Print out json that can be used to make a gutconfig file then quit.') - return opts - - -# Parses options, applying them to the _tester or setting values -# in the options struct. -func extract_command_line_options(from, to): - to.config_file = from.get_value('-gconfig') - to.dirs = from.get_value('-gdir') - to.disable_colors = from.get_value('-gdisable_colors') - to.double_strategy = from.get_value('-gdouble_strategy') - to.ignore_pause = from.get_value('-gignore_pause') - to.include_subdirs = from.get_value('-ginclude_subdirs') - to.inner_class = from.get_value('-ginner_class') - to.log_level = from.get_value('-glog') - to.opacity = from.get_value('-gopacity') - to.post_run_script = from.get_value('-gpost_run_script') - to.pre_run_script = from.get_value('-gpre_run_script') - to.prefix = from.get_value('-gprefix') - to.selected = from.get_value('-gselect') - to.should_exit = from.get_value('-gexit') - to.should_exit_on_success = from.get_value('-gexit_on_success') - to.should_maximize = from.get_value('-gmaximize') - to.suffix = from.get_value('-gsuffix') - to.tests = from.get_value('-gtest') - to.unit_test_name = from.get_value('-gunit_test_name') - - -func load_options_from_config_file(file_path, into): - # SHORTCIRCUIT - var f = File.new() - if(!f.file_exists(file_path)): - if(file_path != 'res://.gutconfig.json'): - print('ERROR: Config File "', file_path, '" does not exist.') - return -1 - else: - return 1 - - f.open(file_path, f.READ) - var json = f.get_as_text() - f.close() - - var results = JSON.parse(json) - # SHORTCIRCUIT - if(results.error != OK): - print("\n\n",'!! ERROR parsing file: ', file_path) - print(' at line ', results.error_line, ':') - print(' ', results.error_string) - return -1 - - # Get all the options out of the config file using the option name. The - # options hash is now the default source of truth for the name of an option. - for key in into: - if(results.result.has(key)): - into[key] = results.result[key] - - return 1 - -# Apply all the options specified to _tester. This is where the rubber meets -# the road. -func apply_options(opts): - _tester = Gut.new() - get_root().add_child(_tester) - _tester.connect('tests_finished', self, '_on_tests_finished', [opts.should_exit, opts.should_exit_on_success]) - _tester.set_yield_between_tests(true) - _tester.set_modulate(Color(1.0, 1.0, 1.0, min(1.0, float(opts.opacity) / 100))) - _tester.show() - - _tester.set_include_subdirectories(opts.include_subdirs) - - if(opts.should_maximize): - _tester.maximize() - - if(opts.inner_class != ''): - _tester.set_inner_class_name(opts.inner_class) - _tester.set_log_level(opts.log_level) - _tester.set_ignore_pause_before_teardown(opts.ignore_pause) - - for i in range(opts.dirs.size()): - _tester.add_directory(opts.dirs[i], opts.prefix, opts.suffix) - - for i in range(opts.tests.size()): - _tester.add_script(opts.tests[i]) - - if(opts.selected != ''): - _tester.select_script(opts.selected) - _run_single = true - - if(opts.double_strategy == 'full'): - _tester.set_double_strategy(_utils.DOUBLE_STRATEGY.FULL) - elif(opts.double_strategy == 'partial'): - _tester.set_double_strategy(_utils.DOUBLE_STRATEGY.PARTIAL) - - _tester.set_unit_test_name(opts.unit_test_name) - _tester.set_pre_run_script(opts.pre_run_script) - _tester.set_post_run_script(opts.post_run_script) - _tester.set_color_output(!opts.disable_colors) - -func _print_gutconfigs(values): - var header = """Here is a sample of a full .gutconfig.json file. -You do not need to specify all values in your own file. The values supplied in -this sample are what would be used if you ran gut w/o the -gprint_gutconfig_sample -option (the resolved values where default < .gutconfig < command line).""" - print("\n", header.replace("\n", ' '), "\n\n") - var resolved = values - - # remove some options that don't make sense to be in config - resolved.erase("config_file") - resolved.erase("show_help") - - print("Here's a config with all the properties set based off of your current command and config.") - var text = JSON.print(resolved) - print(text.replace(',', ",\n")) - - for key in resolved: - resolved[key] = null - - print("\n\nAnd here's an empty config for you fill in what you want.") - text = JSON.print(resolved) - print(text.replace(',', ",\n")) - - -# parse options and run Gut -func _init(): - var opt_resolver = OptionResolver.new() - opt_resolver.set_base_opts(options) - - print("\n\n", ' --- Gut ---') - var o = setup_options() - - var all_options_valid = o.parse() - extract_command_line_options(o, opt_resolver.cmd_opts) - var load_result = \ - load_options_from_config_file(opt_resolver.get_value('config_file'), opt_resolver.config_opts) - - if(load_result == -1): # -1 indicates json parse error - quit() - else: - if(!all_options_valid): - quit() - elif(o.get_value('-gh')): - var v_info = Engine.get_version_info() - print(str('Godot version: ', v_info.major, '.', v_info.minor, '.', v_info.patch)) - print(str('GUT version: ', Gut.new().get_version())) - - o.print_help() - quit() - elif(o.get_value('-gpo')): - print('All command line options and where they are specified. ' + - 'The "final" value shows which value will actually be used ' + - 'based on order of precedence (default < .gutconfig < cmd line).' + "\n") - print(opt_resolver.to_s_verbose()) - quit() - elif(o.get_value('-gprint_gutconfig_sample')): - _print_gutconfigs(opt_resolver.get_resolved_values()) - quit() - else: - _final_opts = opt_resolver.get_resolved_values(); - apply_options(_final_opts) - _tester.test_scripts(!_run_single) - -# exit if option is set. -func _on_tests_finished(should_exit, should_exit_on_success): - if(_final_opts.dirs.size() == 0): - if(_tester.get_summary().get_totals().scripts == 0): - var lgr = _tester.get_logger() - lgr.error('No directories configured. Add directories with options or a .gutconfig.json file. Use the -gh option for more information.') - - if(_tester.get_fail_count()): - OS.exit_code = 1 - - # Overwrite the exit code with the post_script - var post_inst = _tester.get_post_run_script_instance() - if(post_inst != null and post_inst.get_exit_code() != null): - OS.exit_code = post_inst.get_exit_code() - - if(should_exit or (should_exit_on_success and _tester.get_fail_count() == 0)): - quit() - else: - print("Tests finished, exit manually") diff --git a/addons/gut/gut_plugin.gd b/addons/gut/gut_plugin.gd deleted file mode 100644 index 0517c1c..0000000 --- a/addons/gut/gut_plugin.gd +++ /dev/null @@ -1,12 +0,0 @@ -tool -extends EditorPlugin - -func _enter_tree(): - # Initialization of the plugin goes here - # Add the new type with a name, a parent type, a script and an icon - add_custom_type("Gut", "Control", preload("gut.gd"), preload("icon.png")) - -func _exit_tree(): - # Clean-up of the plugin goes here - # Always remember to remove it from the engine when deactivated - remove_custom_type("Gut") diff --git a/addons/gut/hook_script.gd b/addons/gut/hook_script.gd deleted file mode 100644 index 10a5e12..0000000 --- a/addons/gut/hook_script.gd +++ /dev/null @@ -1,35 +0,0 @@ -# ------------------------------------------------------------------------------ -# This script is the base for custom scripts to be used in pre and post -# run hooks. -# ------------------------------------------------------------------------------ - -# This is the instance of GUT that is running the tests. You can get -# information about the run from this object. This is set by GUT when the -# script is instantiated. -var gut = null - -# the exit code to be used by gut_cmdln. See set method. -var _exit_code = null - -var _should_abort = false -# Virtual method that will be called by GUT after instantiating -# this script. -func run(): - pass - -# Set the exit code when running from the command line. If not set then the -# default exit code will be returned (0 when no tests fail, 1 when any tests -# fail). -func set_exit_code(code): - _exit_code = code - -func get_exit_code(): - return _exit_code - -# Usable by pre-run script to cause the run to end AFTER the run() method -# finishes. post-run script will not be ran. -func abort(): - _should_abort = true - -func should_abort(): - return _should_abort diff --git a/addons/gut/icon.png b/addons/gut/icon.png deleted file mode 100644 index 7c589874e72ccdeae8dc1a45907fba0d63e603cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 320 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8Uop6XFV_m%d*!21sKVXw&;xMaGo1Os_Qt@2OC7#SFv=^B{n8kvR|7+D!uSecq=gBS*JG9l+sH00)|WTsW()}ZhBrU+z( Mr>mdKI;Vst04A4Hj{pDw diff --git a/addons/gut/logger.gd b/addons/gut/logger.gd deleted file mode 100644 index c7cd458..0000000 --- a/addons/gut/logger.gd +++ /dev/null @@ -1,105 +0,0 @@ -extends Node2D - -var _gut = null - -var types = { - warn = 'WARNING', - error = 'ERROR', - info = 'INFO', - debug = 'DEBUG', - deprecated = 'DEPRECATED' -} - -var _logs = { - types.warn: [], - types.error: [], - types.info: [], - types.debug: [], - types.deprecated: [] -} - -var _suppress_output = false - -func _gut_log_level_for_type(log_type): - if(log_type == types.warn or log_type == types.error or log_type == types.deprecated): - return 0 - else: - return 2 - -func _log(type, text): - _logs[type].append(text) - var formatted = str('[', type, '] ', text) - if(!_suppress_output): - if(_gut): - # this will keep the text indented under test for readability - _gut.p(formatted, _gut_log_level_for_type(type)) - # IDEA! We could store the current script and test that generated - # this output, which could be useful later if we printed out a summary. - else: - print(formatted) - return formatted - -# --------------- -# Get Methods -# --------------- -func get_warnings(): - return get_log_entries(types.warn) - -func get_errors(): - return get_log_entries(types.error) - -func get_infos(): - return get_log_entries(types.info) - -func get_debugs(): - return get_log_entries(types.debug) - -func get_deprecated(): - return get_log_entries(types.deprecated) - -func get_count(log_type=null): - var count = 0 - if(log_type == null): - for key in _logs: - count += _logs[key].size() - else: - count = _logs[log_type].size() - return count - -func get_log_entries(log_type): - return _logs[log_type] - -# --------------- -# Log methods -# --------------- -func warn(text): - return _log(types.warn, text) - -func error(text): - return _log(types.error, text) - -func info(text): - return _log(types.info, text) - -func debug(text): - return _log(types.debug, text) - -# supply some text or the name of the deprecated method and the replacement. -func deprecated(text, alt_method=null): - var msg = text - if(alt_method): - msg = str('The method ', text, ' is deprecated, use ', alt_method , ' instead.') - return _log(types.deprecated, msg) - -# --------------- -# Misc -# --------------- -func get_gut(): - return _gut - -func set_gut(gut): - _gut = gut - -func clear(): - for key in _logs: - _logs[key].clear() diff --git a/addons/gut/method_maker.gd b/addons/gut/method_maker.gd deleted file mode 100644 index 80503e4..0000000 --- a/addons/gut/method_maker.gd +++ /dev/null @@ -1,211 +0,0 @@ -# This class will generate method declaration lines based on method meta -# data. It will create defaults that match the method data. -# -# -------------------- -# function meta data -# -------------------- -# name: -# flags: -# args: [{ -# (class_name:), -# (hint:0), -# (hint_string:), -# (name:), -# (type:4), -# (usage:7) -# }] -# default_args [] - -var _utils = load('res://addons/gut/utils.gd').new() -var _lgr = _utils.get_logger() -const PARAM_PREFIX = 'p_' - -# ------------------------------------------------------ -# _supported_defaults -# -# This array contains all the data types that are supported for default values. -# If a value is supported it will contain either an empty string or a prefix -# that should be used when setting the parameter default value. -# For example int, real, bool do not need anything func(p1=1, p2=2.2, p3=false) -# but things like Vectors and Colors do since only the parameters to create a -# new Vector or Color are included in the metadata. -# ------------------------------------------------------ - # TYPE_NIL = 0 — Variable is of type nil (only applied for null). - # TYPE_BOOL = 1 — Variable is of type bool. - # TYPE_INT = 2 — Variable is of type int. - # TYPE_REAL = 3 — Variable is of type float/real. - # TYPE_STRING = 4 — Variable is of type String. - # TYPE_VECTOR2 = 5 — Variable is of type Vector2. - # TYPE_RECT2 = 6 — Variable is of type Rect2. - # TYPE_VECTOR3 = 7 — Variable is of type Vector3. - # TYPE_COLOR = 14 — Variable is of type Color. - # TYPE_OBJECT = 17 — Variable is of type Object. - # TYPE_DICTIONARY = 18 — Variable is of type Dictionary. - # TYPE_ARRAY = 19 — Variable is of type Array. - # TYPE_VECTOR2_ARRAY = 24 — Variable is of type PoolVector2Array. - - - -# TYPE_TRANSFORM2D = 8 — Variable is of type Transform2D. -# TYPE_PLANE = 9 — Variable is of type Plane. -# TYPE_QUAT = 10 — Variable is of type Quat. -# TYPE_AABB = 11 — Variable is of type AABB. -# TYPE_BASIS = 12 — Variable is of type Basis. -# TYPE_TRANSFORM = 13 — Variable is of type Transform. -# TYPE_NODE_PATH = 15 — Variable is of type NodePath. -# TYPE_RID = 16 — Variable is of type RID. -# TYPE_RAW_ARRAY = 20 — Variable is of type PoolByteArray. -# TYPE_INT_ARRAY = 21 — Variable is of type PoolIntArray. -# TYPE_REAL_ARRAY = 22 — Variable is of type PoolRealArray. -# TYPE_STRING_ARRAY = 23 — Variable is of type PoolStringArray. -# TYPE_VECTOR3_ARRAY = 25 — Variable is of type PoolVector3Array. -# TYPE_COLOR_ARRAY = 26 — Variable is of type PoolColorArray. -# TYPE_MAX = 27 — Marker for end of type constants. -# ------------------------------------------------------ -var _supported_defaults = [] - -func _init(): - for _i in range(TYPE_MAX): - _supported_defaults.append(null) - - # These types do not require a prefix for defaults - _supported_defaults[TYPE_NIL] = '' - _supported_defaults[TYPE_BOOL] = '' - _supported_defaults[TYPE_INT] = '' - _supported_defaults[TYPE_REAL] = '' - _supported_defaults[TYPE_OBJECT] = '' - _supported_defaults[TYPE_ARRAY] = '' - _supported_defaults[TYPE_STRING] = '' - _supported_defaults[TYPE_DICTIONARY] = '' - _supported_defaults[TYPE_VECTOR2_ARRAY] = '' - - # These require a prefix for whatever default is provided - _supported_defaults[TYPE_VECTOR2] = 'Vector2' - _supported_defaults[TYPE_RECT2] = 'Rect2' - _supported_defaults[TYPE_VECTOR3] = 'Vector3' - _supported_defaults[TYPE_COLOR] = 'Color' - -# ############### -# Private -# ############### -var _func_text = _utils.get_file_as_text('res://addons/gut/double_templates/function_template.gd') - -func _is_supported_default(type_flag): - return type_flag >= 0 and type_flag < _supported_defaults.size() and [type_flag] != null - -# Creates a list of parameters with defaults of null unless a default value is -# found in the metadata. If a default is found in the meta then it is used if -# it is one we know how support. -# -# If a default is found that we don't know how to handle then this method will -# return null. -func _get_arg_text(method_meta): - var text = '' - var args = method_meta.args - var defaults = [] - var has_unsupported_defaults = false - - # fill up the defaults with null defaults for everything that doesn't have - # a default in the meta data. default_args is an array of default values - # for the last n parameters where n is the size of default_args so we only - # add nulls for everything up to the first parameter with a default. - for _i in range(args.size() - method_meta.default_args.size()): - defaults.append('null') - - # Add meta-data defaults. - for i in range(method_meta.default_args.size()): - var t = args[defaults.size()]['type'] - var value = '' - if(_is_supported_default(t)): - # strings are special, they need quotes around the value - if(t == TYPE_STRING): - value = str("'", str(method_meta.default_args[i]), "'") - # Colors need the parens but things like Vector2 and Rect2 don't - elif(t == TYPE_COLOR): - value = str(_supported_defaults[t], '(', str(method_meta.default_args[i]), ')') - elif(t == TYPE_OBJECT): - if(str(method_meta.default_args[i]) == "[Object:null]"): - value = str(_supported_defaults[t], 'null') - else: - value = str(_supported_defaults[t], str(method_meta.default_args[i]).to_lower()) - - # Everything else puts the prefix (if one is there) form _supported_defaults - # in front. The to_lower is used b/c for some reason the defaults for - # null, true, false are all "Null", "True", "False". - else: - value = str(_supported_defaults[t], str(method_meta.default_args[i]).to_lower()) - else: - _lgr.warn(str( - 'Unsupported default param type: ',method_meta.name, '-', args[defaults.size()].name, ' ', t, ' = ', method_meta.default_args[i])) - value = str('unsupported=',t) - has_unsupported_defaults = true - - defaults.append(value) - - # construct the string of parameters - for i in range(args.size()): - text += str(PARAM_PREFIX, args[i].name, '=', defaults[i]) - if(i != args.size() -1): - text += ', ' - - # if we don't know how to make a default then we have to return null b/c - # it will cause a runtime error and it's one thing we could return to let - # callers know it didn't work. - if(has_unsupported_defaults): - text = null - - return text - -# ############### -# Public -# ############### - -# Creates a delceration for a function based off of function metadata. All -# types whose defaults are supported will have their values. If a datatype -# is not supported and the parameter has a default, a warning message will be -# printed and the declaration will return null. -func get_function_text(meta): - var method_params = _get_arg_text(meta) - var text = null - - var param_array = get_spy_call_parameters_text(meta) - if(param_array == 'null'): - param_array = '[]' - - if(method_params != null): - var decleration = str('func ', meta.name, '(', method_params, '):') - text = _func_text.format({ - "func_decleration":decleration, - "method_name":meta.name, - "param_array":param_array, - "super_call":get_super_call_text(meta) - }) - return text - -# creates a call to the function in meta in the super's class. -func get_super_call_text(meta): - var params = '' - - for i in range(meta.args.size()): - params += PARAM_PREFIX + meta.args[i].name - if(meta.args.size() > 1 and i != meta.args.size() -1): - params += ', ' - - return str('.', meta.name, '(', params, ')') - -func get_spy_call_parameters_text(meta): - var called_with = 'null' - if(meta.args.size() > 0): - called_with = '[' - for i in range(meta.args.size()): - called_with += str(PARAM_PREFIX, meta.args[i].name) - if(i < meta.args.size() - 1): - called_with += ', ' - called_with += ']' - return called_with - -func get_logger(): - return _lgr - -func set_logger(logger): - _lgr = logger diff --git a/addons/gut/one_to_many.gd b/addons/gut/one_to_many.gd deleted file mode 100644 index 6a0f818..0000000 --- a/addons/gut/one_to_many.gd +++ /dev/null @@ -1,38 +0,0 @@ -# ------------------------------------------------------------------------------ -# This datastructure represents a simple one-to-many relationship. It manages -# a dictionary of value/array pairs. It ignores duplicates of both the "one" -# and the "many". -# ------------------------------------------------------------------------------ -var _items = {} - -# return the size of _items or the size of an element in _items if "one" was -# specified. -func size(one=null): - var to_return = 0 - if(one == null): - to_return = _items.size() - elif(_items.has(one)): - to_return = _items[one].size() - return to_return - -# Add an element to "one" if it does not already exist -func add(one, many_item): - if(_items.has(one) and !_items[one].has(many_item)): - _items[one].append(many_item) - else: - _items[one] = [many_item] - -func clear(): - _items.clear() - -func has(one, many_item): - var to_return = false - if(_items.has(one)): - to_return = _items[one].has(many_item) - return to_return - -func to_s(): - var to_return = '' - for key in _items: - to_return += str(key, ": ", _items[key], "\n") - return to_return diff --git a/addons/gut/optparse.gd b/addons/gut/optparse.gd deleted file mode 100644 index 0f6ccf5..0000000 --- a/addons/gut/optparse.gd +++ /dev/null @@ -1,250 +0,0 @@ -################################################################################ -#(G)odot (U)nit (T)est class -# -################################################################################ -#The MIT License (MIT) -#===================== -# -#Copyright (c) 2019 Tom "Butch" Wesley -# -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: -# -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. -# -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# -################################################################################ -# Description -# ----------- -# Command line interface for the GUT unit testing tool. Allows you to run tests -# from the command line instead of running a scene. Place this script along with -# gut.gd into your scripts directory at the root of your project. Once there you -# can run this script (from the root of your project) using the following command: -# godot -s -d test/gut/gut_cmdln.gd -# -# See the readme for a list of options and examples. You can also use the -gh -# option to get more information about how to use the command line interface. -# -# Version 6.8.2 -################################################################################ - -#------------------------------------------------------------------------------- -# Parses the command line arguments supplied into an array that can then be -# examined and parsed based on how the gut options work. -#------------------------------------------------------------------------------- -class CmdLineParser: - var _used_options = [] - # an array of arrays. Each element in this array will contain an option - # name and if that option contains a value then it will have a sedond - # element. For example: - # [[-gselect, test.gd], [-gexit]] - var _opts = [] - - func _init(): - for i in range(OS.get_cmdline_args().size()): - var opt_val = OS.get_cmdline_args()[i].split('=') - _opts.append(opt_val) - - # Parse out multiple comma delimited values from a command line - # option. Values are separated from option name with "=" and - # additional values are comma separated. - func _parse_array_value(full_option): - var value = _parse_option_value(full_option) - var split = value.split(',') - return split - - # Parse out the value of an option. Values are separated from - # the option name with "=" - func _parse_option_value(full_option): - if(full_option.size() > 1): - return full_option[1] - else: - return null - - # Search _opts for an element that starts with the option name - # specified. - func find_option(name): - var found = false - var idx = 0 - - while(idx < _opts.size() and !found): - if(_opts[idx][0] == name): - found = true - else: - idx += 1 - - if(found): - return idx - else: - return -1 - - func get_array_value(option): - _used_options.append(option) - var to_return = [] - var opt_loc = find_option(option) - if(opt_loc != -1): - to_return = _parse_array_value(_opts[opt_loc]) - _opts.remove(opt_loc) - - return to_return - - # returns the value of an option if it was specified, null otherwise. This - # used to return the default but that became problemnatic when trying to - # punch through the different places where values could be specified. - func get_value(option): - _used_options.append(option) - var to_return = null - var opt_loc = find_option(option) - if(opt_loc != -1): - to_return = _parse_option_value(_opts[opt_loc]) - _opts.remove(opt_loc) - - return to_return - - # returns true if it finds the option, false if not. - func was_specified(option): - _used_options.append(option) - return find_option(option) != -1 - - # Returns any unused command line options. I found that only the -s and - # script name come through from godot, all other options that godot uses - # are not sent through OS.get_cmdline_args(). - # - # This is a onetime thing b/c i kill all items in _used_options - func get_unused_options(): - var to_return = [] - for i in range(_opts.size()): - to_return.append(_opts[i][0]) - - var script_option = to_return.find('-s') - if script_option != -1: - to_return.remove(script_option + 1) - to_return.remove(script_option) - - while(_used_options.size() > 0): - var index = to_return.find(_used_options[0].split("=")[0]) - if(index != -1): - to_return.remove(index) - _used_options.remove(0) - - return to_return - -#------------------------------------------------------------------------------- -# Simple class to hold a command line option -#------------------------------------------------------------------------------- -class Option: - var value = null - var option_name = '' - var default = null - var description = '' - - func _init(name, default_value, desc=''): - option_name = name - default = default_value - description = desc - value = null#default_value - - func pad(to_pad, size, pad_with=' '): - var to_return = to_pad - for _i in range(to_pad.length(), size): - to_return += pad_with - - return to_return - - func to_s(min_space=0): - var subbed_desc = description - if(subbed_desc.find('[default]') != -1): - subbed_desc = subbed_desc.replace('[default]', str(default)) - return pad(option_name, min_space) + subbed_desc - -#------------------------------------------------------------------------------- -# The high level interface between this script and the command line options -# supplied. Uses Option class and CmdLineParser to extract information from -# the command line and make it easily accessible. -#------------------------------------------------------------------------------- -var options = [] -var _opts = [] -var _banner = '' - -func add(name, default, desc): - options.append(Option.new(name, default, desc)) - -func get_value(name): - var found = false - var idx = 0 - - while(idx < options.size() and !found): - if(options[idx].option_name == name): - found = true - else: - idx += 1 - - if(found): - return options[idx].value - else: - print("COULD NOT FIND OPTION " + name) - return null - -func set_banner(banner): - _banner = banner - -func print_help(): - var longest = 0 - for i in range(options.size()): - if(options[i].option_name.length() > longest): - longest = options[i].option_name.length() - - print('---------------------------------------------------------') - print(_banner) - - print("\nOptions\n-------") - for i in range(options.size()): - print(' ' + options[i].to_s(longest + 2)) - print('---------------------------------------------------------') - -func print_options(): - for i in range(options.size()): - print(options[i].option_name + '=' + str(options[i].value)) - -func parse(): - var parser = CmdLineParser.new() - - for i in range(options.size()): - var t = typeof(options[i].default) - # only set values that were specified at the command line so that - # we can punch through default and config values correctly later. - # Without this check, you can't tell the difference between the - # defaults and what was specified, so you can't punch through - # higher level options. - if(parser.was_specified(options[i].option_name)): - if(t == TYPE_INT): - options[i].value = int(parser.get_value(options[i].option_name)) - elif(t == TYPE_STRING): - options[i].value = parser.get_value(options[i].option_name) - elif(t == TYPE_ARRAY): - options[i].value = parser.get_array_value(options[i].option_name) - elif(t == TYPE_BOOL): - options[i].value = parser.was_specified(options[i].option_name) - elif(t == TYPE_NIL): - print(options[i].option_name + ' cannot be processed, it has a nil datatype') - else: - print(options[i].option_name + ' cannot be processed, it has unknown datatype:' + str(t)) - - var unused = parser.get_unused_options() - if(unused.size() > 0): - print("Unrecognized options: ", unused) - return false - - return true diff --git a/addons/gut/plugin.cfg b/addons/gut/plugin.cfg deleted file mode 100644 index 31fd316..0000000 --- a/addons/gut/plugin.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[plugin] - -name="Gut" -description="Unit Testing tool for Godot." -author="Butch Wesley" -version="6.8.2" -script="gut_plugin.gd" diff --git a/addons/gut/signal_watcher.gd b/addons/gut/signal_watcher.gd deleted file mode 100644 index 5fae3fe..0000000 --- a/addons/gut/signal_watcher.gd +++ /dev/null @@ -1,166 +0,0 @@ -################################################################################ -#The MIT License (MIT) -#===================== -# -#Copyright (c) 2019 Tom "Butch" Wesley -# -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: -# -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. -# -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# -################################################################################ - -# Some arbitrary string that should never show up by accident. If it does, then -# shame on you. -const ARG_NOT_SET = '_*_argument_*_is_*_not_set_*_' - -# This hash holds the objects that are being watched, the signals that are being -# watched, and an array of arrays that contains arguments that were passed -# each time the signal was emitted. -# -# For example: -# _watched_signals => { -# ref1 => { -# 'signal1' => [[], [], []], -# 'signal2' => [[p1, p2]], -# 'signal3' => [[p1]] -# }, -# ref2 => { -# 'some_signal' => [], -# 'other_signal' => [[p1, p2, p3], [p1, p2, p3], [p1, p2, p3]] -# } -# } -# -# In this sample: -# - signal1 on the ref1 object was emitted 3 times and each time, zero -# parameters were passed. -# - signal3 on ref1 was emitted once and passed a single parameter -# - some_signal on ref2 was never emitted. -# - other_signal on ref2 was emitted 3 times, each time with 3 parameters. -var _watched_signals = {} -var _utils = load('res://addons/gut/utils.gd').new() - -func _add_watched_signal(obj, name): - # SHORTCIRCUIT - ignore dupes - if(_watched_signals.has(obj) and _watched_signals[obj].has(name)): - return - - if(!_watched_signals.has(obj)): - _watched_signals[obj] = {name:[]} - else: - _watched_signals[obj][name] = [] - obj.connect(name, self, '_on_watched_signal', [obj, name]) - -# This handles all the signals that are watched. It supports up to 9 parameters -# which could be emitted by the signal and the two parameters used when it is -# connected via watch_signal. I chose 9 since you can only specify up to 9 -# parameters when dynamically calling a method via call (per the Godot -# documentation, i.e. some_object.call('some_method', 1, 2, 3...)). -# -# Based on the documentation of emit_signal, it appears you can only pass up -# to 4 parameters when firing a signal. I haven't verified this, but this should -# future proof this some if the value ever grows. -func _on_watched_signal(arg1=ARG_NOT_SET, arg2=ARG_NOT_SET, arg3=ARG_NOT_SET, \ - arg4=ARG_NOT_SET, arg5=ARG_NOT_SET, arg6=ARG_NOT_SET, \ - arg7=ARG_NOT_SET, arg8=ARG_NOT_SET, arg9=ARG_NOT_SET, \ - arg10=ARG_NOT_SET, arg11=ARG_NOT_SET): - var args = [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11] - - # strip off any unused vars. - var idx = args.size() -1 - while(str(args[idx]) == ARG_NOT_SET): - args.remove(idx) - idx -= 1 - - # retrieve object and signal name from the array and remove them. These - # will always be at the end since they are added when the connect happens. - var signal_name = args[args.size() -1] - args.pop_back() - var object = args[args.size() -1] - args.pop_back() - - _watched_signals[object][signal_name].append(args) - -func does_object_have_signal(object, signal_name): - var signals = object.get_signal_list() - for i in range(signals.size()): - if(signals[i]['name'] == signal_name): - return true - return false - -func watch_signals(object): - var signals = object.get_signal_list() - for i in range(signals.size()): - _add_watched_signal(object, signals[i]['name']) - -func watch_signal(object, signal_name): - var did = false - if(does_object_have_signal(object, signal_name)): - _add_watched_signal(object, signal_name) - did = true - return did - -func get_emit_count(object, signal_name): - var to_return = -1 - if(is_watching(object, signal_name)): - to_return = _watched_signals[object][signal_name].size() - return to_return - -func did_emit(object, signal_name): - var did = false - if(is_watching(object, signal_name)): - did = get_emit_count(object, signal_name) != 0 - return did - -func print_object_signals(object): - var list = object.get_signal_list() - for i in range(list.size()): - print(list[i].name, "\n ", list[i]) - -func get_signal_parameters(object, signal_name, index=-1): - var params = null - if(is_watching(object, signal_name)): - var all_params = _watched_signals[object][signal_name] - if(all_params.size() > 0): - if(index == -1): - index = all_params.size() -1 - params = all_params[index] - return params - -func is_watching_object(object): - return _watched_signals.has(object) - -func is_watching(object, signal_name): - return _watched_signals.has(object) and _watched_signals[object].has(signal_name) - -func clear(): - for obj in _watched_signals: - for signal_name in _watched_signals[obj]: - if(_utils.is_not_freed(obj)): - obj.disconnect(signal_name, self, '_on_watched_signal') - _watched_signals.clear() - -# Returns a list of all the signal names that were emitted by the object. -# If the object is not being watched then an empty list is returned. -func get_signals_emitted(obj): - var emitted = [] - if(is_watching_object(obj)): - for signal_name in _watched_signals[obj]: - if(_watched_signals[obj][signal_name].size() > 0): - emitted.append(signal_name) - - return emitted diff --git a/addons/gut/source_code_pro.fnt b/addons/gut/source_code_pro.fnt deleted file mode 100644 index 3367650f77041f8020c39f0b1bb90d21e2ddf425..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26499 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

F`ud^Dyusp94YMD$s2-p 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 at the -# end. Used for displaying the number of scripts without including all the -# Inner Classes. -func get_non_inner_class_script_count(): - var unique_scripts = {} - for i in range(_scripts.size()): - var ext_loc = _scripts[i].name.find_last('.gd.') - if(ext_loc == -1): - unique_scripts[_scripts[i].name] = 1 - else: - unique_scripts[_scripts[i].name.substr(0, ext_loc + 3)] = 1 - return unique_scripts.keys().size() - -func get_totals(): - var totals = { - passing = 0, - pending = 0, - failing = 0, - tests = 0, - scripts = 0 - } - - for s in range(_scripts.size()): - totals.passing += _scripts[s].get_pass_count() - totals.pending += _scripts[s].get_pending_count() - totals.failing += _scripts[s].get_fail_count() - totals.tests += _scripts[s]._test_order.size() - - totals.scripts = get_non_inner_class_script_count() - - return totals - -func get_summary_text(): - var _totals = get_totals() - - var to_return = '' - for s in range(_scripts.size()): - if(_scripts[s].get_fail_count() > 0 or _scripts[s].get_pending_count() > 0): - to_return += _scripts[s].name + "\n" - for t in range(_scripts[s]._test_order.size()): - var tname = _scripts[s]._test_order[t] - var test = _scripts[s].get_test_obj(tname) - if(test.fail_texts.size() > 0 or test.pending_texts.size() > 0): - to_return += str(' - ', tname, "\n", test.to_s()) - - var header = "*** Totals ***\n" - header += str(' Scripts: ', get_non_inner_class_script_count(), "\n") - header += str(' Tests: ', _totals.tests, "\n") - header += str(' Passing asserts: ', _totals.passing, "\n") - header += str(' Failing asserts: ',_totals.failing, "\n") - header += str(' Pending: ', _totals.pending, "\n") - - return to_return + "\n" + header diff --git a/addons/gut/test.gd b/addons/gut/test.gd deleted file mode 100644 index 0be9b15..0000000 --- a/addons/gut/test.gd +++ /dev/null @@ -1,1173 +0,0 @@ -################################################################################ -#(G)odot (U)nit (T)est class -# -################################################################################ -#The MIT License (MIT) -#===================== -# -#Copyright (c) 2019 Tom "Butch" Wesley -# -#Permission is hereby granted, free of charge, to any person obtaining a copy -#of this software and associated documentation files (the "Software"), to deal -#in the Software without restriction, including without limitation the rights -#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -#copies of the Software, and to permit persons to whom the Software is -#furnished to do so, subject to the following conditions: -# -#The above copyright notice and this permission notice shall be included in -#all copies or substantial portions of the Software. -# -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -#THE SOFTWARE. -# -################################################################################ -# View readme for usage details. -# -# Version - see gut.gd -################################################################################ -# Class that all test scripts must extend. -# -# This provides all the asserts and other testing features. Test scripts are -# run by the Gut class in gut.gd -################################################################################ -extends Node - -# ------------------------------------------------------------------------------ -# Helper class to hold info for objects to double. This extracts info and has -# some convenience methods. This is key in being able to make the "smart double" -# method which makes doubling much easier for the user. -# ------------------------------------------------------------------------------ -class DoubleInfo: - var path - var subpath - var strategy - var make_partial - var extension - var _utils = load('res://addons/gut/utils.gd').new() - var _is_native = false - - # Flexible init method. p2 can be subpath or stategy unless p3 is - # specified, then p2 must be subpath and p3 is strategy. - # - # Examples: - # (object_to_double) - # (object_to_double, subpath) - # (object_to_double, strategy) - # (object_to_double, subpath, strategy) - func _init(thing, p2=null, p3=null): - strategy = p2 - - if(typeof(p2) == TYPE_STRING): - strategy = p3 - subpath = p2 - - if(typeof(thing) == TYPE_OBJECT): - if(_utils.is_native_class(thing)): - path = thing - _is_native = true - extension = 'native_class_not_used' - else: - path = thing.resource_path - else: - path = thing - - if(!_is_native): - extension = path.get_extension() - - func is_scene(): - return extension == 'tscn' - - func is_script(): - return extension == 'gd' - - func is_native(): - return _is_native - -# ------------------------------------------------------------------------------ -# Begin test.gd -# ------------------------------------------------------------------------------ - -# constant for signal when calling yield_for -const YIELD = 'timeout' - -# Need a reference to the instance that is running the tests. This -# is set by the gut class when it runs the tests. This gets you -# access to the asserts in the tests you write. -var gut = null -var passed = false -var failed = false -var _disable_strict_datatype_checks = false -# Holds all the text for a test's fail/pass. This is used for testing purposes -# to see the text of a failed sub-test in test_test.gd -var _fail_pass_text = [] - -# Hash containing all the built in types in Godot. This provides an English -# name for the types that corosponds with the type constants defined in the -# engine. This is used for priting out messages when comparing types fails. -var types = {} - -func _init_types_dictionary(): - types[TYPE_NIL] = 'TYPE_NIL' - types[TYPE_BOOL] = 'Bool' - types[TYPE_INT] = 'Int' - types[TYPE_REAL] = 'Float/Real' - types[TYPE_STRING] = 'String' - types[TYPE_VECTOR2] = 'Vector2' - types[TYPE_RECT2] = 'Rect2' - types[TYPE_VECTOR3] = 'Vector3' - #types[8] = 'Matrix32' - types[TYPE_PLANE] = 'Plane' - types[TYPE_QUAT] = 'QUAT' - types[TYPE_AABB] = 'AABB' - #types[12] = 'Matrix3' - types[TYPE_TRANSFORM] = 'Transform' - types[TYPE_COLOR] = 'Color' - #types[15] = 'Image' - types[TYPE_NODE_PATH] = 'Node Path' - types[TYPE_RID] = 'RID' - types[TYPE_OBJECT] = 'TYPE_OBJECT' - #types[19] = 'TYPE_INPUT_EVENT' - types[TYPE_DICTIONARY] = 'Dictionary' - types[TYPE_ARRAY] = 'Array' - types[TYPE_RAW_ARRAY] = 'TYPE_RAW_ARRAY' - types[TYPE_INT_ARRAY] = 'TYPE_INT_ARRAY' - types[TYPE_REAL_ARRAY] = 'TYPE_REAL_ARRAY' - types[TYPE_STRING_ARRAY] = 'TYPE_STRING_ARRAY' - types[TYPE_VECTOR2_ARRAY] = 'TYPE_VECTOR2_ARRAY' - types[TYPE_VECTOR3_ARRAY] = 'TYPE_VECTOR3_ARRAY' - types[TYPE_COLOR_ARRAY] = 'TYPE_COLOR_ARRAY' - types[TYPE_MAX] = 'TYPE_MAX' - -const EDITOR_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT -const VARIABLE_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE - -# Summary counts for the test. -var _summary = { - asserts = 0, - passed = 0, - failed = 0, - tests = 0, - pending = 0 -} - -# This is used to watch signals so we can make assertions about them. -var _signal_watcher = load('res://addons/gut/signal_watcher.gd').new() - -# Convenience copy of _utils.DOUBLE_STRATEGY -var DOUBLE_STRATEGY = null -var _utils = load('res://addons/gut/utils.gd').new() -var _lgr = _utils.get_logger() - -func _init(): - _init_types_dictionary() - DOUBLE_STRATEGY = _utils.DOUBLE_STRATEGY # yes, this is right - -# ------------------------------------------------------------------------------ -# Fail an assertion. Causes test and script to fail as well. -# ------------------------------------------------------------------------------ -func _fail(text): - _summary.asserts += 1 - _summary.failed += 1 - var msg = 'FAILED: ' + text - _fail_pass_text.append(msg) - if(gut): - gut.p(msg, gut.LOG_LEVEL_FAIL_ONLY) - gut._fail(text) - -# ------------------------------------------------------------------------------ -# Pass an assertion. -# ------------------------------------------------------------------------------ -func _pass(text): - _summary.asserts += 1 - _summary.passed += 1 - var msg = "PASSED: " + text - _fail_pass_text.append(msg) - if(gut): - gut.p(msg, gut.LOG_LEVEL_ALL_ASSERTS) - gut._pass(text) - -# ------------------------------------------------------------------------------ -# Checks if the datatypes passed in match. If they do not then this will cause -# a fail to occur. If they match then TRUE is returned, FALSE if not. This is -# used in all the assertions that compare values. -# ------------------------------------------------------------------------------ -func _do_datatypes_match__fail_if_not(got, expected, text): - var did_pass = true - - if(!_disable_strict_datatype_checks): - var got_type = typeof(got) - var expect_type = typeof(expected) - if(got_type != expect_type and got != null and expected != null): - # If we have a mismatch between float and int (types 2 and 3) then - # print out a warning but do not fail. - if([2, 3].has(got_type) and [2, 3].has(expect_type)): - _lgr.warn(str('Warn: Float/Int comparison. Got ', types[got_type], ' but expected ', types[expect_type])) - else: - _fail('Cannot compare ' + types[got_type] + '[' + str(got) + '] to ' + types[expect_type] + '[' + str(expected) + ']. ' + text) - did_pass = false - - return did_pass - -# ------------------------------------------------------------------------------ -# Create a string that lists all the methods that were called on an spied -# instance. -# ------------------------------------------------------------------------------ -func _get_desc_of_calls_to_instance(inst): - var BULLET = ' * ' - var calls = gut.get_spy().get_call_list_as_string(inst) - # indent all the calls - calls = BULLET + calls.replace("\n", "\n" + BULLET) - # remove trailing newline and bullet - calls = calls.substr(0, calls.length() - BULLET.length() - 1) - return "Calls made on " + str(inst) + "\n" + calls - -# ------------------------------------------------------------------------------ -# Signal assertion helper. Do not call directly, use _can_make_signal_assertions -# ------------------------------------------------------------------------------ -func _fail_if_does_not_have_signal(object, signal_name): - var did_fail = false - if(!_signal_watcher.does_object_have_signal(object, signal_name)): - _fail(str('Object ', object, ' does not have the signal [', signal_name, ']')) - did_fail = true - return did_fail -# ------------------------------------------------------------------------------ -# Signal assertion helper. Do not call directly, use _can_make_signal_assertions -# ------------------------------------------------------------------------------ -func _fail_if_not_watching(object): - var did_fail = false - if(!_signal_watcher.is_watching_object(object)): - _fail(str('Cannot make signal assertions because the object ', object, \ - ' is not being watched. Call watch_signals(some_object) to be able to make assertions about signals.')) - did_fail = true - return did_fail - -# ------------------------------------------------------------------------------ -# Returns text that contains original text and a list of all the signals that -# were emitted for the passed in object. -# ------------------------------------------------------------------------------ -func _get_fail_msg_including_emitted_signals(text, object): - return str(text," (Signals emitted: ", _signal_watcher.get_signals_emitted(object), ")") - -# ------------------------------------------------------------------------------ -# This validates that parameters is an array and generates a specific error -# and a failure with a specific message -# ------------------------------------------------------------------------------ -func _fail_if_parameters_not_array(parameters): - var invalid = parameters != null and typeof(parameters) != TYPE_ARRAY - if(invalid): - _lgr.error('The "parameters" parameter must be an array of expected parameter values.') - _fail('Cannot compare paramter values because an array was not passed.') - return invalid -# ####################### -# Virtual Methods -# ####################### - -# alias for prerun_setup -func before_all(): - pass - -# alias for setup -func before_each(): - pass - -# alias for postrun_teardown -func after_all(): - pass - -# alias for teardown -func after_each(): - pass - -# ####################### -# Public -# ####################### - -func get_logger(): - return _lgr - -func set_logger(logger): - _lgr = logger - - -# ####################### -# Asserts -# ####################### - -# ------------------------------------------------------------------------------ -# Asserts that the expected value equals the value got. -# ------------------------------------------------------------------------------ -func assert_eq(got, expected, text=""): - var disp = "[" + str(got) + "] expected to equal [" + str(expected) + "]: " + text - if(_do_datatypes_match__fail_if_not(got, expected, text)): - if(expected != got): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Asserts that the value got does not equal the "not expected" value. -# ------------------------------------------------------------------------------ -func assert_ne(got, not_expected, text=""): - var disp = "[" + str(got) + "] expected to be anything except [" + str(not_expected) + "]: " + text - if(_do_datatypes_match__fail_if_not(got, not_expected, text)): - if(got == not_expected): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Asserts that the expected value almost equals the value got. -# ------------------------------------------------------------------------------ -func assert_almost_eq(got, expected, error_interval, text=''): - var disp = "[" + str(got) + "] expected to equal [" + str(expected) + "] +/- [" + str(error_interval) + "]: " + text - if(_do_datatypes_match__fail_if_not(got, expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)): - if(got < (expected - error_interval) or got > (expected + error_interval)): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Asserts that the expected value does not almost equal the value got. -# ------------------------------------------------------------------------------ -func assert_almost_ne(got, not_expected, error_interval, text=''): - var disp = "[" + str(got) + "] expected to not equal [" + str(not_expected) + "] +/- [" + str(error_interval) + "]: " + text - if(_do_datatypes_match__fail_if_not(got, not_expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)): - if(got < (not_expected - error_interval) or got > (not_expected + error_interval)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts got is greater than expected -# ------------------------------------------------------------------------------ -func assert_gt(got, expected, text=""): - var disp = "[" + str(got) + "] expected to be > than [" + str(expected) + "]: " + text - if(_do_datatypes_match__fail_if_not(got, expected, text)): - if(got > expected): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts got is less than expected -# ------------------------------------------------------------------------------ -func assert_lt(got, expected, text=""): - var disp = "[" + str(got) + "] expected to be < than [" + str(expected) + "]: " + text - if(_do_datatypes_match__fail_if_not(got, expected, text)): - if(got < expected): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# asserts that got is true -# ------------------------------------------------------------------------------ -func assert_true(got, text=""): - if(!got): - _fail(text) - else: - _pass(text) - -# ------------------------------------------------------------------------------ -# Asserts that got is false -# ------------------------------------------------------------------------------ -func assert_false(got, text=""): - if(got): - _fail(text) - else: - _pass(text) - -# ------------------------------------------------------------------------------ -# Asserts value is between (inclusive) the two expected values. -# ------------------------------------------------------------------------------ -func assert_between(got, expect_low, expect_high, text=""): - var disp = "[" + str(got) + "] expected to be between [" + str(expect_low) + "] and [" + str(expect_high) + "]: " + text - - if(_do_datatypes_match__fail_if_not(got, expect_low, text) and _do_datatypes_match__fail_if_not(got, expect_high, text)): - if(expect_low > expect_high): - disp = "INVALID range. [" + str(expect_low) + "] is not less than [" + str(expect_high) + "]" - _fail(disp) - else: - if(got < expect_low or got > expect_high): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Uses the 'has' method of the object passed in to determine if it contains -# the passed in element. -# ------------------------------------------------------------------------------ -func assert_has(obj, element, text=""): - var disp = str('Expected [', obj, '] to contain value: [', element, ']: ', text) - if(obj.has(element)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func assert_does_not_have(obj, element, text=""): - var disp = str('Expected [', obj, '] to NOT contain value: [', element, ']: ', text) - if(obj.has(element)): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Asserts that a file exists -# ------------------------------------------------------------------------------ -func assert_file_exists(file_path): - var disp = 'expected [' + file_path + '] to exist.' - var f = File.new() - if(f.file_exists(file_path)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts that a file should not exist -# ------------------------------------------------------------------------------ -func assert_file_does_not_exist(file_path): - var disp = 'expected [' + file_path + '] to NOT exist' - var f = File.new() - if(!f.file_exists(file_path)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts the specified file is empty -# ------------------------------------------------------------------------------ -func assert_file_empty(file_path): - var disp = 'expected [' + file_path + '] to be empty' - var f = File.new() - if(f.file_exists(file_path) and gut.is_file_empty(file_path)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts the specified file is not empty -# ------------------------------------------------------------------------------ -func assert_file_not_empty(file_path): - var disp = 'expected [' + file_path + '] to contain data' - if(!gut.is_file_empty(file_path)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts the object has the specified method -# ------------------------------------------------------------------------------ -func assert_has_method(obj, method): - assert_true(obj.has_method(method), 'Should have method: ' + method) - -# Old deprecated method name -func assert_get_set_methods(obj, property, default, set_to): - _lgr.deprecated('assert_get_set_methods', 'assert_accessors') - assert_accessors(obj, property, default, set_to) - -# ------------------------------------------------------------------------------ -# Verifies the object has get and set methods for the property passed in. The -# property isn't tied to anything, just a name to be appended to the end of -# get_ and set_. Asserts the get_ and set_ methods exist, if not, it stops there. -# If they exist then it asserts get_ returns the expected default then calls -# set_ and asserts get_ has the value it was set to. -# ------------------------------------------------------------------------------ -func assert_accessors(obj, property, default, set_to): - var fail_count = _summary.failed - var get = 'get_' + property - var set = 'set_' + property - assert_has_method(obj, get) - assert_has_method(obj, set) - # SHORT CIRCUIT - if(_summary.failed > fail_count): - return - assert_eq(obj.call(get), default, 'It should have the expected default value.') - obj.call(set, set_to) - assert_eq(obj.call(get), set_to, 'The set value should have been returned.') - - -# --------------------------------------------------------------------------- -# Property search helper. Used to retrieve Dictionary of specified property -# from passed object. Returns null if not found. -# If provided, property_usage constrains the type of property returned by -# passing either: -# EDITOR_PROPERTY for properties defined as: export(int) var some_value -# VARIABLE_PROPERTY for properties defunded as: var another_value -# --------------------------------------------------------------------------- -func _find_object_property(obj, property_name, property_usage=null): - var result = null - var found = false - var properties = obj.get_property_list() - - while !found and !properties.empty(): - var property = properties.pop_back() - if property['name'] == property_name: - if property_usage == null or property['usage'] == property_usage: - result = property - found = true - return result - -# ------------------------------------------------------------------------------ -# Asserts a class exports a variable. -# ------------------------------------------------------------------------------ -func assert_exports(obj, property_name, type): - var disp = 'expected %s to have editor property [%s]' % [obj, property_name] - var property = _find_object_property(obj, property_name, EDITOR_PROPERTY) - if property != null: - disp += ' of type [%s]. Got type [%s].' % [types[type], types[property['type']]] - if property['type'] == type: - _pass(disp) - else: - _fail(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Signal assertion helper. -# -# Verifies that the object and signal are valid for making signal assertions. -# This will fail with specific messages that indicate why they are not valid. -# This returns true/false to indicate if the object and signal are valid. -# ------------------------------------------------------------------------------ -func _can_make_signal_assertions(object, signal_name): - return !(_fail_if_not_watching(object) or _fail_if_does_not_have_signal(object, signal_name)) - -# ------------------------------------------------------------------------------ -# Check if an object is connected to a signal on another object. Returns True -# if it is and false otherwise -# ------------------------------------------------------------------------------ -func _is_connected(signaler_obj, connect_to_obj, signal_name, method_name=""): - if(method_name != ""): - return signaler_obj.is_connected(signal_name, connect_to_obj, method_name) - else: - var connections = signaler_obj.get_signal_connection_list(signal_name) - for conn in connections: - if((conn.source == signaler_obj) and (conn.target == connect_to_obj)): - return true - return false -# ------------------------------------------------------------------------------ -# Watch the signals for an object. This must be called before you can make -# any assertions about the signals themselves. -# ------------------------------------------------------------------------------ -func watch_signals(object): - _signal_watcher.watch_signals(object) - -# ------------------------------------------------------------------------------ -# Asserts that an object is connected to a signal on another object -# -# This will fail with specific messages if the target object is not connected -# to the specified signal on the source object. -# ------------------------------------------------------------------------------ -func assert_connected(signaler_obj, connect_to_obj, signal_name, method_name=""): - pass - var method_disp = '' - if (method_name != ""): - method_disp = str(' using method: [', method_name, '] ') - var disp = str('Expected object ', signaler_obj,\ - ' to be connected to signal: [', signal_name, '] on ',\ - connect_to_obj, method_disp) - if(_is_connected(signaler_obj, connect_to_obj, signal_name, method_name)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts that an object is not connected to a signal on another object -# -# This will fail with specific messages if the target object is connected -# to the specified signal on the source object. -# ------------------------------------------------------------------------------ -func assert_not_connected(signaler_obj, connect_to_obj, signal_name, method_name=""): - var method_disp = '' - if (method_name != ""): - method_disp = str(' using method: [', method_name, '] ') - var disp = str('Expected object ', signaler_obj,\ - ' to not be connected to signal: [', signal_name, '] on ',\ - connect_to_obj, method_disp) - if(_is_connected(signaler_obj, connect_to_obj, signal_name, method_name)): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Asserts that a signal has been emitted at least once. -# -# This will fail with specific messages if the object is not being watched or -# the object does not have the specified signal -# ------------------------------------------------------------------------------ -func assert_signal_emitted(object, signal_name, text=""): - var disp = str('Expected object ', object, ' to have emitted signal [', signal_name, ']: ', text) - if(_can_make_signal_assertions(object, signal_name)): - if(_signal_watcher.did_emit(object, signal_name)): - _pass(disp) - else: - _fail(_get_fail_msg_including_emitted_signals(disp, object)) - -# ------------------------------------------------------------------------------ -# Asserts that a signal has not been emitted. -# -# This will fail with specific messages if the object is not being watched or -# the object does not have the specified signal -# ------------------------------------------------------------------------------ -func assert_signal_not_emitted(object, signal_name, text=""): - var disp = str('Expected object ', object, ' to NOT emit signal [', signal_name, ']: ', text) - if(_can_make_signal_assertions(object, signal_name)): - if(_signal_watcher.did_emit(object, signal_name)): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Asserts that a signal was fired with the specified parameters. The expected -# parameters should be passed in as an array. An optional index can be passed -# when a signal has fired more than once. The default is to retrieve the most -# recent emission of the signal. -# -# This will fail with specific messages if the object is not being watched or -# the object does not have the specified signal -# ------------------------------------------------------------------------------ -func assert_signal_emitted_with_parameters(object, signal_name, parameters, index=-1): - var disp = str('Expected object ', object, ' to emit signal [', signal_name, '] with parameters ', parameters, ', got ') - if(_can_make_signal_assertions(object, signal_name)): - if(_signal_watcher.did_emit(object, signal_name)): - var parms_got = _signal_watcher.get_signal_parameters(object, signal_name, index) - if(parameters == parms_got): - _pass(str(disp, parms_got)) - else: - _fail(str(disp, parms_got)) - else: - var text = str('Object ', object, ' did not emit signal [', signal_name, ']') - _fail(_get_fail_msg_including_emitted_signals(text, object)) - -# ------------------------------------------------------------------------------ -# Assert that a signal has been emitted a specific number of times. -# -# This will fail with specific messages if the object is not being watched or -# the object does not have the specified signal -# ------------------------------------------------------------------------------ -func assert_signal_emit_count(object, signal_name, times, text=""): - - if(_can_make_signal_assertions(object, signal_name)): - var count = _signal_watcher.get_emit_count(object, signal_name) - var disp = str('Expected the signal [', signal_name, '] emit count of [', count, '] to equal [', times, ']: ', text) - if(count== times): - _pass(disp) - else: - _fail(_get_fail_msg_including_emitted_signals(disp, object)) - -# ------------------------------------------------------------------------------ -# Assert that the passed in object has the specified signal -# ------------------------------------------------------------------------------ -func assert_has_signal(object, signal_name, text=""): - var disp = str('Expected object ', object, ' to have signal [', signal_name, ']: ', text) - if(_signal_watcher.does_object_have_signal(object, signal_name)): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Returns the number of times a signal was emitted. -1 returned if the object -# is not being watched. -# ------------------------------------------------------------------------------ -func get_signal_emit_count(object, signal_name): - return _signal_watcher.get_emit_count(object, signal_name) - -# ------------------------------------------------------------------------------ -# Get the parmaters of a fired signal. If the signal was not fired null is -# returned. You can specify an optional index (use get_signal_emit_count to -# determine the number of times it was emitted). The default index is the -# latest time the signal was fired (size() -1 insetead of 0). The parameters -# returned are in an array. -# ------------------------------------------------------------------------------ -func get_signal_parameters(object, signal_name, index=-1): - return _signal_watcher.get_signal_parameters(object, signal_name, index) - -# ------------------------------------------------------------------------------ -# Get the parameters for a method call to a doubled object. By default it will -# return the most recent call. You can optionally specify an index. -# -# Returns: -# * an array of parameter values if a call the method was found -# * null when a call to the method was not found or the index specified was -# invalid. -# ------------------------------------------------------------------------------ -func get_call_parameters(object, method_name, index=-1): - var to_return = null - if(_utils.is_double(object)): - to_return = gut.get_spy().get_call_parameters(object, method_name, index) - else: - _lgr.error('You must pass a doulbed object to get_call_parameters.') - - return to_return - -# ------------------------------------------------------------------------------ -# Assert that object is an instance of a_class -# ------------------------------------------------------------------------------ -func assert_extends(object, a_class, text=''): - _lgr.deprecated('assert_extends', 'assert_is') - assert_is(object, a_class, text) - -# Alias for assert_extends -func assert_is(object, a_class, text=''): - var disp = str('Expected [', object, '] to be type of [', a_class, ']: ', text) - var NATIVE_CLASS = 'GDScriptNativeClass' - var GDSCRIPT_CLASS = 'GDScript' - var bad_param_2 = 'Parameter 2 must be a Class (like Node2D or Label). You passed ' - - if(typeof(object) != TYPE_OBJECT): - _fail(str('Parameter 1 must be an instance of an object. You passed: ', types[typeof(object)])) - elif(typeof(a_class) != TYPE_OBJECT): - _fail(str(bad_param_2, types[typeof(a_class)])) - else: - disp = str('Expected [', object.get_class(), '] to extend [', a_class.get_class(), ']: ', text) - if(a_class.get_class() != NATIVE_CLASS and a_class.get_class() != GDSCRIPT_CLASS): - _fail(str(bad_param_2, a_class.get_class(), ' ', types[typeof(a_class)])) - else: - if(object is a_class): - _pass(disp) - else: - _fail(disp) - -func _get_typeof_string(the_type): - var to_return = "" - if(types.has(the_type)): - to_return += str(the_type, '(', types[the_type], ')') - else: - to_return += str(the_type) - return to_return - - # ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func assert_typeof(object, type, text=''): - var disp = str('Expected [typeof(', object, ') = ') - disp += _get_typeof_string(typeof(object)) - disp += '] to equal [' - disp += _get_typeof_string(type) + ']' - disp += '. ' + text - if(typeof(object) == type): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func assert_not_typeof(object, type, text=''): - var disp = str('Expected [typeof(', object, ') = ') - disp += _get_typeof_string(typeof(object)) - disp += '] to not equal [' - disp += _get_typeof_string(type) + ']' - disp += '. ' + text - if(typeof(object) != type): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Assert that text contains given search string. -# The match_case flag determines case sensitivity. -# ------------------------------------------------------------------------------ -func assert_string_contains(text, search, match_case=true): - var empty_search = 'Expected text and search strings to be non-empty. You passed \'%s\' and \'%s\'.' - var disp = 'Expected \'%s\' to contain \'%s\', match_case=%s' % [text, search, match_case] - if(text == '' or search == ''): - _fail(empty_search % [text, search]) - elif(match_case): - if(text.find(search) == -1): - _fail(disp) - else: - _pass(disp) - else: - if(text.to_lower().find(search.to_lower()) == -1): - _fail(disp) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Assert that text starts with given search string. -# match_case flag determines case sensitivity. -# ------------------------------------------------------------------------------ -func assert_string_starts_with(text, search, match_case=true): - var empty_search = 'Expected text and search strings to be non-empty. You passed \'%s\' and \'%s\'.' - var disp = 'Expected \'%s\' to start with \'%s\', match_case=%s' % [text, search, match_case] - if(text == '' or search == ''): - _fail(empty_search % [text, search]) - elif(match_case): - if(text.find(search) == 0): - _pass(disp) - else: - _fail(disp) - else: - if(text.to_lower().find(search.to_lower()) == 0): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Assert that text ends with given search string. -# match_case flag determines case sensitivity. -# ------------------------------------------------------------------------------ -func assert_string_ends_with(text, search, match_case=true): - var empty_search = 'Expected text and search strings to be non-empty. You passed \'%s\' and \'%s\'.' - var disp = 'Expected \'%s\' to end with \'%s\', match_case=%s' % [text, search, match_case] - var required_index = len(text) - len(search) - if(text == '' or search == ''): - _fail(empty_search % [text, search]) - elif(match_case): - if(text.find(search) == required_index): - _pass(disp) - else: - _fail(disp) - else: - if(text.to_lower().find(search.to_lower()) == required_index): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Assert that a method was called on an instance of a doubled class. If -# parameters are supplied then the params passed in when called must match. -# TODO make 3rd parameter "param_or_text" and add fourth parameter of "text" and -# then work some magic so this can have a "text" parameter without being -# annoying. -# ------------------------------------------------------------------------------ -func assert_called(inst, method_name, parameters=null): - var disp = str('Expected [',method_name,'] to have been called on ',inst) - - if(_fail_if_parameters_not_array(parameters)): - return - - if(!_utils.is_double(inst)): - _fail('You must pass a doubled instance to assert_called. Check the wiki for info on using double.') - else: - if(gut.get_spy().was_called(inst, method_name, parameters)): - _pass(disp) - else: - if(parameters != null): - disp += str(' with parameters ', parameters) - _fail(str(disp, "\n", _get_desc_of_calls_to_instance(inst))) - -# ------------------------------------------------------------------------------ -# Assert that a method was not called on an instance of a doubled class. If -# parameters are specified then this will only fail if it finds a call that was -# sent matching parameters. -# ------------------------------------------------------------------------------ -func assert_not_called(inst, method_name, parameters=null): - var disp = str('Expected [', method_name, '] to NOT have been called on ', inst) - - if(_fail_if_parameters_not_array(parameters)): - return - - if(!_utils.is_double(inst)): - _fail('You must pass a doubled instance to assert_not_called. Check the wiki for info on using double.') - else: - if(gut.get_spy().was_called(inst, method_name, parameters)): - if(parameters != null): - disp += str(' with parameters ', parameters) - _fail(str(disp, "\n", _get_desc_of_calls_to_instance(inst))) - else: - _pass(disp) - -# ------------------------------------------------------------------------------ -# Assert that a method on an instance of a doubled class was called a number -# of times. If parameters are specified then only calls with matching -# parameter values will be counted. -# ------------------------------------------------------------------------------ -func assert_call_count(inst, method_name, expected_count, parameters=null): - var count = gut.get_spy().call_count(inst, method_name, parameters) - - if(_fail_if_parameters_not_array(parameters)): - return - - var param_text = '' - if(parameters): - param_text = ' with parameters ' + str(parameters) - var disp = 'Expected [%s] on %s to be called [%s] times%s. It was called [%s] times.' - disp = disp % [method_name, inst, expected_count, param_text, count] - - if(!_utils.is_double(inst)): - _fail('You must pass a doubled instance to assert_call_count. Check the wiki for info on using double.') - else: - if(count == expected_count): - _pass(disp) - else: - _fail(str(disp, "\n", _get_desc_of_calls_to_instance(inst))) - -# ------------------------------------------------------------------------------ -# Asserts the passed in value is null -# ------------------------------------------------------------------------------ -func assert_null(got, text=''): - var disp = str('Expected [', got, '] to be NULL: ', text) - if(got == null): - _pass(disp) - else: - _fail(disp) - -# ------------------------------------------------------------------------------ -# Asserts the passed in value is null -# ------------------------------------------------------------------------------ -func assert_not_null(got, text=''): - var disp = str('Expected [', got, '] to be anything but NULL: ', text) - if(got == null): - _fail(disp) - else: - _pass(disp) - -# ----------------------------------------------------------------------------- -# Asserts object has been freed from memory -# We pass in a title (since if it is freed, we lost all identity data) -# ----------------------------------------------------------------------------- -func assert_freed(obj, title): - assert_true(not is_instance_valid(obj), "Object %s is freed" % title) - -# ------------------------------------------------------------------------------ -# Asserts Object has not been freed from memory -# ----------------------------------------------------------------------------- -func assert_not_freed(obj, title): - assert_true(is_instance_valid(obj), "Object %s is not freed" % title) - -# ------------------------------------------------------------------------------ -# Mark the current test as pending. -# ------------------------------------------------------------------------------ -func pending(text=""): - _summary.pending += 1 - if(gut): - if(text == ""): - gut.p("PENDING") - else: - gut.p("PENDING: " + text) - gut._pending(text) - -# ------------------------------------------------------------------------------ -# Returns the number of times a signal was emitted. -1 returned if the object -# is not being watched. -# ------------------------------------------------------------------------------ - -# ------------------------------------------------------------------------------ -# Yield for the time sent in. The optional message will be printed when -# Gut detects the yield. When the time expires the YIELD signal will be -# emitted. -# ------------------------------------------------------------------------------ -func yield_for(time, msg=''): - return gut.set_yield_time(time, msg) - -# ------------------------------------------------------------------------------ -# Yield to a signal or a maximum amount of time, whichever comes first. When -# the conditions are met the YIELD signal will be emitted. -# ------------------------------------------------------------------------------ -func yield_to(obj, signal_name, max_wait, msg=''): - watch_signals(obj) - gut.set_yield_signal_or_time(obj, signal_name, max_wait, msg) - - return gut - -# ------------------------------------------------------------------------------ -# Ends a test that had a yield in it. You only need to use this if you do -# not make assertions after a yield. -# ------------------------------------------------------------------------------ -func end_test(): - _lgr.deprecated('end_test is no longer necessary, you can remove it.') - #gut.end_yielded_test() - -func get_summary(): - return _summary - -func get_fail_count(): - return _summary.failed - -func get_pass_count(): - return _summary.passed - -func get_pending_count(): - return _summary.pending - -func get_assert_count(): - return _summary.asserts - -func clear_signal_watcher(): - _signal_watcher.clear() - -func get_double_strategy(): - return gut.get_doubler().get_strategy() - -func set_double_strategy(double_strategy): - gut.get_doubler().set_strategy(double_strategy) - -func pause_before_teardown(): - gut.pause_before_teardown() -# ------------------------------------------------------------------------------ -# Convert the _summary dictionary into text -# ------------------------------------------------------------------------------ -func get_summary_text(): - var to_return = get_script().get_path() + "\n" - to_return += str(' ', _summary.passed, ' of ', _summary.asserts, ' passed.') - if(_summary.pending > 0): - to_return += str("\n ", _summary.pending, ' pending') - if(_summary.failed > 0): - to_return += str("\n ", _summary.failed, ' failed.') - return to_return - -# ------------------------------------------------------------------------------ -# Double a script, inner class, or scene using a path or a loaded script/scene. -# -# -# ------------------------------------------------------------------------------ - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func _smart_double(double_info): - var override_strat = _utils.nvl(double_info.strategy, gut.get_doubler().get_strategy()) - var to_return = null - - if(double_info.is_scene()): - if(double_info.make_partial): - to_return = gut.get_doubler().partial_double_scene(double_info.path, override_strat) - else: - to_return = gut.get_doubler().double_scene(double_info.path, override_strat) - - elif(double_info.is_native()): - if(double_info.make_partial): - to_return = gut.get_doubler().partial_double_gdnative(double_info.path) - else: - to_return = gut.get_doubler().double_gdnative(double_info.path) - - elif(double_info.is_script()): - if(double_info.subpath == null): - if(double_info.make_partial): - to_return = gut.get_doubler().partial_double(double_info.path, override_strat) - else: - to_return = gut.get_doubler().double(double_info.path, override_strat) - else: - if(double_info.make_partial): - to_return = gut.get_doubler().partial_double_inner(double_info.path, double_info.subpath, override_strat) - else: - to_return = gut.get_doubler().double_inner(double_info.path, double_info.subpath, override_strat) - return to_return - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func double(thing, p2=null, p3=null): - var double_info = DoubleInfo.new(thing, p2, p3) - double_info.make_partial = false - - return _smart_double(double_info) - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func partial_double(thing, p2=null, p3=null): - var double_info = DoubleInfo.new(thing, p2, p3) - double_info.make_partial = true - - return _smart_double(double_info) - - -# ------------------------------------------------------------------------------ -# Specifically double a scene -# ------------------------------------------------------------------------------ -func double_scene(path, strategy=null): - var override_strat = _utils.nvl(strategy, gut.get_doubler().get_strategy()) - return gut.get_doubler().double_scene(path, override_strat) - -# ------------------------------------------------------------------------------ -# Specifically double a script -# ------------------------------------------------------------------------------ -func double_script(path, strategy=null): - var override_strat = _utils.nvl(strategy, gut.get_doubler().get_strategy()) - return gut.get_doubler().double(path, override_strat) - -# ------------------------------------------------------------------------------ -# Specifically double an Inner class in a a script -# ------------------------------------------------------------------------------ -func double_inner(path, subpath, strategy=null): - var override_strat = _utils.nvl(strategy, gut.get_doubler().get_strategy()) - return gut.get_doubler().double_inner(path, subpath, override_strat) - -# ------------------------------------------------------------------------------ -# Add a method that the doubler will ignore. You can pass this the path to a -# script or scene or a loaded script or scene. When running tests, these -# ignores are cleared after every test. -# ------------------------------------------------------------------------------ -func ignore_method_when_doubling(thing, method_name): - var double_info = DoubleInfo.new(thing) - var path = double_info.path - - if(double_info.is_scene()): - var inst = thing.instance() - if(inst.get_script()): - path = inst.get_script().get_path() - - gut.get_doubler().add_ignored_method(path, method_name) - -# ------------------------------------------------------------------------------ -# Stub something. -# -# Parameters -# 1: the thing to stub, a file path or a instance or a class -# 2: either an inner class subpath or the method name -# 3: the method name if an inner class subpath was specified -# NOTE: right now we cannot stub inner classes at the path level so this should -# only be called with two parameters. I did the work though so I'm going -# to leave it but not update the wiki. -# ------------------------------------------------------------------------------ -func stub(thing, p2, p3=null): - var method_name = p2 - var subpath = null - if(p3 != null): - subpath = p2 - method_name = p3 - var sp = _utils.StubParams.new(thing, method_name, subpath) - gut.get_stubber().add_stub(sp) - return sp - -# ------------------------------------------------------------------------------ -# convenience wrapper. -# ------------------------------------------------------------------------------ -func simulate(obj, times, delta): - gut.simulate(obj, times, delta) - -# ------------------------------------------------------------------------------ -# Replace the node at base_node.get_node(path) with with_this. All references -# to the node via $ and get_node(...) will now return with_this. with_this will -# get all the groups that the node that was replaced had. -# -# The node that was replaced is queued to be freed. -# -# TODO see replace_by method, this could simplify the logic here. -# ------------------------------------------------------------------------------ -func replace_node(base_node, path_or_node, with_this): - var path = path_or_node - - if(typeof(path_or_node) != TYPE_STRING): - # This will cause an engine error if it fails. It always returns a - # NodePath, even if it fails. Checking the name count is the only way - # I found to check if it found something or not (after it worked I - # didn't look any farther). - path = base_node.get_path_to(path_or_node) - if(path.get_name_count() == 0): - _lgr.error('You passed an object that base_node does not have. Cannot replace node.') - return - - if(!base_node.has_node(path)): - _lgr.error(str('Could not find node at path [', path, ']')) - return - - var to_replace = base_node.get_node(path) - var parent = to_replace.get_parent() - var replace_name = to_replace.get_name() - - parent.remove_child(to_replace) - parent.add_child(with_this) - with_this.set_name(replace_name) - with_this.set_owner(parent) - - var groups = to_replace.get_groups() - for i in range(groups.size()): - with_this.add_to_group(groups[i]) - - to_replace.queue_free() diff --git a/addons/gut/test_collector.gd b/addons/gut/test_collector.gd deleted file mode 100644 index 8d8264a..0000000 --- a/addons/gut/test_collector.gd +++ /dev/null @@ -1,241 +0,0 @@ -# ------------------------------------------------------------------------------ -# Used to keep track of info about each test ran. -# ------------------------------------------------------------------------------ -class Test: - # indicator if it passed or not. defaults to true since it takes only - # one failure to make it not pass. _fail in gut will set this. - var passed = true - # the name of the function - var name = "" - # flag to know if the name has been printed yet. - var has_printed_name = false - # the line number the test is on - var line_number = -1 - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -class TestScript: - var inner_class_name = null - var tests = [] - var path = null - var _utils = null - var _lgr = null - - func _init(utils=null, logger=null): - _utils = utils - _lgr = logger - - func to_s(): - var to_return = path - if(inner_class_name != null): - to_return += str('.', inner_class_name) - to_return += "\n" - for i in range(tests.size()): - to_return += str(' ', tests[i].name, "\n") - return to_return - - func get_new(): - var TheScript = load(path) - var inst = null - if(inner_class_name != null): - inst = TheScript.get(inner_class_name).new() - else: - inst = TheScript.new() - return inst - - func get_full_name(): - var to_return = path - if(inner_class_name != null): - to_return += '.' + inner_class_name - return to_return - - func get_filename(): - return path.get_file() - - func has_inner_class(): - return inner_class_name != null - - func export_to(config_file, section): - config_file.set_value(section, 'path', path) - config_file.set_value(section, 'inner_class', inner_class_name) - var names = [] - for i in range(tests.size()): - names.append(tests[i].name) - config_file.set_value(section, 'tests', names) - - func _remap_path(source_path): - var to_return = source_path - if(!_utils.file_exists(source_path)): - _lgr.debug('Checking for remap for: ' + source_path) - var remap_path = source_path.get_basename() + '.gd.remap' - if(_utils.file_exists(remap_path)): - var cf = ConfigFile.new() - cf.load(remap_path) - to_return = cf.get_value('remap', 'path') - else: - _lgr.warn('Could not find remap file ' + remap_path) - return to_return - - func import_from(config_file, section): - path = config_file.get_value(section, 'path') - path = _remap_path(path) - var test_names = config_file.get_value(section, 'tests') - for i in range(test_names.size()): - var t = Test.new() - t.name = test_names[i] - tests.append(t) - # Null is an acceptable value, but you can't pass null as a default to - # get_value since it thinks you didn't send a default...then it spits - # out red text. This works around that. - var inner_name = config_file.get_value(section, 'inner_class', 'Placeholder') - if(inner_name != 'Placeholder'): - inner_class_name = inner_name - else: # just being explicit - inner_class_name = null - - -# ------------------------------------------------------------------------------ -# start test_collector, I don't think I like the name. -# ------------------------------------------------------------------------------ -var scripts = [] -var _test_prefix = 'test_' -var _test_class_prefix = 'Test' - -var _utils = load('res://addons/gut/utils.gd').new() -var _lgr = _utils.get_logger() - -func _parse_script(script): - var file = File.new() - var line = "" - var line_count = 0 - var inner_classes = [] - var scripts_found = [] - - file.open(script.path, 1) - while(!file.eof_reached()): - line_count += 1 - line = file.get_line() - #Add a test - if(line.begins_with("func " + _test_prefix)): - var from = line.find(_test_prefix) - var line_len = line.find("(") - from - var new_test = Test.new() - new_test.name = line.substr(from, line_len) - new_test.line_number = line_count - script.tests.append(new_test) - - if(line.begins_with('class ')): - var iclass_name = line.replace('class ', '') - iclass_name = iclass_name.replace(':', '') - if(iclass_name.begins_with(_test_class_prefix)): - inner_classes.append(iclass_name) - - scripts_found.append(script.path) - - for i in range(inner_classes.size()): - var ts = TestScript.new(_utils, _lgr) - ts.path = script.path - ts.inner_class_name = inner_classes[i] - if(_parse_inner_class_tests(ts)): - scripts.append(ts) - scripts_found.append(script.path + '[' + inner_classes[i] +']') - - file.close() - return scripts_found - -func _parse_inner_class_tests(script): - var inst = script.get_new() - - if(!inst is _utils.Test): - _lgr.warn('Ignoring ' + script.inner_class_name + ' because it starts with "' + _test_class_prefix + '" but does not extend addons/gut/test.gd') - return false - - var methods = inst.get_method_list() - for i in range(methods.size()): - var name = methods[i]['name'] - if(name.begins_with(_test_prefix) and methods[i]['flags'] == 65): - var t = Test.new() - t.name = name - script.tests.append(t) - - return true -# ----------------- -# Public -# ----------------- -func add_script(path): - # SHORTCIRCUIT - if(has_script(path)): - return [] - - var f = File.new() - # SHORTCIRCUIT - if(!f.file_exists(path)): - _lgr.error('Could not find script: ' + path) - return - - var ts = TestScript.new(_utils, _lgr) - ts.path = path - scripts.append(ts) - return _parse_script(ts) - -func to_s(): - var to_return = '' - for i in range(scripts.size()): - to_return += scripts[i].to_s() + "\n" - return to_return -func get_logger(): - return _lgr - -func set_logger(logger): - _lgr = logger - -func get_test_prefix(): - return _test_prefix - -func set_test_prefix(test_prefix): - _test_prefix = test_prefix - -func get_test_class_prefix(): - return _test_class_prefix - -func set_test_class_prefix(test_class_prefix): - _test_class_prefix = test_class_prefix - -func clear(): - scripts.clear() - -func has_script(path): - var found = false - var idx = 0 - while(idx < scripts.size() and !found): - if(scripts[idx].path == path): - found = true - else: - idx += 1 - return found - -func export_tests(path): - var success = true - var f = ConfigFile.new() - for i in range(scripts.size()): - scripts[i].export_to(f, str('TestScript-', i)) - var result = f.save(path) - if(result != OK): - _lgr.error(str('Could not save exported tests to [', path, ']. Error code: ', result)) - success = false - return success - -func import_tests(path): - var success = false - var f = ConfigFile.new() - var result = f.load(path) - if(result != OK): - _lgr.error(str('Could not load exported tests from [', path, ']. Error code: ', result)) - else: - var sections = f.get_sections() - for key in sections: - var ts = TestScript.new(_utils, _lgr) - ts.import_from(f, key) - scripts.append(ts) - success = true - return success diff --git a/addons/gut/thing_counter.gd b/addons/gut/thing_counter.gd deleted file mode 100644 index a9b0b48..0000000 --- a/addons/gut/thing_counter.gd +++ /dev/null @@ -1,43 +0,0 @@ -var things = {} - -func get_unique_count(): - return things.size() - -func add(thing): - if(things.has(thing)): - things[thing] += 1 - else: - things[thing] = 1 - -func has(thing): - return things.has(thing) - -func get(thing): - var to_return = 0 - if(things.has(thing)): - to_return = things[thing] - return to_return - -func sum(): - var count = 0 - for key in things: - count += things[key] - return count - -func to_s(): - var to_return = "" - for key in things: - to_return += str(key, ": ", things[key], "\n") - to_return += str("sum: ", sum()) - return to_return - -func get_max_count(): - var max_val = null - for key in things: - if(max_val == null or things[key] > max_val): - max_val = things[key] - return max_val - -func add_array_items(array): - for i in range(array.size()): - add(array[i]) diff --git a/addons/gut/utils.gd b/addons/gut/utils.gd deleted file mode 100644 index 7f44f26..0000000 --- a/addons/gut/utils.gd +++ /dev/null @@ -1,160 +0,0 @@ -var _Logger = load('res://addons/gut/logger.gd') # everything should use get_logger - -var Doubler = load('res://addons/gut/doubler.gd') -var Gut = load('res://addons/gut/gut.gd') -var HookScript = load('res://addons/gut/hook_script.gd') -var MethodMaker = load('res://addons/gut/method_maker.gd') -var Spy = load('res://addons/gut/spy.gd') -var Stubber = load('res://addons/gut/stubber.gd') -var StubParams = load('res://addons/gut/stub_params.gd') -var Summary = load('res://addons/gut/summary.gd') -var Test = load('res://addons/gut/test.gd') -var TestCollector = load('res://addons/gut/test_collector.gd') -var ThingCounter = load('res://addons/gut/thing_counter.gd') -var OneToMany = load('res://addons/gut/one_to_many.gd') - -const GUT_METADATA = '__gut_metadata_' - -enum DOUBLE_STRATEGY{ - FULL, - PARTIAL -} - -var escape = PoolByteArray([0x1b]).get_string_from_ascii() -var CMD_COLORS = { - RED = escape + '[31m', - YELLOW = escape + '[33m', - DEFAULT = escape + '[0m', - GREEN = escape + '[32m', - UNDERLINE = escape + '[4m', - BOLD = escape + '[1m' -} - -func colorize_word(source, word, c): - var new_word = c + word + CMD_COLORS.DEFAULT - return source.replace(word, new_word) - -func colorize_text(text): - var t = colorize_word(text, 'FAILED', CMD_COLORS.RED) - t = colorize_word(t, 'PASSED', CMD_COLORS.GREEN) - t = colorize_word(t, 'PENDING', CMD_COLORS.YELLOW) - t = colorize_word(t, '[ERROR]', CMD_COLORS.RED) - t = colorize_word(t, '[WARNING]', CMD_COLORS.YELLOW) - t = colorize_word(t, '[DEBUG]', CMD_COLORS.BOLD) - t = colorize_word(t, '[DEPRECATED]', CMD_COLORS.BOLD) - t = colorize_word(t, '[INFO]', CMD_COLORS.BOLD) - return t - - -var _file_checker = File.new() - -func is_version_30(): - var info = Engine.get_version_info() - return info.major == 3 and info.minor == 0 - -func is_version_31(): - var info = Engine.get_version_info() - return info.major == 3 and info.minor == 1 - -# ------------------------------------------------------------------------------ -# Everything should get a logger through this. -# -# Eventually I want to make this get a single instance of a logger but I'm not -# sure how to do that without everything having to be in the tree which I -# DO NOT want to to do. I'm thinking of writings some instance ids to a file -# and loading them in the _init for this. -# ------------------------------------------------------------------------------ -func get_logger(): - return _Logger.new() - -# ------------------------------------------------------------------------------ -# Returns an array created by splitting the string by the delimiter -# ------------------------------------------------------------------------------ -func split_string(to_split, delim): - var to_return = [] - - var loc = to_split.find(delim) - while(loc != -1): - to_return.append(to_split.substr(0, loc)) - to_split = to_split.substr(loc + 1, to_split.length() - loc) - loc = to_split.find(delim) - to_return.append(to_split) - return to_return - -# ------------------------------------------------------------------------------ -# Returns a string containing all the elements in the array separated by delim -# ------------------------------------------------------------------------------ -func join_array(a, delim): - var to_return = '' - for i in range(a.size()): - to_return += str(a[i]) - if(i != a.size() -1): - to_return += str(delim) - return to_return - -# ------------------------------------------------------------------------------ -# return if_null if value is null otherwise return value -# ------------------------------------------------------------------------------ -func nvl(value, if_null): - if(value == null): - return if_null - else: - return value - -# ------------------------------------------------------------------------------ -# returns true if the object has been freed, false if not -# -# From what i've read, the weakref approach should work. It seems to work most -# of the time but sometimes it does not catch it. The str comparison seems to -# fill in the gaps. I've not seen any errors after adding that check. -# ------------------------------------------------------------------------------ -func is_freed(obj): - var wr = weakref(obj) - return !(wr.get_ref() and str(obj) != '[Deleted Object]') - -func is_not_freed(obj): - return !is_freed(obj) - -func is_double(obj): - return obj.get(GUT_METADATA) != null - -func extract_property_from_array(source, property): - var to_return = [] - for i in (source.size()): - to_return.append(source[i].get(property)) - return to_return - -func file_exists(path): - return _file_checker.file_exists(path) - -func write_file(path, content): - var f = File.new() - f.open(path, f.WRITE) - f.store_string(content) - f.close() - -func is_null_or_empty(text): - return text == null or text == '' - -func get_native_class_name(thing): - var to_return = null - if(is_native_class(thing)): - to_return = thing.new().get_class() - return to_return - -func is_native_class(thing): - var it_is = false - if(typeof(thing) == TYPE_OBJECT): - it_is = str(thing).begins_with("[GDScriptNativeClass:") - return it_is - -# ------------------------------------------------------------------------------ -# ------------------------------------------------------------------------------ -func get_file_as_text(path): - var to_return = '' - var f = File.new() - var result = f.open(path, f.READ) - if(result == OK): - to_return = f.get_as_text() - f.close() - return to_return diff --git a/demo.gif b/demo.gif deleted file mode 100644 index eb0ffb1c86ef8dd016373bca29c0b89b7a885ec1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32652 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 buam!(s-qrFm29QZ7W1Q*kLUmO6C3^sbb0Ay diff --git a/docker-compose.yml b/docker-compose.yml index 5707d3b..e2312e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,17 @@ version: "3.3" services: - tests: - image: barichello/godot-ci:3.2.2 - environment: - - RUN_FULL_CODEPOINT_TESTS=true - volumes: - - .:/src - command: godot --path /src -s addons/gut/gut_cmdln.gd build-archlinux: build: context: . dockerfile: ./dockerfiles/archlinux - command: bash /src/addons/godot_xterm_native/build.sh + command: bash /src/addons/godot_xterm/build.sh build-nixos: build: context: . dockerfile: ./dockerfiles/nixos - command: /src/addons/godot_xterm_native/build.sh + command: /src/addons/godot_xterm/build.sh build-ubuntu: build: context: . dockerfile: ./dockerfiles/ubuntu - command: bash /src/addons/godot_xterm_native/build.sh + command: bash /src/addons/godot_xterm/build.sh diff --git a/examples/asciicast/demo.cast b/examples/asciicast/demo.cast new file mode 100644 index 0000000..afba5c6 --- /dev/null +++ b/examples/asciicast/demo.cast @@ -0,0 +1,61 @@ +{"version": 2, "width": 86, "height": 29, "timestamp": 1589772748, "env": {"SHELL": "/run/current-system/sw/bin/bash", "TERM": "xterm"}} +[0.082961, "o", "> "] +[0.798002, "o", "e"] +[0.893414, "o", "c"] +[0.956255, "o", "h"] +[1.008677, "o", "o"] +[1.089472, "o", " "] +[1.189602, "o", "h"] +[1.266892, "o", "e"] +[1.347483, "o", "l"] +[1.46568, "o", "l"] +[1.541039, "o", "o"] +[1.726772, "o", "\r\n"] +[1.727475, "o", "hello\r\n> "] +[2.060109, "o", "#"] +[2.179668, "o", " "] +[2.471941, "o", "T"] +[2.652735, "o", "h"] +[2.746515, "o", "i"] +[2.810578, "o", "s"] +[2.921342, "o", " "] +[2.98886, "o", "i"] +[3.069095, "o", "s"] +[3.31728, "o", " "] +[3.399615, "o", "a"] +[3.513605, "o", " "] +[3.72609, "o", "d"] +[3.811197, "o", "e"] +[3.94649, "o", "m"] +[4.047162, "o", "o"] +[4.225042, "o", "\r\n"] +[4.225402, "o", "> "] +[4.935288, "o", "t"] +[5.163552, "o", "o"] +[5.323205, "o", "i"] +[5.46746, "o", "l"] +[5.561098, "o", "et "] +[6.064937, "o", "-"] +[6.41563, "o", "-"] +[6.60443, "o", "g"] +[6.666621, "o", "a"] +[6.768317, "o", "y"] +[6.848917, "o", " "] +[7.076406, "o", "H"] +[7.250067, "o", "E"] +[7.410878, "o", "L"] +[7.537016, "o", "L"] +[7.604155, "o", "O"] +[7.888992, "o", " "] +[8.193437, "o", "W"] +[8.365871, "o", "O"] +[8.454678, "o", "R"] +[8.525163, "o", "L"] +[8.60286, "o", "D"] +[8.873053, "o", "!"] +[9.216434, "o", "\r\n"] +[9.251462, "o", " \r\n \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;33;93mm\u001b[0m \u001b[0;1;35;95mm\u001b[0m \u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;36;96mm\u001b[0m \u001b[0;1;31;91mm\u001b[0m \u001b[0;1;33;93mm\u001b[0;1;32;92mmm\u001b[0;1;36;96mm\u001b[0m \u001b[0;1;34;94mm\u001b[0;1;35;95mmm\u001b[0;1;31;91mmm\u001b[0m \u001b[0;1;32;92mm\u001b[0m \u001b[0;1;35;95mm\u001b[0;1;31;91mmm\u001b[0;1;33;93mm\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;36;96mm\u001b[0;1;34;94m\"\u001b[0m \u001b[0;1;35;95m\"\u001b[0;1;31;91mm\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92mm\"\u001b[0m \u001b[0;1;34;94m\"m\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;33;93m\"\u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;32;92m\"\u001b[0;1;36;96mm\u001b[0m\r\n \u001b[0;1;32;92m#\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b"] +[9.251901, "o", "[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#\"\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93mmm\u001b[0;1;32;92mmm\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m\r\n \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;31;91m#\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m##\u001b[0;1;36;96m\"\u001b[0m \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;31;91m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m\"\u001b[0;1;34;94mm\u001b[0m \u001b[0;1;35;95m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m#\u001b[0m\r\n \u001b[0;1;34;94m#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#m\u001b[0;1;36;96mmm\u001b[0;1;34;94mmm\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93mmm\u001b[0;1;32;92mm\u001b[0m \u001b[0;1;36;96m#m\u001b[0;1;34;94mmm\u001b[0;1;35;95mmm\u001b[0m \u001b[0;1;33;93m#m\u001b[0;1;32;92mm#\u001b[0m \u001b[0;1;33;93m#\u001b[0m \u001b[0;1;36;96m#\u001b[0m \u001b[0;1;35;95m#\u001b[0;1;31;91mmm\u001b[0;1;33;93m#\u001b[0m \u001b[0;1;32;92m#\u001b[0m \u001b[0;1;35;95m\"\u001b[0m \u001b[0;1;31;91m#m\u001b[0;1;33;93mmm\u001b[0;1"] +[9.251944, "o", ";32;92mmm\u001b[0m \u001b[0;1;36;96m#\u001b[0;1;34;94mmm\u001b[0;1;35;95mm\"\u001b[0m \r\n \r\n \r\n \r\n \u001b[0;1;36;96mm\u001b[0m \r\n \u001b[0;1;34;94m#\u001b[0m \r\n \u001b[0;1;35;95m#\u001b[0m \r\n \u001b[0;1;31;91m\"\u001b[0m \r\n \u001b[0;1;33;93m#\u001b[0m \r\n \r\n \r\n"] +[9.252259, "o", "> "] +[12.56287, "o", "exit\r\n"] diff --git a/examples/terminal/Terminal.tscn b/examples/terminal/Terminal.tscn index 71be21b..42e0583 100644 --- a/examples/terminal/Terminal.tscn +++ b/examples/terminal/Terminal.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=5 format=2] -[ext_resource path="res://addons/godot_xterm_native/terminal.gdns" type="Script" id=1] -[ext_resource path="res://addons/godot_xterm_native/themes/default.theme" type="Theme" id=2] -[ext_resource path="res://addons/godot_xterm_native/pseudoterminal.gdns" type="Script" id=3] +[ext_resource path="res://addons/godot_xterm/terminal.gdns" type="Script" id=1] +[ext_resource path="res://addons/godot_xterm/themes/default.theme" type="Theme" id=2] +[ext_resource path="res://addons/godot_xterm/pseudoterminal.gdns" type="Script" id=3] [ext_resource path="res://examples/terminal/container.gd" type="Script" id=4] [node name="Container" type="Container"] diff --git a/project.godot b/project.godot index a0bada7..0a0e2db 100644 --- a/project.godot +++ b/project.godot @@ -15,13 +15,12 @@ _global_script_class_icons={ [application] -config/name="Godot Xterm Native" -run/main_scene="res://examples/terminal/Terminal.tscn" +config/name="Godot Xterm" config/icon="res://icon.png" [editor_plugins] -enabled=PoolStringArray( "godot_xterm_native" ) +enabled=PoolStringArray( "godot_xterm" ) [rendering] diff --git a/scenes/demo.gd b/scenes/demo.gd deleted file mode 100644 index 3dbb928..0000000 --- a/scenes/demo.gd +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2020 The GodotTerm authors. All rights reserved. -# License MIT -extends Control - - -signal data_received(data) - -# The user must have these programs installed for this to work. -const dependencies = PoolStringArray(['which', 'socat', 'bash']) -const host = '127.0.0.1' -const port = 7154 - -# Enable recording of all data send to the psuedoterminal master. -# This is useful if you want to record a session if you are trying -# to make a showcase of the terminal ;-) -export var record: bool = false -export(String) var record_file_path = '/tmp/godot-xterm-record.json' - -var socat_pid = -1 -var stream_peer = StreamPeerTCP.new() -var record_file - - -func _ready(): - # First check that dependencies are installed and in $PATH. - var exit_code = OS.execute("which", dependencies) - if exit_code != 0: - OS.alert("Make sure the following programs are installed and in your $PATH: " + \ - dependencies.join(", ") + ".", "Misssing Dependencies!") - else: - # Start socat. - socat_pid = OS.execute("socat", - ["-d", "-d", "tcp-l:%d,bind=%s,reuseaddr,fork" % [port, host], - "exec:bash,pty,setsid,stderr,login,ctty"], false) - - # Create a StreamPeerTCP to connect to socat. - var err = stream_peer.connect_to_host(host, port) - if err != OK: - OS.alert("Couldn't connect to socat on %s:%d" % [host, port], "Connection Failed!") - - var status = stream_peer.get_status() - var connected = stream_peer.is_connected_to_host() - - # Set the TERM environment variable, so that the correct escape sequences - # are sent to Terminal. By default this is set to dumb, which lacks support - # for even simple commands such as clear and reset. - stream_peer.put_data("export TERM=xterm\n".to_ascii()) - stream_peer.put_data("clear\n".to_ascii()) - - # Connect the Terminal and StreamPeer. - $Terminal.connect('output', self, 'send_data') - connect("data_received", $Terminal, "write") - - connect("resized", self, "_resize_terminal") - _resize_terminal() - - -func _resize_terminal(): - $Terminal.rect_size = OS.window_size - - -func send_data(data: PoolByteArray): - if record: - # Save the data and timestamp to a file - record_file.write() - stream_peer.put_data(data) - - -func _process(delta): - if stream_peer.get_status() == StreamPeerTCP.STATUS_CONNECTED: - var res = stream_peer.get_data(stream_peer.get_available_bytes()) - var error = res[0] - var data = res[1] - if error != OK: - OS.alert("Something went wrong with the TCP connection to socat.", - "Connection Error!") - elif not data.empty(): - emit_signal("data_received", data) - - -func _exit_tree(): - if record: - record_file.close() - if socat_pid != -1: - OS.execute("kill", ["-9", socat_pid], false) diff --git a/scenes/demo.tscn b/scenes/demo.tscn deleted file mode 100644 index ac1a8f4..0000000 --- a/scenes/demo.tscn +++ /dev/null @@ -1,55 +0,0 @@ -[gd_scene load_steps=7 format=2] - -[ext_resource path="res://scenes/demo.gd" type="Script" id=1] -[ext_resource path="res://addons/godot_xterm/terminal.gd" type="Script" id=2] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres" type="DynamicFont" id=3] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres" type="DynamicFont" id=4] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold_italic.tres" type="DynamicFont" id=5] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres" type="DynamicFont" id=6] - -[node name="Demo" type="Control"] -show_behind_parent = true -anchor_right = 1.0 -anchor_bottom = 1.0 -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Terminal" type="Control" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -rect_min_size = Vector2( 600, 400 ) -script = ExtResource( 2 ) -__meta__ = { -"_edit_use_anchors_": false -} -auto_resize = true -font_family = { -"bold": ExtResource( 6 ), -"bold_italic": ExtResource( 5 ), -"italic": ExtResource( 4 ), -"regular": ExtResource( 3 ) -} -line_height = 1.15 -colors = { -"black": Color( 0.180392, 0.203922, 0.211765, 1 ), -"blue": Color( 0.203922, 0.396078, 0.643137, 1 ), -"bright_black": Color( 0.333333, 0.341176, 0.32549, 1 ), -"bright_blue": Color( 0.447059, 0.623529, 0.811765, 1 ), -"bright_cyan": Color( 0.203922, 0.886275, 0.886275, 1 ), -"bright_green": Color( 0.541176, 0.886275, 0.203922, 1 ), -"bright_magenta": Color( 0.678431, 0.498039, 0.658824, 1 ), -"bright_red": Color( 0.937255, 0.160784, 0.160784, 1 ), -"bright_white": Color( 0.933333, 0.933333, 0.92549, 1 ), -"bright_yellow": Color( 0.988235, 0.913725, 0.309804, 1 ), -"cyan": Color( 0.0235294, 0.596078, 0.603922, 1 ), -"green": Color( 0.305882, 0.603922, 0.0235294, 1 ), -"magenta": Color( 0.458824, 0.313726, 0.482353, 1 ), -"red": Color( 0.8, 0, 0, 1 ), -"white": Color( 0.827451, 0.843137, 0.811765, 1 ), -"yellow": Color( 0.768627, 0.627451, 0, 1 ) -} -window_options = { -"set_win_lines": false -} diff --git a/scenes/showcase.tscn b/scenes/showcase.tscn deleted file mode 100644 index 14aa48e..0000000 --- a/scenes/showcase.tscn +++ /dev/null @@ -1,39 +0,0 @@ -[gd_scene load_steps=7 format=2] - -[ext_resource path="res://scenes/demo.gd" type="Script" id=1] -[ext_resource path="res://addons/godot_xterm/terminal.gd" type="Script" id=2] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres" type="DynamicFont" id=3] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_italic.tres" type="DynamicFont" id=4] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold_italic.tres" type="DynamicFont" id=5] -[ext_resource path="res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres" type="DynamicFont" id=6] - -[node name="Demo" type="Control"] -anchor_right = 1.0 -anchor_bottom = 1.0 -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Terminal" type="Control" parent="."] -margin_left = 163.651 -margin_top = 68.7974 -margin_right = 763.651 -margin_bottom = 468.797 -rect_min_size = Vector2( 600, 400 ) -script = ExtResource( 2 ) -__meta__ = { -"_edit_use_anchors_": false -} -font_family = { -"bold": ExtResource( 6 ), -"bold_italic": ExtResource( 5 ), -"italic": ExtResource( 4 ), -"regular": ExtResource( 3 ) -} -colors = { -"black": Color( 0.121569, 0.00784314, 0.00784314, 1 ) -} -window_options = { - -} diff --git a/test/integration/test_terminal.gd b/test/integration/test_terminal.gd deleted file mode 100644 index 9eca2b1..0000000 --- a/test/integration/test_terminal.gd +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright (c) 2020 The GodotXterm authors. All rights reserved. -# License MIT -extends "res://addons/gut/test.gd" - -const Parser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd") -const Terminal = preload("res://addons/godot_xterm/terminal.gd") -const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd") -const Constants = preload("res://addons/godot_xterm/parser/constants.gd") - -const C0 = Constants.C0 -const C1 = Constants.C1 - -class TestBuffer: - var calls = [] - var printed = '' - - func handle_print(data, start, end): - var string = Decoder.utf32_to_string(data.slice(start, end - 1)) - calls.append(['print', string]) - printed += string - - - func handle_exec(): - calls.append(['exec']) - - - func handle_csi(params): - calls.append(['csi', params.to_array()]) - - - func clear(): - printed = '' - calls.resize(0) - - -var parser -var buffer -var decoder - - -func parse(parser, string): - var container = [] - container.resize(string.length()) - var length = decoder.decode(string.to_utf8(), container) - parser.parse(container, length) - - -func before_all(): - buffer = TestBuffer.new() - decoder = Decoder.Utf8ToUtf32.new() - - -func before_each(): - parser = Parser.new() - parser.set_print_handler(buffer, 'handle_print') - buffer.clear() - - -func test_prints_printables(): - var string = 'bash-4.4# ' - var data = string.to_utf8() - var length = decoder.decode(data, data) - parser.parse(data, length) - assert_eq(buffer.calls, [['print', 'bash-4.4# ']]) - assert_eq(buffer.printed, 'bash-4.4# ') - - -func skip_test_c0(): - for code in C0.values(): - parser.set_execute_handler(code, buffer, 'handle_exec') - parse(parser, char(code)) - if code == 0x0 or code == 0x1b or code == 0x20 or code == 0x7f: - assert_eq(buffer.calls, []) - else: - assert_eq(buffer.calls, [['exec']], 'code: 0x%x' % code) - assert_eq(buffer.printed, '') - parser.reset() - buffer.clear() - - -func skip_test_c1(): - for code in C1.values(): - parser.set_execute_handler(code, buffer, 'handle_exec') - parse(parser, char(code)) - assert_eq(buffer.calls, [['exec']], 'code: 0x%x' % code) - assert_eq(buffer.printed, '') - parser.reset() - buffer.clear() - - -func test_print_csi_print(): - parser.set_csi_handler({'final': 'g'}, buffer, 'handle_csi') - parse(parser, 'a\u001b[gb') - assert_eq(buffer.calls, [['print', 'a'],['csi', [0]], ['print', 'b']]) - assert_eq(buffer.printed, 'ab') - - -func test_csi_position_cursor(): - parser.set_csi_handler({'final': 'H'}, buffer, 'handle_csi') - parse(parser, '\u001b[1;5H') - assert_eq(buffer.calls, [['csi', [1, 5]]]) - assert_eq(buffer.printed, '') diff --git a/test/test.tscn b/test/test.tscn deleted file mode 100644 index fe35273..0000000 --- a/test/test.tscn +++ /dev/null @@ -1,17 +0,0 @@ -[gd_scene load_steps=2 format=2] - -[ext_resource path="res://addons/gut/gut.gd" type="Script" id=1] - -[node name="Gut" type="Control"] -self_modulate = Color( 1, 1, 1, 0 ) -margin_right = 1031.0 -margin_bottom = 597.0 -rect_min_size = Vector2( 740, 250 ) -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} -_yield_between_tests = false -_include_subdirectories = true -_directory1 = "res://test" -_double_strategy = 1 diff --git a/test/test_utils.gd b/test/test_utils.gd deleted file mode 100644 index a43614c..0000000 --- a/test/test_utils.gd +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2019 The xterm.js authors. All rights reserved. -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends "res://addons/gut/test.gd" - - -const Buffer = preload("res://addons/godot_xterm/buffer/buffer.gd") -const BufferSet = preload("res://addons/godot_xterm/buffer/buffer_set.gd") -const OptionsService = preload("res://addons/godot_xterm/services/options_service.gd") - - -class MockBufferService: - extends Reference - - - signal resized(cols, rows) - - var service_brand - var buffer setget ,_get_buffer - var buffers - var is_user_scrolling: bool = false - var cols - var rows - - - func _get_buffer(): - return buffers.active - - - func _init(cols: int, rows: int, options_service = MockOptionsService.new()): - self.cols = cols - self.rows = rows - buffers = BufferSet.new(options_service, self) - - - func resize(cols: int, rows: int) -> void: - self.cols = cols - self.rows = rows - - - func reset() -> void: - pass - - -class MockOptionsService: - extends Reference - - - signal option_changed - - var service_brand - var options = OptionsService.TerminalOptions.new() - - - func _init(test_options = null): - if test_options: - for key in test_options.keys(): - self.options.set(key, test_options[key]) - - - func set_option(key: String, value) -> void: - pass - - - func get_option(key: String): - pass diff --git a/test/unit/.test_input_handler.gd.swp b/test/unit/.test_input_handler.gd.swp deleted file mode 100644 index edf75473cd0f53742c267ffd8039046644c05ece..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI3TWlOx8OKjcOR}^Hfq>|R(vw|~cNJ&6ySD4#B!wo>5P_8lCnd;ohuPh;_9Ww( zWzN~y=28R#B&eFEm8f_?0xB*=sss;-P#;Qo;1x?ETMgev-X?fH6n--Xfh} z<)db8_jt={tWu0Uper0|`Q9;6S8`~^Yj|qMf)f7RkP=c2wOw23U#9K0+RB`@+y?G3 zW>&3wwWhP{nFOwF3A9yn$IeY`xLPTbAPV_yeCr#JT-!3qMxRL_lRzecOahq%G6`f7 z$Rv`nQ{W&N10`@LcpJC_ zT)2s`r@;5Ym%)B82JQl*U>o?$>lphQSOE8d7jumL6Z{sO2fqPlz!$+Jcq{nhM#i28 z=fIQTB=|768T@$zV}Am_1m6RXf@!b?TzW0q1LwhM@EEYbo4^YL1T*mYslJx7)X*;OrLNwls^@zEl^`eD{RR|!`YlRX>c z%5RI(AV1`J&ZP2fcjg}7w-$?2EH_;$#d^&99Mwp<#JBTE7Cr9bJ2=W+Wr3{M1obJ{ zrsc>Ul1;vMBYaB=yo+NtY}s-k`$Lv*%}Jx_dvm50%tl#HMsn_q;Lup$dmv4g>wa|+ zS4UZ{z+1XjK2@HE^nH6~R&l)e!)wCv=DF?z%VEBgeZx&s6rs848k>j-`aDJX7kuS_~Ii zN_s}8TU1B4^;BJDJ&mono~ldhX{tiY$q`KzTSJM_k7QgliLp?6jjk(mBWpy14^m4KTTGllID1g55 z4d_Wqe!-!k_gG}Y?6|yx0Xg=Zt;jFP>0it1A-@4TsmWbWp;?qh;c!k+dTKftKN(5g zR%%HMhWB(tpq*Qi0M(J$p!!UFLj@<2UWGoF9I8H*TJwy-+m+r4~sDY@wpAjQ<7~5WvOmf;2%IB=1RdQ4jjhO zA^B8Sy>k03HW*@GfvW_!r{*XTW#CgJ1^Kz&pU5;8w5|Yyum=b>QEK`(FZ-A9x0w z244d22bU4|KM1}6YM=^UM7;kjI0HToc7t(nE4Yk!{{nafyc?WHd`~g|6JP<1gWJJH z#P*MaI;a5)7~nQ=1Gt13|EJ(5;K$%;@JT>9fSuqr@E^?61wixl0L|I|`V$KlpzB`Y zQS$a4inH1+DUg5R>^JcT&z64WwIiL}$ASA6mZLII`bmWunQ01J;ZSzFeV|eu9vL0L zttrK7gvW-g`<~;}tom`(0+&411a_YNF;N^HI6CoOEP-@qVWd@&5-S#rpg+jr4^`0V&KYu& zG025#xx6=a+6cQ>DWfnMqRtH{9ho%U*A}&4HasCs7(labwUH^XTqFycHvSinFdffp z1-?=tCl*@x4?igj7)iThi4jR|Gi>=GjIkveB|}G$yI3JLwoU}OLQ;YPDY`Qy{NPZh zCEi)y#i2Xfz9c_OBH6I$A?M&&FpkKHwddTPw^QDtLbboVVUfE+cNa*wO&iJ_<`+L3 zHf05%=q>Um;JQ?)Uo_*8hAtmiOp0rDR7*J$tLB{$L9&8MvH}W{=U8-vg8R6K5AVX) z?z9s`&dV;oeY;&sx*B1O6C~PF!lO(f@@EKnk9oFR9Iyrg8F$+zD^`HOF;)X+L65eg zb3Rq{f6Wzkt*?xlQX_~>f4TSSpy}0@3))NuxhS|q+=?bccUX)1<*k4#y0Ax98Co@4 zx!KAMGwAs|CA$0gH(clBdhWXl+4^3o$#`1+Ra7P>y`B#{)-@+jIaK5%daFND5K ztURtp=`=o7r9A5Vtmg<0=SB$ks$}CSL{Zd|cI7&G+g^$O^wJoq#2@zbxC`S#xmEYT zQaXHHo>%qgOyZ#vt?o4rA$k<@D%u!*3U*!h#%ekCHEoThM+_Zj3 void: - var line = BufferLineTest.new(3) - line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)])) - line.set_cell(1, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)])) - line.set_cell(2, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)])) - line.insert_cells(1, 3, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)])) - assert_eq(line.to_array(), [ - [1, 'a', 0, 'a'.ord_at(0)], - [4, 'd', 0, 'd'.ord_at(0)], - [4, 'd', 0, 'd'.ord_at(0)] - ]) - - - func test_delete_cells() -> void: - var line = BufferLineTest.new(5) - line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)])) - line.set_cell(1, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)])) - line.set_cell(2, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)])) - line.set_cell(3, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)])) - line.set_cell(4, CellData.from_char_data([5, 'e', 0, 'e'.ord_at(0)])) - line.delete_cells(1, 2, CellData.from_char_data([6, 'f', 0, 'f'.ord_at(0)])) - assert_eq(line.to_array(), [ - [1, 'a', 0, 'a'.ord_at(0)], - [4, 'd', 0, 'd'.ord_at(0)], - [5, 'e', 0, 'e'.ord_at(0)], - [6, 'f', 0, 'f'.ord_at(0)], - [6, 'f', 0, 'f'.ord_at(0)] - ]) - - - func test_replace_cells(): - var line = BufferLineTest.new(5) - line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)])) - line.set_cell(1, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)])) - line.set_cell(2, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)])) - line.set_cell(3, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)])) - line.set_cell(4, CellData.from_char_data([5, 'e', 0, 'e'.ord_at(0)])) - line.replace_cells(2, 4, CellData.from_char_data([6, 'f', 0, 'f'.ord_at(0)])) - assert_eq(line.to_array(), [ - [1, 'a', 0, 'a'.ord_at(0)], - [2, 'b', 0, 'b'.ord_at(0)], - [6, 'f', 0, 'f'.ord_at(0)], - [6, 'f', 0, 'f'.ord_at(0)], - [5, 'e', 0, 'e'.ord_at(0)], - ]) - - - func test_copy_from(): - var line = BufferLineTest.new(5) - line.set_cell(0, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)])) - line.set_cell(0, CellData.from_char_data([2, 'b', 0, 'b'.ord_at(0)])) - line.set_cell(0, CellData.from_char_data([3, 'c', 0, 'c'.ord_at(0)])) - line.set_cell(0, CellData.from_char_data([4, 'd', 0, 'd'.ord_at(0)])) - line.set_cell(0, CellData.from_char_data([5, 'e', 0, 'e'.ord_at(0)])) - var line2 = BufferLineTest.new(5, CellData.from_char_data([1, 'a', 0, 'a'.ord_at(0)]), true) - line2.copy_from(line) - assert_eq(line2.to_array(), line.to_array()) - assert_eq(line2.length, line.length) - assert_eq(line2.is_wrapped, line.is_wrapped) - - -class TestResize: - extends "res://addons/gut/test.gd" - - - var CHAR_DATA = [1, 'a', 0, 'a'.ord_at(0)] - var line - - func repeat(el, times: int) -> Array: - var result = [] - result.resize(times) - for i in range(times): - result[i] = el - return result - - - func test_enlarge(): - line = BufferLineTest.new(5, CellData.from_char_data(CHAR_DATA), false) - line.resize(10, CellData.from_char_data(CHAR_DATA)) - assert_eq(line.to_array(), repeat(CHAR_DATA, 10)) - - - func test_shrink(): - line = BufferLineTest.new(10, CellData.from_char_data(CHAR_DATA), false) - line.resize(5, CellData.from_char_data(CHAR_DATA)) - assert_eq(line.to_array(), repeat(CHAR_DATA, 5)) - - - func test_shrink_to_0_length(): - line = BufferLineTest.new(10, CellData.from_char_data(CHAR_DATA), false) - line.resize(0, CellData.from_char_data(CHAR_DATA)) - assert_eq(line.to_array(), repeat(CHAR_DATA, 0)) - - func shrink_then_enlarge(): - line = BufferLineTest.new(10, CellData.from_char_data(CHAR_DATA), false); - line.set_cell(2, CellData.from_char_data([0, 'ðŸ˜', 1, 'ðŸ˜'.ord_at(0)])) - line.set_cell(9, CellData.from_char_data([0, 'ðŸ˜', 1, 'ðŸ˜'.ord_at(0)])) - assert_eq(line.translate_to_string(), 'aaðŸ˜aaaaaaðŸ˜') - line.resize(5, CellData.from_char_data(CHAR_DATA)) - assert_eq(line.translate_to_string(), 'aaðŸ˜aa') - line.resize(10, CellData.from_char_data(CHAR_DATA)) - assert_eq(line.translate_to_string(), 'aaðŸ˜aaaaaaa') - - -class TestTrimLength: - extends "res://addons/gut/test.gd" - - - var line - - - func before_each(): - line = BufferLineTest.new(3, CellData.from_char_data([Constants.DEFAULT_ATTR, - Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE])) - - - func test_empty_line(): - assert_eq(line.get_trimmed_length(), 0) - - - func test_ascii(): - line.set_cell(0, CellData.from_char_data([1, "a", 1, "a".ord_at(0)])) - line.set_cell(2, CellData.from_char_data([1, "a", 1, "a".ord_at(0)])) - assert_eq(line.get_trimmed_length(), 3) - - - func test_unicode(): - line.set_cell(0, CellData.from_char_data([1, "\u1f914", 1, "\u1f914".ord_at(0)])) - line.set_cell(2, CellData.from_char_data([1, "\u1f914", 1, "\u1f914".ord_at(0)])) - assert_eq(line.get_trimmed_length(), 3) - - - func test_one_cell(): - line.set_cell(0, CellData.from_char_data([1, "a", 1, "a".ord_at(0)])) - assert_eq(line.get_trimmed_length(), 1) - - -class TestAddCharToCell: - extends "res://addons/gut/test.gd" - - - var line - var cell - - - func before_each(): - line = BufferLineTest.new(3, CellData.from_char_data([Constants.DEFAULT_ATTR, - Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE])) - cell = line.load_cell(0, CellData.new()) - - - func test_sets_width_to_1_for_empty_cell(): - line.add_codepoint_to_cell(0, "\u0301".ord_at(0)) - cell = line.load_cell(0, CellData.new()) - # chars contains single combining char - # width is set to 1 - assert_eq(cell.get_as_char_data(), [Constants.DEFAULT_ATTR, '\u0301', 1, 0x0301]) - # do not account a single combining char as combined - assert_eq(cell.is_combined(), 0) - - - func test_add_char_to_combining_string_in_cell(): - cell.set_from_char_data([123, "e\u0301", 1, "e\u0301".ord_at(1)]) - line.set_cell(0, cell) - line.add_codepoint_to_cell(0, "\u0301".ord_at(0)) - line.load_cell(0, cell) - # char contains 3 chars - # width is set to 1 - assert_eq(cell.get_as_char_data(), [123, "e\u0301\u0301", 1, 0x0301]) - # do not account a single combining char as combined - assert_eq(cell.is_combined(), Content.IS_COMBINED_MASK) - - - func test_create_combining_string_on_taken_cell(): - cell.set_from_char_data([123, "e", 1, "e".ord_at(1)]) - line.set_cell(0, cell) - line.add_codepoint_to_cell(0, "\u0301".ord_at(0)) - line.load_cell(0, cell) - # chars contains 2 chars - # width is set to 1 - assert_eq(cell.get_as_char_data(), [123, "e\u0301", 1, 0x0301]) - # do not account a single combining char as combined - assert_eq(cell.is_combined(), Content.IS_COMBINED_MASK) - - -class TestTranslateToString: - extends "res://addons/gut/test.gd" - - - var line - - - func before_each(): - line = BufferLineTest.new(10, CellData.from_char_data([Constants.DEFAULT_ATTR, - Constants.NULL_CELL_CHAR, Constants.NULL_CELL_WIDTH, Constants.NULL_CELL_CODE]), false) - - - func test_empty_line(): - assert_eq(line.translate_to_string(false), ' ') - assert_eq(line.translate_to_string(true), '') - - - func test_ASCII(): - line.set_cell(0, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - line.set_cell(2, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - line.set_cell(4, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - line.set_cell(5, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - assert_eq(line.translate_to_string(false), 'a a aa ') - assert_eq(line.translate_to_string(true), 'a a aa') - assert_eq(line.translate_to_string(false, 0, 5), 'a a a') - assert_eq(line.translate_to_string(false, 0, 4), 'a a ') - assert_eq(line.translate_to_string(false, 0, 3), 'a a') - assert_eq(line.translate_to_string(true, 0, 5), 'a a a') - assert_eq(line.translate_to_string(true, 0, 4), 'a a ') - assert_eq(line.translate_to_string(true, 0, 3), 'a a') - - - func test_space_at_end(): - line.set_cell(0, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - line.set_cell(2, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - line.set_cell(4, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - line.set_cell(5, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - line.set_cell(6, CellData.from_char_data([1, ' ', 1, ' '.ord_at(0)])) - assert_eq(line.translate_to_string(false), 'a a aa ') - assert_eq(line.translate_to_string(true), 'a a aa ') - - - func test_always_returns_some_sane_value(): - # sanity check - broken line with invalid out of bound null width cells - # this can atm happen with deleting/inserting chars in inputhandler by "breaking" - # fullwidth pairs --> needs to be fixed after settling BufferLine impl - assert_eq(line.translate_to_string(false), ' ') - assert_eq(line.translate_to_string(true), '') - - - func test_works_with_end_col_0(): - line.set_cell(0, CellData.from_char_data([1, 'a', 1, 'a'.ord_at(0)])) - assert_eq(line.translate_to_string(true, 0, 0), '') diff --git a/test/unit/input/test_text_decoder.gd b/test/unit/input/test_text_decoder.gd deleted file mode 100644 index 65b5686..0000000 --- a/test/unit/input/test_text_decoder.gd +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (c) 2019 The xterm.js authors. All rights reserved. -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends 'res://addons/gut/test.gd' - -const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd") - -# Note: There might be some invisible characters (such as emoji) depending -# on your editor and font settings. -const TEST_STRINGS = [ - "Лорем ипÑум долор Ñит амет, ех Ñеа аццуÑам диÑÑентиет. Ðн ÐµÐ¾Ñ Ñтет еирмод витуперата. Ð˜ÑƒÑ Ð´Ð¸Ñ†ÐµÑ€ÐµÑ‚ ÑƒÑ€Ð±Ð°Ð½Ð¸Ñ‚Ð°Ñ ÐµÑ‚. Ðн при алтера Ð´Ð¾Ð»Ð¾Ñ€ÐµÑ Ñплендиде, цу Ñуо интегре дениÑуе, игнота волуптариа инÑтруцтиор цу вим.", - "ლáƒáƒ áƒ”მ იფსუმ დáƒáƒšáƒáƒ  სით áƒáƒ›áƒ”თ, ფáƒáƒªáƒ”რ მუციუს ცáƒáƒœáƒ¡áƒ”თეთურ ყურიდ, ფერ ვივენდუმ ყუáƒáƒ”რენდუმ ეáƒ, ესთ áƒáƒ›áƒ”თ მáƒáƒ•áƒ”თ სუáƒáƒ•áƒ˜áƒ—áƒáƒ—ე ცუ. ვითáƒáƒ” სენსიბუს áƒáƒœ ვიხ. ეხერცი დეთერრუისსეთ უთ ყუი. ვáƒáƒªáƒ”ნთ დებითის áƒáƒ“იფისცი ეთ ფერ. ნეც áƒáƒœ ფეუგáƒáƒ˜áƒ— ფáƒáƒ áƒ”ნსიბუს ინთერესსეთ. იდ დიცრრიდენს იუს. დისსენთიეთ ცáƒáƒœáƒ¡áƒ”ყუუნთურ სედ ნე, ნáƒáƒ•áƒ£áƒ› მუნერე ეუმ áƒáƒ—, ნე ეუმ ნიჰილ ირáƒáƒªáƒ£áƒœáƒ“ირურბáƒáƒœáƒ˜áƒ—áƒáƒ¡.", - "अधिकांश अमितकà¥à¤®à¤¾à¤° पà¥à¤°à¥‹à¤¤à¥à¤¸à¤¾à¤¹à¤¿à¤¤ मà¥à¤–à¥à¤¯ जाने पà¥à¤°à¤¸à¤¾à¤°à¤¨ विशà¥à¤²à¥‡à¤·à¤£ विशà¥à¤µ दारी अनà¥à¤µà¤¾à¤¦à¤• अधिकांश नवंबर विषय गटकउसि गोपनीयता विकास जनित परसà¥à¤ªà¤° गटकउसि अनà¥à¤¤à¤°à¤°à¤¾à¤·à¥à¤Ÿà¥à¤°à¥€à¤¯à¤•à¤°à¤¨ होसके मानव पà¥à¤°à¥à¤£à¤¤à¤¾ कमà¥à¤ªà¥à¤¯à¥à¤Ÿà¤° यनà¥à¤¤à¥à¤°à¤¾à¤²à¤¯ पà¥à¤°à¤¤à¤¿ साधन", - "覧六å­å½“èžç¤¾è¨ˆæ–‡è­·è¡Œæƒ…投身斗æ¥ã€‚増è½ä¸–çš„æ³ä¸Šå¸­å‚™ç•Œå…ˆé–¢æ¨©èƒ½ä¸‡ã€‚本物挙歯乳全事æºä¾›æ¿æ ƒæžœä»¥ã€‚頭月患端撤競見界記引去法æ¡å…¬æ³Šå€™ã€‚決海備駆å–å“目芸方用æœç¤ºä¸Šç”¨å ±ã€‚講申務紙約週堂出応ç†ç”°æµå›£å¹¸ç¨¿ã€‚èµ·ä¿å¸¯å‰å¯¾é˜œåº­æ”¯è‚¯è±ªå½°å±žæœ¬èºã€‚é‡æŠ‘熊事府募動極都掲仮読岸。自続工就断庫指北速é…鳴約事新ä½ç±³ä¿¡ä¸­é¨“。婚浜袋著金市生交ä¿ä»–å–情è·ã€‚", - "八メル務å•ã¸ãµã‚‰ãåšè¾žèª¬ã„ã‚ょ読全タヨムケæ±æ ¡ã©ã£çŸ¥å£ãƒ†ã‚±ç¦åŽ»ãƒ•ãƒŸäººéŽã‚’装5階ãŒã­ãœæ³•é€†ã¯ã˜ç«¯40è½ãƒŸäºˆç«¹ãƒžãƒ˜ãƒŠã‚»ä»»1悪ãŸã€‚çœãœã‚Šã›è£½æš‡ã‚‡ã¸ãã‘風井イ劣手ã¯ã¼ã¾ãšéƒµå¯Œæ³•ã作断タオイå–座ゅょãŒå‡ºä½œãƒ›ã‚·æœˆçµ¦26島ツãƒçš‡é¢ãƒ¦ãƒˆã‚¯ã‚¤æš®çŠ¯ãƒªãƒ¯ãƒŠãƒ¤æ–­é€£ã“ã†ã§ã¤è”­æŸ”è–„ã¨ãƒ¬ã«ã®ã€‚æ¼”ã‚ã‘ãµã±æ田転10得観ã³ãƒˆã’ãŽçŽ‹ç‰©é‰„夜ãŒã¾ã‘ç†æƒœãã¡ç‰¡æã¥è»Šæƒ‘å‚ヘカユモ長臓超漫ã¼ãƒ‰ã‹ã‚。", - "모든 êµ­ë¯¼ì€ í–‰ìœ„ì‹œì˜ ë²•ë¥ ì— ì˜í•˜ì—¬ 범죄를 구성하지 아니하는 행위로 소추ë˜ì§€ 아니하며. ì „ì§ëŒ€í†µë ¹ì˜ 신분과 ì˜ˆìš°ì— ê´€í•˜ì—¬ëŠ” 법률로 정한다, 국회는 헌법 ë˜ëŠ” ë²•ë¥ ì— íŠ¹ë³„í•œ ê·œì •ì´ ì—†ëŠ” í•œ 재ì ì˜ì› ê³¼ë°˜ìˆ˜ì˜ ì¶œì„ê³¼ 출ì„ì˜ì› ê³¼ë°˜ìˆ˜ì˜ ì°¬ì„±ìœ¼ë¡œ ì˜ê²°í•œë‹¤. êµ°ì¸Â·êµ°ë¬´ì›Â·ê²½ì°°ê³µë¬´ì› 기타 ë²•ë¥ ì´ ì •í•˜ëŠ” ìžê°€ 전투·훈련등 ì§ë¬´ì§‘행과 관련하여 ë°›ì€ ì†í•´ì— 대하여는 ë²•ë¥ ì´ ì •í•˜ëŠ” ë³´ìƒì™¸ì— êµ­ê°€ ë˜ëŠ” ê³µê³µë‹¨ì²´ì— ê³µë¬´ì›ì˜ ì§ë¬´ìƒ 불법행위로 ì¸í•œ ë°°ìƒì€ 청구할 수 없다.", - "كان Ùشكّل الشرقي مع, واحدة للمجهود تزامناً بعض بل. وتم جنوب للصين غينيا لم, ان وبدون وكسبت الأمور ذلك, أسر الخاسر الانجليزية هو. Ù†Ùس لغزو مواقعها هو. الجو علاقة الصعداء انه أي, كما مع بمباركة للإتحاد الوزراء. ترتيب الأولى أن حدى, الشتوية باستحداث مدن بل, كان قد أوسع عملية. الأوضاع بالمطالبة كل قام, دون إذ شمال الربيع،. Ù‡Ùزم الخاصّة ٣٠ أما, مايو الصينية مع قبل.", - "×ו סדר החול מיזמי קרימינולוגיה. קהילה בגרסה ×œ×•×™×§×™×¤×“×™× ×ל ×”×™×, של צעד ציור ו×לקטרוניקה. מדע מה ברית המזנון ×רכי×ולוגיה, ×ל טבל×ות ×ž×‘×•×§×©×™× ×›×œ×œ. מ×מרשיחהצפה העריכהגירס×ות שכל ×ל, כתב עיצוב מושגי של. קבלו קל××¡×™×™× ×‘ מתן. × ×‘×—×¨×™× ×ווירונ×וטיקה ×× ×ž×œ×, לוח למנוע ×רכי×ולוגיה מה. ×רץ לערוך בקרבת ×ž×•× ×—×•× ×™× ×ו, עזרה רקטות ×œ×•×™×§×™×¤×“×™× ×חר ×’×.", - "Лорем ლáƒáƒ áƒ”მ अधिकांश è¦§å…­å­ å…«ãƒ¡ãƒ« 모든 בקרבת äggg 123€ .", -] - - -func test_utf32_to_utf8(): - # 1 byte utf8 character - assert_eq( - Decoder.utf32_to_utf8(0x00000061), - PoolByteArray([0x61]) - ) - # 2 byte utf8 character - assert_eq( - Decoder.utf32_to_utf8(0x00000761), - PoolByteArray([0xdd, 0xa1]) - ) - # 3 byte utf8 character - assert_eq( - Decoder.utf32_to_utf8(0x00002621), - PoolByteArray([0xe2, 0x98, 0xa1]) - ) - # 4 byte utf8 character - assert_eq( - Decoder.utf32_to_utf8(0x00010144), - PoolByteArray([0xf0, 0x90, 0x85, 0x84]) - ) - assert_eq( - Decoder.utf32_to_utf8(0x0001f427) as Array, - PoolByteArray([0xf0, 0x9f, 0x90, 0xa7]) as Array - ) - - -func test_utf32_to_string(): - assert_eq( - Decoder.utf32_to_string([49, 50, 51, 0x1d11e, 49, 50, 51]), - '123ð„ž123' - ) - -class TestUtf8ToUtf32Decoder: - extends 'res://addons/gut/test.gd' - - var decoder = Decoder.Utf8ToUtf32.new() - var target = [] - - # Full codepoint tests take a long time to run, so skip them unless this - # environment variable is set. - var skip_full_codepoint_tests = not OS.get_environment("RUN_FULL_CODEPOINT_TESTS") - - - func before_each(): - decoder.clear() - target.clear() - target.resize(5) - - func test_lol(): - var target = [0, 0, 0, 0] - decoder.decode('💩'.substr(0, 1).to_utf8(), target) - assert_eq(target, []) - - - func test_full_code_point_0_to_65535(): # 1/2/3 byte sequences - if skip_full_codepoint_tests: - print("Skipping full codepoint test") - return - for i in range(65536): - # skip surrogate pairs - if i >= 0xD800 and i <= 0xDFFF: - continue - # FIXME: Skip 0xfeff (zero width no-break space) - # which fails for some reason - if i == 0xfeff: - continue - var utf8_data = Decoder.utf32_to_utf8(i) - var length = decoder.decode(utf8_data, target) - assert_eq(length, 1) - assert_eq(char(target[0]), utf8_data.get_string_from_utf8(), - "Wrong string for codepoint 0x%x" % target[0]) - decoder.clear() - - func test_full_codepoint_65536_to_0x10FFFF(): # 4 byte sequences - if skip_full_codepoint_tests: - print("Skipping full codepoint test") - return - for i in range(65536, 0x10FFFF): - var utf8_data = Decoder.utf32_to_utf8(i) - var length = decoder.decode(utf8_data, target) - assert_eq(length, 1) - assert_eq(target[0], i) - - func test_test_strings(): - target.resize(500) - for string in TEST_STRINGS: - var utf8_data = string.to_utf8() - var length = decoder.decode(utf8_data, target) - assert_eq(Decoder.utf32_to_string(target, 0, length), string) - decoder.clear() diff --git a/test/unit/parser/test_dcs_parser.gd b/test/unit/parser/test_dcs_parser.gd deleted file mode 100644 index cbe6459..0000000 --- a/test/unit/parser/test_dcs_parser.gd +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) 2020 The GodotXterm authors. -# Copyright (c) 2019 The xterm.js authors. All rights reserved. -# License MIT -extends "res://addons/gut/test.gd" - -const DcsParser = preload("res://addons/godot_xterm/parser/dcs_parser.gd") -const Parser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd") -const Params = preload("res://addons/godot_xterm/parser/params.gd") -const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd") - -class Handler: - extends Reference - - - var _output - var _msg - var _return_false - - - func _init(output: Array, msg: String, return_false: bool = false): - _output = output - _msg = msg - _return_false = return_false - - - func hook(params): - _output.append([_msg, 'HOOK', params.to_array()]) - - - func put(data: Array, start: int, end: int): - _output.append([_msg, 'PUT', Decoder.utf32_to_string(data, start, end)]) - - - func unhook(success: bool): - _output.append([_msg, 'UNHOOK', success]) - if _return_false: - return false - - -var parser: DcsParser -var reports: Array - - -func to_utf32(s: String): - var utf32 = [] - utf32.resize(s.length()) - var decoder = Decoder.Utf8ToUtf32.new() - var length = decoder.decode(s.to_utf8(), utf32) - assert_eq(length, s.length()) - return utf32.slice(0, length - 1) - - -func handler_fallback(id, action, data): - if action == 'HOOK': - data = data.to_array() - reports.append([id, action, data]) - - -func before_each(): - parser = DcsParser.new() - parser.set_handler_fallback(self, 'handler_fallback') - reports = [] - - -func test_set_dcs_handler(): - parser.set_handler(Parser.identifier({'intermediates': '+', 'final': 'p'}), - Handler.new(reports, 'th')) - parser.hook(Parser.identifier({'intermediates': '+', 'final': 'p'}), - Params.from_array([1, 2, 3])) - var data = to_utf32('Here comes') - parser.put(data, 0, data.size()) - data = to_utf32('the mouse!') - parser.put(data, 0, data.size()) - parser.unhook(true) - assert_eq(reports, [ - # messages from Handler - ['th', 'HOOK', [1, 2, 3]], - ['th', 'PUT', 'Here comes'], - ['th', 'PUT', 'the mouse!'], - ['th', 'UNHOOK', true], - ]) - - -func test_clear_dcs_handler(): - var ident = Parser.identifier({'intermediates': '+', 'final': 'p'}) - parser.set_handler(ident, Handler.new(reports, 'th')) - parser.clear_handler(ident) - parser.hook(ident, Params.from_array([1, 2, 3])) - var data = to_utf32('Here comes') - parser.put(data, 0, data.size()) - data = to_utf32('the mouse!') - parser.put(data, 0, data.size()) - parser.unhook(true) - assert_eq(reports, [ - # messages from fallback handler - [ident, 'HOOK', [1, 2, 3]], - [ident, 'PUT', 'Here comes'], - [ident, 'PUT', 'the mouse!'], - [ident, 'UNHOOK', true], - ]) diff --git a/test/unit/parser/test_escape_sequence_parser.gd b/test/unit/parser/test_escape_sequence_parser.gd deleted file mode 100644 index c2fc64f..0000000 --- a/test/unit/parser/test_escape_sequence_parser.gd +++ /dev/null @@ -1,411 +0,0 @@ -# Copyright (c) 2020 The GodotXterm authors. -# Copyright (c) 2018 The xterm.js authors. All rights reserved. -# License MIT -extends 'res://addons/gut/test.gd' - -const Parser = preload("res://addons/godot_xterm/parser/escape_sequence_parser.gd") -const Params = preload("res://addons/godot_xterm/parser/params.gd") -const Decoder = preload("res://addons/godot_xterm/input/text_decoder.gd") -const Constants = preload("res://addons/godot_xterm/parser/constants.gd") -const ParserState = Constants.ParserState - -class TestTerminal: - var calls = [] - - func clear(): - calls = [] - - - func handle_print(data, start, end): - var string = Decoder.utf32_to_string(data, start, end) - calls.append(['print', string]) - - - func handle_csi(ident, params): - var id = Parser.ident_to_string(ident) - var collect = id.substr(0, id.length() - 1) - var flag = id.substr(id.length() - 1, 1) - calls.append(['csi', collect, params, flag]) - - - func handle_esc(ident: int): - var id = Parser.ident_to_string(ident) - var collect = id.substr(0, id.length() - 1) - var flag = id.substr(id.length() - 1, 1) - calls.append(['esc', collect, flag]) - - - func handle_execute(code: int): - var flag = char(code) - calls.append(['exe', flag]) - - - func handle_dcs(collect_and_flag, action, payload): - match action: - 'HOOK': - calls.append(['dcs hook', payload.to_array()]) - 'PUT': - calls.append(['dcs put', payload]) - 'UNHOOK': - calls.append(['dcs unhook', payload]) - - -# derived parser with access to internal states -class TestParser: - extends Parser - - var params setget _set_params,_get_params - var collect setget _set_collect,_get_collect - - - func _init(): - pass - - - func _set_params(value): - _params = Params.from_array(value) - - - func _get_params(): - return _params.to_array() - - - func _set_collect(value: String): - _collect = 0 - for c in value.to_ascii(): - _collect <<= 8 - _collect |= c - - - func _get_collect() -> String: - return ident_to_string(_collect) - - - func real_params(): - return _params - -# translate string based parse calls into typed array based -func parse(parser: TestParser, data): - if data == '': # handle the 0x00 codepoint - data = PoolByteArray([0]) - else: - data = data.to_utf8() - var container = [] - var decoder = Decoder.Utf8ToUtf32.new() - decoder.clear() - var length = decoder.decode(data, container) - parser.parse(container, length) - - -var parser -var test_terminal - - -func before_all(): - parser = TestParser.new() - test_terminal = TestTerminal.new() - - parser.set_print_handler(test_terminal, 'handle_print') - parser.set_csi_handler_fallback(test_terminal, 'handle_csi') - parser.set_esc_handler_fallback(test_terminal, 'handle_esc') - parser.set_execute_handler_fallback(test_terminal, "handle_execute") - parser.set_dcs_handler_fallback(test_terminal, "handle_dcs") - - -func before_each(): - parser.reset() - test_terminal.clear() - - -func test_initial_states(): - assert_eq(parser.initial_state, ParserState.GROUND) - assert_eq(parser.current_state, ParserState.GROUND) - assert_eq(parser._params.to_array(), [0]) - -func test_reset_states(): - var params = Params.new() - params.add_param(123) - parser.current_state = 124 - parser._params = params - parser.reset() - assert_eq(parser.current_state, ParserState.GROUND) - assert_eq(parser._params.to_array(), [0]) - -# state transitions and actions - -func test_state_GROUND_execute_action(): - var exes = range(0x00, 0x18) + [0x19] + range(0x1c, 0x20) - for exe in exes: - parser.current_state = ParserState.GROUND - parse(parser, char(exe)) - assert_eq(parser.current_state, ParserState.GROUND) - parser.reset() - -func test_state_GROUND_print_action(): - var printables = range(0x20, 0x7f) # NOTE: DEL excluded - for printable in printables: - var string = char(printable) - parser.current_state = ParserState.GROUND - parse(parser, string) - assert_eq(parser.current_state, ParserState.GROUND) - assert_eq(test_terminal.calls, [['print', string]]) - parser.reset() - test_terminal.clear() - -func test_trans_ANYWHERE_to_GROUND_with_actions(): - var exes = [ - '\u0018', '\u001a', - '\u0080', '\u0081', '\u0082', '\u0083', '\u0084', '\u0085', '\u0086', '\u0087', '\u0088', - '\u0089', '\u008a', '\u008b', '\u008c', '\u008d', '\u008e', '\u008f', - '\u0091', '\u0092', '\u0093', '\u0094', '\u0095', '\u0096', '\u0097', '\u0099', '\u009a' - ] - var exceptions = { - 8: { '\u0018': [], '\u001a': [] }, # abort OSC_STRING - 13: { '\u0018': [['dcs unhook', false]], '\u001a': [['dcs unhook', false]] } # abort DCS_PASSTHROUGH - } - for state in ParserState.values(): - for exe in exes: - if exe != '\u0018' and exe != '\u001a': - continue - parser.current_state = state - parse(parser, exe) - assert_eq(parser.current_state, ParserState.GROUND) - assert_eq( - test_terminal.calls, - exceptions[state][exe] if exceptions.has(state) and exceptions[state].has(exe) else [['exe', exe]], - 'state: %s exe: %x' % [ParserState.keys()[state], exe.to_utf8()[0]] - ) - parser.reset() - test_terminal.clear() - parse(parser, '\u009c') - assert_eq(parser.current_state, ParserState.GROUND) - assert_eq(test_terminal.calls, []) - parser.reset() - test_terminal.clear() - - -func skip_test_trans_ANYWHERE_to_ESCAPE_with_clear(): - for state in ParserState.values(): - var state_name = ParserState.keys()[state] - parser.current_state = state - parser.params = [23] - parser.collect = '#' - parse(parser, '\u001b') - assert_eq(parser.current_state, ParserState.ESCAPE, - 'wrong current_state. start state: %s' % state_name) - assert_eq(parser.params, [0], - 'wrong params. start state: %s' % state_name) - assert_eq(parser.collect, '', - 'wrong collect. start state: %s' % state_name) - parser.reset() - - -func test_state_ESCAPE_execute_rules(): - var exes = range(0x00, 0x18) + [0x19] + range(0x1c, 0x20) - for exe in exes: - parser.current_state = ParserState.ESCAPE - var data = char(exe) - parse(parser, data) - assert_eq(parser.current_state, ParserState.ESCAPE, 'exe: %x' % exe) - assert_eq(test_terminal.calls, [['exe', data]], 'exe: %x' % exe) - parser.reset() - test_terminal.clear() - - -func test_state_ESCAPE_ignore(): - parser.current_state = ParserState.ESCAPE - parse(parser, '\u007f') - assert_eq(parser.current_state, ParserState.ESCAPE) - assert_eq(test_terminal.calls, []) - -func test_trans_ESCAPE_to_GROUND_with_esc_dispatch_action(): - var dispatches = range(0x30, 0x50) + range(0x51, 0x58) + [0x59, 0x5a] + range(0x60, 0x7f) - for dispatch in dispatches: - parser.current_state = ParserState.ESCAPE - var data = char(dispatch) - parse(parser, data) - assert_eq(parser.current_state, ParserState.GROUND, - 'wrong state: %s, dispatch: %x' % [ParserState.keys()[parser.current_state], dispatch]) - assert_eq(test_terminal.calls, [['esc', '', data]], - 'wrong call. dispatch: %x' % dispatch) - parser.reset() - test_terminal.clear() - - -func test_trans_ESCAPE_to_ESCAPE_INTERMEDIATE_with_collect_action(): - var collect = range(0x20, 0x30) - for c in collect: - parser.current_state = ParserState.ESCAPE - var data = char(c) - parse(parser, data) - assert_eq(parser.current_state, ParserState.ESCAPE_INTERMEDIATE) - assert_eq(parser.collect, data) - parser.reset() - - -func test_state_ESCAPE_INTERMEDIATE_execute_rules(): - var exes = range(0x00, 0x18) + [0x19] + range(0x1c, 0x20) - for exe in exes: - var data = char(exe) - parser.current_state = ParserState.ESCAPE_INTERMEDIATE - parse(parser, data) - assert_eq(parser.current_state, ParserState.ESCAPE_INTERMEDIATE) - assert_eq(test_terminal.calls, [['exe', data]]) - parser.reset() - test_terminal.clear() - - -func test_state_ESCAPE_INTERMEDIATE_ignore(): - parser.current_state = ParserState.ESCAPE_INTERMEDIATE - parse(parser, '\u007f') - assert_eq(parser.current_state, ParserState.ESCAPE_INTERMEDIATE) - assert_eq(test_terminal.calls, []) - - -func test_state_ESCAPE_INTERMEDIATE_collect_action(): - var collect = range(0x20, 0x30) - for c in collect: - var data = char(c) - parser.current_state = ParserState.ESCAPE_INTERMEDIATE - parse(parser, data) - assert_eq(parser.current_state, ParserState.ESCAPE_INTERMEDIATE) - assert_eq(parser.collect, data) - parser.reset() - - -func test_trans_ESCAPE_INTERMEDIATE_to_GROUND_with_esc_dispatch_action(): - var collect = range(0x30, 0x7f) - for c in collect: - var data = char(c) - parser.current_state = ParserState.ESCAPE_INTERMEDIATE - parse(parser, data) - assert_eq(parser.current_state, ParserState.GROUND) - # '\u005c' --> ESC + \ (7bit ST) parser does not expose this as it already got handled - assert_eq(test_terminal.calls, [] if c == 0x5c else [['esc', '', data]], 'c: 0x%x' % c) - parser.reset() - test_terminal.clear() - -func test_ANYWHERE_or_ESCAPE_to_CSI_ENTRY_with_clear(): - # C0 - parser.current_state = ParserState.ESCAPE - parser.params = [123] - parser.collect = '#' - parse(parser, '[') - assert_eq(parser.current_state, ParserState.CSI_ENTRY) - assert_eq(parser.params, [0]) - assert_eq(parser.collect, '') - parser.reset() - # C1 - for state in ParserState.values(): - parser.current_state = state - parser.params = [123] - parser.collect = '#' - parse(parser, '\u009b') - assert_eq(parser.current_state, ParserState.CSI_ENTRY) - assert_eq(parser.collect, '') - parser.reset() - - -func test_CSI_ENTRY_execute_rules(): - var exes = range(0x00, 0x18) + [0x19] + range(0x1c, 0x20) - for exe in exes: - var data = char(exe) - parser.current_state = ParserState.CSI_ENTRY - parse(parser, data) - assert_eq(parser.current_state, ParserState.CSI_ENTRY) - assert_eq(test_terminal.calls, [['exe', data]]) - parser.reset() - test_terminal.clear() - - -func test_state_CSI_ENTRY_ignore(): - parser.current_state = ParserState.CSI_ENTRY - parse(parser, '\u007f') - assert_eq(parser.current_state, ParserState.CSI_ENTRY) - assert_eq(test_terminal.calls, []) - - -func test_trans_CSI_ENTRY_to_GROUND_with_csi_dispatch_action(): - var dispatches = range(0x40, 0x7f) - for dispatch in dispatches: - var data = char(dispatch) - parser.current_state = ParserState.CSI_ENTRY - parse(parser, data) - assert_eq(parser.current_state, ParserState.GROUND) - assert_eq(test_terminal.calls, [['csi', '', [0], data]]) - parser.reset() - test_terminal.clear() - - -func test_trans_CSI_ENTRY_to_CSI_PARAMS_with_param_or_collect_action(): - var params = range(0x30, 0x3a) - var collect = ['\u003c', '\u003d', '\u003e', '\u003f'] - for param in params: - parser.current_state = ParserState.CSI_ENTRY - parse(parser, char(param)) - assert_eq(parser.current_state, ParserState.CSI_PARAM) - assert_eq(parser.params, [param - 48], 'param: 0x%x' % param) - parser.reset() - parser.current_state = ParserState.CSI_ENTRY - parse(parser, '\u003b') - assert_eq(parser.current_state, ParserState.CSI_PARAM) - assert_eq(parser.params, [0, 0]) - parser.reset() - for c in collect: - parser.current_state = ParserState.CSI_ENTRY - parse(parser, c) - assert_eq(parser.current_state, ParserState.CSI_PARAM) - assert_eq(parser.collect, c) - parser.reset() - - -func test_state_CSI_PARAM_execute_rules(): - var exes = range(0x00, 0x018) + [0x19] + range(0x1c, 0x20) - for exe in exes: - var data = char(exe) - parser.current_state = ParserState.CSI_PARAM - parse(parser, data) - assert_eq(parser.current_state, ParserState.CSI_PARAM) - assert_eq(test_terminal.calls, [['exe', data]]) - parser.reset() - test_terminal.clear() - - -func test_state_CSI_PARAM_param_action(): - var params = range(0x30, 0x3a) - for param in params: - parser.current_state = ParserState.CSI_PARAM - parse(parser, char(param)) - assert_eq(parser.current_state, ParserState.CSI_PARAM) - assert_eq(parser.params, [param - 48], 'param: 0x%x' % param) - parser.reset() - - -func test_state_CSI_PARAM_ignore(): - parser.current_state = ParserState.CSI_PARAM - parse(parser, '\u007f') - assert_eq(parser.current_state, ParserState.CSI_PARAM) - assert_eq(test_terminal.calls, []) - - -func test_trans_CSI_PARAM_to_GROUND_with_csi_dispatch_action(): - var dispatches = range(0x40, 0x7f) - for dispatch in dispatches: - var data = char(dispatch) - parser.current_state = ParserState.CSI_PARAM - parser.params = [0, 1] - parse(parser, data) - assert_eq(parser.current_state, ParserState.GROUND) - assert_eq(test_terminal.calls, [['csi', '', [0, 1], data]]) - parser.reset() - test_terminal.clear() - - -func test_trans_CSI_ENTRY_to_CSI_INTERMEDIATE_with_collect_action(): - for collect in range(0x20, 0x30): - var data = char(collect) - parser.current_state = ParserState.CSI_ENTRY - parse(parser, data) - assert_eq(parser.current_state, ParserState.CSI_INTERMEDIATE) - assert_eq(parser.collect, data) - parser.reset() diff --git a/test/unit/parser/test_parser.gd b/test/unit/parser/test_parser.gd deleted file mode 100644 index c3987cc..0000000 --- a/test/unit/parser/test_parser.gd +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright (c) 2019 The xterm.js authors. All rights reserved. -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends 'res://addons/gut/test.gd' - -const Params = preload("res://addons/godot_xterm/parser/params.gd") - -class TestParams: - extends 'res://addons/gut/test.gd' - - var params - - func before_each(): - params = Params.new() - - func test_respects_ctor_args(): - params = Params.new(12, 23) - assert_eq(params.params.size(), 12) - assert_eq(params.sub_params.size(), 23) - assert_eq(params.to_array(), []) - - func test_add_param(): - params.add_param(1) - assert_eq(params.length, 1) - assert_eq(params.params.slice(0, params.length - 1), [1]) - assert_eq(params.to_array(), [1]) - params.add_param(23) - assert_eq(params.length, 2) - assert_eq(params.params.slice(0, params.length - 1), [1, 23]) - assert_eq(params.to_array(), [1, 23]) - assert_eq(params.sub_params_length, 0) - - func test_add_sub_param(): - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - assert_eq(params.length, 1) - assert_eq(params.sub_params_length, 2) - assert_eq(params.to_array(), [1, [2, 3]]) - params.add_param(12345) - params.add_sub_param(-1) - assert_eq(params.length, 2) - assert_eq(params.sub_params_length, 3) - assert_eq(params.to_array(), [1, [2,3], 12345, [-1]]) - - func test_should_not_add_sub_params_without_previous_param(): - params.add_sub_param(2) - params.add_sub_param(3) - assert_eq(params.length, 0) - assert_eq(params.sub_params_length, 0) - assert_eq(params.to_array(), []) - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - assert_eq(params.length, 1) - assert_eq(params.sub_params_length, 2) - assert_eq(params.to_array(), [1, [2, 3]]) - - func test_reset(): - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - params.add_param(12345) - params.reset() - assert_eq(params.length, 0) - assert_eq(params.sub_params_length, 0) - assert_eq(params.to_array(), []) - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - params.add_param(12345) - params.add_sub_param(-1) - assert_eq(params.length, 2) - assert_eq(params.sub_params_length, 3) - assert_eq(params.to_array(), [1, [2, 3], 12345, [-1]]) - - - func test_from_array_to_array(): - var data = [] - assert_eq(params.from_array(data).to_array(), data) - data = [1, [2, 3], 12345, [-1]] - assert_eq(params.from_array(data).to_array(), data) - data = [38, 2, 50, 100, 150] - assert_eq(params.from_array(data).to_array(), data) - data = [38, 2, 50, 100, [150]] - assert_eq(params.from_array(data).to_array(), data) - data = [38, [2, 50, 100, 150]] - assert_eq(params.from_array(data).to_array(), data) - # strip empty sub params - data = [38, [2, 50, 100, 150], 5, [], 6] - assert_eq(Params.from_array(data).to_array(), [38, [2, 50, 100, 150], 5, 6]) - # ignore leading sub params - data = [[1,2], 12345, [-1]] - assert_eq(Params.from_array(data).to_array(), [12345, [-1]]) - - -class TestParse: - extends 'res://addons/gut/test.gd' - - var params - - func parse(params, s): - params.reset() - params.add_param(0) - if typeof(s) == TYPE_STRING: - s = [s] - for chunk in s: - var i = 0 - while i < chunk.length(): - # Start for - var code = chunk.to_ascii()[i] - var do = true - while do: - match code: - 0x3b: - params.add_param(0) - 0x3a: - params.add_sub_param(-1) - _: - params.add_digit(code - 48) - code = chunk.to_ascii()[i] if i < chunk.length() else 0 - i+=1 - do = i < s.size() and code > 0x2f and code < 0x3c - i-=1 - # End for - i+=1 - - func before_each(): - params = Params.new() - - func test_param_defaults_to_0(): # ZDM (Zero Default Mode) - parse(params, '') - assert_eq(params.to_array(), [0]) - - func test_sub_param_defaults_to_neg_1(): - parse(params, ':') - assert_eq(params.to_array(), [0, [-1]]) - - func test_reset_on_new_sequence(): - parse(params, '1;2;3') - assert_eq(params.to_array(), [1, 2, 3]) - parse(params, '4') - assert_eq(params.to_array(), [4]) - parse(params, '4::123:5;6;7') - assert_eq(params.to_array(), [4, [-1, 123, 5], 6, 7]) - parse(params, '') - assert_eq(params.to_array(), [0]) - - func test_should_handle_length_restrictions_correctly(): - params = Params.new(3, 3) - parse(params, '1;2;3') - assert_eq(params.to_array(), [1, 2, 3]) - parse(params, '4') - assert_eq(params.to_array(), [4]) - parse(params, '4::123:5;6;7') - assert_eq(params.to_array(), [4, [-1, 123, 5], 6, 7]) - parse(params, '') - assert_eq(params.to_array(), [0]) - # overlong params - parse(params, '4;38:2::50:100:150;48:5:22') - assert_eq(params.to_array(), [4, 38, [2, -1, 50], 48]) - # overlong sub params - parse(params, '4;38:2::50:100:150;48:5:22') - assert_eq(params.to_array(), [4, 38, [2, -1, 50], 48]) - - func test_typical_sequences(): - # SGR with semicolon syntax - parse(params, '0;4;38;2;50;100;150;48;5;22') - assert_eq(params.to_array(), [0, 4, 38, 2, 50, 100, 150, 48, 5, 22]) - # SGR mixed style (partly wrong) - parse(params, '0;4;38;2;50:100:150;48;5:22') - assert_eq(params.to_array(), [0, 4, 38, 2, 50, [100, 150], 48, 5, [22]]) - # SGR colon style - parse(params, '0;4;38:2::50:100:150;48:5:22') - assert_eq(params.to_array(), [0, 4, 38, [2, -1, 50, 100, 150], 48, [5, 22]]) - - func test_clamp_parsed_params(): - parse(params, '2147483648') - assert_eq(params.to_array(), [0x7FFFFFFF]) - - func test_clamp_parsed_sub_params(): - parse(params, ':2147483648') - assert_eq(params.to_array(), [0, [0x7FFFFFFF]]) - - func test_should_cancel_subdigits_if_beyond_params_limit(): - parse(params, ';;;;;;;;;10;;;;;;;;;;20;;;;;;;;;;30;31;32;33;34;35::::::::') - assert_eq(params.to_array(), [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 31, 32 - ]) - -# func test_should_carry_forward_is_sub_state(): -# parse(params, ['1:22:33', '44']) -# assert_eq(params.to_array(), [1, [22, 3344]]) - - - - - - - diff --git a/test/unit/renderer/test_canvas_rendering_context_2d.gd b/test/unit/renderer/test_canvas_rendering_context_2d.gd deleted file mode 100644 index b4862a1..0000000 --- a/test/unit/renderer/test_canvas_rendering_context_2d.gd +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2020 The GodotXterm authors. All rights reserved. -# License MIT -extends "res://addons/gut/test.gd" - - -const CanvasRenderingContext2D = preload("res://addons/godot_xterm/renderer/canvas_rendering_context_2d.gd") -const RegularFont = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_regular.tres") -const BoldFont = preload("res://addons/godot_xterm/fonts/source_code_pro/source_code_pro_bold.tres") - -var ctx - - -func before_each(): - ctx = CanvasRenderingContext2D.new() - - -func test_measure_text(): - assert_eq(ctx.measure_text("a").width, RegularFont.get_string_size("a").x) - - -func test_save_and_restore(): - # fill_style - ctx.fill_style = Color.red - ctx.save() - ctx.fill_style = Color.blue - assert_eq(ctx.fill_style, Color.blue) - ctx.restore() - assert_eq(ctx.fill_style, Color.red) - # font - ctx.font = RegularFont - ctx.save() - ctx.font = BoldFont - assert_eq(ctx.font, BoldFont) - ctx.restore() - assert_eq(ctx.font, RegularFont) diff --git a/test/unit/renderer/test_character_joiner_registry.gd b/test/unit/renderer/test_character_joiner_registry.gd deleted file mode 100644 index be21289..0000000 --- a/test/unit/renderer/test_character_joiner_registry.gd +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2018 The xterm.js authors. All rights reserved. -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends "res://addons/gut/test.gd" - - -const CharacterJoinerRegistry = preload("res://addons/godot_xterm/renderer/character_joiner_registry.gd") -const Buffer = preload("res://addons/godot_xterm/buffer/buffer.gd") -const BufferLine = preload("res://addons/godot_xterm/buffer/buffer_line.gd") -const CircularList = preload("res://addons/godot_xterm/circular_list.gd") -const CellData = preload("res://addons/godot_xterm/buffer/cell_data.gd") -const AttributeData = preload("res://addons/godot_xterm/buffer/attribute_data.gd") -const TestUtils = preload("res://test/test_utils.gd") - -var registry - - -func line_data(data): - var tline = BufferLine.new(0) - for d in data: - var line = d[0] - var attr = d[1] if d.size() > 1 else 0 - var offset = tline.length - tline.resize(tline.length + line.split('').size(), CellData.from_char_data([0, '', 0, 0])) - - -func before_each(): - var buffer_service = TestUtils.MockBufferService.new(16, 10) - var lines = buffer_service.buffer.lines - lines.set_el(0, line_data([['a -> b -> c -> d']])) - lines.set_el(1, line_data([['a -> b => c -> d']])) - lines.set_el(2, line_data([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]])) - - registry = CharacterJoinerRegistry.new(buffer_service) - - -func test_has_no_joiners_upon_creation(): - assert_eq(registry.get_joined_characters(0), []) diff --git a/test/unit/test_circular_list.gd b/test/unit/test_circular_list.gd deleted file mode 100644 index ccca6bd..0000000 --- a/test/unit/test_circular_list.gd +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright (c) 2016 The xterm.js authors. All rights reserved. -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends "res://addons/gut/test.gd" - - -const CIRCULAR_LIST_PATH = "res://addons/godot_xterm/circular_list.gd" -const CircularList = preload(CIRCULAR_LIST_PATH) - -var list - - -func before_each(): - list = CircularList.new(5) - - -func test_push(): - list.push("1") - list.push("2") - list.push("3") - list.push("4") - list.push("5") - assert_eq(list.get_line(0), "1") - assert_eq(list.get_line(1), "2") - assert_eq(list.get_line(2), "3") - assert_eq(list.get_line(3), "4") - assert_eq(list.get_line(4), "5") - - -func test_splice_deletes_items(): - list = CircularList.new(2) - list.push("1") - list.push("2") - list.splice(0, 1) - assert_eq(list.length, 1) - assert_eq(list.get_el(0), "2") - list.push("3") - list.splice(1, 1) - assert_eq(list.length, 1) - assert_eq(list.get_el(0), "2") - - -func test_splice_inserts_items(): - list = CircularList.new(2) - list.push("1") - list.splice(0, 0, ["2"]) - assert_eq(list.length, 2) - assert_eq(list.get_el(0), "2") - assert_eq(list.get_el(1), "1") - list.splice(1, 0, ["3"]) - assert_eq(list.length, 2) - assert_eq(list.get_el(0), "3") - assert_eq(list.get_el(1), "1") - - -func test_splice_deletes_items_then_inserts_items(): - list = CircularList.new(3) - list.push("1") - list.push("2") - list.splice(0, 1, ["3", "4"]) - assert_eq(list.length, 3) - assert_eq(list.get_el(0), "3") - assert_eq(list.get_el(1), "4") - assert_eq(list.get_el(2), "2") - - -func test_splice_wraps_the_array_correctly_when_more_items_are_inserted_than_deleted(): - list = CircularList.new(3) - list.push("1") - list.push("2") - list.splice(1, 0, ["3", "4"]) - assert_eq(list.length, 3) - assert_eq(list.get_el(0), "3") - assert_eq(list.get_el(1), "4") - assert_eq(list.get_el(2), "2") - - -class TestShiftElements: - extends "res://addons/gut/test.gd" - - - var list - - - func before_each(): - list = CircularList.new(5) - - - func test_does_not_mutate_the_list_when_count_is_0(): - list.push(1) - list.push(2) - list.shift_elements(0, 0, 1) - assert_eq(list.length, 2) - assert_eq(list.get_el(0), 1) - assert_eq(list.get_el(1), 2) - - - func test_pushes_errors_for_invalid_args(): - list = partial_double(CIRCULAR_LIST_PATH).new() - list.max_length = 5 - list.push(1) - list.shift_elements(-1, 1, 1) - assert_called(list, "push_error", ["start argument out of range"]) - list.shift_elements(1, 1, 1) - assert_called(list, "push_error", ["start argument out of range"]) - list.shift_elements(0, 1, -1) - assert_called(list, "push_error", ["cannot shift elements in list beyond index 0"]) - - - func test_trim_start_removes_items_from_the_beginning_of_the_list(): - list.push("1") - list.push("2") - list.push("3") - list.push("4") - list.push("5") - list.trim_start(1) - assert_eq(list.length, 4) - assert_eq(list.get_el(0), "2") - assert_eq(list.get_el(1), "3") - assert_eq(list.get_el(2), "4") - assert_eq(list.get_el(3), "5") - list.trim_start(2) - assert_eq(list.length, 2) - assert_eq(list.get_el(0), "4") - assert_eq(list.get_el(1), "5") - - - func test_trim_start_removes_all_items_if_the_requested_trim_amount_is_larger_than_the_lists_length(): - list.push("1") - list.trim_start(2) - assert_eq(list.length, 0) - - - func test_shifts_an_element_forward(): - list.push(1) - list.push(2) - list.shift_elements(0, 1, 1) - assert_eq(list.length, 2) - assert_eq(list.get_el(0), 1) - assert_eq(list.get_el(1), 1) - - - func test_shifts_elements_forward(): - list.push(1) - list.push(2) - list.push(3) - list.push(4) - list.shift_elements(0, 2, 2) - assert_eq(list.length, 4) - assert_eq(list.get_el(0), 1) - assert_eq(list.get_el(1), 2) - assert_eq(list.get_el(2), 1) - assert_eq(list.get_el(3), 2) - - - func test_shifts_elements_forward_expanding_the_list_if_needed(): - list.push(1) - list.push(2) - list.shift_elements(0, 2, 2) - assert_eq(list.length, 4) - assert_eq(list.get_el(0), 1) - assert_eq(list.get_el(1), 2) - assert_eq(list.get_el(2), 1) - assert_eq(list.get_el(3), 2) - - - func test_shifts_elements_forward_wrapping_the_list_if_needed(): - list.push(1) - list.push(2) - list.push(3) - list.push(4) - list.push(5) - list.shift_elements(2, 2, 3) - assert_eq(list.length, 5) - assert_eq(list.get_el(0), 3) - assert_eq(list.get_el(1), 4) - assert_eq(list.get_el(2), 5) - assert_eq(list.get_el(3), 3) - assert_eq(list.get_el(4), 4) - - - func test_shifts_an_element_backwards(): - list.push(1) - list.push(2) - list.shift_elements(1, 1, -1) - assert_eq(list.length, 2) - assert_eq(list.get_el(0), 2) - assert_eq(list.get_el(1), 2) - - - func test_shiftS_elements_backwards(): - list.push(1) - list.push(2) - list.push(3) - list.push(4) - list.shift_elements(2, 2, -2) - assert_eq(list.length, 4) - assert_eq(list.get_el(0), 3) - assert_eq(list.get_el(1), 4) - assert_eq(list.get_el(2), 3) - assert_eq(list.get_el(3), 4) diff --git a/test/unit/test_input_handler.gd b/test/unit/test_input_handler.gd deleted file mode 100644 index a6b3183..0000000 --- a/test/unit/test_input_handler.gd +++ /dev/null @@ -1,288 +0,0 @@ -# Copyright (c) 2017 The xterm.js authors. All rights reserved. -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends "res://addons/gut/test.gd" - - -const TestUtils = preload("res://test/test_utils.gd") -const InputHandler = preload("res://addons/godot_xterm/input_handler.gd") -const CharsetService = preload("res://addons/godot_xterm/services/charset_service.gd") -const Params = preload("res://addons/godot_xterm/parser/params.gd") -const CoreService = preload("res://addons/godot_xterm/services/core_service.gd") -const Constants = preload("res://addons/godot_xterm/buffer/constants.gd") -const OptionsService = preload("res://addons/godot_xterm/services/options_service.gd") - -const CursorStyle = Constants.CursorStyle - -var options_service -var buffer_service -var charset_service -var core_service -var input_handler - - -func get_lines(buffer_service, limit: int) -> Array: - var res = [] - if not limit: - limit = buffer_service.rows - for i in range(limit): - var line = buffer_service.buffer.lines.get_el(i) - if line: - res.append(line.translate_to_string(true)) - return res - - -func repeat(string: String, times: int) -> String: - var s = "" - for i in range(times): - s += string - return s - - -func term_content(buffer_service, trim: bool) -> PoolStringArray: - var result = PoolStringArray([]) - - for i in buffer_service.rows: - result.append(buffer_service.buffer.lines.get_el(i).translate_to_string(trim)) - return result; - - -func before_each(): - options_service = TestUtils.MockOptionsService.new() - buffer_service = TestUtils.MockBufferService.new(80, 30, options_service) - charset_service = CharsetService.new() - core_service = CoreService.new() - input_handler = InputHandler.new(buffer_service, core_service, charset_service, options_service) - - -func test_save_and_restore_cursor(): - buffer_service.buffer.x = 1 - buffer_service.buffer.y = 2 - buffer_service.buffer.ybase = 0 - input_handler._cur_attr_data.fg = 3 - # Save cursor position - input_handler.save_cursor() - assert_eq(buffer_service.buffer.x, 1) - assert_eq(buffer_service.buffer.y, 2) - assert_eq(input_handler._cur_attr_data.fg, 3) - # Change the cursor position - buffer_service.buffer.x = 10 - buffer_service.buffer.y = 20 - input_handler._cur_attr_data.fg = 30 - # Restore cursor position - input_handler.restore_cursor() - assert_eq(buffer_service.buffer.x, 1) - assert_eq(buffer_service.buffer.y, 2) - assert_eq(input_handler._cur_attr_data.fg, 3) - - -func test_set_cursor_style(): - input_handler.set_cursor_style(Params.from_array([0])) - assert_eq(options_service.options.cursor_style, CursorStyle.BLOCK) - assert_eq(options_service.options.cursor_blink, true) - - options_service.options = OptionsService.TerminalOptions.new() - input_handler.set_cursor_style(Params.from_array([1])) - assert_eq(options_service.options.cursor_style, CursorStyle.BLOCK) - assert_eq(options_service.options.cursor_blink, true) - - options_service.options = OptionsService.TerminalOptions.new() - input_handler.set_cursor_style(Params.from_array([2])) - assert_eq(options_service.options.cursor_style, CursorStyle.BLOCK) - assert_eq(options_service.options.cursor_blink, false) - - options_service.options = OptionsService.TerminalOptions.new() - input_handler.set_cursor_style(Params.from_array([3])) - assert_eq(options_service.options.cursor_style, CursorStyle.UNDERLINE) - assert_eq(options_service.options.cursor_blink, true) - - options_service.options = OptionsService.TerminalOptions.new() - input_handler.set_cursor_style(Params.from_array([4])) - assert_eq(options_service.options.cursor_style, CursorStyle.UNDERLINE) - assert_eq(options_service.options.cursor_blink, false) - - options_service.options = OptionsService.TerminalOptions.new() - input_handler.set_cursor_style(Params.from_array([5])) - assert_eq(options_service.options.cursor_style, CursorStyle.BAR) - assert_eq(options_service.options.cursor_blink, true) - - options_service.options = OptionsService.TerminalOptions.new() - input_handler.set_cursor_style(Params.from_array([6])) - assert_eq(options_service.options.cursor_style, CursorStyle.BAR) - assert_eq(options_service.options.cursor_blink, false) - - -func test_set_mode_toggles_bracketed_paste_mode(): - # Set bracketed paste mode - input_handler.set_mode_private(Params.from_array([2004])) - assert_true(core_service.dec_private_modes.bracketed_paste_mode) - # Reset bracketed paste mode - input_handler.reset_mode_private(Params.from_array([2004])) - assert_false(core_service.dec_private_modes.bracketed_paste_mode) - - -func test_erase_in_line(): - buffer_service = TestUtils.MockBufferService.new(80, 30, options_service) - input_handler = InputHandler.new(buffer_service, core_service, charset_service, options_service) - - # fill 6 lines to test 3 different states - input_handler.parse(repeat("a", buffer_service.cols)) - input_handler.parse(repeat("a", buffer_service.cols)) - input_handler.parse(repeat("a", buffer_service.cols)) - - - # params[0] - right erase - buffer_service.buffer.y = 0 - buffer_service.buffer.x = 70 - input_handler.erase_in_line(Params.from_array([0])) - assert_eq(buffer_service.buffer.lines.get_line(0).translate_to_string(false), - repeat("a", 70) + " ") - - # params[1] - left erase - buffer_service.buffer.y = 1 - buffer_service.buffer.x = 70 - input_handler.erase_in_line(Params.from_array([1])) - assert_eq(buffer_service.buffer.lines.get_line(1).translate_to_string(false), - repeat(" ", 70) + " aaaaaaaaa") - - # params[1] - left erase - buffer_service.buffer.y = 2 - buffer_service.buffer.x = 70 - input_handler.erase_in_line(Params.from_array([2])) - assert_eq(buffer_service.buffer.lines.get_line(2).translate_to_string(false), - repeat(" ", buffer_service.cols)) - - -func test_erase_in_display(): - buffer_service = TestUtils.MockBufferService.new(80, 7, options_service) - input_handler = InputHandler.new(buffer_service, core_service, charset_service, options_service) - - # fill display with a's - for _i in range(buffer_service.rows): - input_handler.parse(repeat("a", buffer_service.cols)) - - # params [0] - right and below erase - buffer_service.buffer.y = 5 - buffer_service.buffer.x = 40 - input_handler.erase_in_display(Params.from_array([0])) - assert_eq(term_content(buffer_service, false), PoolStringArray([ - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", 40) + repeat(" ", buffer_service.cols - 40), - repeat(" ", buffer_service.cols), - ])) - assert_eq(term_content(buffer_service, true), PoolStringArray([ - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", buffer_service.cols), - repeat("a", 40), - "" - ])) - - # reset - for _i in range(buffer_service.rows): - input_handler.parse(repeat("a", buffer_service.cols)) - - # params [1] - left and above - buffer_service.buffer.y = 5; - buffer_service.buffer.x = 40; - input_handler.erase_in_display(Params.from_array([1])) - assert_eq(term_content(buffer_service, false), PoolStringArray([ - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", 41) + repeat("a", buffer_service.cols - 41), - repeat("a", buffer_service.cols), - ])) - assert_eq(term_content(buffer_service, true), PoolStringArray([ - "", - "", - "", - "", - "", - repeat(" ", 41) + repeat("a", buffer_service.cols - 41), - repeat("a", buffer_service.cols), - ])) - - # reset - for _i in range(buffer_service.rows): - input_handler.parse(repeat("a", buffer_service.cols)) - - # params [2] - whole screen - buffer_service.buffer.y = 5; - buffer_service.buffer.x = 40; - input_handler.erase_in_display(Params.from_array([2])); - assert_eq(term_content(buffer_service, false), PoolStringArray([ - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - repeat(" ", buffer_service.cols), - ])) - assert_eq(term_content(buffer_service, true), PoolStringArray([ - "", - "", - "", - "", - "", - "", - "", - ])) - - # reset and add a wrapped line - buffer_service.buffer.y = 0; - buffer_service.buffer.x = 0; - input_handler.parse(repeat("a", buffer_service.cols)) # line 0 - input_handler.parse(repeat("a", buffer_service.cols + 9)) # line 1 and 2 - for i in range(3, buffer_service.rows): - input_handler.parse(repeat("a", buffer_service.cols)) - - # params[1] left and above with wrap - # confirm precondition that line 2 is wrapped - assert_true(buffer_service.buffer.lines.get_line(2).is_wrapped) - buffer_service.buffer.y = 2 - buffer_service.buffer.x = 40 - input_handler.erase_in_display(Params.from_array([1])) - assert_false(buffer_service.buffer.lines.get_line(2).is_wrapped) - - # reset and add a wrapped line - buffer_service.buffer.y = 0 - buffer_service.buffer.x = 0 - input_handler.parse(repeat("a", buffer_service.cols)) # line 0 - input_handler.parse(repeat("a", buffer_service.cols + 9)) # line 1 and 2 - for i in range(3, buffer_service.rows): - input_handler.parse(repeat("a", buffer_service.cols)) - - # params[1] left and above with wrap - # confirm precondition that line 2 is wrapped - assert_true(buffer_service.buffer.lines.get_line(2).is_wrapped) - buffer_service.buffer.y = 1 - buffer_service.buffer.x = 90 # Cursor is beyond last column - input_handler.erase_in_display(Params.from_array([1])); - assert_false(buffer_service.buffer.lines.get_line(2).is_wrapped) - - -func test_print_does_not_cause_an_infinite_loop(): - var container = [] - container.resize(10) - container[0] = 0x200B - input_handler.print(container, 0, 1) - - -# FIXME -func skip_test_clear_cells_to_the_right_on_early_wrap_around(): - buffer_service.resize(5, 5) - options_service.options.scrollback = 1 - input_handler.parse('12345') - buffer_service.buffer.x = 0 - input_handler.parse("¥¥¥") - assert_eq(get_lines(buffer_service, 2), PoolStringArray(["¥¥", "ï¿¥"])) diff --git a/test/unit/test_params.gd b/test/unit/test_params.gd deleted file mode 100644 index d80cf6b..0000000 --- a/test/unit/test_params.gd +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright (c) 2019 The xterm.js authors. All rights reserved. -# Ported to GDScript by the GodotXterm authors. -# License MIT -extends 'res://addons/gut/test.gd' - - -const Params = preload("res://addons/godot_xterm/parser/params.gd") - - -class TestParams: - extends 'res://addons/gut/test.gd' - - var params - - - func before_each(): - params = Params.new() - - - func test_respects_ctor_args(): - params = Params.new(12, 23) - assert_eq(params.params.size(), 12) - assert_eq(params.sub_params.size(), 23) - assert_eq(params.to_array(), []) - - - func test_add_param(): - params.add_param(1) - assert_eq(params.length, 1) - assert_eq(params.params.slice(0, params.length - 1), [1]) - assert_eq(params.to_array(), [1]) - params.add_param(23) - assert_eq(params.length, 2) - assert_eq(params.params.slice(0, params.length - 1), [1, 23]) - assert_eq(params.to_array(), [1, 23]) - assert_eq(params.sub_params_length, 0) - - - func test_add_sub_param(): - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - assert_eq(params.length, 1) - assert_eq(params.sub_params_length, 2) - assert_eq(params.to_array(), [1, [2, 3]]) - params.add_param(12345) - params.add_sub_param(-1) - assert_eq(params.length, 2) - assert_eq(params.sub_params_length, 3) - assert_eq(params.to_array(), [1, [2,3], 12345, [-1]]) - - - func test_should_not_add_sub_params_without_previous_param(): - params.add_sub_param(2) - params.add_sub_param(3) - assert_eq(params.length, 0) - assert_eq(params.sub_params_length, 0) - assert_eq(params.to_array(), []) - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - assert_eq(params.length, 1) - assert_eq(params.sub_params_length, 2) - assert_eq(params.to_array(), [1, [2, 3]]) - - - func test_reset(): - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - params.add_param(12345) - params.reset() - assert_eq(params.length, 0) - assert_eq(params.sub_params_length, 0) - assert_eq(params.to_array(), []) - params.add_param(1) - params.add_sub_param(2) - params.add_sub_param(3) - params.add_param(12345) - params.add_sub_param(-1) - assert_eq(params.length, 2) - assert_eq(params.sub_params_length, 3) - assert_eq(params.to_array(), [1, [2, 3], 12345, [-1]]) - - - func test_from_array_to_array(): - var data = [] - assert_eq(params.from_array(data).to_array(), data) - data = [1, [2, 3], 12345, [-1]] - assert_eq(params.from_array(data).to_array(), data) - data = [38, 2, 50, 100, 150] - assert_eq(params.from_array(data).to_array(), data) - data = [38, 2, 50, 100, [150]] - assert_eq(params.from_array(data).to_array(), data) - data = [38, [2, 50, 100, 150]] - assert_eq(params.from_array(data).to_array(), data) - # strip empty sub params - data = [38, [2, 50, 100, 150], 5, [], 6] - assert_eq(Params.from_array(data).to_array(), [38, [2, 50, 100, 150], 5, 6]) - # ignore leading sub params - data = [[1,2], 12345, [-1]] - assert_eq(Params.from_array(data).to_array(), [12345, [-1]]) - - - func test_has_sub_params_get_sub_params(): - params = Params.from_array([38, [2, 50, 100, 150], 5, [], 6]) - assert_eq(params.has_sub_params(0), true) - assert_eq(params.get_sub_params(0), [2, 50, 100, 150]) - assert_eq(params.has_sub_params(1), false) - assert_eq(params.get_sub_params(1), null) - assert_eq(params.has_sub_params(2), false) - assert_eq(params.get_sub_params(2), null) - - -class TestParse: - extends 'res://addons/gut/test.gd' - - - var params - - - func parse(params, s): - params.reset() - params.add_param(0) - if typeof(s) == TYPE_STRING: - s = [s] - for chunk in s: - var i = 0 - while i < chunk.length(): - # Start for - var code = chunk.to_ascii()[i] - var do = true - while do: - match code: - 0x3b: - params.add_param(0) - 0x3a: - params.add_sub_param(-1) - _: - params.add_digit(code - 48) - code = chunk.to_ascii()[i] if i < chunk.length() else 0 - i+=1 - do = i < s.size() and code > 0x2f and code < 0x3c - i-=1 - # End for - i+=1 - - - func before_each(): - params = Params.new() - - - func test_param_defaults_to_0(): # ZDM (Zero Default Mode) - parse(params, '') - assert_eq(params.to_array(), [0]) - - - func test_sub_param_defaults_to_neg_1(): - parse(params, ':') - assert_eq(params.to_array(), [0, [-1]]) - - - func test_reset_on_new_sequence(): - parse(params, '1;2;3') - assert_eq(params.to_array(), [1, 2, 3]) - parse(params, '4') - assert_eq(params.to_array(), [4]) - parse(params, '4::123:5;6;7') - assert_eq(params.to_array(), [4, [-1, 123, 5], 6, 7]) - parse(params, '') - assert_eq(params.to_array(), [0]) - - - func test_should_handle_length_restrictions_correctly(): - params = Params.new(3, 3) - parse(params, '1;2;3') - assert_eq(params.to_array(), [1, 2, 3]) - parse(params, '4') - assert_eq(params.to_array(), [4]) - parse(params, '4::123:5;6;7') - assert_eq(params.to_array(), [4, [-1, 123, 5], 6, 7]) - parse(params, '') - assert_eq(params.to_array(), [0]) - # overlong params - parse(params, '4;38:2::50:100:150;48:5:22') - assert_eq(params.to_array(), [4, 38, [2, -1, 50], 48]) - # overlong sub params - parse(params, '4;38:2::50:100:150;48:5:22') - assert_eq(params.to_array(), [4, 38, [2, -1, 50], 48]) - - - func test_typical_sequences(): - # SGR with semicolon syntax - parse(params, '0;4;38;2;50;100;150;48;5;22') - assert_eq(params.to_array(), [0, 4, 38, 2, 50, 100, 150, 48, 5, 22]) - # SGR mixed style (partly wrong) - parse(params, '0;4;38;2;50:100:150;48;5:22') - assert_eq(params.to_array(), [0, 4, 38, 2, 50, [100, 150], 48, 5, [22]]) - # SGR colon style - parse(params, '0;4;38:2::50:100:150;48:5:22') - assert_eq(params.to_array(), [0, 4, 38, [2, -1, 50, 100, 150], 48, [5, 22]]) - - - func test_clamp_parsed_params(): - parse(params, '2147483648') - assert_eq(params.to_array(), [0x7FFFFFFF]) - - - func test_clamp_parsed_sub_params(): - parse(params, ':2147483648') - assert_eq(params.to_array(), [0, [0x7FFFFFFF]]) - - - func test_should_cancel_subdigits_if_beyond_params_limit(): - parse(params, ';;;;;;;;;10;;;;;;;;;;20;;;;;;;;;;30;31;32;33;34;35::::::::') - assert_eq(params.to_array(), [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 31, 32 - ]) - -

Hh(PxITw^iVb2F@LzX{jJs1a62v! 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