Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#: Implement tooltips for Signals and Properties in the inspector. #83505

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions editor/editor_file_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2016,6 +2016,10 @@ bool EditorFileSystem::_should_reload_script(const String &p_path) {
return false;
}

if (scr->get_language()->get_name() == "C#") {
return false;
}

// Scripts are reloaded via the script editor if they are currently opened.
if (ScriptEditor::get_singleton()->get_open_scripts().has(scr)) {
return false;
Expand Down
71 changes: 70 additions & 1 deletion modules/mono/csharp_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,10 @@ Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p
bool CSharpLanguage::overrides_external_editor() {
return get_godotsharp_editor()->call("OverridesExternalEditor");
}

bool CSharpLanguage::supports_documentation() const {
return true;
}
#endif

bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
Expand Down Expand Up @@ -2100,6 +2104,56 @@ void GD_CLR_STDCALL CSharpScript::_add_property_default_values_callback(CSharpSc
p_script->exported_members_defval_cache[name] = value;
}
}

void CSharpScript::get_docs(Ref<CSharpScript> p_script) {
Dictionary class_doc_dict;
class_doc_dict.~Dictionary();

GDMonoCache::managed_callbacks.ScriptManagerBridge_GetDocs(p_script.ptr(), &class_doc_dict);

p_script->docs.clear();

if (class_doc_dict.is_empty()) {
// Script has no docs.
return;
}

String inherits;
Ref<CSharpScript> base = p_script->get_base_script();
if (base.is_null()) {
// Must be a native base type.
inherits = p_script->get_instance_base_type();
} else {
inherits = base->get_global_name();
if (inherits == StringName()) {
inherits = base->get_path();
}
}

DocData::ClassDoc class_doc = DocData::ClassDoc();
magian1127 marked this conversation as resolved.
Show resolved Hide resolved
class_doc.inherits = inherits;
class_doc.name = class_doc_dict["name"];
class_doc.brief_description = class_doc_dict["description"];
class_doc.is_script_doc = true;

Array property_docs_dict = class_doc_dict["properties"];
for (Dictionary property_doc_dict : property_docs_dict) {
DocData::PropertyDoc prop_doc;
prop_doc.name = property_doc_dict["name"];
prop_doc.description = property_doc_dict["description"];
class_doc.properties.push_back(prop_doc);
}

Array signals_docs_dict = class_doc_dict["signals"];
for (Dictionary signals_doc_dict : signals_docs_dict) {
DocData::MethodDoc signal_doc;
signal_doc.name = signals_doc_dict["name"];
signal_doc.description = signals_doc_dict["description"];
class_doc.signals.push_back(signal_doc);
}

p_script->docs.append(class_doc);
}
#endif

bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) {
Expand All @@ -2120,7 +2174,11 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
#endif
{
#ifdef TOOLS_ENABLED
exports_invalidated = false;
if (!get_path().is_empty()) {
// If the script doesn't have a path we can't properly add the CATEGORY PropertyInfo,
// so we keep the exports invalidated so the next update can fix them.
exports_invalidated = false;
}
#endif

changed = true;
Expand Down Expand Up @@ -2223,6 +2281,15 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
p_script->_update_exports();

#ifdef TOOLS_ENABLED
get_docs(p_script);
Vector<DocData::ClassDoc> docs = p_script->docs;
// At this point, `EditorFileSystem::_update_script_documentation()` cannot be triggered.
if (EditorHelp::get_doc_data()) {
for (int i = 0; i < docs.size(); i++) {
EditorHelp::get_doc_data()->add_doc(docs[i]);
}
}

// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
Expand Down Expand Up @@ -2603,6 +2670,8 @@ Error CSharpScript::reload(bool p_keep_state) {
_update_exports();

#ifdef TOOLS_ENABLED
get_docs(this);

// If the EditorFileSystem singleton is available, update the file;
// otherwise, the file will be updated when the singleton becomes available.
EditorFileSystem *efs = EditorFileSystem::get_singleton();
Expand Down
6 changes: 4 additions & 2 deletions modules/mono/csharp_script.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ class CSharpScript : public Script {
bool exports_invalidated = true;
void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames);
void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override;

Vector<DocData::ClassDoc> docs;
#endif

#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
Expand All @@ -206,6 +208,7 @@ class CSharpScript : public Script {
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);
#ifdef TOOLS_ENABLED
static void GD_CLR_STDCALL _add_property_default_values_callback(CSharpScript *p_script, void *p_def_vals, int32_t p_count);
static void get_docs(Ref<CSharpScript> p_script);
#endif
bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr);

Expand Down Expand Up @@ -239,8 +242,6 @@ class CSharpScript : public Script {

#ifdef TOOLS_ENABLED
virtual Vector<DocData::ClassDoc> get_documentation() const override {
// TODO
Vector<DocData::ClassDoc> docs;
return docs;
}
virtual String get_class_icon_path() const override {
Expand Down Expand Up @@ -567,6 +568,7 @@ class CSharpLanguage : public ScriptLanguage {
#ifdef TOOLS_ENABLED
Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) override;
bool overrides_external_editor() override;
virtual bool supports_documentation() const override;
#endif

RBMap<Object *, CSharpScriptBinding>::Element *insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Godot.SourceGenerators.Sample;

/// <summary>
/// class description
/// test
/// </summary>
public partial class ClassDoc : GodotObject
{
/// <summary>
/// field description
/// test
/// </summary>
[Export]
private int _fieldDocTest = 1;

/// <summary>
/// property description
/// test
/// </summary>
[Export]
public int PropertyDocTest { get; set; }

/// <summary>
/// signal description
/// test
/// </summary>
/// <param name="num"></param>
[Signal]
public delegate void SignalDocTestEventHandler(int num);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Xunit;

namespace Godot.SourceGenerators.Tests;

public class ScriptDocsGeneratorTests
{
[Fact]
public async void Docs()
{
await CSharpSourceGeneratorVerifier<ScriptDocsGenerator>.Verify(
"ClassDoc.cs",
"ClassDoc_ScriptDocs.generated.cs"
);
}

[Fact]
public async void AllDocs()
{
await CSharpSourceGeneratorVerifier<ScriptDocsGenerator>.Verify(
"ClassAllDoc.cs",
"ClassAllDoc_ScriptDocs.generated.cs"
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
partial class ClassAllDoc
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
#if TOOLS
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::Godot.Collections.Dictionary GetGodotClassDocs()
{
var docs = new global::Godot.Collections.Dictionary();
docs.Add("name","\"ClassAllDoc.cs\"");
docs.Add("description","class description\ntest");

var propertyDocs = new global::Godot.Collections.Array();
propertyDocs.Add(new global::Godot.Collections.Dictionary { { "name", PropertyName.PropertyDocTest}, { "description", "property description\ntest" } });
propertyDocs.Add(new global::Godot.Collections.Dictionary { { "name", PropertyName._fieldDocTest}, { "description", "field description\ntest" } });
docs.Add("properties", propertyDocs);

var signalDocs = new global::Godot.Collections.Array();
signalDocs.Add(new global::Godot.Collections.Dictionary { { "name", SignalName.SignalDocTest}, { "description", "signal description\ntest" } });
docs.Add("signals", signalDocs);

return docs;
}

#endif // TOOLS
#pragma warning restore CS0109
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
partial class ClassDoc
{
#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword
#if TOOLS
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal new static global::Godot.Collections.Dictionary GetGodotClassDocs()
{
var docs = new global::Godot.Collections.Dictionary();
docs.Add("name","\"ClassDoc.cs\"");
docs.Add("description","This is the class documentation.");

return docs;
}

#endif // TOOLS
#pragma warning restore CS0109
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Godot;

/// <summary>
/// class description
/// test
/// </summary>
public partial class ClassAllDoc : GodotObject
{
/// <summary>
/// field description
/// test
/// </summary>
[Export]
private int _fieldDocTest = 1;

/// <summary>
/// property description
/// test
/// </summary>
[Export]
public int PropertyDocTest { get; set; }

/// <summary>
/// signal description
/// test
/// </summary>
/// <param name="num"></param>
[Signal]
public delegate void SignalDocTestEventHandler(int num);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Godot;

/// <summary>
/// This is the class documentation.
/// </summary>
public partial class ClassDoc : GodotObject
{
[Export]
public int MyProperty { get; set; }
magian1127 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -48,6 +49,22 @@ public static bool InheritsFrom(this ITypeSymbol? symbol, string assemblyName, s
return false;
}

public static string? GetDocumentationSummaryText(this ISymbol symbol)
{
// TODO: Can't use GetDocumentationCommentXml in source generators: https://github.com/dotnet/roslyn/issues/23673
var syntax = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax();
while (syntax is VariableDeclaratorSyntax vds)
{
syntax = vds.Parent?.Parent;
}
var trivia = syntax?.GetLeadingTrivia();
magian1127 marked this conversation as resolved.
Show resolved Hide resolved

string summaryContent = Regex.Match(trivia.ToString(), "(?<=<summary>)[.\\s\\S]*?(?=</summary>)", RegexOptions.Singleline).Value;
string cleanedSummary = Regex.Replace(summaryContent, @"^\s*///\s?", "", RegexOptions.Multiline);

return cleanedSummary.Trim().Replace("\n", "\\n").Replace("\r", "\\r");
magian1127 marked this conversation as resolved.
Show resolved Hide resolved
}

public static INamedTypeSymbol? GetGodotScriptNativeClass(this INamedTypeSymbol classTypeSymbol)
{
var symbol = classTypeSymbol;
Expand Down
Loading
Loading