Skip to content

Commit 9e32e1e

Browse files
magian1127Delsin-Yu
andcommitted
C#: Documentation on supporting C# scripts in the Godot editor.
Co-authored-by: DE YU <[email protected]> Co-authored-by: Magian <[email protected]>
1 parent 570577a commit 9e32e1e

File tree

18 files changed

+1424
-17
lines changed

18 files changed

+1424
-17
lines changed

editor/file_system/editor_file_system.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,6 +2274,10 @@ bool EditorFileSystem::_should_reload_script(const String &p_path) {
22742274
return false;
22752275
}
22762276

2277+
if (scr->get_language()->get_name() == "C#") {
2278+
return false;
2279+
}
2280+
22772281
// Scripts are reloaded via the script editor if they are currently opened.
22782282
if (ScriptEditor::get_singleton()->get_open_scripts().has(scr)) {
22792283
return false;

editor/scene/connections_dialog.cpp

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,6 @@ void ConnectionsDock::update_tree() {
14961496
}
14971497

14981498
TreeItem *root = tree->create_item();
1499-
DocTools *doc_data = EditorHelp::get_doc_data();
15001499
EditorData &editor_data = EditorNode::get_editor_data();
15011500
StringName native_base = selected_node->get_class();
15021501
Ref<Script> script_base = selected_node->get_script();
@@ -1513,12 +1512,12 @@ void ConnectionsDock::update_tree() {
15131512
class_name = script_base->get_path().get_file();
15141513
}
15151514

1516-
doc_class_name = script_base->get_global_name();
1515+
doc_class_name = script_base->get_doc_class_name();
15171516
if (doc_class_name.is_empty()) {
1518-
doc_class_name = script_base->get_path().trim_prefix("res://").quote();
1519-
}
1520-
if (!doc_class_name.is_empty() && !doc_data->class_list.find(doc_class_name)) {
1521-
doc_class_name = String();
1517+
doc_class_name = script_base->get_global_name();
1518+
if (doc_class_name.is_empty()) {
1519+
doc_class_name = script_base->get_path().trim_prefix("res://").quote();
1520+
}
15221521
}
15231522

15241523
class_icon = editor_data.get_script_icon(script_base->get_path());
@@ -1551,10 +1550,6 @@ void ConnectionsDock::update_tree() {
15511550
class_name = native_base;
15521551
doc_class_name = native_base;
15531552

1554-
if (!doc_data->class_list.find(doc_class_name)) {
1555-
doc_class_name = String();
1556-
}
1557-
15581553
if (has_theme_icon(native_base, EditorStringName(EditorIcons))) {
15591554
class_icon = get_editor_theme_icon(native_base);
15601555
}

modules/mono/csharp_script.cpp

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,10 @@ Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p
10441044
bool CSharpLanguage::overrides_external_editor() {
10451045
return get_godotsharp_editor()->call("OverridesExternalEditor");
10461046
}
1047+
1048+
bool CSharpLanguage::supports_documentation() const {
1049+
return true;
1050+
}
10471051
#endif
10481052

10491053
bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
@@ -2080,9 +2084,15 @@ void GD_CLR_STDCALL CSharpScript::_add_property_info_list_callback(CSharpScript
20802084
GDMonoCache::godotsharp_property_info *props = (GDMonoCache::godotsharp_property_info *)p_props;
20812085

20822086
#ifdef TOOLS_ENABLED
2083-
p_script->exported_members_cache.push_back(PropertyInfo(
2084-
Variant::NIL, p_script->type_info.class_name, PROPERTY_HINT_NONE,
2085-
p_script->get_path(), PROPERTY_USAGE_CATEGORY));
2087+
if (p_script->get_path().is_empty()) {
2088+
p_script->exported_members_cache.push_back(PropertyInfo(
2089+
Variant::NIL, p_script->type_info.class_name, PROPERTY_HINT_NONE,
2090+
String("res://") + String(p_script->doc_class_name).lstrip("\"").rstrip("\""), PROPERTY_USAGE_CATEGORY));
2091+
} else {
2092+
p_script->exported_members_cache.push_back(PropertyInfo(
2093+
Variant::NIL, p_script->type_info.class_name, PROPERTY_HINT_NONE,
2094+
p_script->get_path(), PROPERTY_USAGE_CATEGORY));
2095+
}
20862096
#endif
20872097

20882098
for (int i = 0; i < p_count; i++) {
@@ -2120,6 +2130,38 @@ void GD_CLR_STDCALL CSharpScript::_add_property_default_values_callback(CSharpSc
21202130
p_script->exported_members_defval_cache[name] = value;
21212131
}
21222132
}
2133+
2134+
void CSharpScript::get_docs(Ref<CSharpScript> p_script) {
2135+
Dictionary class_doc_dict;
2136+
class_doc_dict.~Dictionary();
2137+
2138+
GDMonoCache::managed_callbacks.ScriptManagerBridge_GetDocs(p_script.ptr(), &class_doc_dict);
2139+
2140+
p_script->docs.clear();
2141+
2142+
if (class_doc_dict.is_empty()) {
2143+
// Script has no docs.
2144+
return;
2145+
}
2146+
2147+
String inherits;
2148+
Ref<CSharpScript> base = p_script->get_base_script();
2149+
if (base.is_null()) {
2150+
// Must be a native base type.
2151+
inherits = p_script->get_instance_base_type();
2152+
} else {
2153+
inherits = base->get_global_name();
2154+
if (inherits == StringName()) {
2155+
inherits = base->get_path().trim_prefix("res://").quote();
2156+
}
2157+
}
2158+
2159+
DocData::ClassDoc class_doc = DocData::ClassDoc().from_dict(class_doc_dict);
2160+
class_doc.inherits = inherits;
2161+
2162+
p_script->doc_class_name = class_doc.name;
2163+
p_script->docs.append(class_doc);
2164+
}
21232165
#endif
21242166

21252167
bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) {
@@ -2344,6 +2386,10 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
23442386
}
23452387

23462388
p_script->base_script = base_script;
2389+
2390+
#ifdef TOOLS_ENABLED
2391+
get_docs(p_script);
2392+
#endif
23472393
}
23482394

23492395
bool CSharpScript::can_instantiate() const {

modules/mono/csharp_script.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ class CSharpScript : public Script {
197197
bool exports_invalidated = true;
198198
void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames);
199199
void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override;
200+
StringName doc_class_name;
201+
Vector<DocData::ClassDoc> docs;
200202
#endif
201203

202204
#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
@@ -210,6 +212,7 @@ class CSharpScript : public Script {
210212
static void GD_CLR_STDCALL _add_property_info_list_callback(CSharpScript *p_script, const String *p_current_class_name, void *p_props, int32_t p_count);
211213
#ifdef TOOLS_ENABLED
212214
static void GD_CLR_STDCALL _add_property_default_values_callback(CSharpScript *p_script, void *p_def_vals, int32_t p_count);
215+
static void get_docs(Ref<CSharpScript> p_script);
213216
#endif
214217
bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr);
215218

@@ -242,10 +245,8 @@ class CSharpScript : public Script {
242245
void set_source_code(const String &p_code) override;
243246

244247
#ifdef TOOLS_ENABLED
245-
virtual StringName get_doc_class_name() const override { return StringName(); } // TODO
248+
virtual StringName get_doc_class_name() const override { return doc_class_name; }
246249
virtual Vector<DocData::ClassDoc> get_documentation() const override {
247-
// TODO
248-
Vector<DocData::ClassDoc> docs;
249250
return docs;
250251
}
251252
virtual String get_class_icon_path() const override {
@@ -572,6 +573,7 @@ class CSharpLanguage : public ScriptLanguage {
572573
#ifdef TOOLS_ENABLED
573574
Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) override;
574575
bool overrides_external_editor() override;
576+
virtual bool supports_documentation() const override;
575577
#endif
576578

577579
RBMap<Object *, CSharpScriptBinding>::Element *insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace Godot.SourceGenerators.Sample;
2+
3+
/// <summary>
4+
/// class description
5+
/// test
6+
/// </summary>
7+
public partial class ClassDoc : GodotObject
8+
{
9+
/// <summary>
10+
/// field description
11+
/// test
12+
/// </summary>
13+
[Export]
14+
private int _fieldDocTest = 1;
15+
16+
/// <summary>
17+
/// property description
18+
/// test
19+
/// </summary>
20+
[Export]
21+
public int PropertyDocTest { get; set; }
22+
23+
/// <summary>
24+
/// signal description
25+
/// test
26+
/// </summary>
27+
/// <param name="num"></param>
28+
[Signal]
29+
public delegate void SignalDocTestEventHandler(int num);
30+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Xunit;
2+
3+
namespace Godot.SourceGenerators.Tests;
4+
5+
public class ScriptDocsGeneratorTests
6+
{
7+
[Fact]
8+
public async void Docs()
9+
{
10+
await CSharpSourceGeneratorVerifier<ScriptDocsGenerator>.Verify(
11+
"ClassDoc.cs",
12+
"ClassDoc_ScriptDocs.generated.cs"
13+
);
14+
}
15+
16+
[Fact]
17+
public async void AllDocs()
18+
{
19+
await CSharpSourceGeneratorVerifier<ScriptDocsGenerator>.Verify(
20+
"ClassAllDoc.cs",
21+
"ClassAllDoc_ScriptDocs.generated.cs"
22+
);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
partial class ClassAllDoc
2+
{
3+
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
4+
#if TOOLS
5+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
6+
internal new static global::Godot.Collections.Dictionary GetGodotClassDocs()
7+
{
8+
var docs = new global::Godot.Collections.Dictionary();
9+
docs.Add("name","\"ClassAllDoc.cs\"");
10+
docs.Add("brief_description",@"class description test");
11+
docs.Add("description",@"class description test");
12+
13+
var propertyDocs = new global::Godot.Collections.Array();
14+
propertyDocs.Add(new global::Godot.Collections.Dictionary { { "name", PropertyName.PropertyDocTest }, { @"type", @"int" }, { "description", @"property description test [code]ClassAllDoc[/code]" }});
15+
propertyDocs.Add(new global::Godot.Collections.Dictionary { { "name", PropertyName._fieldDocTest }, { @"type", @"int" }, { "description", @"field description [code]true[/code] test [code]ClassAllDoc[/code]" }});
16+
docs.Add("properties", propertyDocs);
17+
18+
var signalDocs = new global::Godot.Collections.Array();
19+
signalDocs.Add(new global::Godot.Collections.Dictionary { { "name", SignalName.SignalDocTest }, { "description", @"signal description ~!@#$%^*()_+{}| test [code]ClassAllDoc[/code][br][br][b]Parameters:[/b][br] • [b]num[/b]:" }});
20+
docs.Add("signals", signalDocs);
21+
22+
docs.Add("is_script_doc", true);
23+
24+
docs.Add("script_path", "ClassAllDoc.cs");
25+
26+
return docs;
27+
}
28+
29+
#endif // TOOLS
30+
#pragma warning restore CS0109
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
partial class ClassDoc
2+
{
3+
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
4+
#if TOOLS
5+
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
6+
internal new static global::Godot.Collections.Dictionary GetGodotClassDocs()
7+
{
8+
var docs = new global::Godot.Collections.Dictionary();
9+
docs.Add("name","\"ClassDoc.cs\"");
10+
docs.Add("brief_description",@"This is the class documentation.");
11+
docs.Add("description",@"This is the class documentation.");
12+
13+
var propertyDocs = new global::Godot.Collections.Array();
14+
propertyDocs.Add(new global::Godot.Collections.Dictionary { { "name", PropertyName.MyProperty }, { @"type", @"int" }, { "description", @"There is currently no description for this property." }});
15+
docs.Add("properties", propertyDocs);
16+
17+
docs.Add("is_script_doc", true);
18+
19+
docs.Add("script_path", "ClassDoc.cs");
20+
21+
return docs;
22+
}
23+
24+
#endif // TOOLS
25+
#pragma warning restore CS0109
26+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Godot;
2+
3+
/// <summary>
4+
/// class description
5+
/// test
6+
/// </summary>
7+
public partial class ClassAllDoc : GodotObject
8+
{
9+
/// <summary>
10+
/// field description <c>true</c>
11+
/// test <see cref="ClassAllDoc"/>
12+
/// </summary>
13+
[Export]
14+
private int _fieldDocTest = 1;
15+
16+
/// <summary>
17+
/// property description
18+
/// test <see cref="ClassAllDoc"/>
19+
/// </summary>
20+
[Export]
21+
public int PropertyDocTest { get; set; }
22+
23+
/// <summary>
24+
/// signal description ~!@#$%^*()_+{}|
25+
/// test <see cref="ClassAllDoc"/>
26+
/// </summary>
27+
/// <param name="num"></param>
28+
[Signal]
29+
public delegate void SignalDocTestEventHandler(int num);
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Godot;
2+
3+
/// <summary>
4+
/// This is the class documentation.
5+
/// </summary>
6+
public partial class ClassDoc : GodotObject
7+
{
8+
[Export]
9+
public int MyProperty { get; set; }
10+
}

0 commit comments

Comments
 (0)