Files
MovementTests/addons/gdUnit4/src/core/writers/GdUnitCSIMessageWriter.gd
Minimata caeae26a09
Some checks failed
Create tag and build when new code gets to main / BumpTag (push) Successful in 22s
Create tag and build when new code gets to main / Test (push) Failing after 2m10s
Create tag and build when new code gets to main / Export (push) Has been skipped
fixed camera and sword animation issue and upgraded to Godot 4.6
2026-01-27 17:47:19 +01:00

228 lines
6.8 KiB
GDScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@tool
class_name GdUnitCSIMessageWriter
extends GdUnitMessageWriter
## A message writer implementation using ANSI/CSI escape codes for console output.[br]
## [br]
## This writer provides formatted message output using CSI (Control Sequence Introducer) codes.[br]
## It supports:[br]
## - Color using RGB values[br]
## - Text styles (bold, italic, underline)[br]
## - Cursor positioning and text alignment[br]
## [br]
## Used primarily for console-based test execution and CI/CD environments.
enum {
COLOR_TABLE,
COLOR_RGB
}
const CSI_BOLD = ""
const CSI_ITALIC = ""
const CSI_UNDERLINE = ""
const CSI_RESET = ""
# Control Sequence Introducer
var _debug_show_color_codes := false
var _color_mode := COLOR_TABLE
## Current cursor position in the line
var _current_pos := 0
# Pre-compiled regex patterns for tag matching
var _tag_regex: RegEx
## Constructs CSI style codes based on flags.[br]
## [br]
## [param flags] The style flags to apply (BOLD, ITALIC, UNDERLINE).[br]
## Returns the corresponding CSI codes.
func _apply_style_flags(flags: int) -> String:
var _style := ""
if flags & BOLD:
_style += CSI_BOLD
if flags & ITALIC:
_style += CSI_ITALIC
if flags & UNDERLINE:
_style += CSI_UNDERLINE
return _style
## Converts a color string (named or hex) to a Color object
func _parse_color(color_str: String) -> Color:
return Color.from_string(color_str.strip_edges().to_lower(), Color.WHITE)
## Generates CSI color code for foreground color
func _color_to_csi_fg(c: Color) -> String:
return "[38;2;%d;%d;%dm" % [c.r8 * c.a, c.g8 * c.a, c.b8 * c.a]
## Generates CSI color code for background color
func _color_to_csi_bg(c: Color) -> String:
return "[48;2;%d;%d;%dm" % [c.r8 * c.a, c.g8 * c.a, c.b8 * c.a]
func _init_regex_patterns() -> void:
if not _tag_regex:
_tag_regex = RegEx.new()
# Match all richtext tags: [tag], [tag=value], [/tag]
_tag_regex.compile(r"\[/?(?:color|bgcolor|b|i|u)(?:=[^\]]+)?\]")
func _extract_color_from_tag(tag: String, tag_assign: String) -> Color:
var tag_assign_length := tag_assign.length()
var color_value := tag.substr(tag_assign_length, tag.length() - tag_assign_length - 1)
return _parse_color(color_value)
## Optimized richtext to CSI conversion using regex and lookup processing
func _bbcode_tags_to_csi_codes(message: String) -> String:
_init_regex_patterns()
var result := ""
var last_pos := 0
var color_stack: Array[Color] = []
var bgcolor_stack: Array[Color] = []
# Find all richtext tags
var matches := _tag_regex.search_all(message)
for match in matches:
var start_pos := match.get_start()
var end_pos := match.get_end()
var tag := match.get_string(0)
# Add text before this tag
result += message.substr(last_pos, start_pos - last_pos)
# Process the tag
if tag.begins_with("[color="):
var fg_color := _extract_color_from_tag(tag, "[color=")
color_stack.push_back(fg_color)
result += _color_to_csi_fg(fg_color)
elif tag.begins_with("[bgcolor="):
var bg_color := _extract_color_from_tag(tag, "[bgcolor=")
bgcolor_stack.push_back(bg_color)
result += _color_to_csi_bg(bg_color)
elif tag == "[b]":
result += CSI_BOLD
elif tag == "[i]":
result += CSI_ITALIC
elif tag == "[u]":
result += CSI_UNDERLINE
elif tag == "[/color]":
result += CSI_RESET
if color_stack.size() > 0:
color_stack.pop_back()
# Restore remaining styles and colors
if color_stack.size() > 0:
result += _color_to_csi_fg(color_stack[-1])
if bgcolor_stack.size() > 0:
result += _color_to_csi_bg(bgcolor_stack[-1])
elif tag == "[/bgcolor]":
result += CSI_RESET
if bgcolor_stack.size() > 0:
bgcolor_stack.pop_back()
# Restore remaining styles and colors
if color_stack.size() > 0:
result += _color_to_csi_fg(color_stack[-1])
if bgcolor_stack.size() > 0:
result += _color_to_csi_bg(bgcolor_stack[-1])
elif tag in ["[/b]", "[/i]", "[/u]"]:
result += CSI_RESET
# Restore remaining colors after style reset
if color_stack.size() > 0:
result += _color_to_csi_fg(color_stack[-1])
if bgcolor_stack.size() > 0:
result += _color_to_csi_bg(bgcolor_stack[-1])
last_pos = end_pos
# Add remaining text after last tag
result += message.substr(last_pos)
return result
## Implementation of basic message output with formatting.
func _print_message(_message: String, _color: Color, _indent: int, _flags: int) -> void:
var text := _bbcode_tags_to_csi_codes(_message)
var indent_text := "".lpad(_indent * 2)
var _style := _apply_style_flags(_flags)
printraw("%s[38;2;%d;%d;%dm%s%s" % [indent_text, _color.r8, _color.g8, _color.b8, _style, text])
_current_pos += _indent * 2 + text.length()
## Implementation of line-ending message output with formatting.
func _println_message(_message: String, _color: Color, _indent: int, _flags: int) -> void:
_print_message(_message, _color, _indent, _flags)
prints()
_current_pos = 0
## Implementation of positioned message output with formatting.
func _print_at(_message: String, cursor_pos: int, _color: Color, _effect: GdUnitMessageWriter.Effect, _align: Align, _flags: int) -> void:
if _align == Align.RIGHT:
cursor_pos = cursor_pos - _message.length()
if cursor_pos > _current_pos:
printraw("[%dG" % cursor_pos) # Move cursor to absolute position
else:
_message = " " + _message
var _style := _apply_style_flags(_flags)
printraw("[38;2;%d;%d;%dm%s%s" % [_color.r8, _color.g8, _color.b8, _style, _message])
_current_pos = cursor_pos + _message.length()
## Writes a line break and returns self for chaining.
func new_line() -> GdUnitCSIMessageWriter:
prints()
return self
## Saves the current cursor position.[br]
## Returns self for chaining.
func save_cursor() -> GdUnitCSIMessageWriter:
printraw("")
return self
## Restores previously saved cursor position.[br]
## Returns self for chaining.
func restore_cursor() -> GdUnitCSIMessageWriter:
printraw("")
return self
## Clears screen content and resets cursor position.
func clear() -> void:
printraw("") # Clear screen and move cursor to home
_current_pos = 0
## Debug method to display the available color table.[br]
## Shows both 6x6x6 color cube and RGB color modes.
@warning_ignore("return_value_discarded")
func _print_color_table() -> void:
color(Color.ANTIQUE_WHITE).println_message("Color Table 6x6x6")
_debug_show_color_codes = true
for green in range(0, 6):
for red in range(0, 6):
for blue in range(0, 6):
color(Color8(red * 42, green * 42, blue * 42)).println_message("████████ ")
new_line()
new_line()
color(Color.ANTIQUE_WHITE).println_message("Color Table RGB")
_color_mode = COLOR_RGB
for green in range(0, 6):
for red in range(0, 6):
for blue in range(0, 6):
color(Color8(red * 42, green * 42, blue * 42)).println_message("████████ ")
new_line()
new_line()
_color_mode = COLOR_TABLE
_debug_show_color_codes = false