Skip to content

Commit ba8538f

Browse files
committed
Add editor plugin with Lua REPL
1 parent de88c2d commit ba8538f

File tree

8 files changed

+243
-1
lines changed

8 files changed

+243
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This plugin is available in the Asset Library as [Lua GDExtension](https://godot
2222
+ Global enums, like `OK`, `TYPE_STRING` and `SIDE_LEFT`
2323
+ (TODO) Patch Lua `package.searchers` to accept paths relative to `res://` and `user://`
2424
- Create Godot scripts directly in Lua
25+
- Editor plugin with Lua REPL for testing out Lua snippets
2526

2627

2728
## Calling Lua from Godot
@@ -201,7 +202,7 @@ return LuaBouncingLogo
201202
- [ ] Use framework in iOS (possibly a xcframework supporting the iOS simulator as well)
202203
- [X] Automated unit tests
203204
- [X] Automated build and distribution
204-
- [ ] Lua REPL editor plugin
205+
- [X] Lua REPL editor plugin
205206

206207

207208
## Other projects for using Lua in Godot 4

addons/lua-gdextension/lua_repl.gd

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Copyright (C) 2025 Gil Barbosa Reis.
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
# this software and associated documentation files (the “Software”), to deal in
5+
# the Software without restriction, including without limitation the rights to
6+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
# of the Software, and to permit persons to whom the Software is furnished to do
8+
# so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
# SOFTWARE.
20+
21+
@tool
22+
extends Node
23+
24+
@onready var _output: RichTextLabel = $Output
25+
@onready var _input: LineEdit = $Footer/Input
26+
@onready var _history_popup: PopupMenu = $Header/HistoryButton.get_popup()
27+
var _lua: LuaState
28+
var _history = PackedStringArray()
29+
var _current_history = 0
30+
31+
32+
func _ready():
33+
_history_popup.about_to_popup.connect(_on_history_popup_about_to_popup)
34+
_history_popup.id_pressed.connect(_on_history_popup_id_pressed)
35+
reset()
36+
37+
38+
func reset():
39+
_lua = LuaState.new()
40+
_lua.open_libraries()
41+
_lua.registry.print = _printn
42+
_lua.do_string(r"print = function(...) debug.getregistry().print(table.concat({...}, '\t')) end")
43+
44+
_history.clear()
45+
_current_history = 0
46+
clear()
47+
48+
49+
func do_string(text: String):
50+
text = text.strip_edges()
51+
if text.is_empty():
52+
return
53+
54+
_history.append(text)
55+
_current_history = _history.size()
56+
_input.clear()
57+
_printn(text)
58+
59+
# support for "= value" idiom from Lua 5.1 REPL
60+
text.trim_prefix("=")
61+
62+
var result = _lua.do_string("return " + text)
63+
if result is LuaError:
64+
result = _lua.do_string(text)
65+
66+
if result is LuaError:
67+
_print_error(result.message)
68+
else:
69+
_printn("Out[%d]: %s" % [_current_history, result])
70+
_prompt()
71+
72+
73+
func clear():
74+
_output.clear()
75+
_prompt()
76+
77+
78+
func set_history(index: int):
79+
if index < 0 or index >= _history.size():
80+
return
81+
82+
_current_history = index
83+
var text = _history[index]
84+
_input.text = text
85+
_input.caret_column = text.length()
86+
87+
88+
func _prompt():
89+
_print("\nIn [%d]: " % [_current_history + 1])
90+
91+
92+
func _print(msg: String):
93+
self._output.add_text(msg)
94+
95+
96+
func _printn(msg: String):
97+
_print(msg)
98+
_print("\n")
99+
100+
101+
func _print_error(msg: String):
102+
var color: Color = EditorInterface.get_editor_settings().get_setting("text_editor/theme/highlighting/brace_mismatch_color")
103+
self._output.append_text("[color=%s]%s[/color]\n" % [color.to_html(), msg.replace("[", "[lb]")])
104+
105+
106+
func _on_history_popup_about_to_popup():
107+
_history_popup.clear()
108+
for line in _history:
109+
_history_popup.add_item(line)
110+
111+
112+
func _on_history_popup_id_pressed(id: int):
113+
set_history(id)
114+
115+
116+
func _on_input_text_submitted(new_text: String):
117+
do_string(new_text)
118+
119+
120+
func _on_run_button_pressed():
121+
do_string(_input.text)
122+
123+
124+
func _on_input_gui_input(event: InputEvent):
125+
var key_event = event as InputEventKey
126+
if not key_event or not key_event.pressed:
127+
return
128+
129+
if key_event.keycode == KEY_UP:
130+
set_history(_current_history - 1)
131+
elif key_event.keycode == KEY_DOWN:
132+
set_history(_current_history + 1)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://bjod0yq2efea8
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
[gd_scene load_steps=2 format=3 uid="uid://4lq5s4lnqg8c"]
2+
3+
[ext_resource type="Script" uid="uid://bjod0yq2efea8" path="res://addons/lua-gdextension/lua_repl.gd" id="1_gf8ka"]
4+
5+
[node name="LuaRepl" type="VBoxContainer"]
6+
anchors_preset = 15
7+
anchor_right = 1.0
8+
anchor_bottom = 1.0
9+
grow_horizontal = 2
10+
grow_vertical = 2
11+
script = ExtResource("1_gf8ka")
12+
13+
[node name="Header" type="HBoxContainer" parent="."]
14+
layout_mode = 2
15+
16+
[node name="Title" type="Label" parent="Header"]
17+
layout_mode = 2
18+
size_flags_horizontal = 3
19+
text = "Lua REPL"
20+
21+
[node name="HistoryButton" type="MenuButton" parent="Header"]
22+
layout_mode = 2
23+
text = "History"
24+
flat = false
25+
26+
[node name="ResetButton" type="Button" parent="Header"]
27+
layout_mode = 2
28+
tooltip_text = "Reset the Lua environment and REPL history"
29+
text = "Reset"
30+
31+
[node name="ClearButton" type="Button" parent="Header"]
32+
layout_mode = 2
33+
tooltip_text = "Clear the output text"
34+
text = "Clear"
35+
36+
[node name="Output" type="RichTextLabel" parent="."]
37+
layout_mode = 2
38+
size_flags_vertical = 3
39+
focus_mode = 2
40+
scroll_following = true
41+
selection_enabled = true
42+
43+
[node name="Footer" type="HBoxContainer" parent="."]
44+
layout_mode = 2
45+
46+
[node name="Input" type="LineEdit" parent="Footer"]
47+
layout_mode = 2
48+
size_flags_horizontal = 3
49+
keep_editing_on_text_submit = true
50+
51+
[node name="RunButton" type="Button" parent="Footer"]
52+
layout_mode = 2
53+
text = "Run"
54+
55+
[connection signal="pressed" from="Header/ResetButton" to="." method="reset"]
56+
[connection signal="pressed" from="Header/ClearButton" to="." method="clear"]
57+
[connection signal="gui_input" from="Footer/Input" to="." method="_on_input_gui_input"]
58+
[connection signal="text_submitted" from="Footer/Input" to="." method="_on_input_text_submitted"]
59+
[connection signal="pressed" from="Footer/RunButton" to="." method="_on_run_button_pressed"]

addons/lua-gdextension/plugin.cfg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[plugin]
2+
3+
name="Lua GDExtension"
4+
description="Tools for Lua GDExtension: REPL tab"
5+
author="gilzoide"
6+
version="0.2.0"
7+
script="plugin.gd"

addons/lua-gdextension/plugin.gd

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (C) 2025 Gil Barbosa Reis.
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
# this software and associated documentation files (the “Software”), to deal in
5+
# the Software without restriction, including without limitation the rights to
6+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
# of the Software, and to permit persons to whom the Software is furnished to do
8+
# so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in all
11+
# copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
# SOFTWARE.
20+
21+
@tool
22+
extends EditorPlugin
23+
24+
25+
var _lua_repl: Control
26+
27+
28+
func _enter_tree():
29+
_lua_repl = preload("lua_repl.tscn").instantiate()
30+
add_control_to_bottom_panel(_lua_repl, "Lua REPL")
31+
32+
33+
func _exit_tree():
34+
if _lua_repl:
35+
remove_control_from_bottom_panel(_lua_repl)
36+
_lua_repl.queue_free()
37+
_lua_repl = null
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://cwp2hwkpbitgx

test/project.godot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ run/main_scene="uid://d3pmpkdvfqjca"
1515
config/features=PackedStringArray("4.4", "GL Compatibility")
1616
config/icon="uid://cih6ia8rwpfa3"
1717

18+
[editor_plugins]
19+
20+
enabled=PackedStringArray("res://addons/lua-gdextension/plugin.cfg")
21+
1822
[rendering]
1923

2024
renderer/rendering_method="gl_compatibility"

0 commit comments

Comments
 (0)