Skip to content

Conversation

@raulsntos
Copy link
Member

@raulsntos raulsntos commented Sep 1, 2025

This plugin can provide source-code editing capabilities for GDExtension classes, such as:

  • Providing the path to source code files so they can be opened by the editor.
  • Opening source code files in external editors.
  • Creating class source code files.
  • Providing templates to use when creating source code files.
  • Editing class source code files to add callback methods when connecting signals to a non-existent method.

I also made a C++ plugin demo to show how the plugin would be implemented, it's a mock up so it's not entirely functional:

Screenshots
CreateCSharpClass CreateCppClass
SceneTree

@raulsntos raulsntos added this to the 4.x milestone Sep 1, 2025
@raulsntos raulsntos force-pushed the extension-source-code-plugin branch 2 times, most recently from 986d117 to 6ada699 Compare September 1, 2025 18:45
@raulsntos raulsntos force-pushed the extension-source-code-plugin branch from 6ada699 to 45fabe8 Compare October 6, 2025 05:29
@KoBeWi

This comment was marked as resolved.

Comment on lines 3581 to 3583
int item_idx = tree_popup->get_item_count() - 1;
tree_popup->set_item_disabled(item_idx, true);
tree_popup->set_item_tooltip(item_idx, "No extension source code plugins available.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int item_idx = tree_popup->get_item_count() - 1;
tree_popup->set_item_disabled(item_idx, true);
tree_popup->set_item_tooltip(item_idx, "No extension source code plugins available.");
tree_popup->set_item_disabled(-1, true);
tree_popup->set_item_tooltip(-1, "No extension source code plugins available.");

@raulsntos raulsntos force-pushed the extension-source-code-plugin branch from 45fabe8 to d53f65c Compare October 10, 2025 04:57
@raulsntos
Copy link
Member Author

Sorry about that, I didn't put too much effort in handling errors in the demo and just assumed it would be tested in a project that follows the directory structure of the GDExtension template. I updated the demo (raulsntos@97b425b) to fix the issues you mentioned, I also pushed an update to the PR to apply the suggestions from the review. Thanks!

@KoBeWi
Copy link
Member

KoBeWi commented Oct 10, 2025

I gave it a bit testing and managed to create a working class, although it wasn't easy. I encountered a few problems. They are probably related to the C++ plugin itself and out of scope, but some of them might be limitation of source plugins.

  • The user gets no feedback after the class file(s) are created. They are not visible in the FileSystem.
  • The plugin is a bit awkward to use with the C++ project template. I cloned it and opened the demo project. Extension classes are created inside demo folder, which requires some additional scons setup to have them properly compiled.
  • The class files are created the same regardless if script template is enabled or not. It does respect selected template though.
  • GDREGISTER_CLASS is added in the wrong place and does not add necessary includes.
  • The default cpp file uses res:// path in the include.
  • The Open Source Code button in Scene dock only allows opening cpp file, not h.

I don't know what plans are here exactly, but if EditorExtensionSourceCodePlugin is supposed to provide scripting-like experience to extensions, it's missing a few things:

  • The source files should be visible in FileSystem. Though probably not as Resources, just as allowed file extensions, like text files.
  • Creating extension class should open the new file(s) immediately.
  • There needs to be some way to either setup the project to run extensions, or at least detect that it's not prepared and provide instructions.

Also I'm not sure about showing two script buttons. IMO the option to edit extension from Scene dock should only show in the context menu. It would also allow opening both cpp and h files in C++ (by providing 2 options).

I'd be interested to test C# source plugin, as I imagine it will be more friendly to use than C++.

This method is only called if [method _overrides_external_editor] returns [code]true[/code].
</description>
</method>
<method name="_overrides_external_editor" qualifiers="virtual const">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GDVIRTUAL_IS_OVERRIDDEN should be enough for that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to let the plugin indicate if it can handle it or not, even if it overrides the _open_in_external_editor method.

For example, in C# we implement custom logic to open IDEs but it depends on a custom setting which may be set to "None". In this case we want to use this method to let Godot know the plugin doesn't have a preference for how to open files, and should just fall back to the default editor.

Returns the description for the given template ID. This description will be shown on the create new extension class dialog, and should provide more information about the template's purpose and usage.
</description>
</method>
<method name="_get_template_display_name" qualifiers="virtual const">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of multiple methods for getting template info, there could be one method _get_templates() expecting array of Dictionaries with template info (name, descriptions, files, options). Though it would be more friendly if we had structs support.

Plugins that do not use templates should still implement [method _create_class_source] if they can create source files according to [method _can_create_class_source] but don't need to implement [method _create_class_source_from_template_id] or [method _create_class_source_from_template_file].
</description>
</method>
<method name="_open_in_external_editor" qualifiers="virtual const">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The editor already has settings for configuring external editor. Why not just reuse them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that different languages would have different settings for external editor. If you have multiple languages installed, you might want to open some in one editor and some in another (or not use an external editor).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows the plugin to provide their own implementation, which allows us to keep compatibility with the mono module's settings.

The mono module has a setting that allows to select an IDE from a dropdown. We provide custom integration for these IDEs, some of which can't be simply opened with a command (like Visual Studio which requires using the EnvDTE library).

Also, as @vnen said, since the mono module provides its own setting, it means you can select a different editor for C# than the general one that will be used for GDScript files.

case NOTIFICATION_ENTER_TREE: {
// We use the same setting as the script templates intentionally, since it's the same feature
// just applied to extension classes so we want to remember the user's choice.
is_using_templates = EDITOR_DEF("_script_setup_use_script_templates", false);
Copy link
Member

@KoBeWi KoBeWi Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
is_using_templates = EDITOR_DEF("_script_setup_use_script_templates", false);
is_using_templates = EDITOR_GET("_script_setup_use_script_templates", false);


/* Templates */

gc->add_child(memnew(Label(TTR("Template:"))));
Copy link
Member

@KoBeWi KoBeWi Oct 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static strings should use TTRC.

Suggested change
gc->add_child(memnew(Label(TTR("Template:"))));
gc->add_child(memnew(Label(TTRC("Template:"))));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also go to extension folder.

empty_tree_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);

script_methods_only = memnew(CheckButton(TTR("Script Methods Only")));
script_methods_only = memnew(CheckButton(TTR("Script/Class Methods Only")));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about "Class"; someone might understand it as built-in methods, making the option confusing.
Maybe "Script/Extension"?

@vnen
Copy link
Member

vnen commented Oct 12, 2025

Also I'm not sure about showing two script buttons. IMO the option to edit extension from Scene dock should only show in the context menu. It would also allow opening both cpp and h files in C++ (by providing 2 options).

I disagree, IMO the workflow for extensions should be the same as scripts. The dialogs and options should be the same (e.g. it should be New script/extension class instead of two options). I even think you should be able to create an extension to an existing (though I admit this requires more plumbing for languages that need compilation, so I don't think this is a blocker for a first version).

As for opening headers, we can think of options. I think the easiest way is for the user to open the source and then use the editor to open the header. Of course it needs better source editor integration, but that would be required anyway if you plan to used the integrated editor. I also think C++ is an exception here, pretty much all languages will have 1 class = 1 file.

@raulsntos raulsntos force-pushed the extension-source-code-plugin branch from d53f65c to b6e3ac2 Compare October 20, 2025 04:08
@raulsntos
Copy link
Member Author

The user gets no feedback after the class file(s) are created. They are not visible in the FileSystem.

Since the FileSystem doesn't show non-Resource files, I don't know if there's much we can do here. Also, the C++ source files, at least when following the godot-cpp template directory structure, would be outside of the game project anyway.

For C# we can add the .cs extension to the list of allowed file extensions. But I guess this will only change it for users that didn't change the default?

The plugin is a bit awkward to use with the C++ project template. I cloned it and opened the demo project. Extension classes are created inside demo folder, which requires some additional scons setup to have them properly compiled.

Yeah, it wasn't my intention to provide a fully functional plugin for C++ projects, so I think this is out of scope.

The class files are created the same regardless if script template is enabled or not. It does respect selected template though.

It should fall back to the default template, but that's up to the plugin implementation. I believe the current implementation for creating scripts falls back to the "Empty" template so that's what I'm planning to do for C#.

GDREGISTER_CLASS is added in the wrong place and does not add necessary includes.

I don't know how to implement this properly, and it would probably be a lot of work. I think it's out of scope.

The default cpp file uses res:// path in the include.

Oops. I guess we can use ProjectSettings->globalize_path but it probably doesn't matter for a demo anyway. Although, maybe the parameter should always be the globalized path? Or should plugins be expected to globalize it themselves?

The Open Source Code button in Scene dock only allows opening cpp file, not h.

Yeah, unfortunately for languages that allow defining classes in multiple files. I had to choose one and decided opening the .cpp would be more useful. I was thinking we could show a dialog to select between the available files if there's more than one.

In C# this is less of a problem, but it can still happen if you use partial declarations. So we'll want to solve it, but I've often seen IDEs just open the first file they find in this case so I thought it'd be good enough for the initial implementation.

Creating extension class should open the new file(s) immediately.

This should now be fixed.

There needs to be some way to either setup the project to run extensions, or at least detect that it's not prepared and provide instructions.

In C# that's handled by our editor plugin, there's no additional setup. I think it's up to the language providers to add editor plugins as needed to handle this.

Also I'm not sure about showing two script buttons. IMO the option to edit extension from Scene dock should only show in the context menu. It would also allow opening both cpp and h files in C++ (by providing 2 options).

C# users want to see at a glance whether the node is one of their C# classes, for which the button provides a clear indication. It would also be a bit more inconvenient to open C# classes since it would now take 2 clicks instead of 1.

In practice, I don't know how often users would attach scripts to extension classes. So there may not be many cases where we're showing 2 buttons for the same Node.

I also think C++ is an exception here, pretty much all languages will have 1 class = 1 file.

@vnen Although it may not be very common, you can have N class = M files in C#. You can define multiple classes in a single file. You can also define a single class across multiple files using the partial keyword.

@raulsntos raulsntos force-pushed the extension-source-code-plugin branch 2 times, most recently from f6a1078 to 43b2965 Compare October 20, 2025 04:39
@KoBeWi
Copy link
Member

KoBeWi commented Oct 20, 2025

Since the FileSystem doesn't show non-Resource files, I don't know if there's much we can do here. For C# we can add the .cs extension to the list of allowed file extensions. But I guess this will only change it for users that didn't change the default?

EditorExtensionSourceCodePlugin could provide a list of extensions it manages and FileSystem dock would use them to show the files, similar to how textfile_extensions works.

Comment on lines +47 to +51
String EditorExtensionSourceCodePlugin::get_source_path(const StringName &p_class_name) const {
String path;
GDVIRTUAL_CALL(_get_source_path, p_class_name, path);
return path;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to this, I think we need to add something to the GDExtension interface to get information about the source code for a particular GDExtension. Like, it should be able to query the extension binding that was used (ex "godot-cpp"), the base path of the source code for that extension, and get the paths for classes that are already in the version that's loaded in the editor. I'll do some thinking about how I think this should look...

But that doesn't need to be in this PR, because I think we need what you already have here too, since we need to be able to find the source paths for new (or moved) classes, not just those that are already compiled into an extension

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to this, I think we need to add something to the GDExtension interface to get information about the source code for a particular GDExtension

I just posted draft PR #111922 with a quick sketch of how this could look

Comment on lines +117 to +121
virtual Error create_class_source(const String &p_class_name, const String &p_base_class_name, const PackedStringArray &p_paths) const;
virtual Error create_class_source_from_template_id(const String &p_class_name, const String &p_base_class_name, const PackedStringArray &p_paths, const String &p_template_id, const Dictionary &p_template_options) const;
virtual Error create_class_source_from_template_file(const String &p_class_name, const String &p_base_class_name, const PackedStringArray &p_paths, const String &p_template_path);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this meant to account for multiple GDExtensions from the same language?

Like, I'm envisioning that there would be one EditorExtensionSourceCodePlugin for godot-cpp, but you could have multiple GDExtensions using godot-cpp in your project.

I feel like these functions need to be able to "target" a specific extension. Or, did you have a different idea in mind for how this would work?

#include "core/object/class_db.h"
#include "core/object/object.h"

class ValidationContext : public Object {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ValidationContext feels like an overly generic name, that could conflict with future Godot classes or classes in user projects.

Maybe SourceCodeValidationContext? Or, even throw Editor in there somewhere?

Copy link
Member

@KoBeWi KoBeWi Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a very generic class intended to be used with EditorValidationPanel.
EditorValidationContext makes sense probably.

}

for (const Ref<EditorExtensionSourceCodePlugin> &plugin : plugins) {
if (plugin->can_handle_object(p_object)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this pass in the library to check if this one has source available? Not all GDExtensions in use will have their source as part of the project (some are just 3rd party stuff), and Object::get_extension_library() isn't exposed, so the plugins can't call that method themselves. (Another option would be to expose it?)

This plugin can provide source-code editing capabilities for GDExtension classes, such as:

- Providing the path to source code files so they can be opened by the editor.
- Opening source code files in external editors.
- Creating class source code files.
- Providing templates to use when creating source code files.
- Editing class source code files to add callback methods when connecting signals to a non-existent method.
Adds support for the following capabilities:

- Opening source code files in external editor when double-clicking in the FileSystem dock.
- Replacing nodes in scene tree by drag-n-drop a source code file from FileSystem dock.
@raulsntos raulsntos force-pushed the extension-source-code-plugin branch from 43b2965 to 032b78f Compare November 17, 2025 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow providing source code editing capabilities for GDExtension classes

4 participants