trace, console

This commit is contained in:
Face
2025-09-01 17:08:47 +03:00
parent f973bb55f6
commit a6e96328ae
34 changed files with 1353 additions and 65 deletions

View File

@@ -8,7 +8,7 @@ local password_input = gurt.select('#password')
local log_output = gurt.select('#log-output')
function addLog(message)
gurt.log(message)
trace.log(message)
log_output.text = log_output.text .. message .. '\\n'
end

View File

@@ -9,7 +9,7 @@ local confirm_password_input = gurt.select('#confirm-password')
local log_output = gurt.select('#log-output')
function addLog(message)
gurt.log(message)
trace.log(message)
log_output.text = log_output.text .. message .. '\n'
end

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eraser-icon lucide-eraser"><path d="M21 21H8a2 2 0 0 1-1.42-.587l-3.994-3.999a2 2 0 0 1 0-2.828l10-10a2 2 0 0 1 2.829 0l5.999 6a2 2 0 0 1 0 2.828L12.834 21"/><path d="m5.082 11.09 8.828 8.828"/></svg>

After

Width:  |  Height:  |  Size: 397 B

View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://custohlvwclqs"
path="res://.godot/imported/eraser.svg-e6400a62a38066fbd6aaec113f0b7fb0.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Assets/Icons/eraser.svg"
dest_files=["res://.godot/imported/eraser.svg-e6400a62a38066fbd6aaec113f0b7fb0.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-funnel-icon lucide-funnel"><path d="M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"/></svg>

After

Width:  |  Height:  |  Size: 388 B

View File

@@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cqg4eny0nyojd"
path="res://.godot/imported/funnel.svg-cd3708ed9907c314e118f9c06364d430.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Assets/Icons/funnel.svg"
dest_files=["res://.godot/imported/funnel.svg-cd3708ed9907c314e118f9c06364d430.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,16 @@
[gd_resource type="SyntaxHighlighter" script_class="LuaSyntaxHighlighter" load_steps=2 format=3 uid="uid://d0aeuvwp0545i"]
[ext_resource type="Script" uid="uid://qicpfnrmje1v" path="res://Scripts/LuaSyntaxHighlighter.gd" id="1_5xwa7"]
[resource]
script = ExtResource("1_5xwa7")
font_color = Color(1, 1, 1, 1)
keyword_color = Color(1, 0.584314, 0.772549, 1)
gurt_globals_color = Color(0.584314, 1, 0.635294, 1)
function_color = Color(0.584314, 0.839216, 1, 1)
member_color = Color(1, 1, 0.584314, 1)
number_color = Color(0.772549, 0.584314, 1, 1)
string_color = Color(1, 0.772549, 0.584314, 1)
comment_color = Color(0.490196, 0.54902, 0.545098, 1)
symbol_color = Color(0.470588, 0.87451, 0.827451, 1)
metadata/_custom_type_script = "uid://qicpfnrmje1v"

243
flumi/Scenes/DevTools.tscn Normal file
View File

@@ -0,0 +1,243 @@
[gd_scene load_steps=19 format=3 uid="uid://cgav3xl2xgupb"]
[ext_resource type="Script" uid="uid://vrobqac6makc" path="res://Scripts/DevToolsConsole.gd" id="2_3m6n9"]
[ext_resource type="Texture2D" uid="uid://custohlvwclqs" path="res://Assets/Icons/eraser.svg" id="3_6hj4c"]
[ext_resource type="Texture2D" uid="uid://cqg4eny0nyojd" path="res://Assets/Icons/funnel.svg" id="4_ynqb1"]
[ext_resource type="SyntaxHighlighter" uid="uid://d0aeuvwp0545i" path="res://Resources/LuaSyntaxHighlighter.tres" id="5_xkykt"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6hj4c"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6hj4c"]
content_margin_left = 2.0
content_margin_top = 2.0
content_margin_right = 2.0
content_margin_bottom = 2.0
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qb7hm"]
content_margin_left = 15.0
content_margin_top = 15.0
content_margin_right = 15.0
content_margin_bottom = 15.0
bg_color = Color(0.137255, 0.137255, 0.137255, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ynqb1"]
content_margin_left = 8.0
content_margin_top = 4.0
content_margin_right = 8.0
content_margin_bottom = 4.0
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
border_width_bottom = 1
border_color = Color(0.247059, 0.466667, 0.807843, 1)
corner_radius_top_left = 10
corner_radius_top_right = 10
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8muo7"]
content_margin_left = 8.0
content_margin_top = 4.0
content_margin_right = 8.0
content_margin_bottom = 4.0
bg_color = Color(0.137255, 0.137255, 0.137255, 1)
corner_radius_top_left = 10
corner_radius_top_right = 10
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xkykt"]
content_margin_left = 8.0
content_margin_top = 4.0
content_margin_right = 8.0
content_margin_bottom = 4.0
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
border_color = Color(0.8, 0.8, 1, 0)
corner_radius_top_left = 10
corner_radius_top_right = 10
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ynqb1"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pqhy6"]
bg_color = Color(0.168627, 0.168627, 0.168627, 1)
corner_radius_top_left = 50
corner_radius_top_right = 50
corner_radius_bottom_right = 50
corner_radius_bottom_left = 50
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_apu5o"]
bg_color = Color(0.6, 0.6, 0.6, 0)
draw_center = false
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dderp"]
bg_color = Color(0.6, 0.6, 0.6, 0)
draw_center = false
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ko37l"]
content_margin_left = 8.0
content_margin_right = 8.0
bg_color = Color(0.168627, 0.168627, 0.168627, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.247059, 0.466667, 0.807843, 1)
corner_radius_top_left = 15
corner_radius_top_right = 15
corner_radius_bottom_right = 15
corner_radius_bottom_left = 15
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_65n6c"]
content_margin_left = 8.0
content_margin_right = 8.0
bg_color = Color(0.168627, 0.168627, 0.168627, 1)
corner_radius_top_left = 15
corner_radius_top_right = 15
corner_radius_bottom_right = 15
corner_radius_bottom_left = 15
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_up43w"]
content_margin_left = 8.0
content_margin_top = 8.0
content_margin_bottom = 4.0
bg_color = Color(0.168627, 0.168627, 0.168627, 1)
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
expand_margin_left = 4.0
expand_margin_right = 4.0
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qb3ke"]
content_margin_left = 8.0
content_margin_top = 8.0
content_margin_bottom = 4.0
bg_color = Color(0.168627, 0.168627, 0.168627, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.247059, 0.466667, 0.807843, 1)
corner_radius_top_left = 8
corner_radius_top_right = 8
corner_radius_bottom_right = 8
corner_radius_bottom_left = 8
expand_margin_left = 4.0
expand_margin_right = 4.0
[node name="DevTools" type="VBoxContainer"]
custom_minimum_size = Vector2(450, 400)
size_flags_vertical = 3
[node name="TabContainer" type="TabContainer" parent="."]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
size_flags_vertical = 3
theme_override_colors/drop_mark_color = Color(0.247059, 0.466667, 0.807843, 1)
theme_override_styles/tab_focus = SubResource("StyleBoxEmpty_6hj4c")
theme_override_styles/tabbar_background = SubResource("StyleBoxFlat_6hj4c")
theme_override_styles/panel = SubResource("StyleBoxFlat_qb7hm")
theme_override_styles/tab_selected = SubResource("StyleBoxFlat_ynqb1")
theme_override_styles/tab_hovered = SubResource("StyleBoxFlat_8muo7")
theme_override_styles/tab_unselected = SubResource("StyleBoxFlat_xkykt")
tab_alignment = 1
current_tab = 0
drag_to_rearrange_enabled = true
[node name="Console" type="VBoxContainer" parent="TabContainer"]
layout_mode = 2
script = ExtResource("2_3m6n9")
metadata/_tab_index = 0
[node name="Toolbar" type="HBoxContainer" parent="TabContainer/Console"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="ClearButton" type="Button" parent="TabContainer/Console/Toolbar"]
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
theme_override_constants/icon_max_width = 20
theme_override_styles/focus = SubResource("StyleBoxEmpty_ynqb1")
theme_override_styles/hover = SubResource("StyleBoxFlat_pqhy6")
theme_override_styles/pressed = SubResource("StyleBoxFlat_apu5o")
theme_override_styles/normal = SubResource("StyleBoxFlat_dderp")
icon = ExtResource("3_6hj4c")
icon_alignment = 1
[node name="LineEdit" type="LineEdit" parent="TabContainer/Console/Toolbar"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/focus = SubResource("StyleBoxFlat_ko37l")
theme_override_styles/normal = SubResource("StyleBoxFlat_65n6c")
placeholder_text = "Filter"
right_icon = ExtResource("4_ynqb1")
[node name="Spacer2" type="Control" parent="TabContainer/Console"]
custom_minimum_size = Vector2(0, 15)
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/Console"]
custom_minimum_size = Vector2(0, 200)
layout_mode = 2
size_flags_vertical = 3
[node name="LogContainer" type="VBoxContainer" parent="TabContainer/Console/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Spacer" type="Control" parent="TabContainer/Console"]
custom_minimum_size = Vector2(0, 15)
layout_mode = 2
[node name="InputContainer" type="HBoxContainer" parent="TabContainer/Console"]
layout_mode = 2
theme_override_constants/separation = 4
[node name="InputLine" type="CodeEdit" parent="TabContainer/Console/InputContainer"]
clip_contents = false
custom_minimum_size = Vector2(0, 35)
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/normal = SubResource("StyleBoxFlat_up43w")
theme_override_styles/focus = SubResource("StyleBoxFlat_qb3ke")
text = "test"
placeholder_text = "Enter Lua code..."
scroll_fit_content_height = true
caret_blink = true
syntax_highlighter = ExtResource("5_xkykt")
highlight_all_occurrences = true
highlight_current_line = true
symbol_tooltip_on_hover = true
gutters_draw_line_numbers = true
auto_brace_completion_enabled = true
auto_brace_completion_highlight_matching = true
[node name="PositioningTimer" type="Timer" parent="TabContainer/Console/InputContainer"]
[node name="Elements" type="Label" parent="TabContainer"]
visible = false
layout_mode = 2
text = "Elements tab - Coming soon"
horizontal_alignment = 1
vertical_alignment = 1
metadata/_tab_index = 1
[node name="Sources" type="Label" parent="TabContainer"]
visible = false
layout_mode = 2
text = "Sources tab - Coming soon"
horizontal_alignment = 1
vertical_alignment = 1
metadata/_tab_index = 2
[node name="Network" type="Label" parent="TabContainer"]
visible = false
layout_mode = 2
text = "Network tab - Coming soon"
horizontal_alignment = 1
vertical_alignment = 1
metadata/_tab_index = 3
[node name="Application" type="Label" parent="TabContainer"]
visible = false
layout_mode = 2
text = "Application tab - Coming soon"
horizontal_alignment = 1
vertical_alignment = 1
metadata/_tab_index = 4

View File

@@ -29,7 +29,7 @@ func _init():
timeout_manager = LuaTimeoutManager.new()
threaded_vm = ThreadedLuaVM.new()
threaded_vm.script_completed.connect(_on_threaded_script_completed)
threaded_vm.script_error.connect(func(e): print(e))
threaded_vm.script_error.connect(_on_threaded_script_error)
threaded_vm.dom_operation_request.connect(_handle_dom_operation)
threaded_vm.print_output.connect(_on_print_output)
@@ -645,8 +645,11 @@ func execute_lua_script(code: String):
func _on_threaded_script_completed(_result: Dictionary):
pass
func _on_print_output(message: String):
LuaPrintUtils.lua_print_direct(message)
func _on_threaded_script_error(error_message: String):
Trace.trace_error("RuntimeError: " + error_message)
func _on_print_output(message: Dictionary):
Trace.get_instance().log_message.emit(message, "lua", Time.get_ticks_msec() / 1000.0)
func kill_script_execution():
threaded_vm.stop_lua_thread()

View File

@@ -0,0 +1,574 @@
class_name DevToolsConsole
extends VBoxContainer
@onready var log_container: VBoxContainer = $ScrollContainer/LogContainer
@onready var input_line: CodeEdit = $InputContainer/InputLine
@onready var scroll_container: ScrollContainer = $ScrollContainer
@onready var clear_button: Button = $Toolbar/ClearButton
@onready var filter_input: LineEdit = $Toolbar/LineEdit
var log_entries: Array[Dictionary] = []
var current_filter: String = ""
var last_log_item: Control = null
var last_log_entry: Dictionary = {}
var input_history: Array[String] = []
var history_index: int = -1
func _ready():
connect_signals()
initialize_filter()
load_existing_logs()
visibility_changed.connect(_on_visibility_changed)
func initialize_filter() -> void:
filter_input.placeholder_text = "Filter"
current_filter = ""
func connect_signals() -> void:
Trace.get_instance().log_message.connect(_on_trace_message)
clear_button.pressed.connect(_on_clear_pressed)
input_line.gui_input.connect(_on_input_gui_input)
filter_input.text_changed.connect(_on_filter_changed)
func load_existing_logs() -> void:
var existing_messages = Trace.get_all_messages()
for msg in existing_messages:
add_log_entry(msg.message, msg.level, msg.timestamp)
func _on_trace_message(message: Variant, level: String, timestamp: float) -> void:
call_deferred("add_log_entry", message, level, timestamp)
func _on_lua_print(message: String) -> void:
add_log_entry(message, "lua", Time.get_ticks_msec() / 1000.0)
func add_log_entry(message: Variant, level: String, timestamp: float) -> void:
var entry = {
"message": message,
"level": level,
"timestamp": timestamp,
"count": 1
}
if can_group_with_last_entry(entry):
last_log_entry.count += 1
last_log_entry.timestamp = timestamp
log_entries[log_entries.size() - 1] = last_log_entry
update_log_item_display(last_log_item, last_log_entry)
return
log_entries.append(entry)
last_log_entry = entry
var should_add_separator = false
if level == "log" or level == "lua" or level == "input":
if log_container.get_child_count() > 0:
var last_displayed_entry = get_last_displayed_entry()
if last_displayed_entry and last_displayed_entry.level != "warning" and last_displayed_entry.level != "error":
should_add_separator = true
if should_add_separator:
var separator = HSeparator.new()
separator.add_theme_color_override("separator", Color.GRAY * 0.3)
log_container.add_child(separator)
var log_item = create_log_item(entry)
log_container.add_child(log_item)
last_log_item = log_item
apply_filter_to_item(log_item, entry)
call_deferred("_scroll_to_bottom")
func create_log_item(entry: Dictionary) -> Control:
if entry.level == "input":
var message_code_edit = CodeEdit.new()
var input_display_text = get_display_text_for_entry(entry)
message_code_edit.text = input_display_text
message_code_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
message_code_edit.scroll_fit_content_height = true
message_code_edit.editable = true
message_code_edit.context_menu_enabled = true
message_code_edit.shortcut_keys_enabled = true
message_code_edit.selecting_enabled = true
message_code_edit.deselect_on_focus_loss_enabled = true
message_code_edit.drag_and_drop_selection_enabled = false
message_code_edit.virtual_keyboard_enabled = false
message_code_edit.middle_mouse_paste_enabled = false
var code_style_normal = StyleBoxFlat.new()
code_style_normal.bg_color = Color.TRANSPARENT
code_style_normal.border_width_left = 0
code_style_normal.border_width_top = 0
code_style_normal.border_width_right = 0
code_style_normal.border_width_bottom = 0
code_style_normal.content_margin_bottom = 8
message_code_edit.add_theme_stylebox_override("normal", code_style_normal)
message_code_edit.add_theme_stylebox_override("focus", code_style_normal)
message_code_edit.syntax_highlighter = input_line.syntax_highlighter.duplicate()
message_code_edit.gui_input.connect(_on_log_code_edit_gui_input)
message_code_edit.focus_entered.connect(_on_log_code_edit_focus_entered.bind(message_code_edit))
message_code_edit.text_changed.connect(_on_log_code_edit_text_changed.bind(message_code_edit, input_display_text))
return message_code_edit
if entry.level == "lua" and entry.message is Dictionary and entry.message.has("parts"):
return create_structured_log_item(entry)
var panel = PanelContainer.new()
var style_box = StyleBoxFlat.new()
match entry.level:
"warning":
style_box.bg_color = Color.YELLOW
style_box.bg_color.a = 0.2
style_box.corner_radius_top_left = 6
style_box.corner_radius_top_right = 6
style_box.corner_radius_bottom_left = 6
style_box.corner_radius_bottom_right = 6
style_box.content_margin_left = 8
style_box.content_margin_right = 8
style_box.content_margin_top = 4
style_box.content_margin_bottom = 4
"error":
style_box.bg_color = Color.RED
style_box.bg_color.a = 0.2
style_box.corner_radius_top_left = 6
style_box.corner_radius_top_right = 6
style_box.corner_radius_bottom_left = 6
style_box.corner_radius_bottom_right = 6
style_box.content_margin_left = 8
style_box.content_margin_right = 8
style_box.content_margin_top = 4
style_box.content_margin_bottom = 4
_:
style_box.bg_color = Color.TRANSPARENT
panel.add_theme_stylebox_override("panel", style_box)
var container: Control
if entry.level == "warning" or entry.level == "error":
var margin_container = MarginContainer.new()
margin_container.add_child(panel)
container = margin_container
else:
container = panel
var message_label = Label.new()
var display_text = get_display_text_for_entry(entry)
message_label.text = display_text
message_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
message_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
match entry.level:
"warning":
message_label.add_theme_color_override("font_color", Color.YELLOW)
"error":
message_label.add_theme_color_override("font_color", Color.WHITE)
_:
message_label.add_theme_color_override("font_color", Color.WHITE)
panel.add_child(message_label)
return container
func _scroll_to_bottom() -> void:
if scroll_container:
scroll_container.scroll_vertical = int(scroll_container.get_v_scroll_bar().max_value)
func _on_clear_pressed() -> void:
for child in log_container.get_children():
child.queue_free()
log_entries.clear()
last_log_item = null
last_log_entry = {}
Trace.clear_messages()
func _on_input_gui_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed:
if event.keycode == KEY_ENTER and event.ctrl_pressed:
var text = input_line.text.strip_edges()
if not text.is_empty():
_on_input_submitted(text)
input_line.text = ""
get_viewport().set_input_as_handled()
elif event.keycode == KEY_UP and not event.ctrl_pressed and not event.shift_pressed and not event.alt_pressed:
if input_line.get_caret_column() == 0 and input_line.get_caret_line() == 0:
navigate_history_up()
get_viewport().set_input_as_handled()
elif event.keycode == KEY_DOWN and not event.ctrl_pressed and not event.shift_pressed and not event.alt_pressed:
var caret_pos = input_line.get_caret_column()
var last_line = input_line.get_line_count() - 1
var last_line_length = input_line.get_line(last_line).length()
if input_line.get_caret_line() == last_line and caret_pos == last_line_length:
navigate_history_down()
get_viewport().set_input_as_handled()
func navigate_history_up() -> void:
if input_history.is_empty():
return
if history_index == -1:
history_index = input_history.size() - 1
elif history_index > 0:
history_index -= 1
if history_index >= 0 and history_index < input_history.size():
input_line.text = input_history[history_index]
call_deferred("move_caret_to_end")
func navigate_history_down() -> void:
if input_history.is_empty() or history_index == -1:
return
history_index += 1
if history_index >= input_history.size():
history_index = -1
input_line.text = ""
else:
input_line.text = input_history[history_index]
call_deferred("move_caret_to_end")
func move_caret_to_end() -> void:
var last_line = input_line.get_line_count() - 1
var last_line_length = input_line.get_line(last_line).length()
input_line.set_caret_line(last_line)
input_line.set_caret_column(last_line_length)
func _on_input_submitted(text: String) -> void:
if text.strip_edges().is_empty():
return
if input_history.is_empty() or input_history[input_history.size() - 1] != text:
input_history.append(text)
if input_history.size() > 100:
input_history.pop_front()
history_index = -1
add_log_entry("> " + text, "input", Time.get_ticks_msec() / 1000.0)
execute_lua_command(text)
func execute_lua_command(code: String) -> void:
var main_scene = Engine.get_main_loop().current_scene
if main_scene and main_scene.has_method("get_active_tab"):
var active_tab = main_scene.get_active_tab()
if active_tab and active_tab.lua_apis.size() > 0:
var lua_api = active_tab.lua_apis[0]
if lua_api:
var is_expression = is_likely_expression(code)
if is_expression:
var wrapped_code = "print(" + code + ")"
lua_api.execute_lua_script(wrapped_code)
else:
lua_api.execute_lua_script(code)
return
add_log_entry("No Lua context available", "error", Time.get_ticks_msec() / 1000.0)
func is_likely_expression(code: String) -> bool:
var trimmed = code.strip_edges()
var statement_keywords = ["if", "for", "while", "do", "function", "local", "return", "break"]
for keyword in statement_keywords:
if trimmed.begins_with(keyword + " ") or trimmed.begins_with(keyword + "("):
return false
if "=" in trimmed and not ("==" in trimmed or "!=" in trimmed or ">=" in trimmed or "<=" in trimmed):
return false
if "print(" in trimmed or "console.log(" in trimmed or "_trace_" in trimmed:
return false
if trimmed.ends_with(")") or trimmed.ends_with("]") or not (" " in trimmed):
return true
return true
func _on_filter_changed(new_text: String) -> void:
current_filter = new_text.strip_edges()
refresh_log_display()
func refresh_log_display() -> void:
for i in range(log_container.get_child_count()):
var child = log_container.get_child(i)
if child is HSeparator:
child.visible = should_separator_be_visible(i)
else:
var entry_index = get_entry_index_for_child(i)
if entry_index >= 0 and entry_index < log_entries.size():
var entry = log_entries[entry_index]
apply_filter_to_item(child, entry)
call_deferred("_scroll_to_bottom")
func apply_filter_to_item(item: Control, entry: Dictionary) -> void:
item.visible = entry_matches_filter(entry)
func entry_matches_filter(entry: Dictionary) -> bool:
if current_filter.is_empty():
return true
var message_text = ""
if entry.message is Dictionary and entry.message.has("parts"):
message_text = get_display_text_for_entry(entry)
else:
message_text = str(entry.message)
if current_filter.to_lower() in message_text.to_lower():
return true
if current_filter.to_lower() == entry.level.to_lower():
return true
return false
func get_last_displayed_entry() -> Dictionary:
for i in range(log_entries.size() - 2, -1, -1): # Start from second-to-last entry
var entry = log_entries[i]
if entry_matches_filter(entry):
return entry
return {}
func should_separator_be_visible(separator_index: int) -> bool:
var before_visible = false
var after_visible = false
if separator_index > 0:
var before_child = log_container.get_child(separator_index - 1)
before_visible = before_child.visible
if separator_index < log_container.get_child_count() - 1:
var after_child = log_container.get_child(separator_index + 1)
after_visible = after_child.visible
return before_visible and after_visible
func get_entry_index_for_child(child_index: int) -> int:
var entry_index = 0
for i in range(child_index):
var child = log_container.get_child(i)
if not child is HSeparator:
entry_index += 1
return entry_index
func can_group_with_last_entry(entry: Dictionary) -> bool:
if last_log_entry.is_empty() or last_log_item == null:
return false
if entry.level != last_log_entry.level:
return false
var current_message_text = ""
var last_message_text = ""
if entry.message is Dictionary and entry.message.has("parts"):
current_message_text = get_display_text_for_entry(entry)
else:
current_message_text = str(entry.message)
if last_log_entry.message is Dictionary and last_log_entry.message.has("parts"):
last_message_text = get_display_text_for_entry(last_log_entry)
else:
last_message_text = str(last_log_entry.message)
if current_message_text != last_message_text:
return false
if entry.level == "input":
return false
return true
func update_log_item_display(log_item: Control, entry: Dictionary) -> void:
if entry.level == "input":
var code_edit = log_item as CodeEdit
if code_edit:
code_edit.text = get_display_text_for_entry(entry)
else:
var label = find_message_label_in_item(log_item)
if label:
label.text = get_display_text_for_entry(entry)
func find_message_code_edit_in_item(item: Control) -> CodeEdit:
if item is CodeEdit:
return item as CodeEdit
if item is MarginContainer:
var panel = item.get_child(0) as PanelContainer
if panel:
return panel.get_child(0) as CodeEdit
elif item is PanelContainer:
return item.get_child(0) as CodeEdit
return null
func find_message_label_in_item(item: Control) -> Label:
if item is MarginContainer:
var panel = item.get_child(0) as PanelContainer
if panel:
return panel.get_child(0) as Label
elif item is PanelContainer:
return item.get_child(0) as Label
return null
func _on_log_code_edit_gui_input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed:
var key = event.keycode
if event.ctrl_pressed and (key == KEY_C or key == KEY_A):
return
if key in [KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_HOME, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN]:
return
if event.shift_pressed and key in [KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_HOME, KEY_END]:
return
get_viewport().set_input_as_handled()
elif event is InputEventMouseButton:
return
func _on_log_code_edit_focus_entered(code_edit: CodeEdit) -> void:
code_edit.release_focus()
func _on_log_code_edit_text_changed(code_edit: CodeEdit, original_text: String) -> void:
if code_edit.text != original_text:
code_edit.text = original_text
func get_display_text_for_entry(entry: Dictionary) -> String:
var count = entry.get("count", 1)
var message = entry.message
var base_text = ""
if message is Dictionary and message.has("parts"):
var parts = message.parts
var text_parts = []
for part in parts:
if part.type == "primitive":
text_parts.append(str(part.data))
elif part.type == "table":
var key_count = part.data.keys().size()
text_parts.append("Object {" + str(key_count) + "}")
base_text = "\t".join(text_parts)
else:
base_text = str(message)
if count > 1:
return "(" + str(count) + ") " + base_text
else:
return base_text
func create_structured_log_item(entry) -> Control:
var container = VBoxContainer.new()
var parts = entry.message.parts
if parts.size() == 1 and parts[0].type == "primitive":
var simple_panel = PanelContainer.new()
var style_box = StyleBoxFlat.new()
style_box.bg_color = Color.TRANSPARENT
style_box.content_margin_left = 10
style_box.content_margin_top = 5
style_box.content_margin_right = 10
style_box.content_margin_bottom = 5
simple_panel.add_theme_stylebox_override("panel", style_box)
var label = Label.new()
label.text = str(parts[0].data)
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
simple_panel.add_child(label)
container.add_child(simple_panel)
return container
for i in range(parts.size()):
var part = parts[i]
if part.type == "primitive":
var text_panel = PanelContainer.new()
var style_box = StyleBoxFlat.new()
style_box.bg_color = Color.TRANSPARENT
style_box.content_margin_left = 10
style_box.content_margin_top = 2
style_box.content_margin_right = 10
style_box.content_margin_bottom = 2
text_panel.add_theme_stylebox_override("panel", style_box)
var label = Label.new()
label.text = str(part.data)
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
text_panel.add_child(label)
container.add_child(text_panel)
elif part.type == "table":
var table_item = create_expandable_table_item(part.data)
container.add_child(table_item)
return container
func create_expandable_table_item(table_data) -> Control:
var main_container = VBoxContainer.new()
var header_container = HBoxContainer.new()
header_container.custom_minimum_size.y = 24
var chevron_button = Button.new()
chevron_button.text = ""
chevron_button.custom_minimum_size = Vector2(20, 20)
chevron_button.flat = true
chevron_button.focus_mode = Control.FOCUS_NONE
header_container.add_child(chevron_button)
var summary_label = Label.new()
var key_count = table_data.keys().size()
if table_data.has("__type"):
summary_label.text = str(table_data.get("__type", "Object")) + " {" + str(key_count) + "}"
else:
summary_label.text = "Object {" + str(key_count) + "}"
summary_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
summary_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header_container.add_child(summary_label)
main_container.add_child(header_container)
var content_container = VBoxContainer.new()
content_container.visible = false
for key in table_data.keys():
if key == "__type":
continue
var value = table_data[key]
var row_container = HBoxContainer.new()
var key_label = Label.new()
key_label.text = str(key) + ": "
key_label.custom_minimum_size.x = 80
key_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP
row_container.add_child(key_label)
var value_label = Label.new()
if value is Dictionary:
var nested_count = value.keys().size()
value_label.text = "Object {" + str(nested_count) + "}"
value_label.modulate = Color.GRAY
elif value is Array:
value_label.text = "Array [" + str(value.size()) + "]"
value_label.modulate = Color.GRAY
else:
value_label.text = str(value)
value_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
value_label.vertical_alignment = VERTICAL_ALIGNMENT_TOP
value_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row_container.add_child(value_label)
content_container.add_child(row_container)
main_container.add_child(content_container)
chevron_button.pressed.connect(func():
content_container.visible = !content_container.visible
chevron_button.text = "" if content_container.visible else ""
)
return main_container
func _on_visibility_changed() -> void:
if visible:
input_line.grab_focus()

View File

@@ -0,0 +1 @@
uid://vrobqac6makc

View File

@@ -0,0 +1 @@
uid://cer1rniskhi24

View File

@@ -0,0 +1,190 @@
@tool
class_name LuaSyntaxHighlighter
extends SyntaxHighlighter
@export_group("Colors")
@export var font_color: Color = Color("#d4d4d4", Color.WHITE)
@export var keyword_color: Color = Color.from_string("#c586c0", Color.WHITE)
@export var gurt_globals_color: Color = Color.from_string("#569cd6", Color.WHITE)
@export var function_color: Color = Color.from_string("#dcdcaa", Color.WHITE)
@export var member_color: Color = Color.from_string("#9cdcfe", Color.WHITE)
@export var number_color: Color = Color.from_string("#b5cea8", Color.WHITE)
@export var string_color: Color = Color.from_string("#ce9178", Color.WHITE)
@export var comment_color: Color = Color.from_string("#6a9955", Color.WHITE)
@export var symbol_color: Color = Color.from_string("#c586c0", Color.WHITE)
enum State { DEFAULT, IN_MULTILINE_COMMENT, IN_MULTILINE_STRING }
var _state_cache: Dictionary = {}
var _keywords: Dictionary = {}
var _built_in_functions: Dictionary = {}
var _gurt_globals: Dictionary = {}
var _member_keywords: Dictionary = {}
func _init() -> void:
for k in ["and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"]:
_keywords[k] = true
for f in ["assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "load", "loadfile", "next", "pairs", "pcall", "print", "rawequal", "rawget", "rawlen", "rawset", "require", "select", "setmetatable", "tonumber", "tostring", "type", "xpcall"]:
_built_in_functions[f] = true
for g in ["gurt", "trace", "JSON", "Time", "WebSocket", "Clipboard", "Regex", "setTimeout", "setInterval", "clearTimeout", "clearInterval", "fetch", "urlEncode", "urlDecode"]:
_gurt_globals[g] = true
var members = [
"select", "selectAll", "create", "body", "location", "href", "reload", "goto", "query", "get", "has", "getAll",
"log", "warn", "error", "text", "value", "visible", "children", "parent", "nextSibling", "previousSibling",
"firstChild", "lastChild", "classList", "on", "append", "remove", "insertBefore", "insertAfter", "replace",
"clone", "getAttribute", "setAttribute", "show", "hide", "focus", "unfocus", "createTween", "unsubscribe",
"add", "remove", "toggle", "contains", "item", "length", "to", "duration", "easing", "transition", "play",
"pause", "stop", "currentTime", "volume", "loop", "src", "playing", "paused", "withContext", "fillRect",
"strokeRect", "clearRect", "drawCircle", "drawText", "setFont", "measureText", "beginPath", "moveTo",
"lineTo", "closePath", "stroke", "fill", "arc", "quadraticCurveTo", "bezierCurveTo", "setStrokeStyle",
"setFillStyle", "setLineWidth", "save", "translate", "rotate", "scale", "restore", "source", "ok", "json",
"status", "statusText", "headers", "stringify", "parse", "now", "format", "date", "sleep", "benchmark",
"timer", "delay", "elapsed", "reset", "complete", "remaining", "new", "send", "close", "readyState",
"test", "match", "tostring", "replace", "replaceAll", "trim",
]
for m in members:
_member_keywords[m] = true
func _is_whitespace(char: String) -> bool:
return char == " " or char == "\t"
func _clear_highlighting_cache():
_state_cache.clear()
func _get_initial_state() -> int:
return State.DEFAULT
func _get_line_state(p_line: int, p_state: int) -> int:
var current_state: int = p_state
var line_text: String = get_text_edit().get_line(p_line)
var line_len: int = line_text.length()
var i := 0
while i < line_len:
if current_state == State.DEFAULT:
if i + 3 < line_len and line_text.substr(i, 4) == "--[[":
current_state = State.IN_MULTILINE_COMMENT
i += 4
continue
if i + 1 < line_len and line_text.substr(i, 2) == "[[":
current_state = State.IN_MULTILINE_STRING
i += 2
continue
if line_text[i] == "'" or line_text[i] == "\"":
var quote = line_text[i]
i += 1
while i < line_len:
if line_text[i] == "\\": i += 2; continue
if line_text[i] == quote: break
i += 1
else:
var end_idx = line_text.find("]]", i)
if end_idx != -1:
current_state = State.DEFAULT
i = end_idx + 2
continue
else:
i = line_len
i += 1
_state_cache[p_line] = current_state
return current_state
func _get_line_syntax_highlighting(p_line: int) -> Dictionary:
var color_map := {}
var start_state: int = _state_cache.get(p_line - 1, _get_initial_state())
var line_text: String = get_text_edit().get_line(p_line)
var line_len: int = line_text.length()
color_map[0] = {"color": font_color}
var i := 0
if start_state != State.DEFAULT:
var end_idx = line_text.find("]]")
var region_color = comment_color if start_state == State.IN_MULTILINE_COMMENT else string_color
color_map[0] = {"color": region_color}
if end_idx == -1:
return color_map
else:
i = end_idx + 2
if i < line_len:
color_map[i] = {"color": font_color}
start_state = State.DEFAULT
while i < line_len:
var start_col: int = i
var current_char: String = line_text[i]
if current_char == "-" and i + 1 < line_len and line_text[i+1] == "-":
if not (i + 3 < line_len and line_text.substr(i, 4) == "--[["):
color_map[i] = {"color": comment_color}
return color_map
if current_char == "\"" or current_char == "'":
var quote = current_char
var string_start = i
i += 1
while i < line_len:
if line_text[i] == "\\": i += 2; continue
if line_text[i] == quote: i += 1; break
i += 1
var string_end = i
color_map[string_start] = {"color": string_color}
if string_end < line_len:
color_map[string_end] = {"color": font_color}
continue
if current_char.is_valid_int() or (current_char == "." and i + 1 < line_len and line_text[i+1].is_valid_int()):
var is_hex = false
if current_char == "0" and i + 1 < line_len and line_text[i+1].to_lower() == "x":
i += 2; is_hex = true
while i < line_len:
var char = line_text[i]
if (is_hex and char.is_valid_hex_number(false)) or \
(not is_hex and (char.is_valid_int() or char in "Ee.-+")):
i += 1
else:
break
var number_end = i
color_map[start_col] = {"color": number_color}
if number_end < line_len:
color_map[number_end] = {"color": font_color}
continue
if current_char.is_valid_identifier() and not current_char.is_valid_int():
while i < line_len and line_text[i].is_valid_identifier():
i += 1
var word = line_text.substr(start_col, i - start_col)
var color = font_color
if _keywords.has(word): color = keyword_color
elif _gurt_globals.has(word): color = gurt_globals_color
elif _built_in_functions.has(word): color = function_color
else:
var prev_char_idx = start_col - 1
while prev_char_idx >= 0 and _is_whitespace(line_text[prev_char_idx]):
prev_char_idx -= 1
var next_char_idx = i
while next_char_idx < line_len and _is_whitespace(line_text[next_char_idx]):
next_char_idx += 1
var is_member = prev_char_idx >= 0 and line_text[prev_char_idx] in [".", ":"]
var is_function_call = next_char_idx < line_len and line_text[next_char_idx] == "("
if is_member and _member_keywords.has(word): color = member_color
elif is_function_call: color = function_color
if color != font_color: color_map[start_col] = {"color": color}
continue
if not _is_whitespace(current_char):
color_map[i] = {"color": symbol_color}
if i + 1 < line_len:
color_map[i + 1] = {"color": font_color}
i += 1
return color_map

View File

@@ -0,0 +1 @@
uid://qicpfnrmje1v

View File

@@ -35,6 +35,9 @@ var loading_tween: Tween
var scroll_container: ScrollContainer = null
var website_container: VBoxContainer = null
var background_panel: PanelContainer = null
var main_hbox: HBoxContainer = null
var dev_tools: Control = null
var dev_tools_visible: bool = false
var lua_apis: Array[LuaAPI] = []
var current_url: String = ""
var has_content: bool = false
@@ -131,6 +134,9 @@ func _exit_tree():
background_panel.get_parent().remove_child(background_panel)
background_panel.queue_free()
if dev_tools and is_instance_valid(dev_tools):
dev_tools.queue_free()
remove_from_group("tabs")
func init_scene(parent_container: Control) -> void:
@@ -144,9 +150,15 @@ func init_scene(parent_container: Control) -> void:
style_box.bg_color = Color(1, 1, 1, 1) # White background
background_panel.add_theme_stylebox_override("panel", style_box)
main_hbox = HBoxContainer.new()
main_hbox.name = "Tab_MainHBox_" + str(get_instance_id())
main_hbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
main_hbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll_container = ScrollContainer.new()
scroll_container.name = "Tab_ScrollContainer_" + str(get_instance_id())
scroll_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
scroll_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL # Take 2/3 of space
website_container = VBoxContainer.new()
website_container.name = "Tab_WebsiteContainer_" + str(get_instance_id())
@@ -154,8 +166,15 @@ func init_scene(parent_container: Control) -> void:
website_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
website_container.add_theme_constant_override("separation", 22)
var dev_tools_scene = preload("res://Scenes/DevTools.tscn")
dev_tools = dev_tools_scene.instantiate()
dev_tools.name = "Tab_DevTools_" + str(get_instance_id())
dev_tools.visible = false
parent_container.call_deferred("add_child", background_panel)
background_panel.call_deferred("add_child", scroll_container)
background_panel.call_deferred("add_child", main_hbox)
main_hbox.call_deferred("add_child", scroll_container)
main_hbox.call_deferred("add_child", dev_tools)
scroll_container.call_deferred("add_child", website_container)
background_panel.visible = is_active
@@ -215,3 +234,21 @@ func _on_close_button_pressed() -> void:
await close_tween.finished
tab_closed.emit()
queue_free()
func toggle_dev_tools() -> void:
if not dev_tools:
return
dev_tools_visible = not dev_tools_visible
dev_tools.visible = dev_tools_visible
if dev_tools_visible:
scroll_container.size_flags_stretch_ratio = 2.0
dev_tools.size_flags_stretch_ratio = 1.0
else:
scroll_container.size_flags_stretch_ratio = 1.0
func get_dev_tools_console() -> DevToolsConsole:
if dev_tools and dev_tools.has_method("get_console"):
return dev_tools.get_console()
return null

View File

@@ -0,0 +1 @@
uid://8tv4fx7krev1

View File

@@ -272,7 +272,7 @@ static func render_new_element(element: HTMLParser.HTMLElement, parent_node: Nod
# Create the visual node for the element
var element_node = await main_scene.create_element_node(element, dom_parser)
if not element_node:
LuaPrintUtils.lua_print_direct("Failed to create visual node for element: " + str(element))
Trace.trace_log("Failed to create visual node for element: " + str(element))
return
# Set metadata so ul/ol can detect dynamically added li elements

View File

@@ -10,12 +10,9 @@ static func lua_print(vm: LuauVM) -> int:
message_parts.append(value_str)
var final_message = "\t".join(message_parts)
lua_print_direct(final_message)
Trace.trace_log(final_message)
return 0
static func lua_print_direct(msg) -> void:
print("GURT LOG: ", msg)
static func lua_value_to_string(vm: LuauVM, index: int) -> String:
var lua_type = vm.lua_type(index)

View File

@@ -3,7 +3,7 @@ extends RefCounted
signal script_completed(result: Dictionary)
signal script_error(error: String)
signal print_output(message: String)
signal print_output(print_data: Dictionary)
signal dom_operation_request(operation: Dictionary)
var lua_thread: Thread
@@ -238,23 +238,26 @@ func _print_handler(vm: LuauVM) -> int:
var num_args = vm.lua_gettop()
for i in range(1, num_args + 1):
var arg_str = ""
if vm.lua_isstring(i):
arg_str = vm.lua_tostring(i)
elif vm.lua_isnumber(i):
arg_str = str(vm.lua_tonumber(i))
elif vm.lua_isboolean(i):
arg_str = "true" if vm.lua_toboolean(i) else "false"
elif vm.lua_isnil(i):
arg_str = "nil"
var lua_type = vm.lua_type(i)
if lua_type == vm.LUA_TTABLE:
var table_data = vm.lua_todictionary(i)
message_parts.append({
"type": "table",
"data": table_data
})
else:
arg_str = vm.lua_typename(vm.lua_type(i))
message_parts.append(arg_str)
var value_str = LuaPrintUtils.lua_value_to_string(vm, i)
message_parts.append({
"type": "primitive",
"data": value_str
})
var final_message = "\t".join(message_parts)
var print_data = {
"parts": message_parts,
"count": message_parts.size()
}
call_deferred("_emit_print_output", final_message)
call_deferred("_emit_print_output", print_data)
return 0
@@ -267,10 +270,35 @@ func _time_sleep_handler(vm: LuauVM) -> int:
return 0
func _trace_log_handler(vm: LuauVM) -> int:
var message = vm.luaL_checkstring(1)
call_deferred("_emit_trace_message", message, "lua")
return 0
func _trace_warning_handler(vm: LuauVM) -> int:
var message = vm.luaL_checkstring(1)
call_deferred("_emit_trace_message", message, "warning")
return 0
func _trace_error_handler(vm: LuauVM) -> int:
var message = vm.luaL_checkstring(1)
call_deferred("_emit_trace_message", message, "error")
return 0
func _setup_threaded_gurt_api():
lua_vm.lua_pushcallable(_print_handler, "print")
lua_vm.lua_setglobal("print")
# Setup trace functions
lua_vm.lua_pushcallable(_trace_log_handler, "_trace_log")
lua_vm.lua_setglobal("_trace_log")
lua_vm.lua_pushcallable(_trace_warning_handler, "_trace_warning")
lua_vm.lua_setglobal("_trace_warning")
lua_vm.lua_pushcallable(_trace_error_handler, "_trace_error")
lua_vm.lua_setglobal("_trace_error")
LuaTimeUtils.setup_time_api(lua_vm)
lua_vm.lua_getglobal("Time")
@@ -281,9 +309,6 @@ func _setup_threaded_gurt_api():
lua_vm.lua_newtable()
lua_vm.lua_pushcallable(_print_handler, "gurt.log")
lua_vm.lua_setfield(-2, "log")
lua_vm.lua_pushcallable(_gurt_select_handler, "gurt.select")
lua_vm.lua_setfield(-2, "select")
@@ -357,6 +382,7 @@ func _setup_additional_lua_apis():
LuaCrumbsUtils.setup_crumbs_api(lua_vm)
LuaRegexUtils.setup_regex_api(lua_vm)
LuaURLUtils.setup_url_api(lua_vm)
Trace.setup_trace_api(lua_vm)
func _table_tostring_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
@@ -370,8 +396,8 @@ func _emit_script_completed(result: Dictionary):
func _emit_script_error(error: String):
script_error.emit(error)
func _emit_print_output(message: String):
print_output.emit(message)
func _emit_print_output(print_data: Dictionary):
print_output.emit(print_data)
func _gurt_select_all_handler(vm: LuauVM) -> int:
var selector: String = vm.luaL_checkstring(1)
@@ -567,3 +593,12 @@ func _create_threaded_interval(interval_id: int, delay_ms: int):
timeout_info.timer = timer
lua_api.add_child(timer)
timer.start()
func _emit_trace_message(message: String, level: String):
match level:
"lua", "log":
Trace.trace_log(message)
"warning":
Trace.trace_warning(message)
"error":
Trace.trace_error(message)

View File

@@ -0,0 +1,87 @@
class_name Trace
extends RefCounted
signal log_message(message: String, level: String, timestamp: float)
enum LogLevel {
LOG,
WARNING,
ERROR
}
static var _instance: Trace
static var _messages: Array[Dictionary] = []
static func get_instance() -> Trace:
if not _instance:
_instance = Trace.new()
return _instance
static func trace_log(message: String) -> void:
_emit_message(message, "log")
static func trace_warning(message: String) -> void:
_emit_message(message, "warning")
static func trace_error(message: String) -> void:
_emit_message(message, "error")
static func _emit_message(message: String, level: String) -> void:
var timestamp = Time.get_ticks_msec() / 1000.0
var log_entry = {
"message": message,
"level": level,
"timestamp": timestamp
}
_messages.append(log_entry)
get_instance().call_deferred("emit_signal", "log_message", message, level, timestamp)
match level:
"log":
print("TRACE LOG: ", message)
"warning":
print("TRACE WARNING: ", message)
"error":
print("TRACE ERROR: ", message)
static func get_all_messages() -> Array[Dictionary]:
return _messages.duplicate()
static func clear_messages() -> void:
_messages.clear()
static func _lua_trace_log_handler(vm: LuauVM) -> int:
var message = vm.luaL_checkstring(1)
vm.lua_getglobal("_trace_log")
vm.lua_pushstring(message)
vm.lua_call(1, 0)
return 0
static func _lua_trace_warn_handler(vm: LuauVM) -> int:
var message = vm.luaL_checkstring(1)
vm.lua_getglobal("_trace_warning")
vm.lua_pushstring(message)
vm.lua_call(1, 0)
return 0
static func _lua_trace_error_handler(vm: LuauVM) -> int:
var message = vm.luaL_checkstring(1)
vm.lua_getglobal("_trace_error")
vm.lua_pushstring(message)
vm.lua_call(1, 0)
return 0
static func setup_trace_api(vm: LuauVM) -> void:
vm.lua_newtable()
vm.lua_pushcallable(_lua_trace_log_handler, "trace.log")
vm.lua_setfield(-2, "log")
vm.lua_pushcallable(_lua_trace_warn_handler, "trace.warn")
vm.lua_setfield(-2, "warn")
vm.lua_pushcallable(_lua_trace_error_handler, "trace.error")
vm.lua_setfield(-2, "error")
vm.lua_setglobal("trace")

View File

@@ -0,0 +1 @@
uid://k87sfbjp7myx

View File

@@ -61,6 +61,16 @@ func _ready():
call_deferred("render")
func _input(event: InputEvent) -> void:
if Input.is_action_just_pressed("DevTools"):
_toggle_dev_tools()
get_viewport().set_input_as_handled()
func _toggle_dev_tools() -> void:
var active_tab = get_active_tab()
if active_tab:
active_tab.toggle_dev_tools()
func resolve_url(href: String) -> String:
return URLUtils.resolve_url(current_domain, href)
@@ -280,12 +290,16 @@ func render_content(html_bytes: PackedByteArray) -> void:
parser.register_dom_node(body, target_container)
var scripts = parser.find_all("script")
var lua_api = null
if scripts.size() > 0:
lua_api = LuaAPI.new()
add_child(lua_api)
if active_tab:
active_tab.lua_apis.append(lua_api)
var lua_api = LuaAPI.new()
add_child(lua_api)
if active_tab:
active_tab.lua_apis.append(lua_api)
lua_api.dom_parser = parser
if lua_api.threaded_vm:
lua_api.threaded_vm.dom_parser = parser
var i = 0
if body:
@@ -694,3 +708,9 @@ func get_active_website_container() -> Control:
if active_tab:
return active_tab.website_container
return website_container # fallback to original container
func get_dev_tools_console() -> DevToolsConsole:
var active_tab = get_active_tab()
if active_tab:
return active_tab.get_dev_tools_console()
return null

View File

@@ -72,3 +72,8 @@ FocusSearch={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":76,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
DevTools={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":73,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}

View File

@@ -19,7 +19,7 @@
local list = gurt.select('#item-list')
local counter = 1
gurt.log('List manipulation script started.')
trace.log('List manipulation script started.')
add_button:on('click', function()
local new_item = gurt.create('li', {

View File

@@ -32,7 +32,7 @@
local infoBox = gurt.select('#info-box')
local clickCounter = gurt.select('#click-counter')
gurt.log('Button attribute demo script started.')
trace.log('Button attribute demo script started.')
local clickCount = 0
@@ -64,7 +64,7 @@
targetButton:on('click', function()
clickCount = clickCount + 1
clickCounter.text = 'Button clicked ' .. clickCount .. ' times!'
gurt.log('Target button clicked! Count:', clickCount)
trace.log('Target button clicked! Count:', clickCount)
updateStatus()
end)
@@ -72,7 +72,7 @@
enableBtn:on('click', function()
targetButton:setAttribute('disabled', '') -- Remove disabled attribute
targetButton:setAttribute('data-value', 'enabled')
gurt.log('Target button enabled via setAttribute')
trace.log('Target button enabled via setAttribute')
updateStatus()
end)
@@ -80,7 +80,7 @@
disableBtn:on('click', function()
targetButton:setAttribute('disabled', 'true')
targetButton:setAttribute('data-value', 'disabled')
gurt.log('Target button disabled via setAttribute')
trace.log('Target button disabled via setAttribute')
updateStatus()
end)
@@ -92,12 +92,12 @@
-- Currently disabled, so enable it
targetButton:setAttribute('disabled', '')
targetButton:setAttribute('data-value', 'toggled-enabled')
gurt.log('Target button toggled to enabled state')
trace.log('Target button toggled to enabled state')
else
-- Currently enabled, so disable it
targetButton:setAttribute('disabled', 'true')
targetButton:setAttribute('data-value', 'toggled-disabled')
gurt.log('Target button toggled to disabled state')
trace.log('Target button toggled to disabled state')
end
updateStatus()
@@ -109,12 +109,12 @@
local type = targetButton:getAttribute('type')
local dataValue = targetButton:getAttribute('data-value')
gurt.log('=== BUTTON STATUS CHECK ===')
gurt.log('Disabled attribute:', disabled or 'not set')
gurt.log('Type attribute:', type or 'not set')
gurt.log('Data-value attribute:', dataValue or 'not set')
gurt.log('Click count:', clickCount)
gurt.log('===========================')
trace.log('=== BUTTON STATUS CHECK ===')
trace.log('Disabled attribute:', disabled or 'not set')
trace.log('Type attribute:', type or 'not set')
trace.log('Data-value attribute:', dataValue or 'not set')
trace.log('Click count:', clickCount)
trace.log('===========================')
-- Demonstrate style setAttribute
local randomColors = {'bg-red-500', 'bg-green-500', 'bg-purple-500', 'bg-orange-500', 'bg-pink-500'}

View File

@@ -18,7 +18,7 @@
</style>
<script>
gurt.log('Starting comprehensive Canvas API test...')
trace.log('Starting comprehensive Canvas API test...')
-- Test 1: Basic Rectangle and Circle Drawing
local basicCanvas = gurt.select("#basic-canvas")
@@ -282,7 +282,7 @@
}
]])
gurt.log('Canvas API test completed successfully!')
trace.log('Canvas API test completed successfully!')
</script>
</head>

View File

@@ -17,12 +17,12 @@
<postprocess preset="chrome" />
<script>
gurt.log('Script started!')
trace.log('Script started!')
local copyBtn = gurt.select('#copy-text-btn')
copyBtn:on('click', function()
gurt.log('Copy button clicked!')
trace.log('Copy button clicked!')
Clipboard.write('Hello from GURT!')
end)
</script>

View File

@@ -26,7 +26,7 @@
local loadImageBtn = gurt.select('#load-image-btn')
local imageContainer = gurt.select('#image-container')
gurt.log('setInterval & Network Image demo script started.')
trace.log('setInterval & Network Image demo script started.')
local logMessages = {}
local counter = 0

View File

@@ -17,7 +17,7 @@
local mouse = gurt.select('#mouse')
local btnmouse = gurt.select('#btnmouse')
gurt.log('Starting Lua script execution...')
trace.log('Starting Lua script execution...')
gurt.body:on('keypress', function(el)
typing.text = table.tostring(el)
@@ -68,13 +68,13 @@
subscription:unsubscribe()
end)
gurt.log('Event listener attached to button with subscription ID')
trace.log('Event listener attached to button with subscription ID')
else
gurt.log('Could not find button or event log element')
trace.log('Could not find button or event log element')
end
-- DOM Manipulation Demo
gurt.log('Testing DOM manipulation...')
trace.log('Testing DOM manipulation...')
-- Create a new div with styling
local new_div = gurt.create('div', { style = 'bg-red-500 p-4 rounded-lg mb-4' })

View File

@@ -28,7 +28,7 @@
local urlInput = gurt.select('#url-input')
local jsonInput = gurt.select('#json-input')
gurt.log('Network & JSON API demo script started.')
trace.log('Network & JSON API demo script started.')
local logMessages = {}

View File

@@ -38,7 +38,7 @@
local fireDataBtn = gurt.select('#fire-data-btn')
local clearLogBtn = gurt.select('#clear-log-btn')
gurt.log('Signal API demo script started.')
trace.log('Signal API demo script started.')
local logMessages = {}
local connectionCount = 0

View File

@@ -201,7 +201,7 @@
math.randomseed(Time.now())
render()
gurt.log('Snake game initialized!')
trace.log('Snake game initialized!')
</script>
</head>

View File

@@ -53,7 +53,7 @@
local moveBox = gurt.select('#move-box')
local comboBox = gurt.select('#combo-box')
gurt.log('Tween animation demo started!')
trace.log('Tween animation demo started!')
-- Fade Animation
gurt.select('#fade-btn'):on('click', function()
@@ -143,7 +143,7 @@
-- Callback example
gurt.select('#callback-btn'):on('click', function()
fadeBox:createTween():to('opacity', 0.3):duration(1.0):easing('inout'):callback(function()
gurt.log('Fade animation completed!')
trace.log('Fade animation completed!')
end):play()
end)

View File

@@ -33,7 +33,7 @@
local urlInput = gurt.select('#url-input')
local messageInput = gurt.select('#message-input')
gurt.log('WebSocket API demo script started.')
trace.log('WebSocket API demo script started.')
local logMessages = {}
local socket = nil