└── addons
└── nhb_functions_on_the_fly
├── plugin.gd.uid
├── lib
├── utils.gd.uid
├── class-finder.gd.uid
├── class-finder.gd
└── utils.gd
├── icon.png
├── plugin.cfg
├── icon.png.import
├── LICENSE.md
├── README.md
└── plugin.gd
/addons/nhb_functions_on_the_fly/plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://5fmgdtrj8r3j
2 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/lib/utils.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dct7x5aw6nygl
2 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/lib/class-finder.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8db7kbdv62j0
2 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NickHatBoecker/nhb_functions_on_the_fly/HEAD/addons/nhb_functions_on_the_fly/icon.png
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="NhbCreateFunctionsOnTheFly"
4 | description="With this plugin you can auto generate functions and get/set variables by selecting the function's / variable's name."
5 | author="NickHatBoecker"
6 | version="1.0.5"
7 | script="plugin.gd"
8 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/icon.png.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://wh4gldabavoq"
6 | path="res://.godot/imported/icon.png-76880353bb37aa9ef87ad1674fafb417.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/nhb_functions_on_the_fly/icon.png"
14 | dest_files=["res://.godot/imported/icon.png-76880353bb37aa9ef87ad1674fafb417.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2025 NickHatBoecker
2 |
3 | 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:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | 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.
8 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/lib/class-finder.gd:
--------------------------------------------------------------------------------
1 | ## This script will find a custom class based on its class_name.
2 | ## If a script was found, its script path will be returned.
3 | ## If no script is found within {CANCEL_AFTER_MS} ms, an empty string is returned.
4 | class_name NhbFunctionsOnTheFlyClassFinder
5 | extends Node
6 |
7 |
8 | const CANCEL_AFTER_MS = 3000
9 |
10 |
11 | var _found_path: String
12 |
13 |
14 | func get_found_path() -> String:
15 | return _found_path
16 |
17 |
18 | func find_class_path(name_of_class : String) -> String:
19 | var dir = DirAccess.open("res://")
20 | if not dir:
21 | return ""
22 |
23 | var start_ms = Time.get_ticks_msec()
24 | _search_dir(dir, name_of_class, start_ms)
25 |
26 | return _found_path
27 |
28 |
29 | func _search_dir(dir : DirAccess, target : String, start_ms : int):
30 | if Time.get_ticks_msec() - start_ms > 3000:
31 | _found_path = ""
32 | return
33 |
34 | for item in dir.get_files():
35 | if !item.ends_with(".gd"): continue
36 |
37 | var path = dir.get_current_dir() + "/" + item
38 | var file = FileAccess.open(path, FileAccess.READ)
39 | if !file:
40 | continue
41 |
42 | while not file.eof_reached():
43 | if Time.get_ticks_msec() - start_ms > CANCEL_AFTER_MS:
44 | _found_path = ""
45 | return
46 | var line = file.get_line()
47 | if line.begins_with("class_name "):
48 | var parts = line.split(" ")
49 | if parts.size() > 1 and parts[1] == target:
50 | _found_path = path
51 | return
52 | file.close()
53 |
54 | for sub in dir.get_directories():
55 | dir.change_dir(sub)
56 | _search_dir(dir, target, start_ms)
57 |
58 | if _found_path != "": return
59 |
60 | dir.change_dir("..")
61 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/README.md:
--------------------------------------------------------------------------------
1 | # NHB Functions On The Fly for Godot 4.4+
2 |
3 |
4 |
5 |
6 |
7 | Easily create missing functions or getter/setters for variables in Godot on the fly.\
8 | You can install it via the Asset Library or [downloading a copy](https://github.com/nickhatboecker/nhb_functions_on_the_fly/archive/refs/heads/main.zip) from GitHub.
9 |
10 | ✨ Even function arguments and return types are considered, for both native and custom methods. ✨
11 |
12 | Shortcuts are configurable in the Editor settings. Under "_Plugin > NHB Functions On The Fly_"
13 |
14 |
15 |
16 |
17 | | Create function Ctrl + [
18 | | Create getter/setter variable Ctrl + '
19 | |
20 |
21 |
22 |
23 |
24 |
25 | |
26 |
27 |
28 | |
29 |
30 |
31 |
32 |
33 | ## ❓ How to use
34 |
35 | ### Create function
36 |
37 | 1. Write `my_button.pressed.connect(on_button_pressed)`
38 | 2. Select `on_button_pressed` or put cursor on it
39 | 3. Now you can either
40 | - Right click > "Create function"
41 | - Ctrl + [
42 | - ⌘ Command + [ (Mac)
43 | 4. Function arguments and return type (if any, based on variable/signal signature) will be considered.
44 |
45 | ### Create getter/setter for variable
46 |
47 | 1. Write `var my_var` or `var my_var: String` or `var my_var: String = "Hello world"`
48 | 2. Select `my_var` or put cursor on it
49 | 3. Now you can either
50 | - Right click > "Create get/set variable"
51 | - Ctrl + '
52 | - ⌘ Command + ' (Mac)
53 | 4. Return type (if any) will be considered
54 |
55 | ## ⭐ Contributors
56 |
57 | - [Initial idea](https://www.reddit.com/r/godot/comments/1morndn/im_a_lazy_programmer_and_added_a_generate_code/) and get/set variable creation: [u/siwoku](https://www.reddit.com/user/siwoku/)
58 | - Get text under cursor, so you don't have to select the text: [u/newold25](https://www.reddit.com/user/newold25/)
59 | - Maintainer, considering indentation type, adding shorcuts: [u/NickHatBoecker](https://nickhatboecker.de/linktree/)
60 |
61 | Pleae feel free to create a pull request!
62 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/plugin.gd:
--------------------------------------------------------------------------------
1 | ## Idea by u/siwoku
2 | ##
3 | ## Contributors:
4 | ## - You don't have to select text to create a function u/newold25
5 | ## - Consider editor settings's indentation type u/NickHatBoecker
6 | ## - Added shortcuts (configurable in editor settings) u/NickHatBoecker
7 |
8 | @tool
9 | class_name NhbFunctionsOnTheFly
10 | extends EditorPlugin
11 |
12 | ## Editor setting for the function shortcut
13 | const FUNCTION_SHORTCUT: StringName = "function_shortcut"
14 | ## Editor setting for the get/set variable shortcut
15 | const GET_SET_SHORTCUT: StringName = "get_set_shortcut"
16 |
17 | const DEFAULT_SHORTCUT_FUNCTION = KEY_BRACKETLEFT
18 | const DEFAULT_SHORTCUT_GET_SET = KEY_APOSTROPHE
19 |
20 | ## If the current text matches this expression, the function popup menu item will be displayed.
21 | const FUNCTION_NAME_REGEX = "^[a-zA-Z_][a-zA-Z0-9_]*$"
22 |
23 | ## If the current text matches this expression, the variable popup menu item will be displayed.
24 | ## Must contain the keyword "var".
25 | const VARIABLE_NAME_REGEX = "var [a-zA-Z_][a-zA-Z0-9_]*"
26 |
27 | ## This is used to determine if a variable string already has a return type.
28 | const VARIABLE_RETURN_TYPE_REGEX = VARIABLE_NAME_REGEX + " *(?:: *([a-zA-Z_][a-zA-Z0-9_]*))?"
29 |
30 | const CALLBACK_MENU_PRIORITY = 1500
31 | enum CALLBACK_TYPES { FUNCTION, VARIABLE }
32 |
33 | var utils: NhbFunctionsOnTheFlyUtils
34 | var script_editor: ScriptEditor
35 | var current_popup: PopupMenu
36 |
37 | var function_shortcut: Shortcut
38 | var get_set_shortcut: Shortcut
39 |
40 |
41 | func _enter_tree():
42 | utils = NhbFunctionsOnTheFlyUtils.new()
43 | script_editor = EditorInterface.get_script_editor()
44 | script_editor.connect("editor_script_changed", _on_script_changed)
45 | _setup_current_script()
46 | _init_shortcuts()
47 |
48 |
49 | func _exit_tree():
50 | if script_editor and script_editor.is_connected("editor_script_changed", _on_script_changed):
51 | script_editor.disconnect("editor_script_changed", _on_script_changed)
52 | _cleanup_current_script()
53 |
54 |
55 | func _on_script_changed(_script):
56 | _setup_current_script()
57 |
58 |
59 | func _setup_current_script():
60 | _cleanup_current_script()
61 | var current_editor = script_editor.get_current_editor()
62 | if current_editor:
63 | var code_edit = _find_code_edit(current_editor)
64 | if code_edit:
65 | current_popup = _find_popup_menu(current_editor)
66 | if current_popup:
67 | current_popup.connect("about_to_popup", _on_popup_about_to_show)
68 |
69 |
70 | func _cleanup_current_script():
71 | if current_popup and current_popup.is_connected("about_to_popup", _on_popup_about_to_show):
72 | current_popup.disconnect("about_to_popup", _on_popup_about_to_show)
73 | current_popup = null
74 |
75 |
76 | func _find_code_edit(node: Node) -> CodeEdit:
77 | if node is CodeEdit:
78 | return node
79 | for child in node.get_children():
80 | var result = _find_code_edit(child)
81 | if result:
82 | return result
83 | return null
84 |
85 |
86 | func _find_popup_menu(node: Node) -> PopupMenu:
87 | if node is PopupMenu:
88 | return node
89 | for child in node.get_children():
90 | var result = _find_popup_menu(child)
91 | if result:
92 | return result
93 | return null
94 |
95 |
96 | func _on_popup_about_to_show():
97 | var current_editor = script_editor.get_current_editor()
98 | if not current_editor:
99 | return
100 |
101 | var code_edit = _find_code_edit(current_editor)
102 | if not code_edit:
103 | return
104 |
105 | var selected_text = _get_selected_text(code_edit)
106 | if selected_text.is_empty():
107 | return
108 |
109 | var current_line = utils.get_current_line_text(code_edit)
110 |
111 | ## Because variable regex is more precise, it has to be checked first
112 | if utils.should_show_create_variable(code_edit, current_line, VARIABLE_NAME_REGEX, _get_editor_settings()):
113 | _create_menu_item(
114 | "Create getter/setter for variable: " + selected_text,
115 | selected_text,
116 | code_edit,
117 | CALLBACK_TYPES.VARIABLE
118 | )
119 | elif _should_show_create_function(code_edit, selected_text):
120 | _create_menu_item(
121 | "Create function: " + selected_text,
122 | selected_text,
123 | code_edit,
124 | CALLBACK_TYPES.FUNCTION
125 | )
126 |
127 |
128 | func _create_menu_item(item_text: String, selected_text: String, code_edit: CodeEdit, callback_type: CALLBACK_TYPES) -> void:
129 | current_popup.add_separator()
130 | current_popup.add_item(item_text, CALLBACK_MENU_PRIORITY)
131 |
132 | if current_popup.is_connected("id_pressed", _on_menu_item_pressed):
133 | current_popup.disconnect("id_pressed", _on_menu_item_pressed)
134 |
135 | current_popup.connect("id_pressed", _on_menu_item_pressed.bind(selected_text, code_edit, CALLBACK_MENU_PRIORITY, callback_type))
136 |
137 |
138 | func _should_show_create_function(code_edit: CodeEdit, text: String) -> bool:
139 | var script_text = code_edit.text
140 |
141 | ## Check if valid function name
142 | var regex = RegEx.new()
143 | regex.compile(FUNCTION_NAME_REGEX)
144 | if not regex.search(text):
145 | return false
146 |
147 | if _function_exists_anywhere(script_text, text):
148 | return false
149 |
150 | if _global_variable_exists(script_text, text):
151 | return false
152 |
153 | if _local_variable_exists_in_current_function(code_edit, text):
154 | return false
155 |
156 | if utils.is_in_comment(code_edit, text):
157 | return false
158 |
159 | return true
160 |
161 |
162 | func _function_exists_anywhere(script_text: String, func_name: String) -> bool:
163 | var regex = RegEx.new()
164 | regex.compile("func\\s+" + func_name + "\\s*\\(")
165 | return regex.search(script_text) != null
166 |
167 |
168 | func _global_variable_exists(script_text: String, var_name: String) -> bool:
169 | var lines = script_text.split("\n")
170 | var inside_function = false
171 |
172 | for i in range(lines.size()):
173 | var line = lines[i]
174 | var trimmed = line.strip_edges()
175 |
176 | if trimmed.begins_with("func "):
177 | inside_function = true
178 | continue
179 |
180 | if not inside_function:
181 | var regex = RegEx.new()
182 | regex.compile("^var\\s+" + var_name + "\\b")
183 | if regex.search(trimmed):
184 | return true
185 | else:
186 | if trimmed.is_empty():
187 | continue
188 | elif not line.begins_with("\t") and not line.begins_with(" ") and trimmed != "":
189 | inside_function = false
190 | var regex = RegEx.new()
191 | regex.compile("^var\\s+" + var_name + "\\b")
192 | if regex.search(trimmed):
193 | return true
194 |
195 | return false
196 |
197 |
198 | func _local_variable_exists_in_current_function(code_edit: CodeEdit, var_name: String) -> bool:
199 | var script_text = code_edit.text
200 | var current_line = code_edit.get_caret_line()
201 |
202 | var function_start = -1
203 | var function_end = -1
204 | var lines = script_text.split("\n")
205 |
206 | for i in range(current_line, -1, -1):
207 | if i < lines.size():
208 | var line = lines[i].strip_edges()
209 | if line.begins_with("func "):
210 | function_start = i
211 | break
212 |
213 | if function_start == -1:
214 | return false
215 |
216 | for i in range(function_start + 1, lines.size()):
217 | var line = lines[i].strip_edges()
218 | if line.begins_with("func ") or (line != "" and not lines[i].begins_with("\t") and not lines[i].begins_with(" ")):
219 | function_end = i - 1
220 | break
221 |
222 | if function_end == -1:
223 | function_end = lines.size() - 1
224 |
225 | for i in range(function_start, function_end + 1):
226 | if i < lines.size():
227 | var line = lines[i].strip_edges()
228 |
229 | var regex = RegEx.new()
230 | regex.compile("^var\\s+" + var_name + "\\b")
231 | if regex.search(line):
232 | return true
233 |
234 | if i == function_start:
235 | var param_regex = RegEx.new()
236 | param_regex.compile("func\\s+\\w+\\s*\\([^)]*\\b" + var_name + "\\b")
237 | if param_regex.search(line):
238 | return true
239 |
240 | return false
241 |
242 |
243 | func _on_menu_item_pressed(id: int, original_text: String, code_edit: CodeEdit, target_index: int, callback_type: CALLBACK_TYPES) -> void:
244 | if id != target_index: return
245 |
246 | if callback_type == CALLBACK_TYPES.FUNCTION:
247 | utils.create_function(original_text, code_edit, _get_editor_settings())
248 | elif callback_type == CALLBACK_TYPES.VARIABLE:
249 | utils.create_get_set_variable(original_text, code_edit, VARIABLE_RETURN_TYPE_REGEX, _get_editor_settings())
250 |
251 |
252 | ## Process the user defined shortcuts
253 | func _shortcut_input(event: InputEvent) -> void:
254 | if !event.is_pressed() || event.is_echo():
255 | return
256 |
257 | if function_shortcut.matches_event(event):
258 | ## Function
259 | get_viewport().set_input_as_handled()
260 | var code_edit: CodeEdit = _get_code_edit()
261 | var function_name = utils.get_word_under_cursor(code_edit)
262 | if _should_show_create_function(code_edit, function_name):
263 | utils.create_function(function_name, code_edit, _get_editor_settings())
264 | elif get_set_shortcut.matches_event(event):
265 | ## Get/set variable
266 | get_viewport().set_input_as_handled()
267 | var code_edit: CodeEdit = _get_code_edit()
268 | var variable_name = utils.get_word_under_cursor(code_edit)
269 | utils.create_get_set_variable(variable_name, code_edit, VARIABLE_RETURN_TYPE_REGEX, _get_editor_settings())
270 |
271 |
272 | func _get_editor_settings() -> EditorSettings:
273 | return EditorInterface.get_editor_settings()
274 |
275 |
276 | ## Initializes all shortcuts.
277 | ## Every shortcut can be changed while this plugin is active, which will override them.
278 | func _init_shortcuts():
279 | var editor_settings: EditorSettings = _get_editor_settings()
280 |
281 | if !editor_settings.has_setting(utils.get_shortcut_path(FUNCTION_SHORTCUT)):
282 | var shortcut: Shortcut = Shortcut.new()
283 | var event: InputEventKey = InputEventKey.new()
284 | event.device = -1
285 | event.command_or_control_autoremap = true
286 | event.keycode = DEFAULT_SHORTCUT_FUNCTION
287 |
288 | shortcut.events = [ event ]
289 | editor_settings.set_setting(utils.get_shortcut_path(FUNCTION_SHORTCUT), shortcut)
290 |
291 | if !editor_settings.has_setting(utils.get_shortcut_path(GET_SET_SHORTCUT)):
292 | var shortcut: Shortcut = Shortcut.new()
293 | var event: InputEventKey = InputEventKey.new()
294 | event.device = -1
295 | event.command_or_control_autoremap = true
296 | event.keycode = DEFAULT_SHORTCUT_GET_SET
297 |
298 | shortcut.events = [ event ]
299 | editor_settings.set_setting(utils.get_shortcut_path(GET_SET_SHORTCUT), shortcut)
300 |
301 | function_shortcut = editor_settings.get_setting(utils.get_shortcut_path(FUNCTION_SHORTCUT))
302 | get_set_shortcut = editor_settings.get_setting(utils.get_shortcut_path(GET_SET_SHORTCUT))
303 |
304 |
305 | ## This is the editor window, where code lines can be selected.
306 | func _get_code_edit() -> CodeEdit:
307 | return get_editor_interface().get_script_editor().get_current_editor().get_base_editor()
308 |
309 |
310 | func _get_selected_text(_code_edit: CodeEdit) -> String:
311 | var selected_text = _code_edit.get_selected_text().strip_edges()
312 |
313 | if selected_text.is_empty():
314 | selected_text = utils.get_word_under_cursor(_code_edit)
315 |
316 | return selected_text
317 |
--------------------------------------------------------------------------------
/addons/nhb_functions_on_the_fly/lib/utils.gd:
--------------------------------------------------------------------------------
1 | ## All functions of this script are testable.
2 |
3 | class_name NhbFunctionsOnTheFlyUtils
4 | extends Node
5 |
6 |
7 | enum INDENTATION_TYPES { TABS, SPACES }
8 |
9 |
10 | func is_in_comment(code_edit: CodeEdit, selected_text: String) -> bool:
11 | if selected_text.begins_with("#"): return true
12 |
13 | var caret_line = code_edit.get_caret_line()
14 | var line_text = code_edit.get_line(caret_line)
15 | var selection_start = code_edit.get_selection_from_column()
16 |
17 | var comment_pos = line_text.find("#")
18 | if comment_pos != -1 and comment_pos < selection_start:
19 | return true
20 |
21 | return false
22 |
23 |
24 | ## Returns true if
25 | ## - text contains a valid variable syntax
26 | ## - and text is not a comment line
27 | ##
28 | ## @param `text` contains the whole line of code.
29 | func should_show_create_variable(code_edit: CodeEdit, text: String, variable_name_regex: String, settings) -> bool:
30 | ## Check if valid variable name
31 | var regex = RegEx.new()
32 | regex.compile(variable_name_regex)
33 | if not regex.search(text):
34 | return false
35 |
36 | if is_in_comment(code_edit, text):
37 | return false
38 |
39 | if !is_global_variable(text, settings):
40 | return false
41 |
42 | return true
43 |
44 |
45 | ## Return return type or an empty string if no return type is provided.
46 | ## @example `var button: Button` will return "Button"
47 | ## @example `var button` will return ""
48 | func get_variable_return_type(text: String, variable_return_type_regex: String) -> String:
49 | var regex = RegEx.new()
50 | regex.compile(variable_return_type_regex)
51 |
52 | var result = regex.search(text)
53 | if not result:
54 | return ""
55 |
56 | return result.get_string(1)
57 |
58 |
59 | func get_current_line_text(_code_edit: CodeEdit) -> String:
60 | return _code_edit.get_line(_code_edit.get_caret_line())
61 |
62 |
63 | func get_shortcut_path(parameter: String) -> String:
64 | return "res://addons/nhb_functions_on_the_fly/%s" % parameter
65 |
66 |
67 | ## Get accumulated indentation string.
68 | ## Will return "\t" if tabs are used for indentation.
69 | ## Will return " " * indent/size if spaces are used for indentation.
70 | func get_indentation_character(settings) -> String:
71 | var indentation_type = settings.get_setting("text_editor/behavior/indent/type")
72 | var indentation_character: String = "\t"
73 |
74 | if indentation_type != INDENTATION_TYPES.TABS:
75 | var indentation_size = settings.get_setting("text_editor/behavior/indent/size")
76 | indentation_character = " ".repeat(indentation_size)
77 |
78 | return indentation_character
79 |
80 |
81 | func create_get_set_variable(variable_name: String, code_edit: CodeEdit, variable_return_type_regex: String, settings) -> void:
82 | var current_line : int = code_edit.get_caret_line()
83 | var line_text : String = code_edit.get_line(current_line)
84 |
85 | if !is_global_variable(line_text, settings): return
86 |
87 | var end_column : int = line_text.length()
88 | var indentation_character: String = get_indentation_character(settings)
89 |
90 | var return_type: String = ": Variant"
91 | if not get_variable_return_type(line_text, variable_return_type_regex).is_empty():
92 | ## Variable already has a return type.
93 | return_type = ""
94 | if line_text.contains("="):
95 | ## Variable has a value so omit return type.
96 | return_type = ""
97 |
98 | var code_text: String = "%s:\n%sget:\n%sreturn %s\n%sset(value):\n%s%s = value" % [
99 | return_type,
100 | indentation_character,
101 | indentation_character.repeat(2),
102 | variable_name,
103 | indentation_character,
104 | indentation_character.repeat(2),
105 | variable_name
106 | ]
107 |
108 | code_edit.deselect()
109 | code_edit.insert_text(code_text, current_line, end_column)
110 |
111 |
112 | func create_function(function_name: String, code_edit: CodeEdit, settings):
113 | var current_line : int = code_edit.get_caret_line()
114 | var line_text : String = code_edit.get_line(current_line)
115 |
116 | code_edit.deselect()
117 |
118 | var return_type: String = get_function_return_type(function_name, code_edit)
119 | var return_type_string: String = " -> %s" % return_type
120 | if !return_type:
121 | return_type_string = ""
122 |
123 | var return_value: String = get_return_value_by_return_type(return_type)
124 | var return_value_string: String = " %s" % str(return_value)
125 | if !return_value:
126 | return_value_string = ""
127 |
128 | var function_parameters = find_signal_declaration_parameters(code_edit.get_caret_line(), line_text, code_edit)
129 | if !function_parameters:
130 | function_parameters = find_function_parameters(function_name, code_edit.get_caret_line(), line_text, code_edit)
131 |
132 | var indentation_character: String = get_indentation_character(settings)
133 | var new_function = "\n\nfunc %s(%s)%s:\n%sreturn%s" % [
134 | function_name,
135 | function_parameters,
136 | return_type_string,
137 | indentation_character,
138 | return_value_string
139 | ]
140 |
141 | code_edit.text = code_edit.text + new_function
142 |
143 | var line_with_new_function = code_edit.get_line_count() - 1
144 | code_edit.set_caret_line(line_with_new_function)
145 | code_edit.set_caret_column(code_edit.get_line(line_with_new_function).length())
146 |
147 | code_edit.text_changed.emit()
148 |
149 |
150 | ## @TODO Is there any easier way to create default value based on return type?
151 | func get_return_value_by_return_type(return_type: String) -> String:
152 | match (return_type):
153 | "String":
154 | return "\"\""
155 | "int":
156 | return "0"
157 | "float":
158 | return "0.0"
159 | "bool":
160 | return "false"
161 | "Color":
162 | return "Color.WHITE"
163 | "Array":
164 | return "[]"
165 | "Dictionary":
166 | return "{}"
167 | "Vector2":
168 | return "Vector2.ZERO"
169 | "Vector2i":
170 | return "Vector2i.ZERO"
171 | "Vector3":
172 | return "Vector3.ZERO"
173 | "Vector3i":
174 | return "Vector3i.ZERO"
175 | "Vector4":
176 | return "Vector4.ZERO"
177 | "Vector4i":
178 | return "Vector4i.ZERO"
179 | return ""
180 |
181 |
182 | func get_word_under_cursor(code_edit: CodeEdit) -> String:
183 | var caret_line = code_edit.get_caret_line()
184 | var caret_column = code_edit.get_caret_column()
185 | var line_text = code_edit.get_line(caret_line)
186 |
187 | var start = caret_column
188 | while start > 0 and line_text[start - 1].is_subsequence_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"):
189 | start -= 1
190 |
191 | var end = caret_column
192 | while end < line_text.length() and line_text[end].is_subsequence_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"):
193 | end += 1
194 |
195 | return line_text.substr(start, end - start)
196 |
197 |
198 | func trim_char(text: String, char: String) -> String:
199 | if text.is_empty() or char.is_empty():
200 | return text
201 |
202 | var start := 0
203 | var end := text.length()
204 |
205 | while start < end and text.substr(start, char.length()) == char:
206 | start += char.length()
207 |
208 | while end > start and text.substr(end - char.length(), char.length()) == char:
209 | end -= char.length()
210 |
211 | return text.substr(start, end - start)
212 |
213 |
214 | func get_variable_from_line(line_index: int, line_text : String, code_edit: CodeEdit) -> String:
215 | var parts = line_text.split("=")
216 | if parts.size() < 2:
217 | parts = line_text.split(".")
218 | if parts.size() < 2:
219 | return ""
220 |
221 | var left_part = parts[0].strip_edges()
222 |
223 | var name_token = left_part.split(":")[0].strip_edges()
224 | name_token = name_token.split(".")[0].strip_edges()
225 | if name_token == "":
226 | return ""
227 |
228 | return name_token
229 |
230 |
231 | ## Check for variable declaration in current line and all lines above.
232 | func find_variable_declaration_return_type(variable_name: String, line_index: int, line_text : String, code_edit: CodeEdit) -> String:
233 | for i in range(line_index, -1, -1):
234 | var prev_line = code_edit.get_line(i).strip_edges()
235 |
236 | if !prev_line.begins_with("var") and !prev_line.begins_with("@onready") and !prev_line.begins_with("@export"):
237 | continue
238 |
239 | if variable_name not in prev_line:
240 | continue
241 |
242 | var rest = prev_line.split(variable_name)[1].strip_edges()
243 | if rest.begins_with(":"):
244 | var type_part = rest.split("=")
245 | type_part = type_part[0].split(":")[1]
246 | return type_part.strip_edges()
247 |
248 | return ""
249 |
250 |
251 | func find_signal_declaration_parameters(line_index: int, line_text : String, code_edit: CodeEdit) -> String:
252 | var variable_name: String = get_variable_from_line(line_index, line_text, code_edit)
253 | var object_name: String = find_variable_declaration_return_type(variable_name, line_index, line_text, code_edit)
254 | if !object_name: return ""
255 |
256 | var instance: Variant
257 | if type_exists(object_name):
258 | instance = ClassDB.instantiate(object_name)
259 |
260 | if !instance or !instance.has_method("get_signal_list"):
261 | var class_finder = NhbFunctionsOnTheFlyClassFinder.new()
262 | var script_path: String = class_finder.find_class_path(object_name)
263 | class_finder.free()
264 | if !script_path: return ""
265 |
266 | instance = load(script_path).new()
267 |
268 | var signal_name = get_signal_name_by_line(line_text)
269 | if !instance.has_signal(signal_name): return ""
270 |
271 | var signal_parameters = []
272 | for signature in instance.get_signal_list():
273 | if signature.name != signal_name: continue
274 |
275 | signal_parameters = signature.args
276 |
277 | if signal_parameters.size() == 0:
278 | instance.free()
279 | return ""
280 |
281 | var signal_parameter_string_parts: Array = []
282 | for i: Dictionary in signal_parameters:
283 | if !i.has("class_name"):
284 | signal_parameter_string_parts.push_back(i.name)
285 | elif !i.class_name.is_empty():
286 | signal_parameter_string_parts.push_back("%s: %s" % [i.name, i.class_name])
287 | elif i.class_name.is_empty() and i.has("type"):
288 | signal_parameter_string_parts.push_back("%s: %s" % [i.name, type_string(i.type)])
289 |
290 | instance.free()
291 |
292 | return ", ".join(signal_parameter_string_parts)
293 |
294 |
295 | func find_function_parameters(function_name: String, line_index: int, line_text : String, code_edit: CodeEdit) -> String:
296 | var regex = RegEx.new()
297 | regex.compile(function_name + "(\\(.*\\))")
298 |
299 | var result = regex.search(line_text)
300 | if not result:
301 | return ""
302 |
303 | var arguments = result.get_string(1).lstrip("(").rstrip(")").split(",", false)
304 | var argumentString: String
305 |
306 | for i in arguments.size():
307 | arguments[i] = arguments[i].strip_edges()
308 | var argumentType = find_variable_declaration_return_type(arguments[i], line_index, line_text, code_edit)
309 | if argumentType:
310 | arguments[i] = "%s: %s" % [arguments[i], argumentType]
311 |
312 | return ", ".join(arguments)
313 |
314 |
315 | func get_signal_name_by_line(line_text: String) -> String:
316 | var parts = line_text.split(".connect")
317 | if parts.size() == 1: return ""
318 |
319 | parts = parts[0].split(".")
320 | if parts.size() == 1: return ""
321 |
322 | return parts[parts.size() - 1]
323 |
324 |
325 | func get_function_return_type(function_name: String, code_edit: CodeEdit):
326 | var current_line = get_current_line_text(code_edit).strip_edges()
327 |
328 | if current_line.begins_with(function_name):
329 | ## We cannot determine return type if function name is the only information in this line.
330 | return ""
331 |
332 | if current_line.contains("="):
333 | ## Function is tied to a variable. If this variable was initialized with a return type, we can use it.
334 | var variable_name: String = get_variable_from_line(code_edit.get_caret_line(), current_line, code_edit)
335 | return find_variable_declaration_return_type(variable_name, code_edit.get_caret_line(), current_line, code_edit)
336 |
337 | if current_line.contains(".connect"):
338 | return "void"
339 |
340 | return ""
341 |
342 |
343 | ## Lines with global variables do not start with whitespace.
344 | func is_global_variable(text: String, settings) -> bool:
345 | var indentation_character: String = get_indentation_character(settings)
346 |
347 | var regex = RegEx.new()
348 | regex.compile("^\\s")
349 |
350 | if not regex.search(text):
351 | return true
352 |
353 | return false
354 |
--------------------------------------------------------------------------------