A Quadrilateral Cowboy clone intended to help me learn Game Dev
at main 932 lines 34 kB view raw
1## Copyright (c) 2023-present Marius Hanl under the MIT License. 2## The editor plugin entrypoint for Script-IDE. 3## 4## Some features interfere with the editor code that is inside 'script_editor_plugin.cpp'. 5## That is, the original structure is changed by this plugin, in order to support everything. 6## The internals of the native C++ code are therefore important in order to make this plugin work 7## without interfering with the Engine itself (and their Node's). 8## 9## Script-IDE does not use global class_name's in order to not clutter projects using it. 10## Especially since this is an editor only plugin, we do not want this plugin in the final game. 11## Therefore, components that references the plugin is untyped. 12@tool 13extends EditorPlugin 14 15const BUILT_IN_SCRIPT: StringName = &"::GDScript" 16const QUICK_OPEN_INTERVAL: int = 400 17 18const MULTILINE_TAB_BAR: PackedScene = preload("tabbar/multiline_tab_bar.tscn") 19const MultilineTabBar := preload("tabbar/multiline_tab_bar.gd") 20 21const QUICK_OPEN_SCENE: PackedScene = preload("quickopen/quick_open_panel.tscn") 22const QuickOpenPopup := preload("quickopen/quick_open_panel.gd") 23 24const OVERRIDE_SCENE: PackedScene = preload("override/override_panel.tscn") 25const OverridePopup := preload("override/override_panel.gd") 26 27const Outline := preload("uid://db0be00ai3tfi") 28const SplitCodeEdit := preload("uid://boy48rhhyrph") 29 30#region Settings and Shortcuts 31## Editor setting path 32const SCRIPT_IDE: StringName = &"plugin/script_ide/" 33## Editor setting for the outline position 34const OUTLINE_POSITION_RIGHT: StringName = SCRIPT_IDE + &"outline_position_right" 35## Editor setting to control the order of the outline 36const OUTLINE_ORDER: StringName = SCRIPT_IDE + &"outline_order" 37## Editor setting to control whether private members (annotated with '_' should be hidden or not) 38const HIDE_PRIVATE_MEMBERS: StringName = SCRIPT_IDE + &"hide_private_members" 39## Editor setting to control whether we want to auto navigate to the script 40## in the filesystem (dock) when selected 41const AUTO_NAVIGATE_IN_FS: StringName = SCRIPT_IDE + &"auto_navigate_in_filesystem_dock" 42## Editor setting to control whether the script list should be visible or not 43const SCRIPT_LIST_VISIBLE: StringName = SCRIPT_IDE + &"script_list_visible" 44## Editor setting to control whether the script tabs should be visible or not. 45const SCRIPT_TABS_VISIBLE: StringName = SCRIPT_IDE + &"script_tabs_visible" 46## Editor setting to control where the script tabs should be. 47const SCRIPT_TABS_POSITION_TOP: StringName = SCRIPT_IDE + &"script_tabs_position_top" 48## Editor setting to control if all script tabs should have close button. 49const SCRIPT_TABS_CLOSE_BUTTON_ALWAYS: StringName = SCRIPT_IDE + &"script_tabs_close_button_always" 50## Editor setting to control if all tabs should be shown in a single line. 51const SCRIPT_TABS_SINGLELINE: StringName = SCRIPT_IDE + &"script_tabs_singleline" 52 53## Editor setting for the 'Open Outline Popup' shortcut 54const OPEN_OUTLINE_POPUP: StringName = SCRIPT_IDE + &"open_outline_popup" 55## Editor setting for the 'Open Scripts Popup' shortcut 56const OPEN_SCRIPTS_POPUP: StringName = SCRIPT_IDE + &"open_scripts_popup" 57## Editor setting for the 'Open Scripts Popup' shortcut 58const OPEN_QUICK_SEARCH_POPUP: StringName = SCRIPT_IDE + &"open_quick_search_popup" 59## Editor setting for the 'Open Override Popup' shortcut 60const OPEN_OVERRIDE_POPUP: StringName = SCRIPT_IDE + &"open_override_popup" 61## Editor setting for the 'Tab cycle forward' shortcut 62const TAB_CYCLE_FORWARD: StringName = SCRIPT_IDE + &"tab_cycle_forward" 63## Editor setting for the 'Tab cycle backward' shortcut 64const TAB_CYCLE_BACKWARD: StringName = SCRIPT_IDE + &"tab_cycle_backward" 65 66## Engine editor setting for the icon saturation, so our icons can react. 67const ICON_SATURATION: StringName = &"interface/theme/icon_saturation" 68## Engine editor setting for the show members functionality. 69const SHOW_MEMBERS: StringName = &"text_editor/script_list/show_members_overview" 70## We track the user setting, so we can restore it properly. 71var show_members: bool = true 72#endregion 73 74#region Editor settings 75var is_outline_right: bool = true 76var is_hide_private_members: bool = false 77 78var is_script_tabs_visible: bool = true 79var is_script_tabs_top: bool = true 80var is_script_tabs_close_button_always: bool = false 81var is_script_tabs_singleline: bool = false 82 83var is_auto_navigate_in_fs: bool = true 84var is_script_list_visible: bool = false 85 86var outline_order: PackedStringArray 87 88var open_outline_popup_shc: Shortcut 89var open_scripts_popup_shc: Shortcut 90var open_quick_search_popup_shc: Shortcut 91var open_override_popup_shc: Shortcut 92var tab_cycle_forward_shc: Shortcut 93var tab_cycle_backward_shc: Shortcut 94#endregion 95 96#region Existing controls we modify 97var script_editor_split_container: HSplitContainer 98var files_panel: Control 99 100var old_scripts_tab_container: TabContainer 101var old_scripts_tab_bar: TabBar 102 103var script_filter_txt: LineEdit 104var scripts_item_list: ItemList 105var script_panel_split_container: VSplitContainer 106 107var old_outline: ItemList 108var outline_filter_txt: LineEdit 109var sort_btn: Button 110#endregion 111 112#region Own controls we add 113var outline: Outline 114var outline_popup: PopupPanel 115var multiline_tab_bar: MultilineTabBar 116var scripts_popup: PopupPanel 117var quick_open_popup: QuickOpenPopup 118var override_popup: OverridePopup 119 120var tab_splitter: HSplitContainer 121#endregion 122 123#region Plugin variables 124var keywords: Dictionary[String, bool] = {} # Used as Set. 125 126var old_script_editor_base: ScriptEditorBase 127var old_script_type: StringName 128 129var is_script_changed: bool = false 130var file_to_navigate: String = &"" 131 132var quick_open_tween: Tween 133 134var suppress_settings_sync: bool = false 135#endregion 136 137#region Plugin Enter / Exit setup 138## Change the Engine script UI and transform into an IDE like UI 139func _enter_tree() -> void: 140 init_settings() 141 init_shortcuts() 142 143 # Update on filesystem changed (e.g. save operation). 144 var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() 145 file_system.filesystem_changed.connect(schedule_update) 146 147 # Sync settings changes for this plugin. 148 get_editor_settings().settings_changed.connect(sync_settings) 149 150 var script_editor: ScriptEditor = EditorInterface.get_script_editor() 151 script_editor_split_container = find_or_null(script_editor.find_children("*", "HSplitContainer", true, false)) 152 files_panel = script_editor_split_container.get_child(0) 153 154 # The 'Filter Scripts' Panel 155 var upper_files_panel: Control = files_panel.get_child(0) 156 # The 'Filter Methods' Panel 157 var lower_files_panel: Control = files_panel.get_child(1) 158 159 # Change script item list visibility (based on settings). 160 scripts_item_list = find_or_null(upper_files_panel.find_children("*", "ItemList", true, false)) 161 scripts_item_list.allow_reselect = true 162 scripts_item_list.item_selected.connect(hide_scripts_popup.unbind(1)) 163 update_script_list_visibility() 164 165 # Add script filter navigation. 166 script_filter_txt = find_or_null(scripts_item_list.get_parent().find_children("*", "LineEdit", true, false)) 167 script_filter_txt.gui_input.connect(navigate_on_list.bind(scripts_item_list, select_script)) 168 169 # --- Outline Start --- # 170 old_outline = find_or_null(lower_files_panel.find_children("*", "ItemList", true, false)) 171 lower_files_panel.remove_child(old_outline) 172 173 outline = Outline.new() 174 outline.plugin = self 175 176 # Add navigation to the filter and text filtering. 177 outline_filter_txt = find_or_null(lower_files_panel.find_children("*", "LineEdit", true, false)) 178 outline_filter_txt.gui_input.connect(navigate_on_list.bind(outline, scroll_outline)) 179 outline_filter_txt.text_changed.connect(update_outline.unbind(1)) 180 181 outline.outline_filter_txt = outline_filter_txt 182 lower_files_panel.add_child(outline) 183 184 outline.item_selected.connect(scroll_outline) 185 186 outline.get_parent().add_child(outline.filter_box) 187 outline.get_parent().move_child(outline.filter_box, outline.get_index()) 188 189 # Add callback when the sorting changed. 190 sort_btn = find_or_null(lower_files_panel.find_children("*", "Button", true, false)) 191 sort_btn.pressed.connect(update_outline) 192 193 update_outline_order() 194 update_outline_position() 195 # --- Outline End --- # 196 197 # --- Tabs Start --- # 198 old_scripts_tab_container = find_or_null(script_editor.find_children("*", "TabContainer", true, false)) 199 old_scripts_tab_bar = old_scripts_tab_container.get_tab_bar() 200 201 var tab_container_parent: Control = old_scripts_tab_container.get_parent() 202 tab_splitter = HSplitContainer.new() 203 tab_splitter.size_flags_horizontal = Control.SIZE_EXPAND_FILL 204 tab_splitter.size_flags_vertical = Control.SIZE_EXPAND_FILL 205 206 tab_container_parent.add_child(tab_splitter) 207 tab_container_parent.move_child(tab_splitter, 0) 208 old_scripts_tab_container.reparent(tab_splitter) 209 210 # When something changed, we need to sync our own tab container. 211 old_scripts_tab_container.child_order_changed.connect(notify_order_changed) 212 213 multiline_tab_bar = MULTILINE_TAB_BAR.instantiate() 214 multiline_tab_bar.plugin = self 215 multiline_tab_bar.scripts_item_list = scripts_item_list 216 multiline_tab_bar.script_filter_txt = script_filter_txt 217 multiline_tab_bar.scripts_tab_container = old_scripts_tab_container 218 219 tab_container_parent.add_theme_constant_override(&"separation", 0) 220 tab_container_parent.add_child(multiline_tab_bar) 221 222 multiline_tab_bar.split_btn.toggled.connect(toggle_split_view.unbind(1)) 223 update_tabs_position() 224 update_tabs_close_button() 225 update_tabs_visibility() 226 update_singleline_tabs() 227 228 # Create and set script popup. 229 script_panel_split_container = scripts_item_list.get_parent().get_parent() 230 create_set_scripts_popup() 231 # --- Tabs End --- # 232 233 old_scripts_tab_bar.tab_changed.connect(on_tab_changed) 234 on_tab_changed(old_scripts_tab_bar.current_tab) 235 236## Restore the old Engine script UI and free everything we created 237func _exit_tree() -> void: 238 var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() 239 file_system.filesystem_changed.disconnect(schedule_update) 240 get_editor_settings().settings_changed.disconnect(sync_settings) 241 242 if (tab_splitter != null): 243 var tab_container_parent: Control = tab_splitter.get_parent() 244 old_scripts_tab_container.reparent(tab_container_parent) 245 tab_container_parent.move_child(old_scripts_tab_container, 1) 246 tab_splitter.free() 247 248 if (script_editor_split_container != null): 249 if (script_editor_split_container != files_panel.get_parent()): 250 script_editor_split_container.add_child(files_panel) 251 252 # Try to restore the previous split offset. 253 if (is_outline_right): 254 var split_offset: float = script_editor_split_container.get_child(1).size.x 255 script_editor_split_container.split_offset = split_offset 256 257 script_editor_split_container.move_child(files_panel, 0) 258 259 outline_filter_txt.gui_input.disconnect(navigate_on_list) 260 outline_filter_txt.text_changed.disconnect(update_outline) 261 sort_btn.pressed.disconnect(update_outline) 262 263 outline.item_selected.disconnect(scroll_outline) 264 265 var outline_parent: Control = outline.get_parent() 266 outline_parent.remove_child(outline.filter_box) 267 outline_parent.remove_child(outline) 268 outline_parent.add_child(old_outline) 269 outline_parent.move_child(old_outline, -2) 270 271 outline.filter_box.free() 272 outline.free() 273 274 if (old_scripts_tab_bar != null): 275 old_scripts_tab_bar.tab_changed.disconnect(on_tab_changed) 276 277 if (old_scripts_tab_container != null): 278 old_scripts_tab_container.child_order_changed.disconnect(notify_order_changed) 279 old_scripts_tab_container.get_parent().remove_theme_constant_override(&"separation") 280 old_scripts_tab_container.get_parent().remove_child(multiline_tab_bar) 281 282 if (multiline_tab_bar != null): 283 multiline_tab_bar.free_tabs() 284 multiline_tab_bar.free() 285 scripts_popup.free() 286 287 if (scripts_item_list != null): 288 scripts_item_list.allow_reselect = false 289 scripts_item_list.item_selected.disconnect(hide_scripts_popup) 290 scripts_item_list.get_parent().visible = true 291 292 if (script_filter_txt != null): 293 script_filter_txt.gui_input.disconnect(navigate_on_list) 294 295 if (outline_popup != null): 296 outline_popup.free() 297 if (quick_open_popup != null): 298 quick_open_popup.free() 299 if (override_popup != null): 300 override_popup.free() 301 302 if (!show_members): 303 set_setting(SHOW_MEMBERS, show_members) 304#endregion 305 306#region Plugin and Shortcut processing 307## Lazy pattern to update the editor only once per frame 308func _process(delta: float) -> void: 309 update_editor() 310 set_process(false) 311 312## Process the user defined shortcuts 313func _shortcut_input(event: InputEvent) -> void: 314 if (!event.is_pressed() || event.is_echo()): 315 return 316 317 if (open_outline_popup_shc.matches_event(event)): 318 get_viewport().set_input_as_handled() 319 open_outline_popup() 320 elif (open_scripts_popup_shc.matches_event(event)): 321 get_viewport().set_input_as_handled() 322 open_scripts_popup() 323 elif (open_quick_search_popup_shc.matches_event(event)): 324 if (quick_open_tween != null && quick_open_tween.is_running()): 325 get_viewport().set_input_as_handled() 326 if (quick_open_tween != null): 327 quick_open_tween.kill() 328 329 quick_open_tween = create_tween() 330 quick_open_tween.tween_interval(0.1) 331 quick_open_tween.tween_callback(open_quick_search_popup) 332 quick_open_tween.tween_callback(func(): quick_open_tween = null) 333 else: 334 quick_open_tween = create_tween() 335 quick_open_tween.tween_interval(QUICK_OPEN_INTERVAL / 1000.0) 336 quick_open_tween.tween_callback(func(): quick_open_tween = null) 337 elif (open_override_popup_shc.matches_event(event)): 338 get_viewport().set_input_as_handled() 339 open_override_popup() 340 341## May cancels the quick search shortcut timer. 342func _input(event: InputEvent) -> void: 343 if (event is InputEventKey): 344 if (!open_quick_search_popup_shc.matches_event(event)): 345 if (quick_open_tween != null): 346 quick_open_tween.kill() 347 quick_open_tween = null 348#endregion 349 350#region Settings and Shortcut initialization 351 352## Initializes all settings. 353## Every setting can be changed while this plugin is active, which will override them. 354func init_settings(): 355 is_outline_right = get_setting(OUTLINE_POSITION_RIGHT, is_outline_right) 356 is_hide_private_members = get_setting(HIDE_PRIVATE_MEMBERS, is_hide_private_members) 357 is_script_list_visible = get_setting(SCRIPT_LIST_VISIBLE, is_script_list_visible) 358 is_auto_navigate_in_fs = get_setting(AUTO_NAVIGATE_IN_FS, is_auto_navigate_in_fs) 359 is_script_tabs_visible = get_setting(SCRIPT_TABS_VISIBLE, is_script_tabs_visible) 360 is_script_tabs_top = get_setting(SCRIPT_TABS_POSITION_TOP, is_script_tabs_top) 361 is_script_tabs_close_button_always = get_setting(SCRIPT_TABS_CLOSE_BUTTON_ALWAYS, is_script_tabs_close_button_always) 362 is_script_tabs_singleline = get_setting(SCRIPT_TABS_SINGLELINE, is_script_tabs_singleline) 363 364 outline_order = get_outline_order() 365 366 # Users may disabled this, but with this plugin, we want to show the new Outline. 367 # So we need to reenable it, but restore the old value on exit. 368 show_members = get_setting(SHOW_MEMBERS, true) 369 if (!show_members): 370 set_setting(SHOW_MEMBERS, true) 371 372## Initializes all shortcuts. 373## Every shortcut can be changed while this plugin is active, which will override them. 374func init_shortcuts(): 375 var editor_settings: EditorSettings = get_editor_settings() 376 if (!editor_settings.has_setting(OPEN_OUTLINE_POPUP)): 377 var shortcut: Shortcut = Shortcut.new() 378 var event: InputEventKey = InputEventKey.new() 379 event.device = -1 380 event.command_or_control_autoremap = true 381 event.keycode = KEY_O 382 383 shortcut.events = [ event ] 384 editor_settings.set_setting(OPEN_OUTLINE_POPUP, shortcut) 385 386 if (!editor_settings.has_setting(OPEN_SCRIPTS_POPUP)): 387 var shortcut: Shortcut = Shortcut.new() 388 var event: InputEventKey = InputEventKey.new() 389 event.device = -1 390 event.command_or_control_autoremap = true 391 event.keycode = KEY_U 392 393 shortcut.events = [ event ] 394 editor_settings.set_setting(OPEN_SCRIPTS_POPUP, shortcut) 395 396 if (!editor_settings.has_setting(OPEN_QUICK_SEARCH_POPUP)): 397 var shortcut: Shortcut = Shortcut.new() 398 var event: InputEventKey = InputEventKey.new() 399 event.device = -1 400 event.keycode = KEY_SHIFT 401 402 shortcut.events = [ event ] 403 editor_settings.set_setting(OPEN_QUICK_SEARCH_POPUP, shortcut) 404 405 if (!editor_settings.has_setting(OPEN_OVERRIDE_POPUP)): 406 var shortcut: Shortcut = Shortcut.new() 407 var event: InputEventKey = InputEventKey.new() 408 event.device = -1 409 event.keycode = KEY_INSERT 410 event.alt_pressed = true 411 412 shortcut.events = [ event ] 413 editor_settings.set_setting(OPEN_OVERRIDE_POPUP, shortcut) 414 415 if (!editor_settings.has_setting(TAB_CYCLE_FORWARD)): 416 var shortcut: Shortcut = Shortcut.new() 417 var event: InputEventKey = InputEventKey.new() 418 event.device = -1 419 event.keycode = KEY_TAB 420 event.ctrl_pressed = true 421 422 shortcut.events = [ event ] 423 editor_settings.set_setting(TAB_CYCLE_FORWARD, shortcut) 424 425 if (!editor_settings.has_setting(TAB_CYCLE_BACKWARD)): 426 var shortcut: Shortcut = Shortcut.new() 427 var event: InputEventKey = InputEventKey.new() 428 event.device = -1 429 event.keycode = KEY_TAB 430 event.shift_pressed = true 431 event.ctrl_pressed = true 432 433 shortcut.events = [ event ] 434 editor_settings.set_setting(TAB_CYCLE_BACKWARD, shortcut) 435 436 open_outline_popup_shc = editor_settings.get_setting(OPEN_OUTLINE_POPUP) 437 open_scripts_popup_shc = editor_settings.get_setting(OPEN_SCRIPTS_POPUP) 438 open_quick_search_popup_shc = editor_settings.get_setting(OPEN_QUICK_SEARCH_POPUP) 439 open_override_popup_shc = editor_settings.get_setting(OPEN_OVERRIDE_POPUP) 440 tab_cycle_forward_shc = editor_settings.get_setting(TAB_CYCLE_FORWARD) 441 tab_cycle_backward_shc = editor_settings.get_setting(TAB_CYCLE_BACKWARD) 442#endregion 443 444## Schedules an update on the next frame. 445func schedule_update(): 446 set_process(true) 447 448## Updates all parts of the editor that are needed to be synchronized with the file system change. 449func update_editor(): 450 if (file_to_navigate != &""): 451 EditorInterface.select_file(file_to_navigate) 452 EditorInterface.get_script_editor().get_current_editor().get_base_editor().grab_focus() 453 file_to_navigate = &"" 454 455 update_keywords() 456 457 if (is_script_changed): 458 multiline_tab_bar.tab_changed() 459 outline.tab_changed() 460 is_script_changed = false 461 else: 462 # We saved / filesystem changed. so need to update everything. 463 multiline_tab_bar.update_tabs() 464 outline.update() 465 466func on_tab_changed(index: int): 467 if (old_script_editor_base != null): 468 old_script_editor_base.edited_script_changed.disconnect(update_selected_tab) 469 old_script_editor_base = null 470 471 var script_editor: ScriptEditor = EditorInterface.get_script_editor() 472 var script_editor_base: ScriptEditorBase = script_editor.get_current_editor() 473 474 if (script_editor_base != null): 475 script_editor_base.edited_script_changed.connect(update_selected_tab) 476 477 old_script_editor_base = script_editor_base 478 479 if (!multiline_tab_bar.is_split()): 480 multiline_tab_bar.split_btn.disabled = script_editor_base == null 481 482 is_script_changed = true 483 484 if (is_auto_navigate_in_fs && script_editor.get_current_script() != null): 485 var file: String = script_editor.get_current_script().get_path() 486 487 if (file.contains(BUILT_IN_SCRIPT)): 488 # We navigate to the scene in case of a built-in script. 489 file = file.get_slice(BUILT_IN_SCRIPT, 0) 490 491 file_to_navigate = file 492 else: 493 file_to_navigate = &"" 494 495 schedule_update() 496 497func toggle_split_view(): 498 var script_editor: ScriptEditor = EditorInterface.get_script_editor() 499 var split_script_editor_base: ScriptEditorBase = script_editor.get_current_editor() 500 501 if (!multiline_tab_bar.is_split()): 502 if (split_script_editor_base == null): 503 return 504 505 var base_editor: Control = split_script_editor_base.get_base_editor() 506 if !(base_editor is CodeEdit): 507 return 508 509 multiline_tab_bar.set_split(script_editor.get_current_script()) 510 511 var editor: CodeEdit = SplitCodeEdit.new_from(base_editor) 512 513 var container: PanelContainer = PanelContainer.new() 514 container.custom_minimum_size.x = 200 515 container.size_flags_horizontal = Control.SIZE_EXPAND_FILL 516 517 container.add_child(editor) 518 tab_splitter.add_child(container) 519 else: 520 multiline_tab_bar.set_split(null) 521 tab_splitter.remove_child(tab_splitter.get_child(tab_splitter.get_child_count() - 1)) 522 523 if (split_script_editor_base == null): 524 multiline_tab_bar.split_btn.disabled = true 525 526func notify_order_changed(): 527 multiline_tab_bar.script_order_changed() 528 529func open_quick_search_popup(): 530 var pref_size: Vector2 531 if (quick_open_popup == null): 532 quick_open_popup = QUICK_OPEN_SCENE.instantiate() 533 quick_open_popup.plugin = self 534 quick_open_popup.set_unparent_when_invisible(true) 535 pref_size = Vector2(500, 400) * get_editor_scale() 536 else: 537 pref_size = quick_open_popup.size 538 539 quick_open_popup.popup_exclusive_on_parent(EditorInterface.get_script_editor(), get_center_editor_rect(pref_size)) 540 541func open_override_popup(): 542 var script: Script = get_current_script() 543 if (!script): 544 return 545 546 var pref_size: Vector2 547 if (override_popup == null): 548 override_popup = OVERRIDE_SCENE.instantiate() 549 override_popup.plugin = self 550 override_popup.outline = outline 551 override_popup.set_unparent_when_invisible(true) 552 pref_size = Vector2(500, 400) * get_editor_scale() 553 else: 554 pref_size = override_popup.size 555 556 override_popup.popup_exclusive_on_parent(EditorInterface.get_script_editor(), get_center_editor_rect(pref_size)) 557 558func hide_scripts_popup(): 559 if (scripts_popup != null && scripts_popup.visible): 560 scripts_popup.hide.call_deferred() 561 562func create_set_scripts_popup(): 563 scripts_popup = PopupPanel.new() 564 scripts_popup.popup_hide.connect(restore_scripts_list) 565 566 # Need to be inside the tree, so it can be shown as popup for the tab container. 567 var script_editor: ScriptEditor = EditorInterface.get_script_editor() 568 script_editor.add_child(scripts_popup) 569 570 multiline_tab_bar.set_popup(scripts_popup) 571 572func restore_scripts_list(): 573 script_filter_txt.text = &"" 574 575 update_script_list_visibility() 576 577 scripts_item_list.get_parent().reparent(script_panel_split_container) 578 script_panel_split_container.move_child(scripts_item_list.get_parent(), 0) 579 580func navigate_on_list(event: InputEvent, list: ItemList, submit: Callable): 581 if (event.is_action_pressed(&"ui_text_submit")): 582 list.accept_event() 583 584 var index: int = get_list_index(list) 585 if (index == -1): 586 return 587 588 submit.call(index) 589 list.accept_event() 590 elif (event.is_action_pressed(&"ui_down", true)): 591 var index: int = get_list_index(list) 592 if (index == list.item_count - 1): 593 return 594 595 navigate_list(list, index, 1) 596 elif (event.is_action_pressed(&"ui_up", true)): 597 var index: int = get_list_index(list) 598 if (index <= 0): 599 return 600 601 navigate_list(list, index, -1) 602 elif (event.is_action_pressed(&"ui_page_down", true)): 603 var index: int = get_list_index(list) 604 if (index == list.item_count - 1): 605 return 606 607 navigate_list(list, index, 5) 608 elif (event.is_action_pressed(&"ui_page_up", true)): 609 var index: int = get_list_index(list) 610 if (index <= 0): 611 return 612 613 navigate_list(list, index, -5) 614 elif (event is InputEventKey && list.item_count > 0 && !list.is_anything_selected()): 615 list.select(0) 616 617func get_list_index(list: ItemList) -> int: 618 var items: PackedInt32Array = list.get_selected_items() 619 620 if (items.is_empty()): 621 return -1 622 623 return items[0] 624 625func navigate_list(list: ItemList, index: int, amount: int): 626 index = clamp(index + amount, 0, list.item_count - 1) 627 628 list.select(index) 629 list.ensure_current_is_visible() 630 list.accept_event() 631 632func get_center_editor_rect(pref_size: Vector2) -> Rect2i: 633 var script_editor: ScriptEditor = EditorInterface.get_script_editor() 634 635 var position: Vector2 = script_editor.global_position + script_editor.size / 2 - pref_size / 2 636 637 return Rect2i(position, pref_size) 638 639func open_outline_popup(): 640 if (get_current_script() == null): 641 return 642 643 var button_flags: Array[bool] = outline.save_restore_filter() 644 645 var old_text: String = outline_filter_txt.text 646 outline_filter_txt.text = &"" 647 648 var pref_size: Vector2 649 if (outline_popup == null): 650 outline_popup = PopupPanel.new() 651 outline_popup.set_unparent_when_invisible(true) 652 pref_size = Vector2(500, 400) * get_editor_scale() 653 else: 654 pref_size = outline_popup.size 655 656 var outline_initially_closed: bool = !files_panel.visible 657 if (outline_initially_closed): 658 files_panel.visible = true 659 660 files_panel.reparent(outline_popup) 661 662 outline_popup.popup_hide.connect(on_outline_popup_hidden.bind(outline_initially_closed, old_text, button_flags)) 663 664 outline_popup.popup_exclusive_on_parent(EditorInterface.get_script_editor(), get_center_editor_rect(pref_size)) 665 666 update_outline() 667 outline_filter_txt.grab_focus() 668 669func on_outline_popup_hidden(outline_initially_closed: bool, old_text: String, button_flags: Array[bool]): 670 outline_popup.popup_hide.disconnect(on_outline_popup_hidden) 671 672 if outline_initially_closed: 673 files_panel.visible = false 674 675 files_panel.reparent(script_editor_split_container) 676 if (!is_outline_right): 677 script_editor_split_container.move_child(files_panel, 0) 678 679 outline_filter_txt.text = old_text 680 681 outline.restore_filter(button_flags) 682 683 update_outline() 684 685func open_scripts_popup(): 686 multiline_tab_bar.show_popup() 687 688func get_current_script() -> Script: 689 var script_editor: ScriptEditor = EditorInterface.get_script_editor() 690 return script_editor.get_current_script() 691 692func select_script(selected_idx: int): 693 hide_scripts_popup() 694 695 scripts_item_list.item_selected.emit(selected_idx) 696 697func scroll_outline(selected_idx: int): 698 if (outline_popup != null && outline_popup.visible): 699 outline_popup.hide.call_deferred() 700 701 var script: Script = get_current_script() 702 if (!script): 703 return 704 705 var text: String = outline.get_item_text(selected_idx) 706 var metadata: Dictionary[StringName, StringName] = outline.get_item_metadata(selected_idx) 707 var modifier: StringName = metadata[&"modifier"] 708 var type: StringName = metadata[&"type"] 709 710 var type_with_text: String = type + " " + text 711 if (type == &"func"): 712 type_with_text = type_with_text + "(" 713 714 var source_code: String = script.get_source_code() 715 var lines: PackedStringArray = source_code.split("\n") 716 717 var index: int = 0 718 for line: String in lines: 719 # Easy case, like 'var abc' 720 if (line.begins_with(type_with_text)): 721 goto_line(index) 722 return 723 724 # We have an modifier, e.g. 'static' 725 if (modifier != &"" && line.begins_with(modifier)): 726 if (line.begins_with(modifier + " " + type_with_text)): 727 goto_line(index) 728 return 729 # Special case: An 'enum' is treated different. 730 elif (modifier == &"enum" && line.contains("enum " + text)): 731 goto_line(index) 732 return 733 734 # Hard case, probably something like '@onready var abc' 735 if (type == &"var" && line.contains(type_with_text)): 736 goto_line(index) 737 return 738 739 index += 1 740 741 push_error(type_with_text + " or " + modifier + " not found in source code") 742 743func goto_line(index: int): 744 var script_editor: ScriptEditor = EditorInterface.get_script_editor() 745 script_editor.goto_line(index) 746 747 var code_edit: CodeEdit = script_editor.get_current_editor().get_base_editor() 748 code_edit.set_caret_line(index) 749 code_edit.set_v_scroll(index) 750 code_edit.set_caret_column(code_edit.get_line(index).length()) 751 code_edit.set_h_scroll(0) 752 753 code_edit.grab_focus() 754 755func update_script_list_visibility(): 756 scripts_item_list.get_parent().visible = is_script_list_visible 757 758func sync_settings(): 759 if (suppress_settings_sync): 760 return 761 762 var changed_settings: PackedStringArray = get_editor_settings().get_changed_settings() 763 for setting: String in changed_settings: 764 if (setting == ICON_SATURATION): 765 outline.reset_icons() 766 elif (setting == SHOW_MEMBERS): 767 show_members = get_setting(SHOW_MEMBERS, true) 768 if (!show_members): 769 set_setting(SHOW_MEMBERS, true) 770 771 if (!setting.begins_with(SCRIPT_IDE)): 772 continue 773 774 match (setting): 775 OUTLINE_POSITION_RIGHT: 776 var new_outline_right: bool = get_setting(OUTLINE_POSITION_RIGHT, is_outline_right) 777 if (new_outline_right != is_outline_right): 778 is_outline_right = new_outline_right 779 780 update_outline_position() 781 OUTLINE_ORDER: 782 var new_outline_order: PackedStringArray = get_outline_order() 783 if (new_outline_order != outline_order): 784 outline_order = new_outline_order 785 786 update_outline_order() 787 HIDE_PRIVATE_MEMBERS: 788 var new_hide_private_members: bool = get_setting(HIDE_PRIVATE_MEMBERS, is_hide_private_members) 789 if (new_hide_private_members != is_hide_private_members): 790 is_hide_private_members = new_hide_private_members 791 792 outline.update() 793 SCRIPT_LIST_VISIBLE: 794 var new_script_list_visible: bool = get_setting(SCRIPT_LIST_VISIBLE, is_script_list_visible) 795 if (new_script_list_visible != is_script_list_visible): 796 is_script_list_visible = new_script_list_visible 797 798 update_script_list_visibility() 799 SCRIPT_TABS_VISIBLE: 800 var new_script_tabs_visible: bool = get_setting(SCRIPT_TABS_VISIBLE, is_script_tabs_visible) 801 if (new_script_tabs_visible != is_script_tabs_visible): 802 is_script_tabs_visible = new_script_tabs_visible 803 804 update_tabs_visibility() 805 SCRIPT_TABS_POSITION_TOP: 806 var new_script_tabs_top: bool = get_setting(SCRIPT_TABS_POSITION_TOP, is_script_tabs_top) 807 if (new_script_tabs_top != is_script_tabs_top): 808 is_script_tabs_top = new_script_tabs_top 809 810 update_tabs_position() 811 SCRIPT_TABS_CLOSE_BUTTON_ALWAYS: 812 var new_script_tabs_close_button_always: bool = get_setting(SCRIPT_TABS_CLOSE_BUTTON_ALWAYS, is_script_tabs_close_button_always) 813 if (new_script_tabs_close_button_always != is_script_tabs_close_button_always): 814 is_script_tabs_close_button_always = new_script_tabs_close_button_always 815 816 update_tabs_close_button() 817 SCRIPT_TABS_SINGLELINE: 818 var new_script_tabs_singleline: bool = get_setting(SCRIPT_TABS_SINGLELINE, is_script_tabs_singleline) 819 if (new_script_tabs_singleline != is_script_tabs_singleline): 820 is_script_tabs_singleline = new_script_tabs_singleline 821 822 update_singleline_tabs() 823 AUTO_NAVIGATE_IN_FS: 824 is_auto_navigate_in_fs = get_setting(AUTO_NAVIGATE_IN_FS, is_auto_navigate_in_fs) 825 OPEN_OUTLINE_POPUP: 826 open_outline_popup_shc = get_shortcut(OPEN_OUTLINE_POPUP) 827 OPEN_SCRIPTS_POPUP: 828 open_scripts_popup_shc = get_shortcut(OPEN_SCRIPTS_POPUP) 829 OPEN_OVERRIDE_POPUP: 830 open_override_popup_shc = get_shortcut(OPEN_OVERRIDE_POPUP) 831 TAB_CYCLE_FORWARD: 832 tab_cycle_forward_shc = get_shortcut(TAB_CYCLE_FORWARD) 833 TAB_CYCLE_BACKWARD: 834 tab_cycle_backward_shc = get_shortcut(TAB_CYCLE_BACKWARD) 835 _: 836 outline.update_filter_buttons() 837 838func update_selected_tab(): 839 multiline_tab_bar.update_selected_tab() 840 841func update_tabs_position(): 842 var tab_container_parent: Control = multiline_tab_bar.get_parent() 843 if (is_script_tabs_top): 844 tab_container_parent.move_child(multiline_tab_bar, 0) 845 else: 846 tab_container_parent.move_child(multiline_tab_bar, tab_container_parent.get_child_count() - 1) 847 848func update_tabs_close_button(): 849 multiline_tab_bar.show_close_button_always = is_script_tabs_close_button_always 850 851func update_tabs_visibility(): 852 multiline_tab_bar.visible = is_script_tabs_visible 853 854func update_singleline_tabs(): 855 multiline_tab_bar.is_singleline_tabs = is_script_tabs_singleline 856 857func update_outline(): 858 outline.update_outline() 859 860func update_outline_position(): 861 if (is_outline_right): 862 # Try to restore the previous split offset. 863 var split_offset: float = script_editor_split_container.get_child(1).size.x 864 script_editor_split_container.split_offset = split_offset 865 script_editor_split_container.move_child(files_panel, 1) 866 else: 867 script_editor_split_container.move_child(files_panel, 0) 868 869func update_outline_order(): 870 outline.outline_order = outline_order 871 872func update_keywords(): 873 var script: Script = get_current_script() 874 if (script == null): 875 return 876 877 var new_script_type: StringName = script.get_instance_base_type() 878 if (old_script_type != new_script_type): 879 old_script_type = new_script_type 880 881 keywords.clear() 882 keywords["_static_init"] = true 883 register_virtual_methods(new_script_type) 884 885func register_virtual_methods(clazz: String): 886 for method: Dictionary in ClassDB.class_get_method_list(clazz): 887 if (method[&"flags"] & METHOD_FLAG_VIRTUAL > 0): 888 keywords[method[&"name"]] = true 889 890func get_editor_scale() -> float: 891 return EditorInterface.get_editor_scale() 892 893func get_editor_settings() -> EditorSettings: 894 return EditorInterface.get_editor_settings() 895 896func get_setting(property: StringName, alt: bool) -> bool: 897 var editor_settings: EditorSettings = get_editor_settings() 898 if (editor_settings.has_setting(property)): 899 return editor_settings.get_setting(property) 900 else: 901 editor_settings.set_setting(property, alt) 902 return alt 903 904func set_setting(property: StringName, value: bool): 905 var editor_settings: EditorSettings = get_editor_settings() 906 907 suppress_settings_sync = true 908 editor_settings.set_setting(property, value) 909 suppress_settings_sync = false 910 911func get_shortcut(property: StringName) -> Shortcut: 912 return get_editor_settings().get_setting(property) 913 914func get_outline_order() -> PackedStringArray: 915 var new_outline_order: PackedStringArray 916 var editor_settings: EditorSettings = get_editor_settings() 917 if (editor_settings.has_setting(OUTLINE_ORDER)): 918 new_outline_order = editor_settings.get_setting(OUTLINE_ORDER) 919 else: 920 new_outline_order = Outline.DEFAULT_ORDER 921 editor_settings.set_setting(OUTLINE_ORDER, outline_order) 922 923 return new_outline_order 924 925static func find_or_null(arr: Array[Node]) -> Control: 926 if (arr.is_empty()): 927 push_error("""Node that is needed for Script-IDE not found. 928Plugin will not work correctly. 929This might be due to some other plugins or changes in the Engine. 930Please report this to Script-IDE, so we can figure out a fix.""") 931 return null 932 return arr[0] as Control