diff --git a/core/object.h b/core/object.h index b46ab496ae0a..5f1c798e921c 100644 --- a/core/object.h +++ b/core/object.h @@ -146,6 +146,9 @@ struct PropertyInfo { StringName class_name; //for classes PropertyHint hint; String hint_string; +#ifdef TOOLS_ENABLED + String tooltip; +#endif uint32_t usage; _FORCE_INLINE_ PropertyInfo added_usage(int p_fl) const { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 50c4739a0fbe..1f9eb66c14e5 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -1748,7 +1748,7 @@ void EditorInspector::update_tree() { if (doc_hint != String()) { ep->set_tooltip(property_prefix + p.name + "::" + doc_hint); } else { - ep->set_tooltip(property_prefix + p.name); + ep->set_tooltip(property_prefix + p.name + "::" + p.tooltip); } ep->update_property(); ep->_update_pin_flags(); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 0c5f5d29cccc..f750bad181e7 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3669,6 +3669,28 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { case GDScriptTokenizer::TK_CURSOR: { tokenizer->advance(); } break; + case GDScriptTokenizer::TK_TOOLTIP: { + tokenizer->advance(); +#ifdef TOOLS_ENABLED + if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) { + Variant constant = tokenizer->get_token_constant(); + current_export.tooltip = constant; + } + + // Handle multiline tooltips. + while (tokenizer->get_token(2) == GDScriptTokenizer::TK_TOOLTIP) { + tokenizer->advance(3); + if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) { + Variant constant = tokenizer->get_token_constant(); + current_export.tooltip += constant.operator String(); + } + } +#else + if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) { + tokenizer->advance(); + } +#endif + } break; case GDScriptTokenizer::TK_EOF: p_class->end_line = tokenizer->get_token_line(); case GDScriptTokenizer::TK_ERROR: { @@ -4846,7 +4868,12 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { member._export = current_export; current_export = PropertyInfo(); } - +#ifdef TOOLS_ENABLED + if (autoexport) { + member._export.tooltip = current_export.tooltip; + current_export.tooltip = ""; + } +#endif bool onready = tokenizer->get_token(-1) == GDScriptTokenizer::TK_PR_ONREADY; tokenizer->advance(); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 742a400461ed..af8083db09c5 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -135,7 +135,8 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = { "NAN", "Error", "EOF", - "Cursor" + "Cursor", + "##", // Tooltip comment for exported variables. }; struct _bit { @@ -258,6 +259,7 @@ bool GDScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const case TK_PR_REMOTESYNC: case TK_PR_MASTERSYNC: case TK_PR_PUPPETSYNC: + case TK_TOOLTIP: return true; // Literal for non-variables only: @@ -501,17 +503,100 @@ void GDScriptTokenizerText::_advance() { #ifdef DEBUG_ENABLED String comment; #endif // DEBUG_ENABLED + +#ifdef TOOLS_ENABLED + bool export_var = true; + + if (GETCHAR(1) == '#' || GETCHAR(-1) == '#') { + // Check if it's a tooltip comment + int tip_code_pos = code_pos + 1; + bool multiline = false; + do { + // Move to the end of the line + while (tip_code_pos < len && _code[tip_code_pos] != '\n') { + tip_code_pos++; + } + + // Skip '\n' + tip_code_pos++; + + // Skip whitespaces + while (tip_code_pos < len && (_code[tip_code_pos] == ' ' || _code[tip_code_pos] == '\t' || _code[tip_code_pos] == '\r')) { + tip_code_pos++; + } + + // Check if "##" -> possible multiline tooltip + if (tip_code_pos + 2 < len) { + if (_code[tip_code_pos] == '#' && _code[tip_code_pos + 1] == '#') { + multiline = true; + tip_code_pos += 2; + } else { + multiline = false; + } + } else { + multiline = false; + } + } while (multiline); + + // "export" - 6 CharType characters + const int export_size = 6; + + // Check if there is "export" keyword after "##" comments + if (tip_code_pos + export_size < len) { + const char *export_str = "export"; + for (int e = 0; e < export_size; e++) { + if (_code[tip_code_pos + e] != export_str[e]) { + export_var = false; + break; + } + } + } else { + export_var = false; + } + } else { + export_var = false; + } + + if (export_var) { + if (GETCHAR(1) == '#') { + _make_token(TK_TOOLTIP); + INCPOS(1); + return; + } + } + + String tooltip_text; +#endif + while (GETCHAR(0) != '\n') { #ifdef DEBUG_ENABLED comment += GETCHAR(0); #endif // DEBUG_ENABLED - code_pos++; + +#ifdef TOOLS_ENABLED + if (export_var) { + tooltip_text += GETCHAR(0); + INCPOS(1); + } else +#endif + { + code_pos++; + } + if (GETCHAR(0) == 0) { //end of file //_make_error("Unterminated Comment"); _make_token(TK_EOF); return; } } + +#ifdef TOOLS_ENABLED + if (export_var) { + _make_constant(tooltip_text.trim_prefix("#").trim_prefix(" ")); + return; + } +#endif + #ifdef DEBUG_ENABLED String comment_content = comment.trim_prefix("#").trim_prefix(" "); if (comment_content.begins_with("warning-ignore:")) { diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 01416cbb996b..ae5c96da7781 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -142,6 +142,7 @@ class GDScriptTokenizer { TK_ERROR, TK_EOF, TK_CURSOR, //used for code completion + TK_TOOLTIP, TK_MAX };