Files
agame/addons/GDQuest_GDScript_formatter/menu.gd
T
2026-09-12 02:07:17 +08:00

167 lines
5.7 KiB
GDScript

## This module handles adding a menu to the script editor with formatter commands.
## It safely locates the script editor's menu bar and adds our custom menu.
@tool
extends Node
signal menu_item_selected(command: String)
const MENU_TEXT = "Format"
const MENU_ITEMS = {
"format_script": "Format Current Script",
"lint_script": "Lint Current Script",
"reorder_code": "Reorder Code",
"install_update": "Install or Update Formatter",
"uninstall": "Uninstall Formatter",
"report_issue": "Report Issue",
"help": "Help",
}
var menu_button: MenuButton = null
var popup_menu: PopupMenu = null
func _ready() -> void:
# At the start we insert the menu in the script editor
var script_editor := EditorInterface.get_script_editor()
var last_menu_button := _find_last_menu_button(script_editor)
if not is_instance_valid(last_menu_button):
push_warning(
"GDScript Formatter: Could not find valid menu button in script editor. Menu will not be available. Use the command palette instead."
)
return
menu_button = MenuButton.new()
menu_button.text = MENU_TEXT
menu_button.switch_on_hover = true
menu_button.flat = false
menu_button.theme_type_variation = &"FlatMenuButton"
popup_menu = menu_button.get_popup()
_populate_menu()
popup_menu.id_pressed.connect(_on_menu_item_pressed)
last_menu_button.add_sibling(menu_button)
## Cleans up the menu from the script editor. Call this when disabling the
## plugin (in _exit_tree()).
func remove_formatter_menu() -> void:
if is_instance_valid(menu_button):
if is_instance_valid(popup_menu):
popup_menu.id_pressed.disconnect(_on_menu_item_pressed)
menu_button.queue_free()
menu_button = null
popup_menu = null
func update_menu(show_uninstall: bool) -> void:
if not is_instance_valid(popup_menu):
return
popup_menu.clear()
_populate_menu(show_uninstall)
## Searches for and returns the last menu node in the script editor top menu bar,
## or null if not found.
func _find_last_menu_button(script_editor: Control) -> MenuButton:
# The first child of the script editor should be a VBoxContainer (main container for the script editor main screen)
# Then the first child of that container should be an HBoxContainer (the menu bar)
# This is based on the current structure of Godot's script editor as of Godot 4.5
# Note: We add multiple checks in there with null returns mainly in case something changes in a future
if script_editor.get_child_count() == 0:
return null
var main_container := script_editor.get_child(0)
if not is_instance_valid(main_container) or not main_container is VBoxContainer:
return null
if main_container.get_child_count() == 0:
return null
var menu_bar := main_container.get_child(0)
if not is_instance_valid(menu_bar) or not menu_bar is HBoxContainer:
return null
# We reached the menu bar. So now we loop through all the children look for
# the last menu button, which would be the last menu in the menu list.
# Note: Here the goal is to insert the menu after the debug menu, but we
# don't check for the button text because "debug" would only work in English
# this should work in any language.
var last_menu_button: MenuButton = null
for child in menu_bar.get_children():
if child is MenuButton:
last_menu_button = child as MenuButton
return last_menu_button
func _populate_menu(show_uninstall: bool = true) -> void:
if not is_instance_valid(popup_menu):
return
var current_item_index := 0
popup_menu.add_item(MENU_ITEMS["format_script"], current_item_index)
popup_menu.set_item_metadata(current_item_index, "format_script")
popup_menu.set_item_tooltip(
current_item_index,
"Run the GDScript Formatter over the current script",
)
current_item_index += 1
popup_menu.add_item(MENU_ITEMS["lint_script"], current_item_index)
popup_menu.set_item_metadata(current_item_index, "lint_script")
popup_menu.set_item_tooltip(current_item_index, "Check the current script for linting issues")
current_item_index += 1
popup_menu.add_item(MENU_ITEMS["reorder_code"], current_item_index)
popup_menu.set_item_metadata(current_item_index, "reorder_code")
popup_menu.set_item_tooltip(
current_item_index,
"Reorder the code elements in the current script according to the GDScript Style Guide",
)
popup_menu.add_separator()
# NOTE: When we add separators, it bumps the internal index of menu items.
# That's why we have to increase it even on separators. Otherwise the
# tooltip will lose sync.
current_item_index += 2
popup_menu.add_item(MENU_ITEMS["install_update"], current_item_index)
popup_menu.set_item_metadata(current_item_index, "install_update")
popup_menu.set_item_tooltip(
current_item_index,
"Download the latest version of the GDScript Formatter",
)
if show_uninstall:
current_item_index += 1
popup_menu.add_item(MENU_ITEMS["uninstall"], current_item_index)
popup_menu.set_item_metadata(current_item_index, "uninstall")
popup_menu.set_item_tooltip(
current_item_index,
"Remove the GDScript Formatter installed through this add-on from your computer",
)
popup_menu.add_separator()
# Bumped index by 1 extra step because of the previous separator.
current_item_index += 2
popup_menu.add_item(MENU_ITEMS["report_issue"], current_item_index)
popup_menu.set_item_metadata(current_item_index, "report_issue")
popup_menu.set_item_tooltip(current_item_index, "Tell us about problems or bugs you found")
current_item_index += 1
popup_menu.add_item(MENU_ITEMS["help"], current_item_index)
popup_menu.set_item_metadata(current_item_index, "help")
popup_menu.set_item_tooltip(current_item_index, "Learn how to use the GDScript Formatter")
func _on_menu_item_pressed(id: int) -> void:
if not is_instance_valid(popup_menu):
return
var command: String = popup_menu.get_item_metadata(id)
if not command.is_empty():
menu_item_selected.emit(command)