editor_help.cpp 133 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196
  1. /**************************************************************************/
  2. /* editor_help.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "editor_help.h"
  31. #include "core/config/project_settings.h"
  32. #include "core/core_constants.h"
  33. #include "core/extension/gdextension.h"
  34. #include "core/input/input.h"
  35. #include "core/object/script_language.h"
  36. #include "core/os/keyboard.h"
  37. #include "core/string/string_builder.h"
  38. #include "core/version.h"
  39. #include "editor/doc_data_compressed.gen.h"
  40. #include "editor/editor_node.h"
  41. #include "editor/editor_paths.h"
  42. #include "editor/editor_property_name_processor.h"
  43. #include "editor/editor_settings.h"
  44. #include "editor/editor_string_names.h"
  45. #include "editor/plugins/script_editor_plugin.h"
  46. #include "editor/themes/editor_scale.h"
  47. #include "scene/gui/line_edit.h"
  48. #include "modules/modules_enabled.gen.h" // For gdscript, mono.
  49. // For syntax highlighting.
  50. #ifdef MODULE_GDSCRIPT_ENABLED
  51. #include "modules/gdscript/editor/gdscript_highlighter.h"
  52. #include "modules/gdscript/gdscript.h"
  53. #endif
  54. // For syntax highlighting.
  55. #ifdef MODULE_MONO_ENABLED
  56. #include "editor/plugins/script_editor_plugin.h"
  57. #include "modules/mono/csharp_script.h"
  58. #endif
  59. #define CONTRIBUTE_URL vformat("%s/contributing/documentation/updating_the_class_reference.html", VERSION_DOCS_URL)
  60. #ifdef MODULE_MONO_ENABLED
  61. // Sync with the types mentioned in https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_differences.html
  62. const Vector<String> classes_with_csharp_differences = {
  63. "@GlobalScope",
  64. "String",
  65. "NodePath",
  66. "Signal",
  67. "Callable",
  68. "RID",
  69. "Basis",
  70. "Transform2D",
  71. "Transform3D",
  72. "Rect2",
  73. "Rect2i",
  74. "AABB",
  75. "Quaternion",
  76. "Projection",
  77. "Color",
  78. "Array",
  79. "Dictionary",
  80. "PackedByteArray",
  81. "PackedColorArray",
  82. "PackedFloat32Array",
  83. "PackedFloat64Array",
  84. "PackedInt32Array",
  85. "PackedInt64Array",
  86. "PackedStringArray",
  87. "PackedVector2Array",
  88. "PackedVector3Array",
  89. "PackedVector4Array",
  90. "Variant",
  91. };
  92. #endif
  93. const Vector<String> packed_array_types = {
  94. "PackedByteArray",
  95. "PackedColorArray",
  96. "PackedFloat32Array",
  97. "PackedFloat64Array",
  98. "PackedInt32Array",
  99. "PackedInt64Array",
  100. "PackedStringArray",
  101. "PackedVector2Array",
  102. "PackedVector3Array",
  103. "PackedVector4Array",
  104. };
  105. // TODO: this is sometimes used directly as doc->something, other times as EditorHelp::get_doc_data(), which is thread-safe.
  106. // Might this be a problem?
  107. DocTools *EditorHelp::doc = nullptr;
  108. DocTools *EditorHelp::ext_doc = nullptr;
  109. static bool _attempt_doc_load(const String &p_class) {
  110. // Docgen always happens in the outer-most class: it also generates docs for inner classes.
  111. String outer_class = p_class.get_slice(".", 0);
  112. if (!ScriptServer::is_global_class(outer_class)) {
  113. return false;
  114. }
  115. // ResourceLoader is used in order to have a script-agnostic way to load scripts.
  116. // This forces GDScript to compile the code, which is unnecessary for docgen, but it's a good compromise right now.
  117. Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(outer_class), outer_class);
  118. if (script.is_valid()) {
  119. Vector<DocData::ClassDoc> docs = script->get_documentation();
  120. for (int j = 0; j < docs.size(); j++) {
  121. const DocData::ClassDoc &doc = docs.get(j);
  122. EditorHelp::get_doc_data()->add_doc(doc);
  123. }
  124. return true;
  125. }
  126. return false;
  127. }
  128. // Removes unnecessary prefix from p_class_specifier when within the p_edited_class context
  129. static String _contextualize_class_specifier(const String &p_class_specifier, const String &p_edited_class) {
  130. // If this is a completely different context than the current class, then keep full path
  131. if (!p_class_specifier.begins_with(p_edited_class)) {
  132. return p_class_specifier;
  133. }
  134. // Here equal length + begins_with from above implies p_class_specifier == p_edited_class :)
  135. if (p_class_specifier.length() == p_edited_class.length()) {
  136. int rfind = p_class_specifier.rfind(".");
  137. if (rfind == -1) { // Single identifier
  138. return p_class_specifier;
  139. }
  140. // Multiple specifiers: keep last one only
  141. return p_class_specifier.substr(rfind + 1);
  142. }
  143. // They share a _name_ prefix but not a _class specifier_ prefix, e.g. Tree & TreeItem
  144. // begins_with + lengths being different implies p_class_specifier.length() > p_edited_class.length() so this is safe
  145. if (p_class_specifier[p_edited_class.length()] != '.') {
  146. return p_class_specifier;
  147. }
  148. // Remove class specifier prefix
  149. return p_class_specifier.substr(p_edited_class.length() + 1);
  150. }
  151. void EditorHelp::_update_theme_item_cache() {
  152. VBoxContainer::_update_theme_item_cache();
  153. theme_cache.text_color = get_theme_color(SNAME("text_color"), SNAME("EditorHelp"));
  154. theme_cache.title_color = get_theme_color(SNAME("title_color"), SNAME("EditorHelp"));
  155. theme_cache.headline_color = get_theme_color(SNAME("headline_color"), SNAME("EditorHelp"));
  156. theme_cache.comment_color = get_theme_color(SNAME("comment_color"), SNAME("EditorHelp"));
  157. theme_cache.symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp"));
  158. theme_cache.value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp"));
  159. theme_cache.qualifier_color = get_theme_color(SNAME("qualifier_color"), SNAME("EditorHelp"));
  160. theme_cache.type_color = get_theme_color(SNAME("type_color"), SNAME("EditorHelp"));
  161. theme_cache.override_color = get_theme_color(SNAME("override_color"), SNAME("EditorHelp"));
  162. theme_cache.doc_font = get_theme_font(SNAME("doc"), EditorStringName(EditorFonts));
  163. theme_cache.doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts));
  164. theme_cache.doc_italic_font = get_theme_font(SNAME("doc_italic"), EditorStringName(EditorFonts));
  165. theme_cache.doc_title_font = get_theme_font(SNAME("doc_title"), EditorStringName(EditorFonts));
  166. theme_cache.doc_code_font = get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts));
  167. theme_cache.doc_kbd_font = get_theme_font(SNAME("doc_keyboard"), EditorStringName(EditorFonts));
  168. theme_cache.doc_font_size = get_theme_font_size(SNAME("doc_size"), EditorStringName(EditorFonts));
  169. theme_cache.doc_title_font_size = get_theme_font_size(SNAME("doc_title_size"), EditorStringName(EditorFonts));
  170. theme_cache.doc_code_font_size = get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts));
  171. theme_cache.doc_kbd_font_size = get_theme_font_size(SNAME("doc_keyboard_size"), EditorStringName(EditorFonts));
  172. theme_cache.background_style = get_theme_stylebox(SNAME("background"), SNAME("EditorHelp"));
  173. class_desc->begin_bulk_theme_override();
  174. class_desc->add_theme_font_override("normal_font", theme_cache.doc_font);
  175. class_desc->add_theme_font_size_override("normal_font_size", theme_cache.doc_font_size);
  176. class_desc->add_theme_constant_override(SceneStringName(line_separation), get_theme_constant(SceneStringName(line_separation), SNAME("EditorHelp")));
  177. class_desc->add_theme_constant_override("table_h_separation", get_theme_constant(SNAME("table_h_separation"), SNAME("EditorHelp")));
  178. class_desc->add_theme_constant_override("table_v_separation", get_theme_constant(SNAME("table_v_separation"), SNAME("EditorHelp")));
  179. class_desc->add_theme_constant_override("text_highlight_h_padding", get_theme_constant(SNAME("text_highlight_h_padding"), SNAME("EditorHelp")));
  180. class_desc->add_theme_constant_override("text_highlight_v_padding", get_theme_constant(SNAME("text_highlight_v_padding"), SNAME("EditorHelp")));
  181. class_desc->end_bulk_theme_override();
  182. }
  183. void EditorHelp::_search(bool p_search_previous) {
  184. if (p_search_previous) {
  185. find_bar->search_prev();
  186. } else {
  187. find_bar->search_next();
  188. }
  189. }
  190. void EditorHelp::_class_desc_finished() {
  191. if (scroll_to >= 0) {
  192. class_desc->scroll_to_paragraph(scroll_to);
  193. }
  194. scroll_to = -1;
  195. }
  196. void EditorHelp::_class_list_select(const String &p_select) {
  197. _goto_desc(p_select);
  198. }
  199. void EditorHelp::_class_desc_select(const String &p_select) {
  200. if (p_select.begins_with("$")) { // Enum.
  201. const String link = p_select.substr(1);
  202. String enum_class_name;
  203. String enum_name;
  204. if (CoreConstants::is_global_enum(link)) {
  205. enum_class_name = "@GlobalScope";
  206. enum_name = link;
  207. } else {
  208. const int dot_pos = link.rfind(".");
  209. if (dot_pos >= 0) {
  210. enum_class_name = link.left(dot_pos);
  211. enum_name = link.substr(dot_pos + 1);
  212. } else {
  213. enum_class_name = edited_class;
  214. enum_name = link;
  215. }
  216. }
  217. emit_signal(SNAME("go_to_help"), "class_enum:" + enum_class_name + ":" + enum_name);
  218. } else if (p_select.begins_with("#")) { // Class.
  219. emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1));
  220. } else if (p_select.begins_with("@")) { // Member.
  221. const int tag_end = p_select.find_char(' ');
  222. const String tag = p_select.substr(1, tag_end - 1);
  223. const String link = p_select.substr(tag_end + 1).lstrip(" ");
  224. String topic;
  225. const HashMap<String, int> *table = nullptr;
  226. if (tag == "method") {
  227. topic = "class_method";
  228. table = &method_line;
  229. } else if (tag == "constructor") {
  230. topic = "class_method";
  231. table = &method_line;
  232. } else if (tag == "operator") {
  233. topic = "class_method";
  234. table = &method_line;
  235. } else if (tag == "member") {
  236. topic = "class_property";
  237. table = &property_line;
  238. } else if (tag == "enum") {
  239. topic = "class_enum";
  240. table = &enum_line;
  241. } else if (tag == "signal") {
  242. topic = "class_signal";
  243. table = &signal_line;
  244. } else if (tag == "constant") {
  245. topic = "class_constant";
  246. table = &constant_line;
  247. } else if (tag == "annotation") {
  248. topic = "class_annotation";
  249. table = &annotation_line;
  250. } else if (tag == "theme_item") {
  251. topic = "class_theme_item";
  252. table = &theme_property_line;
  253. } else {
  254. return;
  255. }
  256. // Case order is important here to correctly handle edge cases like Variant.Type in @GlobalScope.
  257. if (table->has(link)) {
  258. // Found in the current page.
  259. if (class_desc->is_ready()) {
  260. emit_signal(SNAME("request_save_history"));
  261. class_desc->scroll_to_paragraph((*table)[link]);
  262. } else {
  263. scroll_to = (*table)[link];
  264. }
  265. } else {
  266. // Look for link in @GlobalScope.
  267. // Note that a link like @GlobalScope.enum_name will not be found in this section, only enum_name will be.
  268. if (topic == "class_enum") {
  269. const DocData::ClassDoc &cd = doc->class_list["@GlobalScope"];
  270. for (const DocData::ConstantDoc &constant : cd.constants) {
  271. if (constant.enumeration == link) {
  272. // Found in @GlobalScope.
  273. emit_signal(SNAME("go_to_help"), topic + ":@GlobalScope:" + link);
  274. return;
  275. }
  276. }
  277. } else if (topic == "class_constant") {
  278. const DocData::ClassDoc &cd = doc->class_list["@GlobalScope"];
  279. for (const DocData::ConstantDoc &constant : cd.constants) {
  280. if (constant.name == link) {
  281. // Found in @GlobalScope.
  282. emit_signal(SNAME("go_to_help"), topic + ":@GlobalScope:" + link);
  283. return;
  284. }
  285. }
  286. }
  287. if (link.contains(".")) {
  288. const int class_end = link.find_char('.');
  289. emit_signal(SNAME("go_to_help"), topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1));
  290. }
  291. }
  292. } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) {
  293. OS::get_singleton()->shell_open(p_select);
  294. } else if (p_select.begins_with("^")) { // Copy button.
  295. DisplayServer::get_singleton()->clipboard_set(p_select.substr(1));
  296. }
  297. }
  298. void EditorHelp::_class_desc_input(const Ref<InputEvent> &p_input) {
  299. }
  300. void EditorHelp::_class_desc_resized(bool p_force_update_theme) {
  301. // Add extra horizontal margins for better readability.
  302. // The margins increase as the width of the editor help container increases.
  303. real_t char_width = theme_cache.doc_code_font->get_char_size('x', theme_cache.doc_code_font_size).width;
  304. const int new_display_margin = MAX(30 * EDSCALE, get_parent_anchorable_rect().size.width - char_width * 120 * EDSCALE) * 0.5;
  305. if (display_margin != new_display_margin || p_force_update_theme) {
  306. display_margin = new_display_margin;
  307. Ref<StyleBox> class_desc_stylebox = theme_cache.background_style->duplicate();
  308. class_desc_stylebox->set_content_margin(SIDE_LEFT, display_margin);
  309. class_desc_stylebox->set_content_margin(SIDE_RIGHT, display_margin);
  310. class_desc->add_theme_style_override("normal", class_desc_stylebox);
  311. class_desc->add_theme_style_override("focused", class_desc_stylebox);
  312. }
  313. }
  314. static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_is_bitfield, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) {
  315. const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp"));
  316. if (p_type.is_empty() || p_type == "void") {
  317. p_rt->push_color(Color(type_color, 0.5));
  318. p_rt->push_hint(TTR("No return value."));
  319. p_rt->add_text("void");
  320. p_rt->pop(); // hint
  321. p_rt->pop(); // color
  322. return;
  323. }
  324. bool is_enum_type = !p_enum.is_empty();
  325. bool is_bitfield = p_is_bitfield && is_enum_type;
  326. bool can_ref = !p_type.contains("*") || is_enum_type;
  327. String link_t = p_type; // For links in metadata
  328. String display_t; // For display purposes.
  329. if (is_enum_type) {
  330. link_t = p_enum; // The link for enums is always the full enum description
  331. display_t = _contextualize_class_specifier(p_enum, p_class);
  332. } else {
  333. display_t = _contextualize_class_specifier(p_type, p_class);
  334. }
  335. p_rt->push_color(type_color);
  336. bool add_array = false;
  337. if (can_ref) {
  338. if (link_t.ends_with("[]")) {
  339. add_array = true;
  340. link_t = link_t.trim_suffix("[]");
  341. display_t = display_t.trim_suffix("[]");
  342. p_rt->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
  343. p_rt->add_text("Array");
  344. p_rt->pop(); // meta
  345. p_rt->add_text("[");
  346. } else if (is_bitfield) {
  347. p_rt->push_color(Color(type_color, 0.5));
  348. p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
  349. p_rt->add_text("BitField");
  350. p_rt->pop(); // hint
  351. p_rt->add_text("[");
  352. p_rt->pop(); // color
  353. }
  354. if (is_enum_type) {
  355. p_rt->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum
  356. } else {
  357. p_rt->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class
  358. }
  359. }
  360. p_rt->add_text(display_t);
  361. if (can_ref) {
  362. p_rt->pop(); // meta
  363. if (add_array) {
  364. p_rt->add_text("]");
  365. } else if (is_bitfield) {
  366. p_rt->push_color(Color(type_color, 0.5));
  367. p_rt->add_text("]");
  368. p_rt->pop(); // color
  369. }
  370. }
  371. p_rt->pop(); // color
  372. }
  373. void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) {
  374. _add_type_to_rt(p_type, p_enum, p_is_bitfield, class_desc, this, edited_class);
  375. }
  376. void EditorHelp::_add_type_icon(const String &p_type, int p_size, const String &p_fallback) {
  377. Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(p_type, p_fallback);
  378. Vector2i size = Vector2i(icon->get_width(), icon->get_height());
  379. if (p_size > 0) {
  380. // Ensures icon scales proportionally on both axes, based on icon height.
  381. float ratio = p_size / float(size.height);
  382. size.width *= ratio;
  383. size.height *= ratio;
  384. }
  385. class_desc->add_image(icon, size.width, size.height);
  386. }
  387. String EditorHelp::_fix_constant(const String &p_constant) const {
  388. if (p_constant.strip_edges() == "4294967295") {
  389. return "0xFFFFFFFF";
  390. }
  391. if (p_constant.strip_edges() == "2147483647") {
  392. return "0x7FFFFFFF";
  393. }
  394. if (p_constant.strip_edges() == "1048575") {
  395. return "0xFFFFF";
  396. }
  397. return p_constant;
  398. }
  399. // Macros for assigning the deprecated/experimental marks to class members in overview.
  400. #define DEPRECATED_DOC_TAG \
  401. class_desc->push_font(theme_cache.doc_bold_font); \
  402. class_desc->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor))); \
  403. Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError")); \
  404. class_desc->add_image(error_icon, error_icon->get_width(), error_icon->get_height()); \
  405. class_desc->add_text(String::chr(160) + TTR("Deprecated")); \
  406. class_desc->pop(); \
  407. class_desc->pop();
  408. #define EXPERIMENTAL_DOC_TAG \
  409. class_desc->push_font(theme_cache.doc_bold_font); \
  410. class_desc->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); \
  411. Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning")); \
  412. class_desc->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height()); \
  413. class_desc->add_text(String::chr(160) + TTR("Experimental")); \
  414. class_desc->pop(); \
  415. class_desc->pop();
  416. // Macros for displaying the deprecated/experimental info in class member descriptions.
  417. #define DEPRECATED_DOC_MSG(m_message, m_default_message) \
  418. Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError")); \
  419. class_desc->add_image(error_icon, error_icon->get_width(), error_icon->get_height()); \
  420. class_desc->add_text(" "); \
  421. class_desc->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor))); \
  422. class_desc->push_font(theme_cache.doc_bold_font); \
  423. class_desc->add_text(TTR("Deprecated:")); \
  424. class_desc->pop(); \
  425. class_desc->pop(); \
  426. class_desc->add_text(" "); \
  427. if ((m_message).is_empty()) { \
  428. class_desc->add_text(m_default_message); \
  429. } else { \
  430. _add_text(m_message); \
  431. }
  432. #define EXPERIMENTAL_DOC_MSG(m_message, m_default_message) \
  433. Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning")); \
  434. class_desc->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height()); \
  435. class_desc->add_text(" "); \
  436. class_desc->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); \
  437. class_desc->push_font(theme_cache.doc_bold_font); \
  438. class_desc->add_text(TTR("Experimental:")); \
  439. class_desc->pop(); \
  440. class_desc->pop(); \
  441. class_desc->add_text(" "); \
  442. if ((m_message).is_empty()) { \
  443. class_desc->add_text(m_default_message); \
  444. } else { \
  445. _add_text(m_message); \
  446. }
  447. void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview, bool p_override) {
  448. if (p_override) {
  449. method_line[p_method.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  450. }
  451. const bool is_vararg = p_method.qualifiers.contains("vararg");
  452. if (p_overview) {
  453. class_desc->push_cell();
  454. class_desc->push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, Control::TEXT_DIRECTION_AUTO, "");
  455. } else {
  456. _add_bulletpoint();
  457. }
  458. _add_type(p_method.return_type, p_method.return_enum, p_method.return_is_bitfield);
  459. if (p_overview) {
  460. class_desc->pop(); // paragraph
  461. class_desc->pop(); // cell
  462. class_desc->push_cell();
  463. } else {
  464. class_desc->add_text(" ");
  465. }
  466. const bool is_documented = p_method.is_deprecated || p_method.is_experimental || !p_method.description.strip_edges().is_empty();
  467. if (p_overview && is_documented) {
  468. class_desc->push_meta("@method " + p_method.name, RichTextLabel::META_UNDERLINE_ON_HOVER);
  469. }
  470. class_desc->push_color(theme_cache.headline_color);
  471. class_desc->add_text(p_method.name);
  472. class_desc->pop(); // color
  473. if (p_overview && is_documented) {
  474. class_desc->pop(); // meta
  475. }
  476. class_desc->push_color(theme_cache.symbol_color);
  477. class_desc->add_text("(");
  478. class_desc->pop(); // color
  479. for (int j = 0; j < p_method.arguments.size(); j++) {
  480. const DocData::ArgumentDoc &argument = p_method.arguments[j];
  481. class_desc->push_color(theme_cache.text_color);
  482. if (j > 0) {
  483. class_desc->add_text(", ");
  484. }
  485. class_desc->add_text(argument.name);
  486. class_desc->add_text(": ");
  487. _add_type(argument.type, argument.enumeration, argument.is_bitfield);
  488. if (!argument.default_value.is_empty()) {
  489. class_desc->push_color(theme_cache.symbol_color);
  490. class_desc->add_text(" = ");
  491. class_desc->pop(); // color
  492. class_desc->push_color(theme_cache.value_color);
  493. class_desc->add_text(_fix_constant(argument.default_value));
  494. class_desc->pop(); // color
  495. }
  496. class_desc->pop(); // color
  497. }
  498. if (is_vararg) {
  499. class_desc->push_color(theme_cache.text_color);
  500. if (!p_method.arguments.is_empty()) {
  501. class_desc->add_text(", ");
  502. }
  503. class_desc->pop(); // color
  504. class_desc->push_color(theme_cache.symbol_color);
  505. class_desc->add_text("...");
  506. class_desc->pop(); // color
  507. }
  508. class_desc->push_color(theme_cache.symbol_color);
  509. class_desc->add_text(")");
  510. class_desc->pop(); // color
  511. if (!p_method.qualifiers.is_empty()) {
  512. class_desc->push_color(theme_cache.qualifier_color);
  513. PackedStringArray qualifiers = p_method.qualifiers.split_spaces();
  514. for (const String &qualifier : qualifiers) {
  515. String hint;
  516. if (qualifier == "vararg") {
  517. hint = TTR("This method supports a variable number of arguments.");
  518. } else if (qualifier == "virtual") {
  519. hint = TTR("This method is called by the engine.\nIt can be overridden to customize built-in behavior.");
  520. } else if (qualifier == "const") {
  521. hint = TTR("This method has no side effects.\nIt does not modify the object in any way.");
  522. } else if (qualifier == "static") {
  523. hint = TTR("This method does not need an instance to be called.\nIt can be called directly using the class name.");
  524. }
  525. class_desc->add_text(" ");
  526. if (!hint.is_empty()) {
  527. class_desc->push_hint(hint);
  528. class_desc->add_text(qualifier);
  529. class_desc->pop(); // hint
  530. } else {
  531. class_desc->add_text(qualifier);
  532. }
  533. }
  534. class_desc->pop(); // color
  535. }
  536. if (p_overview) {
  537. if (p_method.is_deprecated) {
  538. class_desc->add_text(" ");
  539. DEPRECATED_DOC_TAG;
  540. }
  541. if (p_method.is_experimental) {
  542. class_desc->add_text(" ");
  543. EXPERIMENTAL_DOC_TAG;
  544. }
  545. class_desc->pop(); // cell
  546. }
  547. }
  548. void EditorHelp::_add_bulletpoint() {
  549. static const char32_t prefix[3] = { 0x25CF /* filled circle */, ' ', 0 };
  550. class_desc->add_text(String(prefix));
  551. }
  552. void EditorHelp::_push_normal_font() {
  553. class_desc->push_font(theme_cache.doc_font);
  554. class_desc->push_font_size(theme_cache.doc_font_size);
  555. }
  556. void EditorHelp::_pop_normal_font() {
  557. class_desc->pop(); // font_size
  558. class_desc->pop(); // font
  559. }
  560. void EditorHelp::_push_title_font() {
  561. class_desc->push_font(theme_cache.doc_title_font);
  562. class_desc->push_font_size(theme_cache.doc_title_font_size);
  563. class_desc->push_color(theme_cache.title_color);
  564. }
  565. void EditorHelp::_pop_title_font() {
  566. class_desc->pop(); // color
  567. class_desc->pop(); // font_size
  568. class_desc->pop(); // font
  569. }
  570. void EditorHelp::_push_code_font() {
  571. class_desc->push_font(theme_cache.doc_code_font);
  572. class_desc->push_font_size(theme_cache.doc_code_font_size);
  573. }
  574. void EditorHelp::_pop_code_font() {
  575. class_desc->pop(); // font_size
  576. class_desc->pop(); // font
  577. }
  578. Error EditorHelp::_goto_desc(const String &p_class) {
  579. // If class doesn't have docs listed, attempt on-demand docgen
  580. if (!doc->class_list.has(p_class) && !_attempt_doc_load(p_class)) {
  581. return ERR_DOES_NOT_EXIST;
  582. }
  583. select_locked = true;
  584. class_desc->show();
  585. description_line = 0;
  586. if (p_class == edited_class) {
  587. return OK; // Already there.
  588. }
  589. edited_class = p_class;
  590. _update_doc();
  591. return OK;
  592. }
  593. void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods) {
  594. class_desc->add_newline();
  595. class_desc->add_newline();
  596. static const char *titles_by_type[METHOD_TYPE_MAX] = {
  597. TTRC("Methods"),
  598. TTRC("Constructors"),
  599. TTRC("Operators"),
  600. };
  601. const String title = TTRGET(titles_by_type[p_method_type]);
  602. section_line.push_back(Pair<String, int>(title, class_desc->get_paragraph_count() - 2));
  603. _push_title_font();
  604. class_desc->add_text(title);
  605. _pop_title_font();
  606. class_desc->add_newline();
  607. class_desc->add_newline();
  608. class_desc->push_indent(1);
  609. _push_code_font();
  610. class_desc->push_table(2);
  611. class_desc->set_table_column_expand(1, true);
  612. bool any_previous = false;
  613. for (int pass = 0; pass < 2; pass++) {
  614. Vector<DocData::MethodDoc> m;
  615. for (const DocData::MethodDoc &method : p_methods) {
  616. const String &q = method.qualifiers;
  617. if ((pass == 0 && q.contains("virtual")) || (pass == 1 && !q.contains("virtual"))) {
  618. m.push_back(method);
  619. }
  620. }
  621. if (any_previous && !m.is_empty()) {
  622. class_desc->push_cell();
  623. class_desc->pop(); // cell
  624. class_desc->push_cell();
  625. class_desc->pop(); // cell
  626. }
  627. String group_prefix;
  628. for (int i = 0; i < m.size(); i++) {
  629. const String new_prefix = m[i].name.left(3);
  630. bool is_new_group = false;
  631. if (i < m.size() - 1 && new_prefix == m[i + 1].name.left(3) && new_prefix != group_prefix) {
  632. is_new_group = i > 0;
  633. group_prefix = new_prefix;
  634. } else if (!group_prefix.is_empty() && new_prefix != group_prefix) {
  635. is_new_group = true;
  636. group_prefix = "";
  637. }
  638. if (is_new_group && pass == 1) {
  639. class_desc->push_cell();
  640. class_desc->pop(); // cell
  641. class_desc->push_cell();
  642. class_desc->pop(); // cell
  643. }
  644. // For constructors always point to the first one.
  645. _add_method(m[i], true, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
  646. }
  647. any_previous = !m.is_empty();
  648. }
  649. class_desc->pop(); // table
  650. _pop_code_font();
  651. class_desc->pop(); // indent
  652. }
  653. void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods) {
  654. #define HANDLE_DOC(m_string) ((p_classdoc.is_script_doc ? (m_string) : DTR(m_string)).strip_edges())
  655. class_desc->add_newline();
  656. class_desc->add_newline();
  657. class_desc->add_newline();
  658. static const char *titles_by_type[METHOD_TYPE_MAX] = {
  659. TTRC("Method Descriptions"),
  660. TTRC("Constructor Descriptions"),
  661. TTRC("Operator Descriptions"),
  662. };
  663. const String title = TTRGET(titles_by_type[p_method_type]);
  664. section_line.push_back(Pair<String, int>(title, class_desc->get_paragraph_count() - 2));
  665. _push_title_font();
  666. class_desc->add_text(title);
  667. _pop_title_font();
  668. String link_color_text = theme_cache.title_color.to_html(false);
  669. for (int pass = 0; pass < 2; pass++) {
  670. Vector<DocData::MethodDoc> methods_filtered;
  671. for (int i = 0; i < p_methods.size(); i++) {
  672. const String &q = p_methods[i].qualifiers;
  673. if ((pass == 0 && q.contains("virtual")) || (pass == 1 && !q.contains("virtual"))) {
  674. methods_filtered.push_back(p_methods[i]);
  675. }
  676. }
  677. for (int i = 0; i < methods_filtered.size(); i++) {
  678. const DocData::MethodDoc &method = methods_filtered[i];
  679. class_desc->add_newline();
  680. class_desc->add_newline();
  681. class_desc->add_newline();
  682. _push_code_font();
  683. // For constructors always point to the first one.
  684. _add_method(method, false, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
  685. _pop_code_font();
  686. class_desc->add_newline();
  687. class_desc->add_newline();
  688. class_desc->push_indent(1);
  689. _push_normal_font();
  690. class_desc->push_color(theme_cache.text_color);
  691. bool has_prev_text = false;
  692. if (method.is_deprecated) {
  693. has_prev_text = true;
  694. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  695. TTRC("This method may be changed or removed in future versions."),
  696. TTRC("This constructor may be changed or removed in future versions."),
  697. TTRC("This operator may be changed or removed in future versions."),
  698. };
  699. DEPRECATED_DOC_MSG(HANDLE_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type]));
  700. }
  701. if (method.is_experimental) {
  702. if (has_prev_text) {
  703. class_desc->add_newline();
  704. class_desc->add_newline();
  705. }
  706. has_prev_text = true;
  707. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  708. TTRC("This method may be changed or removed in future versions."),
  709. TTRC("This constructor may be changed or removed in future versions."),
  710. TTRC("This operator may be changed or removed in future versions."),
  711. };
  712. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type]));
  713. }
  714. if (!method.errors_returned.is_empty()) {
  715. if (has_prev_text) {
  716. class_desc->add_newline();
  717. class_desc->add_newline();
  718. }
  719. has_prev_text = true;
  720. class_desc->add_text(TTR("Error codes returned:"));
  721. class_desc->add_newline();
  722. class_desc->push_list(0, RichTextLabel::LIST_DOTS, false);
  723. for (int j = 0; j < method.errors_returned.size(); j++) {
  724. if (j > 0) {
  725. class_desc->add_newline();
  726. }
  727. int val = method.errors_returned[j];
  728. String text = itos(val);
  729. for (int k = 0; k < CoreConstants::get_global_constant_count(); k++) {
  730. if (CoreConstants::get_global_constant_value(k) == val && CoreConstants::get_global_constant_enum(k) == SNAME("Error")) {
  731. text = CoreConstants::get_global_constant_name(k);
  732. break;
  733. }
  734. }
  735. class_desc->push_font(theme_cache.doc_bold_font);
  736. class_desc->add_text(text);
  737. class_desc->pop(); // font
  738. }
  739. class_desc->pop(); // list
  740. }
  741. const String descr = HANDLE_DOC(method.description);
  742. const bool is_documented = method.is_deprecated || method.is_experimental || !descr.is_empty();
  743. if (!descr.is_empty()) {
  744. if (has_prev_text) {
  745. class_desc->add_newline();
  746. class_desc->add_newline();
  747. }
  748. has_prev_text = true;
  749. _add_text(descr);
  750. } else if (!is_documented) {
  751. if (has_prev_text) {
  752. class_desc->add_newline();
  753. class_desc->add_newline();
  754. }
  755. has_prev_text = true;
  756. String message;
  757. if (p_classdoc.is_script_doc) {
  758. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  759. TTRC("There is currently no description for this method."),
  760. TTRC("There is currently no description for this constructor."),
  761. TTRC("There is currently no description for this operator."),
  762. };
  763. message = TTRGET(messages_by_type[p_method_type]);
  764. } else {
  765. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  766. TTRC("There is currently no description for this method. Please help us by [color=$color][url=$url]contributing one[/url][/color]!"),
  767. TTRC("There is currently no description for this constructor. Please help us by [color=$color][url=$url]contributing one[/url][/color]!"),
  768. TTRC("There is currently no description for this operator. Please help us by [color=$color][url=$url]contributing one[/url][/color]!"),
  769. };
  770. message = TTRGET(messages_by_type[p_method_type]).replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text);
  771. }
  772. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  773. class_desc->add_text(" ");
  774. class_desc->push_color(theme_cache.comment_color);
  775. class_desc->append_text(message);
  776. class_desc->pop(); // color
  777. }
  778. class_desc->pop(); // color
  779. _pop_normal_font();
  780. class_desc->pop(); // indent
  781. }
  782. }
  783. #undef HANDLE_DOC
  784. }
  785. void EditorHelp::_update_doc() {
  786. if (!doc->class_list.has(edited_class)) {
  787. return;
  788. }
  789. scroll_locked = true;
  790. class_desc->clear();
  791. method_line.clear();
  792. section_line.clear();
  793. section_line.push_back(Pair<String, int>(TTR("Top"), 0));
  794. String link_color_text = theme_cache.title_color.to_html(false);
  795. DocData::ClassDoc cd = doc->class_list[edited_class]; // Make a copy, so we can sort without worrying.
  796. #define HANDLE_DOC(m_string) ((cd.is_script_doc ? (m_string) : DTR(m_string)).strip_edges())
  797. // Class name
  798. _push_title_font();
  799. class_desc->add_text(TTR("Class:") + " ");
  800. _add_type_icon(edited_class, theme_cache.doc_title_font_size, "Object");
  801. class_desc->add_text(" ");
  802. class_desc->push_color(theme_cache.headline_color);
  803. class_desc->add_text(edited_class);
  804. class_desc->pop(); // color
  805. _pop_title_font();
  806. if (cd.is_deprecated) {
  807. class_desc->add_newline();
  808. DEPRECATED_DOC_MSG(HANDLE_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions."));
  809. }
  810. if (cd.is_experimental) {
  811. class_desc->add_newline();
  812. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions."));
  813. }
  814. // Inheritance tree
  815. const String non_breaking_space = String::chr(160);
  816. // Ascendents
  817. if (!cd.inherits.is_empty()) {
  818. class_desc->add_newline();
  819. _push_normal_font();
  820. class_desc->push_color(theme_cache.title_color);
  821. class_desc->add_text(TTR("Inherits:") + " ");
  822. String inherits = cd.inherits;
  823. while (!inherits.is_empty()) {
  824. _add_type_icon(inherits, theme_cache.doc_font_size, "ArrowRight");
  825. class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type().
  826. _add_type(inherits);
  827. inherits = doc->class_list[inherits].inherits;
  828. if (!inherits.is_empty()) {
  829. class_desc->add_text(" < ");
  830. }
  831. }
  832. class_desc->pop(); // color
  833. _pop_normal_font();
  834. }
  835. // Descendants
  836. if ((cd.is_script_doc || ClassDB::class_exists(cd.name)) && doc->inheriting.has(cd.name)) {
  837. class_desc->add_newline();
  838. _push_normal_font();
  839. class_desc->push_color(theme_cache.title_color);
  840. class_desc->add_text(TTR("Inherited by:") + " ");
  841. for (RBSet<String, NaturalNoCaseComparator>::Element *itr = doc->inheriting[cd.name].front(); itr; itr = itr->next()) {
  842. if (itr->prev()) {
  843. class_desc->add_text(" , ");
  844. }
  845. _add_type_icon(itr->get(), theme_cache.doc_font_size, "ArrowRight");
  846. class_desc->add_text(non_breaking_space); // Otherwise icon borrows hyperlink from _add_type().
  847. _add_type(itr->get());
  848. }
  849. class_desc->pop(); // color
  850. _pop_normal_font();
  851. }
  852. bool has_description = false;
  853. // Brief description
  854. const String brief_class_descr = HANDLE_DOC(cd.brief_description);
  855. if (!brief_class_descr.is_empty()) {
  856. has_description = true;
  857. class_desc->add_newline();
  858. class_desc->add_newline();
  859. class_desc->push_indent(1);
  860. class_desc->push_font(theme_cache.doc_bold_font);
  861. class_desc->push_color(theme_cache.text_color);
  862. _add_text(brief_class_descr);
  863. class_desc->pop(); // color
  864. class_desc->pop(); // font
  865. class_desc->pop(); // indent
  866. }
  867. // Class description
  868. const String class_descr = HANDLE_DOC(cd.description);
  869. if (!class_descr.is_empty()) {
  870. has_description = true;
  871. class_desc->add_newline();
  872. class_desc->add_newline();
  873. section_line.push_back(Pair<String, int>(TTR("Description"), class_desc->get_paragraph_count() - 2));
  874. description_line = class_desc->get_paragraph_count() - 2;
  875. _push_title_font();
  876. class_desc->add_text(TTR("Description"));
  877. _pop_title_font();
  878. class_desc->add_newline();
  879. class_desc->add_newline();
  880. class_desc->push_indent(1);
  881. _push_normal_font();
  882. class_desc->push_color(theme_cache.text_color);
  883. _add_text(class_descr);
  884. class_desc->pop(); // color
  885. _pop_normal_font();
  886. class_desc->pop(); // indent
  887. }
  888. if (!has_description) {
  889. class_desc->add_newline();
  890. class_desc->add_newline();
  891. class_desc->push_indent(1);
  892. _push_normal_font();
  893. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  894. class_desc->add_text(" ");
  895. class_desc->push_color(theme_cache.comment_color);
  896. if (cd.is_script_doc) {
  897. class_desc->add_text(TTR("There is currently no description for this class."));
  898. } else {
  899. class_desc->append_text(TTR("There is currently no description for this class. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text));
  900. }
  901. class_desc->pop(); // color
  902. _pop_normal_font();
  903. class_desc->pop(); // indent
  904. }
  905. #ifdef MODULE_MONO_ENABLED
  906. if (classes_with_csharp_differences.has(cd.name)) {
  907. class_desc->add_newline();
  908. class_desc->add_newline();
  909. const String &csharp_differences_url = vformat("%s/tutorials/scripting/c_sharp/c_sharp_differences.html", VERSION_DOCS_URL);
  910. class_desc->push_indent(1);
  911. _push_normal_font();
  912. class_desc->push_color(theme_cache.text_color);
  913. class_desc->append_text("[b]" + TTR("Note:") + "[/b] " + vformat(TTR("There are notable differences when using this API with C#. See [url=%s]C# API differences to GDScript[/url] for more information."), csharp_differences_url));
  914. class_desc->pop(); // color
  915. _pop_normal_font();
  916. class_desc->pop(); // indent
  917. }
  918. #endif
  919. // Online tutorials
  920. if (!cd.tutorials.is_empty()) {
  921. class_desc->add_newline();
  922. class_desc->add_newline();
  923. _push_title_font();
  924. class_desc->add_text(TTR("Online Tutorials"));
  925. _pop_title_font();
  926. class_desc->add_newline();
  927. class_desc->push_indent(1);
  928. _push_code_font();
  929. class_desc->push_color(theme_cache.symbol_color);
  930. for (const DocData::TutorialDoc &tutorial : cd.tutorials) {
  931. const String link = HANDLE_DOC(tutorial.link);
  932. String link_text = HANDLE_DOC(tutorial.title);
  933. if (link_text.is_empty()) {
  934. const int sep_pos = link.find("//");
  935. if (sep_pos >= 0) {
  936. link_text = link.substr(sep_pos + 2);
  937. } else {
  938. link_text = link;
  939. }
  940. }
  941. class_desc->add_newline();
  942. _add_bulletpoint();
  943. class_desc->append_text("[url=" + link + "]" + link_text + "[/url]");
  944. }
  945. class_desc->pop(); // color
  946. _pop_code_font();
  947. class_desc->pop(); // indent
  948. }
  949. // Properties overview
  950. HashSet<String> skip_methods;
  951. bool has_properties = false;
  952. bool has_property_descriptions = false;
  953. for (const DocData::PropertyDoc &prop : cd.properties) {
  954. const bool is_documented = prop.is_deprecated || prop.is_experimental || !prop.description.strip_edges().is_empty();
  955. if (!is_documented && prop.name.begins_with("_")) {
  956. continue;
  957. }
  958. has_properties = true;
  959. if (!prop.overridden) {
  960. has_property_descriptions = true;
  961. break;
  962. }
  963. }
  964. if (has_properties) {
  965. class_desc->add_newline();
  966. class_desc->add_newline();
  967. section_line.push_back(Pair<String, int>(TTR("Properties"), class_desc->get_paragraph_count() - 2));
  968. _push_title_font();
  969. class_desc->add_text(TTR("Properties"));
  970. _pop_title_font();
  971. class_desc->add_newline();
  972. class_desc->add_newline();
  973. class_desc->push_indent(1);
  974. _push_code_font();
  975. class_desc->push_table(4);
  976. class_desc->set_table_column_expand(1, true);
  977. cd.properties.sort_custom<PropertyCompare>();
  978. bool is_generating_overridden_properties = true; // Set to false as soon as we encounter a non-overridden property.
  979. bool overridden_property_exists = false;
  980. for (const DocData::PropertyDoc &prop : cd.properties) {
  981. // Ignore undocumented private.
  982. const bool is_documented = prop.is_deprecated || prop.is_experimental || !prop.description.strip_edges().is_empty();
  983. if (!is_documented && prop.name.begins_with("_")) {
  984. continue;
  985. }
  986. if (is_generating_overridden_properties && !prop.overridden) {
  987. is_generating_overridden_properties = false;
  988. // No need for the extra spacing when there's no overridden property.
  989. if (overridden_property_exists) {
  990. class_desc->push_cell();
  991. class_desc->pop(); // cell
  992. class_desc->push_cell();
  993. class_desc->pop(); // cell
  994. class_desc->push_cell();
  995. class_desc->pop(); // cell
  996. class_desc->push_cell();
  997. class_desc->pop(); // cell
  998. }
  999. }
  1000. property_line[prop.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1001. // Property type.
  1002. class_desc->push_cell();
  1003. class_desc->push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, Control::TEXT_DIRECTION_AUTO, "");
  1004. _add_type(prop.type, prop.enumeration, prop.is_bitfield);
  1005. class_desc->pop(); // paragraph
  1006. class_desc->pop(); // cell
  1007. bool describe = false;
  1008. if (!prop.setter.is_empty()) {
  1009. skip_methods.insert(prop.setter);
  1010. describe = true;
  1011. }
  1012. if (!prop.getter.is_empty()) {
  1013. skip_methods.insert(prop.getter);
  1014. describe = true;
  1015. }
  1016. if (is_documented) {
  1017. describe = true;
  1018. }
  1019. if (prop.overridden) {
  1020. describe = false;
  1021. }
  1022. // Property name.
  1023. class_desc->push_cell();
  1024. class_desc->push_color(theme_cache.headline_color);
  1025. if (describe) {
  1026. class_desc->push_meta("@member " + prop.name, RichTextLabel::META_UNDERLINE_ON_HOVER);
  1027. }
  1028. class_desc->add_text(prop.name);
  1029. if (describe) {
  1030. class_desc->pop(); // meta
  1031. }
  1032. class_desc->pop(); // color
  1033. class_desc->pop(); // cell
  1034. // Property value.
  1035. class_desc->push_cell();
  1036. if (!prop.default_value.is_empty()) {
  1037. if (prop.overridden) {
  1038. class_desc->push_color(theme_cache.override_color);
  1039. class_desc->add_text("[");
  1040. const String link = vformat("[url=@member %s.%s]%s[/url]", prop.overrides, prop.name, prop.overrides);
  1041. class_desc->append_text(vformat(TTR("overrides %s:"), link));
  1042. class_desc->add_text(" " + _fix_constant(prop.default_value) + "]");
  1043. class_desc->pop(); // color
  1044. overridden_property_exists = true;
  1045. } else {
  1046. class_desc->push_color(theme_cache.symbol_color);
  1047. class_desc->add_text("[" + TTR("default:") + " ");
  1048. class_desc->pop(); // color
  1049. class_desc->push_color(theme_cache.value_color);
  1050. class_desc->add_text(_fix_constant(prop.default_value));
  1051. class_desc->pop(); // color
  1052. class_desc->push_color(theme_cache.symbol_color);
  1053. class_desc->add_text("]");
  1054. class_desc->pop(); // color
  1055. }
  1056. }
  1057. class_desc->pop(); // cell
  1058. // Property setter/getter and deprecated/experimental marks.
  1059. class_desc->push_cell();
  1060. bool has_prev_text = false;
  1061. if (cd.is_script_doc && (!prop.setter.is_empty() || !prop.getter.is_empty())) {
  1062. has_prev_text = true;
  1063. class_desc->push_color(theme_cache.symbol_color);
  1064. class_desc->add_text("[" + TTR("property:") + " ");
  1065. class_desc->pop(); // color
  1066. if (!prop.setter.is_empty()) {
  1067. class_desc->push_color(theme_cache.value_color);
  1068. class_desc->add_text("setter");
  1069. class_desc->pop(); // color
  1070. }
  1071. if (!prop.getter.is_empty()) {
  1072. if (!prop.setter.is_empty()) {
  1073. class_desc->push_color(theme_cache.symbol_color);
  1074. class_desc->add_text(", ");
  1075. class_desc->pop(); // color
  1076. }
  1077. class_desc->push_color(theme_cache.value_color);
  1078. class_desc->add_text("getter");
  1079. class_desc->pop(); // color
  1080. }
  1081. class_desc->push_color(theme_cache.symbol_color);
  1082. class_desc->add_text("]");
  1083. class_desc->pop(); // color
  1084. }
  1085. if (prop.is_deprecated) {
  1086. if (has_prev_text) {
  1087. class_desc->add_text(" ");
  1088. }
  1089. has_prev_text = true;
  1090. DEPRECATED_DOC_TAG;
  1091. }
  1092. if (prop.is_experimental) {
  1093. if (has_prev_text) {
  1094. class_desc->add_text(" ");
  1095. }
  1096. has_prev_text = true;
  1097. EXPERIMENTAL_DOC_TAG;
  1098. }
  1099. class_desc->pop(); // cell
  1100. }
  1101. class_desc->pop(); // table
  1102. _pop_code_font();
  1103. class_desc->pop(); // indent
  1104. }
  1105. // Methods overview
  1106. bool sort_methods = EDITOR_GET("text_editor/help/sort_functions_alphabetically");
  1107. Vector<DocData::MethodDoc> methods;
  1108. for (const DocData::MethodDoc &method : cd.methods) {
  1109. if (skip_methods.has(method.name)) {
  1110. if (method.arguments.is_empty() /* getter */ || (method.arguments.size() == 1 && method.return_type == "void" /* setter */)) {
  1111. continue;
  1112. }
  1113. }
  1114. // Ignore undocumented non virtual private.
  1115. const bool is_documented = method.is_deprecated || method.is_experimental || !method.description.strip_edges().is_empty();
  1116. if (!is_documented && method.name.begins_with("_") && !method.qualifiers.contains("virtual")) {
  1117. continue;
  1118. }
  1119. methods.push_back(method);
  1120. }
  1121. if (!cd.constructors.is_empty()) {
  1122. if (sort_methods) {
  1123. cd.constructors.sort();
  1124. }
  1125. _update_method_list(METHOD_TYPE_CONSTRUCTOR, cd.constructors);
  1126. }
  1127. if (!methods.is_empty()) {
  1128. if (sort_methods) {
  1129. methods.sort();
  1130. }
  1131. _update_method_list(METHOD_TYPE_METHOD, methods);
  1132. }
  1133. if (!cd.operators.is_empty()) {
  1134. if (sort_methods) {
  1135. cd.operators.sort();
  1136. }
  1137. _update_method_list(METHOD_TYPE_OPERATOR, cd.operators);
  1138. }
  1139. // Theme properties
  1140. if (!cd.theme_properties.is_empty()) {
  1141. class_desc->add_newline();
  1142. class_desc->add_newline();
  1143. section_line.push_back(Pair<String, int>(TTR("Theme Properties"), class_desc->get_paragraph_count() - 2));
  1144. _push_title_font();
  1145. class_desc->add_text(TTR("Theme Properties"));
  1146. _pop_title_font();
  1147. String theme_data_type;
  1148. HashMap<String, String> data_type_names;
  1149. data_type_names["color"] = TTR("Colors");
  1150. data_type_names["constant"] = TTR("Constants");
  1151. data_type_names["font"] = TTR("Fonts");
  1152. data_type_names["font_size"] = TTR("Font Sizes");
  1153. data_type_names["icon"] = TTR("Icons");
  1154. data_type_names["style"] = TTR("Styles");
  1155. for (const DocData::ThemeItemDoc &theme_item : cd.theme_properties) {
  1156. if (theme_data_type != theme_item.data_type) {
  1157. theme_data_type = theme_item.data_type;
  1158. class_desc->add_newline();
  1159. class_desc->add_newline();
  1160. class_desc->push_indent(1);
  1161. _push_title_font();
  1162. if (data_type_names.has(theme_data_type)) {
  1163. class_desc->add_text(data_type_names[theme_data_type]);
  1164. } else {
  1165. class_desc->add_text(theme_data_type);
  1166. }
  1167. _pop_title_font();
  1168. class_desc->pop(); // indent
  1169. }
  1170. class_desc->add_newline();
  1171. class_desc->add_newline();
  1172. theme_property_line[theme_item.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1173. class_desc->push_indent(1);
  1174. // Theme item header.
  1175. _push_code_font();
  1176. _add_bulletpoint();
  1177. // Theme item object type.
  1178. _add_type(theme_item.type);
  1179. // Theme item name.
  1180. class_desc->push_color(theme_cache.headline_color);
  1181. class_desc->add_text(" ");
  1182. class_desc->add_text(theme_item.name);
  1183. class_desc->pop(); // color
  1184. // Theme item default value.
  1185. if (!theme_item.default_value.is_empty()) {
  1186. class_desc->push_color(theme_cache.symbol_color);
  1187. class_desc->add_text(" [" + TTR("default:") + " ");
  1188. class_desc->pop(); // color
  1189. class_desc->push_color(theme_cache.value_color);
  1190. class_desc->add_text(_fix_constant(theme_item.default_value));
  1191. class_desc->pop(); // color
  1192. class_desc->push_color(theme_cache.symbol_color);
  1193. class_desc->add_text("]");
  1194. class_desc->pop(); // color
  1195. }
  1196. _pop_code_font();
  1197. // Theme item description.
  1198. class_desc->push_indent(1);
  1199. _push_normal_font();
  1200. class_desc->push_color(theme_cache.comment_color);
  1201. const String descr = HANDLE_DOC(theme_item.description);
  1202. if (!descr.is_empty()) {
  1203. _add_text(descr);
  1204. } else {
  1205. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1206. class_desc->add_text(" ");
  1207. class_desc->push_color(theme_cache.comment_color);
  1208. if (cd.is_script_doc) {
  1209. class_desc->add_text(TTR("There is currently no description for this theme property."));
  1210. } else {
  1211. class_desc->append_text(TTR("There is currently no description for this theme property. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text));
  1212. }
  1213. class_desc->pop(); // color
  1214. }
  1215. class_desc->pop(); // color
  1216. _pop_normal_font();
  1217. class_desc->pop(); // indent
  1218. class_desc->pop(); // indent
  1219. }
  1220. }
  1221. // Signals
  1222. if (!cd.signals.is_empty()) {
  1223. if (sort_methods) {
  1224. cd.signals.sort();
  1225. }
  1226. class_desc->add_newline();
  1227. class_desc->add_newline();
  1228. section_line.push_back(Pair<String, int>(TTR("Signals"), class_desc->get_paragraph_count() - 2));
  1229. _push_title_font();
  1230. class_desc->add_text(TTR("Signals"));
  1231. _pop_title_font();
  1232. for (const DocData::MethodDoc &signal : cd.signals) {
  1233. class_desc->add_newline();
  1234. class_desc->add_newline();
  1235. signal_line[signal.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1236. class_desc->push_indent(1);
  1237. // Signal header.
  1238. _push_code_font();
  1239. _add_bulletpoint();
  1240. class_desc->push_color(theme_cache.headline_color);
  1241. class_desc->add_text(signal.name);
  1242. class_desc->pop(); // color
  1243. class_desc->push_color(theme_cache.symbol_color);
  1244. class_desc->add_text("(");
  1245. class_desc->pop(); // color
  1246. for (int j = 0; j < signal.arguments.size(); j++) {
  1247. const DocData::ArgumentDoc &argument = signal.arguments[j];
  1248. class_desc->push_color(theme_cache.text_color);
  1249. if (j > 0) {
  1250. class_desc->add_text(", ");
  1251. }
  1252. class_desc->add_text(argument.name);
  1253. class_desc->add_text(": ");
  1254. _add_type(argument.type, argument.enumeration, argument.is_bitfield);
  1255. // Signals currently do not support default argument values, neither the core nor GDScript.
  1256. // This code is just for completeness.
  1257. if (!argument.default_value.is_empty()) {
  1258. class_desc->push_color(theme_cache.symbol_color);
  1259. class_desc->add_text(" = ");
  1260. class_desc->pop(); // color
  1261. class_desc->push_color(theme_cache.value_color);
  1262. class_desc->add_text(_fix_constant(argument.default_value));
  1263. class_desc->pop(); // color
  1264. }
  1265. class_desc->pop(); // color
  1266. }
  1267. class_desc->push_color(theme_cache.symbol_color);
  1268. class_desc->add_text(")");
  1269. class_desc->pop(); // color
  1270. _pop_code_font();
  1271. class_desc->add_newline();
  1272. // Signal description.
  1273. class_desc->push_indent(1);
  1274. _push_normal_font();
  1275. class_desc->push_color(theme_cache.comment_color);
  1276. const String descr = HANDLE_DOC(signal.description);
  1277. const bool is_multiline = descr.find_char('\n') > 0;
  1278. bool has_prev_text = false;
  1279. if (signal.is_deprecated) {
  1280. has_prev_text = true;
  1281. DEPRECATED_DOC_MSG(HANDLE_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions."));
  1282. }
  1283. if (signal.is_experimental) {
  1284. if (has_prev_text) {
  1285. class_desc->add_newline();
  1286. if (is_multiline) {
  1287. class_desc->add_newline();
  1288. }
  1289. }
  1290. has_prev_text = true;
  1291. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions."));
  1292. }
  1293. if (!descr.is_empty()) {
  1294. if (has_prev_text) {
  1295. class_desc->add_newline();
  1296. if (is_multiline) {
  1297. class_desc->add_newline();
  1298. }
  1299. }
  1300. has_prev_text = true;
  1301. _add_text(descr);
  1302. } else if (!has_prev_text) {
  1303. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1304. class_desc->add_text(" ");
  1305. class_desc->push_color(theme_cache.comment_color);
  1306. if (cd.is_script_doc) {
  1307. class_desc->add_text(TTR("There is currently no description for this signal."));
  1308. } else {
  1309. class_desc->append_text(TTR("There is currently no description for this signal. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text));
  1310. }
  1311. class_desc->pop(); // color
  1312. }
  1313. class_desc->pop(); // color
  1314. _pop_normal_font();
  1315. class_desc->pop(); // indent
  1316. class_desc->pop(); // indent
  1317. }
  1318. }
  1319. // Constants and enums
  1320. if (!cd.constants.is_empty()) {
  1321. HashMap<String, Vector<DocData::ConstantDoc>> enums;
  1322. Vector<DocData::ConstantDoc> constants;
  1323. for (const DocData::ConstantDoc &constant : cd.constants) {
  1324. if (!constant.enumeration.is_empty()) {
  1325. if (!enums.has(constant.enumeration)) {
  1326. enums[constant.enumeration] = Vector<DocData::ConstantDoc>();
  1327. }
  1328. enums[constant.enumeration].push_back(constant);
  1329. } else {
  1330. // Ignore undocumented private.
  1331. const bool is_documented = constant.is_deprecated || constant.is_experimental || !constant.description.strip_edges().is_empty();
  1332. if (!is_documented && constant.name.begins_with("_")) {
  1333. continue;
  1334. }
  1335. constants.push_back(constant);
  1336. }
  1337. }
  1338. // Enums
  1339. bool has_enums = enums.size() && !cd.is_script_doc;
  1340. if (enums.size() && !has_enums) {
  1341. for (KeyValue<String, DocData::EnumDoc> &E : cd.enums) {
  1342. const bool is_documented = E.value.is_deprecated || E.value.is_experimental || !E.value.description.strip_edges().is_empty();
  1343. if (!is_documented && E.key.begins_with("_")) {
  1344. continue;
  1345. }
  1346. has_enums = true;
  1347. break;
  1348. }
  1349. }
  1350. if (has_enums) {
  1351. class_desc->add_newline();
  1352. class_desc->add_newline();
  1353. section_line.push_back(Pair<String, int>(TTR("Enumerations"), class_desc->get_paragraph_count() - 2));
  1354. _push_title_font();
  1355. class_desc->add_text(TTR("Enumerations"));
  1356. _pop_title_font();
  1357. for (KeyValue<String, Vector<DocData::ConstantDoc>> &E : enums) {
  1358. String key = E.key;
  1359. if ((key.get_slice_count(".") > 1) && (key.get_slice(".", 0) == edited_class)) {
  1360. key = key.get_slice(".", 1);
  1361. }
  1362. if (cd.enums.has(key)) {
  1363. const bool is_documented = cd.enums[key].is_deprecated || cd.enums[key].is_experimental || !cd.enums[key].description.strip_edges().is_empty();
  1364. if (!is_documented && cd.is_script_doc && E.key.begins_with("_")) {
  1365. continue;
  1366. }
  1367. }
  1368. class_desc->add_newline();
  1369. class_desc->add_newline();
  1370. // Enum header.
  1371. _push_code_font();
  1372. enum_line[E.key] = class_desc->get_paragraph_count() - 2;
  1373. class_desc->push_color(theme_cache.title_color);
  1374. if (E.value.size() && E.value[0].is_bitfield) {
  1375. class_desc->add_text("flags ");
  1376. } else {
  1377. class_desc->add_text("enum ");
  1378. }
  1379. class_desc->pop(); // color
  1380. class_desc->push_color(theme_cache.headline_color);
  1381. class_desc->add_text(key);
  1382. class_desc->pop(); // color
  1383. class_desc->push_color(theme_cache.symbol_color);
  1384. class_desc->add_text(":");
  1385. class_desc->pop(); // color
  1386. _pop_code_font();
  1387. // Enum description.
  1388. if (key != "@unnamed_enums" && cd.enums.has(key)) {
  1389. const String descr = HANDLE_DOC(cd.enums[key].description);
  1390. const bool is_multiline = descr.find_char('\n') > 0;
  1391. if (cd.enums[key].is_deprecated || cd.enums[key].is_experimental || !descr.is_empty()) {
  1392. class_desc->add_newline();
  1393. class_desc->push_indent(1);
  1394. _push_normal_font();
  1395. class_desc->push_color(theme_cache.text_color);
  1396. bool has_prev_text = false;
  1397. if (cd.enums[key].is_deprecated) {
  1398. has_prev_text = true;
  1399. DEPRECATED_DOC_MSG(HANDLE_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions."));
  1400. }
  1401. if (cd.enums[key].is_experimental) {
  1402. if (has_prev_text) {
  1403. class_desc->add_newline();
  1404. if (is_multiline) {
  1405. class_desc->add_newline();
  1406. }
  1407. }
  1408. has_prev_text = true;
  1409. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions."));
  1410. }
  1411. if (!descr.is_empty()) {
  1412. if (has_prev_text) {
  1413. class_desc->add_newline();
  1414. if (is_multiline) {
  1415. class_desc->add_newline();
  1416. }
  1417. }
  1418. has_prev_text = true;
  1419. _add_text(descr);
  1420. }
  1421. class_desc->pop(); // color
  1422. _pop_normal_font();
  1423. class_desc->pop(); // indent
  1424. }
  1425. }
  1426. HashMap<String, int> enum_values;
  1427. const int enum_start_line = enum_line[E.key];
  1428. bool prev_is_multiline = true; // Use a large margin for the first item.
  1429. for (const DocData::ConstantDoc &enum_value : E.value) {
  1430. const String descr = HANDLE_DOC(enum_value.description);
  1431. const bool is_multiline = descr.find_char('\n') > 0;
  1432. class_desc->add_newline();
  1433. if (prev_is_multiline || is_multiline) {
  1434. class_desc->add_newline();
  1435. }
  1436. prev_is_multiline = is_multiline;
  1437. if (cd.name == "@GlobalScope") {
  1438. enum_values[enum_value.name] = enum_start_line;
  1439. }
  1440. // Add the enum constant line to the constant_line map so we can locate it as a constant.
  1441. constant_line[enum_value.name] = class_desc->get_paragraph_count() - 2;
  1442. class_desc->push_indent(1);
  1443. // Enum value header.
  1444. _push_code_font();
  1445. _add_bulletpoint();
  1446. class_desc->push_color(theme_cache.headline_color);
  1447. class_desc->add_text(enum_value.name);
  1448. class_desc->pop(); // color
  1449. class_desc->push_color(theme_cache.symbol_color);
  1450. class_desc->add_text(" = ");
  1451. class_desc->pop(); // color
  1452. class_desc->push_color(theme_cache.value_color);
  1453. class_desc->add_text(_fix_constant(enum_value.value));
  1454. class_desc->pop(); // color
  1455. _pop_code_font();
  1456. // Enum value description.
  1457. if (enum_value.is_deprecated || enum_value.is_experimental || !descr.is_empty()) {
  1458. class_desc->add_newline();
  1459. class_desc->push_indent(1);
  1460. _push_normal_font();
  1461. class_desc->push_color(theme_cache.comment_color);
  1462. bool has_prev_text = false;
  1463. if (enum_value.is_deprecated) {
  1464. has_prev_text = true;
  1465. DEPRECATED_DOC_MSG(HANDLE_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions."));
  1466. }
  1467. if (enum_value.is_experimental) {
  1468. if (has_prev_text) {
  1469. class_desc->add_newline();
  1470. if (is_multiline) {
  1471. class_desc->add_newline();
  1472. }
  1473. }
  1474. has_prev_text = true;
  1475. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions."));
  1476. }
  1477. if (!descr.is_empty()) {
  1478. if (has_prev_text) {
  1479. class_desc->add_newline();
  1480. if (is_multiline) {
  1481. class_desc->add_newline();
  1482. }
  1483. }
  1484. has_prev_text = true;
  1485. _add_text(descr);
  1486. }
  1487. class_desc->pop(); // color
  1488. _pop_normal_font();
  1489. class_desc->pop(); // indent
  1490. }
  1491. class_desc->pop(); // indent
  1492. }
  1493. if (cd.name == "@GlobalScope") {
  1494. enum_values_line[E.key] = enum_values;
  1495. }
  1496. }
  1497. }
  1498. // Constants
  1499. if (!constants.is_empty()) {
  1500. class_desc->add_newline();
  1501. class_desc->add_newline();
  1502. section_line.push_back(Pair<String, int>(TTR("Constants"), class_desc->get_paragraph_count() - 2));
  1503. _push_title_font();
  1504. class_desc->add_text(TTR("Constants"));
  1505. _pop_title_font();
  1506. bool prev_is_multiline = true; // Use a large margin for the first item.
  1507. for (const DocData::ConstantDoc &constant : constants) {
  1508. const String descr = HANDLE_DOC(constant.description);
  1509. const bool is_multiline = descr.find_char('\n') > 0;
  1510. class_desc->add_newline();
  1511. if (prev_is_multiline || is_multiline) {
  1512. class_desc->add_newline();
  1513. }
  1514. prev_is_multiline = is_multiline;
  1515. constant_line[constant.name] = class_desc->get_paragraph_count() - 2;
  1516. class_desc->push_indent(1);
  1517. // Constant header.
  1518. _push_code_font();
  1519. if (constant.value.begins_with("Color(") && constant.value.ends_with(")")) {
  1520. String stripped = constant.value.replace(" ", "").replace("Color(", "").replace(")", "");
  1521. PackedFloat64Array color = stripped.split_floats(",");
  1522. if (color.size() >= 3) {
  1523. class_desc->push_color(Color(color[0], color[1], color[2]));
  1524. _add_bulletpoint();
  1525. class_desc->pop(); // color
  1526. }
  1527. } else {
  1528. _add_bulletpoint();
  1529. }
  1530. class_desc->push_color(theme_cache.headline_color);
  1531. class_desc->add_text(constant.name);
  1532. class_desc->pop(); // color
  1533. class_desc->push_color(theme_cache.symbol_color);
  1534. class_desc->add_text(" = ");
  1535. class_desc->pop(); // color
  1536. class_desc->push_color(theme_cache.value_color);
  1537. class_desc->add_text(_fix_constant(constant.value));
  1538. class_desc->pop(); // color
  1539. _pop_code_font();
  1540. // Constant description.
  1541. if (constant.is_deprecated || constant.is_experimental || !descr.is_empty()) {
  1542. class_desc->add_newline();
  1543. class_desc->push_indent(1);
  1544. _push_normal_font();
  1545. class_desc->push_color(theme_cache.comment_color);
  1546. bool has_prev_text = false;
  1547. if (constant.is_deprecated) {
  1548. has_prev_text = true;
  1549. DEPRECATED_DOC_MSG(HANDLE_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions."));
  1550. }
  1551. if (constant.is_experimental) {
  1552. if (has_prev_text) {
  1553. class_desc->add_newline();
  1554. if (is_multiline) {
  1555. class_desc->add_newline();
  1556. }
  1557. }
  1558. has_prev_text = true;
  1559. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions."));
  1560. }
  1561. if (!descr.is_empty()) {
  1562. if (has_prev_text) {
  1563. class_desc->add_newline();
  1564. if (is_multiline) {
  1565. class_desc->add_newline();
  1566. }
  1567. }
  1568. has_prev_text = true;
  1569. _add_text(descr);
  1570. }
  1571. class_desc->pop(); // color
  1572. _pop_normal_font();
  1573. class_desc->pop(); // indent
  1574. }
  1575. class_desc->pop(); // indent
  1576. }
  1577. }
  1578. }
  1579. // Annotations
  1580. if (!cd.annotations.is_empty()) {
  1581. if (sort_methods) {
  1582. cd.annotations.sort();
  1583. }
  1584. class_desc->add_newline();
  1585. class_desc->add_newline();
  1586. section_line.push_back(Pair<String, int>(TTR("Annotations"), class_desc->get_paragraph_count() - 2));
  1587. _push_title_font();
  1588. class_desc->add_text(TTR("Annotations"));
  1589. _pop_title_font();
  1590. for (const DocData::MethodDoc &annotation : cd.annotations) {
  1591. class_desc->add_newline();
  1592. class_desc->add_newline();
  1593. annotation_line[annotation.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1594. class_desc->push_indent(1);
  1595. // Annotation header.
  1596. _push_code_font();
  1597. _add_bulletpoint();
  1598. class_desc->push_color(theme_cache.headline_color);
  1599. class_desc->add_text(annotation.name);
  1600. class_desc->pop(); // color
  1601. if (!annotation.arguments.is_empty()) {
  1602. class_desc->push_color(theme_cache.symbol_color);
  1603. class_desc->add_text("(");
  1604. class_desc->pop(); // color
  1605. for (int j = 0; j < annotation.arguments.size(); j++) {
  1606. const DocData::ArgumentDoc &argument = annotation.arguments[j];
  1607. class_desc->push_color(theme_cache.text_color);
  1608. if (j > 0) {
  1609. class_desc->add_text(", ");
  1610. }
  1611. class_desc->add_text(argument.name);
  1612. class_desc->add_text(": ");
  1613. _add_type(argument.type);
  1614. if (!argument.default_value.is_empty()) {
  1615. class_desc->push_color(theme_cache.symbol_color);
  1616. class_desc->add_text(" = ");
  1617. class_desc->pop(); // color
  1618. class_desc->push_color(theme_cache.value_color);
  1619. class_desc->add_text(_fix_constant(argument.default_value));
  1620. class_desc->pop(); // color
  1621. }
  1622. class_desc->pop(); // color
  1623. }
  1624. if (annotation.qualifiers.contains("vararg")) {
  1625. class_desc->push_color(theme_cache.text_color);
  1626. if (!annotation.arguments.is_empty()) {
  1627. class_desc->add_text(", ");
  1628. }
  1629. class_desc->pop(); // color
  1630. class_desc->push_color(theme_cache.symbol_color);
  1631. class_desc->add_text("...");
  1632. class_desc->pop(); // color
  1633. }
  1634. class_desc->push_color(theme_cache.symbol_color);
  1635. class_desc->add_text(")");
  1636. class_desc->pop(); // color
  1637. }
  1638. if (!annotation.qualifiers.is_empty()) {
  1639. class_desc->push_color(theme_cache.qualifier_color);
  1640. class_desc->add_text(" ");
  1641. class_desc->add_text(annotation.qualifiers);
  1642. class_desc->pop(); // color
  1643. }
  1644. _pop_code_font();
  1645. class_desc->add_newline();
  1646. // Annotation description.
  1647. class_desc->push_indent(1);
  1648. _push_normal_font();
  1649. class_desc->push_color(theme_cache.comment_color);
  1650. const String descr = HANDLE_DOC(annotation.description);
  1651. if (!descr.is_empty()) {
  1652. _add_text(descr);
  1653. } else {
  1654. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1655. class_desc->add_text(" ");
  1656. class_desc->push_color(theme_cache.comment_color);
  1657. if (cd.is_script_doc) {
  1658. class_desc->add_text(TTR("There is currently no description for this annotation."));
  1659. } else {
  1660. class_desc->append_text(TTR("There is currently no description for this annotation. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text));
  1661. }
  1662. class_desc->pop(); // color
  1663. }
  1664. class_desc->pop(); // color
  1665. _pop_normal_font();
  1666. class_desc->pop(); // indent
  1667. class_desc->pop(); // indent
  1668. }
  1669. }
  1670. // Property descriptions
  1671. if (has_property_descriptions) {
  1672. class_desc->add_newline();
  1673. class_desc->add_newline();
  1674. class_desc->add_newline();
  1675. section_line.push_back(Pair<String, int>(TTR("Property Descriptions"), class_desc->get_paragraph_count() - 2));
  1676. _push_title_font();
  1677. class_desc->add_text(TTR("Property Descriptions"));
  1678. _pop_title_font();
  1679. for (const DocData::PropertyDoc &prop : cd.properties) {
  1680. if (prop.overridden) {
  1681. continue;
  1682. }
  1683. // Ignore undocumented private.
  1684. const bool is_documented = prop.is_deprecated || prop.is_experimental || !prop.description.strip_edges().is_empty();
  1685. if (!is_documented && prop.name.begins_with("_")) {
  1686. continue;
  1687. }
  1688. class_desc->add_newline();
  1689. class_desc->add_newline();
  1690. class_desc->add_newline();
  1691. property_line[prop.name] = class_desc->get_paragraph_count() - 2;
  1692. class_desc->push_table(2);
  1693. class_desc->set_table_column_expand(1, true);
  1694. class_desc->push_cell();
  1695. _push_code_font();
  1696. _add_bulletpoint();
  1697. _add_type(prop.type, prop.enumeration, prop.is_bitfield);
  1698. _pop_code_font();
  1699. class_desc->pop(); // cell
  1700. class_desc->push_cell();
  1701. _push_code_font();
  1702. class_desc->push_color(theme_cache.headline_color);
  1703. class_desc->add_text(prop.name);
  1704. class_desc->pop(); // color
  1705. if (!prop.default_value.is_empty()) {
  1706. class_desc->push_color(theme_cache.symbol_color);
  1707. class_desc->add_text(" [" + TTR("default:") + " ");
  1708. class_desc->pop(); // color
  1709. class_desc->push_color(theme_cache.value_color);
  1710. class_desc->add_text(_fix_constant(prop.default_value));
  1711. class_desc->pop(); // color
  1712. class_desc->push_color(theme_cache.symbol_color);
  1713. class_desc->add_text("]");
  1714. class_desc->pop(); // color
  1715. }
  1716. if (cd.is_script_doc && (!prop.setter.is_empty() || !prop.getter.is_empty())) {
  1717. class_desc->push_color(theme_cache.symbol_color);
  1718. class_desc->add_text(" [" + TTR("property:") + " ");
  1719. class_desc->pop(); // color
  1720. if (!prop.setter.is_empty()) {
  1721. class_desc->push_color(theme_cache.value_color);
  1722. class_desc->add_text("setter");
  1723. class_desc->pop(); // color
  1724. }
  1725. if (!prop.getter.is_empty()) {
  1726. if (!prop.setter.is_empty()) {
  1727. class_desc->push_color(theme_cache.symbol_color);
  1728. class_desc->add_text(", ");
  1729. class_desc->pop(); // color
  1730. }
  1731. class_desc->push_color(theme_cache.value_color);
  1732. class_desc->add_text("getter");
  1733. class_desc->pop(); // color
  1734. }
  1735. class_desc->push_color(theme_cache.symbol_color);
  1736. class_desc->add_text("]");
  1737. class_desc->pop(); // color
  1738. }
  1739. _pop_code_font();
  1740. class_desc->pop(); // cell
  1741. // Script doc doesn't have setter, getter.
  1742. if (!cd.is_script_doc) {
  1743. HashMap<String, DocData::MethodDoc> method_map;
  1744. for (int j = 0; j < methods.size(); j++) {
  1745. method_map[methods[j].name] = methods[j];
  1746. }
  1747. if (!prop.setter.is_empty()) {
  1748. class_desc->push_cell();
  1749. class_desc->pop(); // cell
  1750. class_desc->push_cell();
  1751. _push_code_font();
  1752. class_desc->push_color(theme_cache.text_color);
  1753. if (method_map[prop.setter].arguments.size() > 1) {
  1754. // Setters with additional arguments are exposed in the method list, so we link them here for quick access.
  1755. class_desc->push_meta("@method " + prop.setter);
  1756. class_desc->add_text(prop.setter + TTR("(value)"));
  1757. class_desc->pop(); // meta
  1758. } else {
  1759. class_desc->add_text(prop.setter + TTR("(value)"));
  1760. }
  1761. class_desc->pop(); // color
  1762. class_desc->push_color(theme_cache.comment_color);
  1763. class_desc->add_text(" setter");
  1764. class_desc->pop(); // color
  1765. _pop_code_font();
  1766. class_desc->pop(); // cell
  1767. method_line[prop.setter] = property_line[prop.name];
  1768. }
  1769. if (!prop.getter.is_empty()) {
  1770. class_desc->push_cell();
  1771. class_desc->pop(); // cell
  1772. class_desc->push_cell();
  1773. _push_code_font();
  1774. class_desc->push_color(theme_cache.text_color);
  1775. if (!method_map[prop.getter].arguments.is_empty()) {
  1776. // Getters with additional arguments are exposed in the method list, so we link them here for quick access.
  1777. class_desc->push_meta("@method " + prop.getter);
  1778. class_desc->add_text(prop.getter + "()");
  1779. class_desc->pop(); // meta
  1780. } else {
  1781. class_desc->add_text(prop.getter + "()");
  1782. }
  1783. class_desc->pop(); // color
  1784. class_desc->push_color(theme_cache.comment_color);
  1785. class_desc->add_text(" getter");
  1786. class_desc->pop(); // color
  1787. _pop_code_font();
  1788. class_desc->pop(); // cell
  1789. method_line[prop.getter] = property_line[prop.name];
  1790. }
  1791. }
  1792. class_desc->pop(); // table
  1793. class_desc->add_newline();
  1794. class_desc->add_newline();
  1795. class_desc->push_indent(1);
  1796. _push_normal_font();
  1797. class_desc->push_color(theme_cache.text_color);
  1798. bool has_prev_text = false;
  1799. if (prop.is_deprecated) {
  1800. has_prev_text = true;
  1801. DEPRECATED_DOC_MSG(HANDLE_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions."));
  1802. }
  1803. if (prop.is_experimental) {
  1804. if (has_prev_text) {
  1805. class_desc->add_newline();
  1806. class_desc->add_newline();
  1807. }
  1808. has_prev_text = true;
  1809. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions."));
  1810. }
  1811. const String descr = HANDLE_DOC(prop.description);
  1812. if (!descr.is_empty()) {
  1813. if (has_prev_text) {
  1814. class_desc->add_newline();
  1815. class_desc->add_newline();
  1816. }
  1817. has_prev_text = true;
  1818. _add_text(descr);
  1819. // Add copy note to built-in properties returning Packed*Array.
  1820. if (!cd.is_script_doc && packed_array_types.has(prop.type)) {
  1821. class_desc->add_newline();
  1822. class_desc->add_newline();
  1823. _add_text(vformat(TTR("[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [%s] for more details."), prop.type));
  1824. }
  1825. } else if (!has_prev_text) {
  1826. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1827. class_desc->add_text(" ");
  1828. class_desc->push_color(theme_cache.comment_color);
  1829. if (cd.is_script_doc) {
  1830. class_desc->add_text(TTR("There is currently no description for this property."));
  1831. } else {
  1832. class_desc->append_text(TTR("There is currently no description for this property. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text));
  1833. }
  1834. class_desc->pop(); // color
  1835. }
  1836. class_desc->pop(); // color
  1837. _pop_normal_font();
  1838. class_desc->pop(); // indent
  1839. }
  1840. }
  1841. // Constructor descriptions
  1842. if (!cd.constructors.is_empty()) {
  1843. _update_method_descriptions(cd, METHOD_TYPE_CONSTRUCTOR, cd.constructors);
  1844. }
  1845. // Method descriptions
  1846. if (!methods.is_empty()) {
  1847. _update_method_descriptions(cd, METHOD_TYPE_METHOD, methods);
  1848. }
  1849. // Operator descriptions
  1850. if (!cd.operators.is_empty()) {
  1851. _update_method_descriptions(cd, METHOD_TYPE_OPERATOR, cd.operators);
  1852. }
  1853. // Allow the document to be scrolled slightly below the end.
  1854. class_desc->add_newline();
  1855. class_desc->add_newline();
  1856. // Free the scroll.
  1857. scroll_locked = false;
  1858. #undef HANDLE_DOC
  1859. }
  1860. void EditorHelp::_request_help(const String &p_string) {
  1861. Error err = _goto_desc(p_string);
  1862. if (err == OK) {
  1863. EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
  1864. }
  1865. }
  1866. void EditorHelp::_help_callback(const String &p_topic) {
  1867. String what = p_topic.get_slice(":", 0);
  1868. String clss = p_topic.get_slice(":", 1);
  1869. String name;
  1870. if (p_topic.get_slice_count(":") == 3) {
  1871. name = p_topic.get_slice(":", 2);
  1872. }
  1873. _request_help(clss); // First go to class.
  1874. int line = 0;
  1875. if (what == "class_desc") {
  1876. line = description_line;
  1877. } else if (what == "class_signal") {
  1878. if (signal_line.has(name)) {
  1879. line = signal_line[name];
  1880. }
  1881. } else if (what == "class_method" || what == "class_method_desc") {
  1882. if (method_line.has(name)) {
  1883. line = method_line[name];
  1884. }
  1885. } else if (what == "class_property") {
  1886. if (property_line.has(name)) {
  1887. line = property_line[name];
  1888. }
  1889. } else if (what == "class_enum") {
  1890. if (enum_line.has(name)) {
  1891. line = enum_line[name];
  1892. }
  1893. } else if (what == "class_theme_item") {
  1894. if (theme_property_line.has(name)) {
  1895. line = theme_property_line[name];
  1896. }
  1897. } else if (what == "class_constant") {
  1898. if (constant_line.has(name)) {
  1899. line = constant_line[name];
  1900. }
  1901. } else if (what == "class_annotation") {
  1902. if (annotation_line.has(name)) {
  1903. line = annotation_line[name];
  1904. }
  1905. } else if (what == "class_global") {
  1906. if (constant_line.has(name)) {
  1907. line = constant_line[name];
  1908. } else if (method_line.has(name)) {
  1909. line = method_line[name];
  1910. } else {
  1911. HashMap<String, HashMap<String, int>>::Iterator iter = enum_values_line.begin();
  1912. while (true) {
  1913. if (iter->value.has(name)) {
  1914. line = iter->value[name];
  1915. break;
  1916. } else if (iter == enum_values_line.last()) {
  1917. break;
  1918. } else {
  1919. ++iter;
  1920. }
  1921. }
  1922. }
  1923. }
  1924. if (class_desc->is_ready()) {
  1925. // call_deferred() is not enough.
  1926. class_desc->connect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph).bind(line), CONNECT_ONE_SHOT | CONNECT_DEFERRED);
  1927. } else {
  1928. scroll_to = line;
  1929. }
  1930. }
  1931. static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) {
  1932. const DocTools *doc = EditorHelp::get_doc_data();
  1933. bool is_native = false;
  1934. {
  1935. const HashMap<String, DocData::ClassDoc>::ConstIterator E = doc->class_list.find(p_class);
  1936. if (E && !E->value.is_script_doc) {
  1937. is_native = true;
  1938. }
  1939. }
  1940. const bool using_tab_indent = int(EDITOR_GET("text_editor/behavior/indent/type")) == 0;
  1941. const Ref<Font> doc_font = p_owner_node->get_theme_font(SNAME("doc"), EditorStringName(EditorFonts));
  1942. const Ref<Font> doc_bold_font = p_owner_node->get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts));
  1943. const Ref<Font> doc_italic_font = p_owner_node->get_theme_font(SNAME("doc_italic"), EditorStringName(EditorFonts));
  1944. const Ref<Font> doc_code_font = p_owner_node->get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts));
  1945. const Ref<Font> doc_kbd_font = p_owner_node->get_theme_font(SNAME("doc_keyboard"), EditorStringName(EditorFonts));
  1946. const int doc_code_font_size = p_owner_node->get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts));
  1947. const int doc_kbd_font_size = p_owner_node->get_theme_font_size(SNAME("doc_keyboard_size"), EditorStringName(EditorFonts));
  1948. const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp"));
  1949. const Color code_color = p_owner_node->get_theme_color(SNAME("code_color"), SNAME("EditorHelp"));
  1950. const Color kbd_color = p_owner_node->get_theme_color(SNAME("kbd_color"), SNAME("EditorHelp"));
  1951. const Color code_dark_color = Color(code_color, 0.8);
  1952. const Color link_color = p_owner_node->get_theme_color(SNAME("link_color"), SNAME("EditorHelp"));
  1953. const Color link_method_color = p_owner_node->get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
  1954. const Color link_property_color = link_color.lerp(p_owner_node->get_theme_color(SNAME("accent_color"), EditorStringName(Editor)), 0.25);
  1955. const Color link_annotation_color = link_color.lerp(p_owner_node->get_theme_color(SNAME("accent_color"), EditorStringName(Editor)), 0.5);
  1956. const Color code_bg_color = p_owner_node->get_theme_color(SNAME("code_bg_color"), SNAME("EditorHelp"));
  1957. const Color kbd_bg_color = p_owner_node->get_theme_color(SNAME("kbd_bg_color"), SNAME("EditorHelp"));
  1958. const Color param_bg_color = p_owner_node->get_theme_color(SNAME("param_bg_color"), SNAME("EditorHelp"));
  1959. String bbcode = p_bbcode.dedent().replace("\t", "").replace("\r", "").strip_edges();
  1960. // Select the correct code examples.
  1961. switch ((int)EDITOR_GET("text_editor/help/class_reference_examples")) {
  1962. case 0: // GDScript
  1963. bbcode = bbcode.replace("[gdscript", "[codeblock lang=gdscript"); // Tag can have extra arguments.
  1964. bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
  1965. for (int pos = bbcode.find("[csharp"); pos != -1; pos = bbcode.find("[csharp")) {
  1966. int end_pos = bbcode.find("[/csharp]");
  1967. if (end_pos == -1) {
  1968. WARN_PRINT("Unclosed [csharp] block or parse fail in code (search for tag errors)");
  1969. break;
  1970. }
  1971. bbcode = bbcode.left(pos) + bbcode.substr(end_pos + 9); // 9 is length of "[/csharp]".
  1972. while (bbcode[pos] == '\n') {
  1973. bbcode = bbcode.left(pos) + bbcode.substr(pos + 1);
  1974. }
  1975. }
  1976. break;
  1977. case 1: // C#
  1978. bbcode = bbcode.replace("[csharp", "[codeblock lang=csharp"); // Tag can have extra arguments.
  1979. bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
  1980. for (int pos = bbcode.find("[gdscript"); pos != -1; pos = bbcode.find("[gdscript")) {
  1981. int end_pos = bbcode.find("[/gdscript]");
  1982. if (end_pos == -1) {
  1983. WARN_PRINT("Unclosed [gdscript] block or parse fail in code (search for tag errors)");
  1984. break;
  1985. }
  1986. bbcode = bbcode.left(pos) + bbcode.substr(end_pos + 11); // 11 is length of "[/gdscript]".
  1987. while (bbcode[pos] == '\n') {
  1988. bbcode = bbcode.left(pos) + bbcode.substr(pos + 1);
  1989. }
  1990. }
  1991. break;
  1992. case 2: // GDScript and C#
  1993. bbcode = bbcode.replace("[csharp", "[b]C#:[/b]\n[codeblock lang=csharp"); // Tag can have extra arguments.
  1994. bbcode = bbcode.replace("[gdscript", "[b]GDScript:[/b]\n[codeblock lang=gdscript"); // Tag can have extra arguments.
  1995. bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
  1996. bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
  1997. break;
  1998. }
  1999. // Remove codeblocks (they would be printed otherwise).
  2000. bbcode = bbcode.replace("[codeblocks]\n", "");
  2001. bbcode = bbcode.replace("\n[/codeblocks]", "");
  2002. bbcode = bbcode.replace("[codeblocks]", "");
  2003. bbcode = bbcode.replace("[/codeblocks]", "");
  2004. // Remove `\n` here because `\n` is replaced by `\n\n` later.
  2005. // Will be compensated when parsing `[/codeblock]`.
  2006. bbcode = bbcode.replace("[/codeblock]\n", "[/codeblock]");
  2007. List<String> tag_stack;
  2008. int pos = 0;
  2009. while (pos < bbcode.length()) {
  2010. int brk_pos = bbcode.find_char('[', pos);
  2011. if (brk_pos < 0) {
  2012. brk_pos = bbcode.length();
  2013. }
  2014. if (brk_pos > pos) {
  2015. p_rt->add_text(bbcode.substr(pos, brk_pos - pos).replace("\n", "\n\n"));
  2016. }
  2017. if (brk_pos == bbcode.length()) {
  2018. break; // Nothing else to add.
  2019. }
  2020. int brk_end = bbcode.find_char(']', brk_pos + 1);
  2021. if (brk_end == -1) {
  2022. p_rt->add_text(bbcode.substr(brk_pos, bbcode.length() - brk_pos).replace("\n", "\n\n"));
  2023. break;
  2024. }
  2025. const String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
  2026. if (tag.begins_with("/")) {
  2027. bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);
  2028. if (!tag_ok) {
  2029. p_rt->add_text("[");
  2030. pos = brk_pos + 1;
  2031. continue;
  2032. }
  2033. tag_stack.pop_front();
  2034. pos = brk_end + 1;
  2035. if (tag != "/img") {
  2036. p_rt->pop();
  2037. }
  2038. } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) {
  2039. const int tag_end = tag.find_char(' ');
  2040. const String link_tag = tag.left(tag_end);
  2041. const String link_target = tag.substr(tag_end + 1).lstrip(" ");
  2042. Color target_color = link_color;
  2043. RichTextLabel::MetaUnderline underline_mode = RichTextLabel::META_UNDERLINE_ON_HOVER;
  2044. if (link_tag == "method" || link_tag == "constructor" || link_tag == "operator") {
  2045. target_color = link_method_color;
  2046. } else if (link_tag == "member" || link_tag == "signal" || link_tag == "theme_item") {
  2047. target_color = link_property_color;
  2048. } else if (link_tag == "annotation") {
  2049. target_color = link_annotation_color;
  2050. } else {
  2051. // Better visibility for constants, enums, etc.
  2052. underline_mode = RichTextLabel::META_UNDERLINE_ALWAYS;
  2053. }
  2054. // Use monospace font to make clickable references
  2055. // easier to distinguish from inline code and other text.
  2056. p_rt->push_font(doc_code_font);
  2057. p_rt->push_font_size(doc_code_font_size);
  2058. p_rt->push_color(target_color);
  2059. p_rt->push_meta("@" + link_tag + " " + link_target, underline_mode);
  2060. if (link_tag == "member" &&
  2061. ((!link_target.contains(".") && (p_class == "ProjectSettings" || p_class == "EditorSettings")) ||
  2062. link_target.begins_with("ProjectSettings.") || link_target.begins_with("EditorSettings."))) {
  2063. // Special formatting for both ProjectSettings and EditorSettings.
  2064. String prefix;
  2065. if (link_target.begins_with("EditorSettings.")) {
  2066. prefix = "(" + TTR("Editor") + ") ";
  2067. }
  2068. const String setting_name = link_target.trim_prefix("ProjectSettings.").trim_prefix("EditorSettings.");
  2069. PackedStringArray setting_sections;
  2070. for (const String &section : setting_name.split("/", false)) {
  2071. setting_sections.append(EditorPropertyNameProcessor::get_singleton()->process_name(section, EditorPropertyNameProcessor::get_settings_style()));
  2072. }
  2073. p_rt->push_bold();
  2074. p_rt->add_text(prefix + String(" > ").join(setting_sections));
  2075. p_rt->pop(); // bold
  2076. } else {
  2077. p_rt->add_text(link_target + (link_tag == "method" ? "()" : ""));
  2078. }
  2079. p_rt->pop(); // meta
  2080. p_rt->pop(); // color
  2081. p_rt->pop(); // font_size
  2082. p_rt->pop(); // font
  2083. pos = brk_end + 1;
  2084. } else if (tag.begins_with("param ")) {
  2085. const int tag_end = tag.find_char(' ');
  2086. const String param_name = tag.substr(tag_end + 1).lstrip(" ");
  2087. // Use monospace font with translucent background color to make code easier to distinguish from other text.
  2088. p_rt->push_font(doc_code_font);
  2089. p_rt->push_font_size(doc_code_font_size);
  2090. p_rt->push_bgcolor(param_bg_color);
  2091. p_rt->push_color(code_color);
  2092. p_rt->add_text(param_name);
  2093. p_rt->pop(); // color
  2094. p_rt->pop(); // bgcolor
  2095. p_rt->pop(); // font_size
  2096. p_rt->pop(); // font
  2097. pos = brk_end + 1;
  2098. } else if (tag == p_class) {
  2099. // Use a bold font when class reference tags are in their own page.
  2100. p_rt->push_font(doc_bold_font);
  2101. p_rt->add_text(tag);
  2102. p_rt->pop(); // font
  2103. pos = brk_end + 1;
  2104. } else if (doc->class_list.has(tag)) {
  2105. // Use a monospace font for class reference tags such as [Node2D] or [SceneTree].
  2106. p_rt->push_font(doc_code_font);
  2107. p_rt->push_font_size(doc_code_font_size);
  2108. p_rt->push_color(type_color);
  2109. p_rt->push_meta("#" + tag, RichTextLabel::META_UNDERLINE_ON_HOVER);
  2110. p_rt->add_text(tag);
  2111. p_rt->pop(); // meta
  2112. p_rt->pop(); // color
  2113. p_rt->pop(); // font_size
  2114. p_rt->pop(); // font
  2115. pos = brk_end + 1;
  2116. } else if (tag == "b") {
  2117. // Use bold font.
  2118. p_rt->push_font(doc_bold_font);
  2119. pos = brk_end + 1;
  2120. tag_stack.push_front(tag);
  2121. } else if (tag == "i") {
  2122. // Use italics font.
  2123. p_rt->push_font(doc_italic_font);
  2124. pos = brk_end + 1;
  2125. tag_stack.push_front(tag);
  2126. } else if (tag == "code" || tag.begins_with("code ")) {
  2127. int end_pos = bbcode.find("[/code]", brk_end + 1);
  2128. if (end_pos < 0) {
  2129. end_pos = bbcode.length();
  2130. }
  2131. // Use monospace font with darkened background color to make code easier to distinguish from other text.
  2132. p_rt->push_font(doc_code_font);
  2133. p_rt->push_font_size(doc_code_font_size);
  2134. p_rt->push_bgcolor(code_bg_color);
  2135. p_rt->push_color(code_color.lerp(p_owner_node->get_theme_color(SNAME("error_color"), EditorStringName(Editor)), 0.6));
  2136. p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)));
  2137. p_rt->pop(); // color
  2138. p_rt->pop(); // bgcolor
  2139. p_rt->pop(); // font_size
  2140. p_rt->pop(); // font
  2141. pos = end_pos + 7; // `len("[/code]")`.
  2142. } else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
  2143. int end_pos = bbcode.find("[/codeblock]", brk_end + 1);
  2144. if (end_pos < 0) {
  2145. end_pos = bbcode.length();
  2146. }
  2147. const String codeblock_text = bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)).strip_edges();
  2148. String codeblock_copy_text = codeblock_text;
  2149. if (using_tab_indent) {
  2150. // Replace the code block's space indentation with tabs.
  2151. StringBuilder builder;
  2152. PackedStringArray text_lines = codeblock_copy_text.split("\n");
  2153. for (const String &line : text_lines) {
  2154. const String stripped_line = line.dedent();
  2155. const int space_count = line.length() - stripped_line.length();
  2156. if (builder.num_strings_appended() > 0) {
  2157. builder.append("\n");
  2158. }
  2159. if (space_count > 0) {
  2160. builder.append(String("\t").repeat(MAX(space_count / 4, 1)) + stripped_line);
  2161. } else {
  2162. builder.append(line);
  2163. }
  2164. }
  2165. codeblock_copy_text = builder.as_string();
  2166. }
  2167. String lang;
  2168. const PackedStringArray args = tag.trim_prefix("codeblock").split(" ", false);
  2169. for (int i = args.size() - 1; i >= 0; i--) {
  2170. if (args[i].begins_with("lang=")) {
  2171. lang = args[i].trim_prefix("lang=");
  2172. break;
  2173. }
  2174. }
  2175. // Use monospace font with darkened background color to make code easier to distinguish from other text.
  2176. // Use a single-column table with cell row background color instead of `[bgcolor]`.
  2177. // This makes the background color highlight cover the entire block, rather than individual lines.
  2178. p_rt->push_font(doc_code_font);
  2179. p_rt->push_font_size(doc_code_font_size);
  2180. p_rt->push_table(2);
  2181. p_rt->push_cell();
  2182. p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99));
  2183. p_rt->set_cell_padding(Rect2(10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE));
  2184. p_rt->push_color(code_dark_color);
  2185. bool codeblock_printed = false;
  2186. #ifdef MODULE_GDSCRIPT_ENABLED
  2187. if (!codeblock_printed && (lang.is_empty() || lang == "gdscript")) {
  2188. EditorHelpHighlighter::get_singleton()->highlight(p_rt, EditorHelpHighlighter::LANGUAGE_GDSCRIPT, codeblock_text, is_native);
  2189. codeblock_printed = true;
  2190. }
  2191. #endif
  2192. #ifdef MODULE_MONO_ENABLED
  2193. if (!codeblock_printed && lang == "csharp") {
  2194. EditorHelpHighlighter::get_singleton()->highlight(p_rt, EditorHelpHighlighter::LANGUAGE_CSHARP, codeblock_text, is_native);
  2195. codeblock_printed = true;
  2196. }
  2197. #endif
  2198. if (!codeblock_printed) {
  2199. p_rt->add_text(codeblock_text);
  2200. codeblock_printed = true;
  2201. }
  2202. p_rt->pop(); // color
  2203. p_rt->pop(); // cell
  2204. // Copy codeblock button.
  2205. p_rt->push_cell();
  2206. p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99));
  2207. p_rt->set_cell_padding(Rect2(0, 10 * EDSCALE, 0, 10 * EDSCALE));
  2208. p_rt->set_cell_size_override(Vector2(1, 1), Vector2(10, 10) * EDSCALE);
  2209. p_rt->push_meta("^" + codeblock_copy_text, RichTextLabel::META_UNDERLINE_ON_HOVER);
  2210. p_rt->add_image(p_owner_node->get_editor_theme_icon(SNAME("ActionCopy")), 24 * EDSCALE, 24 * EDSCALE, Color(link_property_color, 0.3), INLINE_ALIGNMENT_BOTTOM_TO, Rect2(), Variant(), false, TTR("Click to copy."));
  2211. p_rt->pop(); // meta
  2212. p_rt->pop(); // cell
  2213. p_rt->pop(); // table
  2214. p_rt->pop(); // font_size
  2215. p_rt->pop(); // font
  2216. pos = end_pos + 12; // `len("[/codeblock]")`.
  2217. // Compensate for `\n` removed before the loop.
  2218. if (pos < bbcode.length()) {
  2219. p_rt->add_newline();
  2220. }
  2221. } else if (tag == "kbd") {
  2222. int end_pos = bbcode.find("[/kbd]", brk_end + 1);
  2223. if (end_pos < 0) {
  2224. end_pos = bbcode.length();
  2225. }
  2226. // Use keyboard font with custom color and background color.
  2227. p_rt->push_font(doc_kbd_font);
  2228. p_rt->push_font_size(doc_kbd_font_size);
  2229. p_rt->push_bgcolor(kbd_bg_color);
  2230. p_rt->push_color(kbd_color);
  2231. p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)));
  2232. p_rt->pop(); // color
  2233. p_rt->pop(); // bgcolor
  2234. p_rt->pop(); // font_size
  2235. p_rt->pop(); // font
  2236. pos = end_pos + 6; // `len("[/kbd]")`.
  2237. } else if (tag == "center") {
  2238. // Align to center.
  2239. p_rt->push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, Control::TEXT_DIRECTION_AUTO, "");
  2240. pos = brk_end + 1;
  2241. tag_stack.push_front(tag);
  2242. } else if (tag == "br") {
  2243. // Force a line break.
  2244. p_rt->add_newline();
  2245. pos = brk_end + 1;
  2246. } else if (tag == "u") {
  2247. // Use underline.
  2248. p_rt->push_underline();
  2249. pos = brk_end + 1;
  2250. tag_stack.push_front(tag);
  2251. } else if (tag == "s") {
  2252. // Use strikethrough.
  2253. p_rt->push_strikethrough();
  2254. pos = brk_end + 1;
  2255. tag_stack.push_front(tag);
  2256. } else if (tag == "lb") {
  2257. p_rt->add_text("[");
  2258. pos = brk_end + 1;
  2259. } else if (tag == "rb") {
  2260. p_rt->add_text("]");
  2261. pos = brk_end + 1;
  2262. } else if (tag == "url") {
  2263. int end = bbcode.find_char('[', brk_end);
  2264. if (end == -1) {
  2265. end = bbcode.length();
  2266. }
  2267. String url = bbcode.substr(brk_end + 1, end - brk_end - 1);
  2268. p_rt->push_meta(url);
  2269. pos = brk_end + 1;
  2270. tag_stack.push_front(tag);
  2271. } else if (tag.begins_with("url=")) {
  2272. String url = tag.substr(4);
  2273. p_rt->push_meta(url);
  2274. pos = brk_end + 1;
  2275. tag_stack.push_front("url");
  2276. } else if (tag.begins_with("img")) {
  2277. int width = 0;
  2278. int height = 0;
  2279. bool size_in_percent = false;
  2280. if (tag.length() > 4) {
  2281. Vector<String> subtags = tag.substr(4).split(" ");
  2282. HashMap<String, String> bbcode_options;
  2283. for (int i = 0; i < subtags.size(); i++) {
  2284. const String &expr = subtags[i];
  2285. int value_pos = expr.find_char('=');
  2286. if (value_pos > -1) {
  2287. bbcode_options[expr.left(value_pos)] = expr.substr(value_pos + 1).unquote();
  2288. }
  2289. }
  2290. HashMap<String, String>::Iterator width_option = bbcode_options.find("width");
  2291. if (width_option) {
  2292. width = width_option->value.to_int();
  2293. if (width_option->value.ends_with("%")) {
  2294. size_in_percent = true;
  2295. }
  2296. }
  2297. HashMap<String, String>::Iterator height_option = bbcode_options.find("height");
  2298. if (height_option) {
  2299. height = height_option->value.to_int();
  2300. if (height_option->value.ends_with("%")) {
  2301. size_in_percent = true;
  2302. }
  2303. }
  2304. }
  2305. int end = bbcode.find_char('[', brk_end);
  2306. if (end == -1) {
  2307. end = bbcode.length();
  2308. }
  2309. String image_path = bbcode.substr(brk_end + 1, end - brk_end - 1);
  2310. p_rt->add_image(ResourceLoader::load(image_path, "Texture2D"), width, height, Color(1, 1, 1), INLINE_ALIGNMENT_CENTER, Rect2(), Variant(), false, String(), size_in_percent);
  2311. pos = end;
  2312. tag_stack.push_front("img");
  2313. } else if (tag.begins_with("color=")) {
  2314. String col = tag.substr(6);
  2315. Color color = Color::from_string(col, Color());
  2316. p_rt->push_color(color);
  2317. pos = brk_end + 1;
  2318. tag_stack.push_front("color");
  2319. } else if (tag.begins_with("font=")) {
  2320. String font_path = tag.substr(5);
  2321. Ref<Font> font = ResourceLoader::load(font_path, "Font");
  2322. if (font.is_valid()) {
  2323. p_rt->push_font(font);
  2324. } else {
  2325. p_rt->push_font(doc_font);
  2326. }
  2327. pos = brk_end + 1;
  2328. tag_stack.push_front("font");
  2329. } else {
  2330. p_rt->add_text("["); // Ignore.
  2331. pos = brk_pos + 1;
  2332. }
  2333. }
  2334. // Close unclosed tags.
  2335. for (const String &tag : tag_stack) {
  2336. if (tag != "img") {
  2337. p_rt->pop();
  2338. }
  2339. }
  2340. }
  2341. void EditorHelp::_add_text(const String &p_bbcode) {
  2342. _add_text_to_rt(p_bbcode, class_desc, this, edited_class);
  2343. }
  2344. int EditorHelp::doc_generation_count = 0;
  2345. String EditorHelp::doc_version_hash;
  2346. Thread EditorHelp::worker_thread;
  2347. void EditorHelp::_wait_for_thread() {
  2348. if (worker_thread.is_started()) {
  2349. worker_thread.wait_to_finish();
  2350. }
  2351. }
  2352. void EditorHelp::_compute_doc_version_hash() {
  2353. uint32_t version_hash = Engine::get_singleton()->get_version_info().hash();
  2354. doc_version_hash = vformat("%d/%d/%d/%s", version_hash, ClassDB::get_api_hash(ClassDB::API_CORE), ClassDB::get_api_hash(ClassDB::API_EDITOR), _doc_data_hash);
  2355. }
  2356. String EditorHelp::get_cache_full_path() {
  2357. return EditorPaths::get_singleton()->get_cache_dir().path_join("editor_doc_cache.res");
  2358. }
  2359. void EditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) {
  2360. if (!ext_doc) {
  2361. ext_doc = memnew(DocTools);
  2362. }
  2363. ext_doc->load_xml(p_buffer, p_size);
  2364. if (doc) {
  2365. doc->load_xml(p_buffer, p_size);
  2366. }
  2367. }
  2368. void EditorHelp::remove_class(const String &p_class) {
  2369. if (ext_doc && ext_doc->has_doc(p_class)) {
  2370. ext_doc->remove_doc(p_class);
  2371. }
  2372. if (doc && doc->has_doc(p_class)) {
  2373. doc->remove_doc(p_class);
  2374. }
  2375. }
  2376. void EditorHelp::_load_doc_thread(void *p_udata) {
  2377. Ref<Resource> cache_res = ResourceLoader::load(get_cache_full_path());
  2378. if (cache_res.is_valid() && cache_res->get_meta("version_hash", "") == doc_version_hash) {
  2379. Array classes = cache_res->get_meta("classes", Array());
  2380. for (int i = 0; i < classes.size(); i++) {
  2381. doc->add_doc(DocData::ClassDoc::from_dict(classes[i]));
  2382. }
  2383. // Extensions' docs are not cached. Generate them now (on the main thread).
  2384. callable_mp_static(&EditorHelp::_gen_extensions_docs).call_deferred();
  2385. } else {
  2386. // We have to go back to the main thread to start from scratch, bypassing any possibly existing cache.
  2387. callable_mp_static(&EditorHelp::generate_doc).bind(false).call_deferred();
  2388. }
  2389. OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
  2390. }
  2391. void EditorHelp::_gen_doc_thread(void *p_udata) {
  2392. DocTools compdoc;
  2393. compdoc.load_compressed(_doc_data_compressed, _doc_data_compressed_size, _doc_data_uncompressed_size);
  2394. doc->merge_from(compdoc); // Ensure all is up to date.
  2395. Ref<Resource> cache_res;
  2396. cache_res.instantiate();
  2397. cache_res->set_meta("version_hash", doc_version_hash);
  2398. Array classes;
  2399. for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {
  2400. if (ClassDB::class_exists(E.value.name)) {
  2401. ClassDB::APIType api = ClassDB::get_api_type(E.value.name);
  2402. if (api == ClassDB::API_EXTENSION || api == ClassDB::API_EDITOR_EXTENSION) {
  2403. continue;
  2404. }
  2405. }
  2406. classes.push_back(DocData::ClassDoc::to_dict(E.value));
  2407. }
  2408. cache_res->set_meta("classes", classes);
  2409. Error err = ResourceSaver::save(cache_res, get_cache_full_path(), ResourceSaver::FLAG_COMPRESS);
  2410. if (err) {
  2411. ERR_PRINT("Cannot save editor help cache (" + get_cache_full_path() + ").");
  2412. }
  2413. OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
  2414. }
  2415. void EditorHelp::_gen_extensions_docs() {
  2416. doc->generate((DocTools::GENERATE_FLAG_SKIP_BASIC_TYPES | DocTools::GENERATE_FLAG_EXTENSION_CLASSES_ONLY));
  2417. // Append extra doc data, as it gets overridden by the generation step.
  2418. if (ext_doc) {
  2419. doc->merge_from(*ext_doc);
  2420. }
  2421. }
  2422. void EditorHelp::generate_doc(bool p_use_cache) {
  2423. doc_generation_count++;
  2424. OS::get_singleton()->benchmark_begin_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
  2425. // In case not the first attempt.
  2426. _wait_for_thread();
  2427. if (!doc) {
  2428. doc = memnew(DocTools);
  2429. }
  2430. if (doc_version_hash.is_empty()) {
  2431. _compute_doc_version_hash();
  2432. }
  2433. if (p_use_cache && FileAccess::exists(get_cache_full_path())) {
  2434. worker_thread.start(_load_doc_thread, nullptr);
  2435. } else {
  2436. print_verbose("Regenerating editor help cache");
  2437. doc->generate();
  2438. worker_thread.start(_gen_doc_thread, nullptr);
  2439. }
  2440. }
  2441. void EditorHelp::_toggle_scripts_pressed() {
  2442. ScriptEditor::get_singleton()->toggle_scripts_panel();
  2443. update_toggle_scripts_button();
  2444. }
  2445. void EditorHelp::_notification(int p_what) {
  2446. switch (p_what) {
  2447. case NOTIFICATION_POSTINITIALIZE: {
  2448. // Requires theme to be up to date.
  2449. _class_desc_resized(false);
  2450. } break;
  2451. case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
  2452. bool need_update = false;
  2453. if (EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/help")) {
  2454. need_update = true;
  2455. }
  2456. #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
  2457. if (!need_update && EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme/highlighting")) {
  2458. need_update = true;
  2459. }
  2460. #endif
  2461. if (!need_update) {
  2462. break;
  2463. }
  2464. [[fallthrough]];
  2465. }
  2466. case NOTIFICATION_READY: {
  2467. _wait_for_thread();
  2468. _update_doc();
  2469. } break;
  2470. case NOTIFICATION_THEME_CHANGED: {
  2471. if (is_inside_tree()) {
  2472. _class_desc_resized(true);
  2473. }
  2474. update_toggle_scripts_button();
  2475. } break;
  2476. case NOTIFICATION_VISIBILITY_CHANGED: {
  2477. update_toggle_scripts_button();
  2478. } break;
  2479. }
  2480. }
  2481. void EditorHelp::go_to_help(const String &p_help) {
  2482. _wait_for_thread();
  2483. _help_callback(p_help);
  2484. }
  2485. void EditorHelp::go_to_class(const String &p_class) {
  2486. _wait_for_thread();
  2487. _goto_desc(p_class);
  2488. }
  2489. void EditorHelp::update_doc() {
  2490. _wait_for_thread();
  2491. ERR_FAIL_COND(!doc->class_list.has(edited_class));
  2492. ERR_FAIL_COND(!doc->class_list[edited_class].is_script_doc);
  2493. _update_doc();
  2494. }
  2495. void EditorHelp::cleanup_doc() {
  2496. _wait_for_thread();
  2497. memdelete(doc);
  2498. }
  2499. Vector<Pair<String, int>> EditorHelp::get_sections() {
  2500. _wait_for_thread();
  2501. Vector<Pair<String, int>> sections;
  2502. for (int i = 0; i < section_line.size(); i++) {
  2503. sections.push_back(Pair<String, int>(section_line[i].first, i));
  2504. }
  2505. return sections;
  2506. }
  2507. void EditorHelp::scroll_to_section(int p_section_index) {
  2508. _wait_for_thread();
  2509. int line = section_line[p_section_index].second;
  2510. if (class_desc->is_ready()) {
  2511. class_desc->scroll_to_paragraph(line);
  2512. } else {
  2513. scroll_to = line;
  2514. }
  2515. }
  2516. void EditorHelp::popup_search() {
  2517. _wait_for_thread();
  2518. find_bar->popup_search();
  2519. }
  2520. String EditorHelp::get_class() {
  2521. return edited_class;
  2522. }
  2523. void EditorHelp::search_again(bool p_search_previous) {
  2524. _search(p_search_previous);
  2525. }
  2526. int EditorHelp::get_scroll() const {
  2527. return class_desc->get_v_scroll_bar()->get_value();
  2528. }
  2529. void EditorHelp::set_scroll(int p_scroll) {
  2530. class_desc->get_v_scroll_bar()->set_value(p_scroll);
  2531. }
  2532. void EditorHelp::update_toggle_scripts_button() {
  2533. if (is_layout_rtl()) {
  2534. toggle_scripts_button->set_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Forward") : SNAME("Back")));
  2535. } else {
  2536. toggle_scripts_button->set_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Back") : SNAME("Forward")));
  2537. }
  2538. toggle_scripts_button->set_tooltip_text(vformat("%s (%s)", TTR("Toggle Scripts Panel"), ED_GET_SHORTCUT("script_editor/toggle_scripts_panel")->get_as_text()));
  2539. }
  2540. void EditorHelp::_bind_methods() {
  2541. ClassDB::bind_method("_class_list_select", &EditorHelp::_class_list_select);
  2542. ClassDB::bind_method("_request_help", &EditorHelp::_request_help);
  2543. ClassDB::bind_method("_search", &EditorHelp::_search);
  2544. ClassDB::bind_method("_help_callback", &EditorHelp::_help_callback);
  2545. ADD_SIGNAL(MethodInfo("go_to_help"));
  2546. ADD_SIGNAL(MethodInfo("request_save_history"));
  2547. }
  2548. void EditorHelp::init_gdext_pointers() {
  2549. GDExtensionEditorHelp::editor_help_load_xml_buffer = &EditorHelp::load_xml_buffer;
  2550. GDExtensionEditorHelp::editor_help_remove_class = &EditorHelp::remove_class;
  2551. }
  2552. EditorHelp::EditorHelp() {
  2553. set_custom_minimum_size(Size2(150 * EDSCALE, 0));
  2554. EDITOR_DEF("text_editor/help/sort_functions_alphabetically", true);
  2555. class_desc = memnew(RichTextLabel);
  2556. class_desc->set_tab_size(8);
  2557. add_child(class_desc);
  2558. class_desc->set_threaded(true);
  2559. class_desc->set_v_size_flags(SIZE_EXPAND_FILL);
  2560. class_desc->connect(SceneStringName(finished), callable_mp(this, &EditorHelp::_class_desc_finished));
  2561. class_desc->connect("meta_clicked", callable_mp(this, &EditorHelp::_class_desc_select));
  2562. class_desc->connect(SceneStringName(gui_input), callable_mp(this, &EditorHelp::_class_desc_input));
  2563. class_desc->connect(SceneStringName(resized), callable_mp(this, &EditorHelp::_class_desc_resized).bind(false));
  2564. // Added second so it opens at the bottom so it won't offset the entire widget.
  2565. find_bar = memnew(FindBar);
  2566. add_child(find_bar);
  2567. find_bar->hide();
  2568. find_bar->set_rich_text_label(class_desc);
  2569. status_bar = memnew(HBoxContainer);
  2570. add_child(status_bar);
  2571. status_bar->set_h_size_flags(SIZE_EXPAND_FILL);
  2572. status_bar->set_custom_minimum_size(Size2(0, 24 * EDSCALE));
  2573. toggle_scripts_button = memnew(Button);
  2574. toggle_scripts_button->set_flat(true);
  2575. toggle_scripts_button->connect(SceneStringName(pressed), callable_mp(this, &EditorHelp::_toggle_scripts_pressed));
  2576. status_bar->add_child(toggle_scripts_button);
  2577. class_desc->set_selection_enabled(true);
  2578. class_desc->set_context_menu_enabled(true);
  2579. class_desc->hide();
  2580. }
  2581. EditorHelp::~EditorHelp() {
  2582. }
  2583. DocTools *EditorHelp::get_doc_data() {
  2584. _wait_for_thread();
  2585. return doc;
  2586. }
  2587. /// EditorHelpBit ///
  2588. #define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges())
  2589. EditorHelpBit::HelpData EditorHelpBit::_get_class_help_data(const StringName &p_class_name) {
  2590. if (doc_class_cache.has(p_class_name)) {
  2591. return doc_class_cache[p_class_name];
  2592. }
  2593. HelpData result;
  2594. const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
  2595. if (E) {
  2596. // Non-native class shouldn't be cached, nor translated.
  2597. const bool is_native = !E->value.is_script_doc;
  2598. result.description = HANDLE_DOC(E->value.brief_description);
  2599. if (E->value.is_deprecated) {
  2600. if (E->value.deprecated_message.is_empty()) {
  2601. result.deprecated_message = TTR("This class may be changed or removed in future versions.");
  2602. } else {
  2603. result.deprecated_message = HANDLE_DOC(E->value.deprecated_message);
  2604. }
  2605. }
  2606. if (E->value.is_experimental) {
  2607. if (E->value.experimental_message.is_empty()) {
  2608. result.experimental_message = TTR("This class may be changed or removed in future versions.");
  2609. } else {
  2610. result.experimental_message = HANDLE_DOC(E->value.experimental_message);
  2611. }
  2612. }
  2613. if (is_native) {
  2614. doc_class_cache[p_class_name] = result;
  2615. }
  2616. }
  2617. return result;
  2618. }
  2619. EditorHelpBit::HelpData EditorHelpBit::_get_property_help_data(const StringName &p_class_name, const StringName &p_property_name) {
  2620. if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) {
  2621. return doc_property_cache[p_class_name][p_property_name];
  2622. }
  2623. HelpData result;
  2624. const DocTools *dd = EditorHelp::get_doc_data();
  2625. const HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
  2626. if (E) {
  2627. // Non-native properties shouldn't be cached, nor translated.
  2628. const bool is_native = !E->value.is_script_doc;
  2629. for (const DocData::PropertyDoc &property : E->value.properties) {
  2630. HelpData current;
  2631. current.description = HANDLE_DOC(property.description);
  2632. if (property.is_deprecated) {
  2633. if (property.deprecated_message.is_empty()) {
  2634. current.deprecated_message = TTR("This property may be changed or removed in future versions.");
  2635. } else {
  2636. current.deprecated_message = HANDLE_DOC(property.deprecated_message);
  2637. }
  2638. }
  2639. if (property.is_experimental) {
  2640. if (property.experimental_message.is_empty()) {
  2641. current.experimental_message = TTR("This property may be changed or removed in future versions.");
  2642. } else {
  2643. current.experimental_message = HANDLE_DOC(property.experimental_message);
  2644. }
  2645. }
  2646. String enum_class_name;
  2647. String enum_name;
  2648. if (CoreConstants::is_global_enum(property.enumeration)) {
  2649. enum_class_name = "@GlobalScope";
  2650. enum_name = property.enumeration;
  2651. } else {
  2652. const int dot_pos = property.enumeration.rfind(".");
  2653. if (dot_pos >= 0) {
  2654. enum_class_name = property.enumeration.left(dot_pos);
  2655. enum_name = property.enumeration.substr(dot_pos + 1);
  2656. }
  2657. }
  2658. if (!enum_class_name.is_empty() && !enum_name.is_empty()) {
  2659. // Classes can use enums from other classes, so check from which it came.
  2660. const HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(enum_class_name);
  2661. if (enum_class) {
  2662. const String enum_prefix = EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " ";
  2663. for (DocData::ConstantDoc constant : enum_class->value.constants) {
  2664. // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
  2665. if (constant.enumeration == enum_name && !constant.name.ends_with("_MAX")) {
  2666. // Prettify the enum value display, so that "<ENUM_NAME>_<ITEM>" becomes "Item".
  2667. const String item_name = EditorPropertyNameProcessor::get_singleton()->process_name(constant.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED).trim_prefix(enum_prefix);
  2668. String item_descr = HANDLE_DOC(constant.description);
  2669. if (item_descr.is_empty()) {
  2670. item_descr = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]";
  2671. }
  2672. current.description += vformat("\n[b]%s:[/b] %s", item_name, item_descr);
  2673. }
  2674. }
  2675. current.description = current.description.lstrip("\n");
  2676. }
  2677. }
  2678. if (property.name == p_property_name) {
  2679. result = current;
  2680. if (!is_native) {
  2681. break;
  2682. }
  2683. }
  2684. if (is_native) {
  2685. doc_property_cache[p_class_name][property.name] = current;
  2686. }
  2687. }
  2688. }
  2689. return result;
  2690. }
  2691. EditorHelpBit::HelpData EditorHelpBit::_get_method_help_data(const StringName &p_class_name, const StringName &p_method_name) {
  2692. if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) {
  2693. return doc_method_cache[p_class_name][p_method_name];
  2694. }
  2695. HelpData result;
  2696. const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
  2697. if (E) {
  2698. // Non-native methods shouldn't be cached, nor translated.
  2699. const bool is_native = !E->value.is_script_doc;
  2700. for (const DocData::MethodDoc &method : E->value.methods) {
  2701. HelpData current;
  2702. current.description = HANDLE_DOC(method.description);
  2703. if (method.is_deprecated) {
  2704. if (method.deprecated_message.is_empty()) {
  2705. current.deprecated_message = TTR("This method may be changed or removed in future versions.");
  2706. } else {
  2707. current.deprecated_message = HANDLE_DOC(method.deprecated_message);
  2708. }
  2709. }
  2710. if (method.is_experimental) {
  2711. if (method.experimental_message.is_empty()) {
  2712. current.experimental_message = TTR("This method may be changed or removed in future versions.");
  2713. } else {
  2714. current.experimental_message = HANDLE_DOC(method.experimental_message);
  2715. }
  2716. }
  2717. current.doc_type = { method.return_type, method.return_enum, method.return_is_bitfield };
  2718. for (const DocData::ArgumentDoc &argument : method.arguments) {
  2719. const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield };
  2720. current.arguments.push_back({ argument.name, argument_type, argument.default_value });
  2721. }
  2722. if (method.name == p_method_name) {
  2723. result = current;
  2724. if (!is_native) {
  2725. break;
  2726. }
  2727. }
  2728. if (is_native) {
  2729. doc_method_cache[p_class_name][method.name] = current;
  2730. }
  2731. }
  2732. }
  2733. return result;
  2734. }
  2735. EditorHelpBit::HelpData EditorHelpBit::_get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name) {
  2736. if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) {
  2737. return doc_signal_cache[p_class_name][p_signal_name];
  2738. }
  2739. HelpData result;
  2740. const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
  2741. if (E) {
  2742. // Non-native signals shouldn't be cached, nor translated.
  2743. const bool is_native = !E->value.is_script_doc;
  2744. for (const DocData::MethodDoc &signal : E->value.signals) {
  2745. HelpData current;
  2746. current.description = HANDLE_DOC(signal.description);
  2747. if (signal.is_deprecated) {
  2748. if (signal.deprecated_message.is_empty()) {
  2749. current.deprecated_message = TTR("This signal may be changed or removed in future versions.");
  2750. } else {
  2751. current.deprecated_message = HANDLE_DOC(signal.deprecated_message);
  2752. }
  2753. }
  2754. if (signal.is_experimental) {
  2755. if (signal.experimental_message.is_empty()) {
  2756. current.experimental_message = TTR("This signal may be changed or removed in future versions.");
  2757. } else {
  2758. current.experimental_message = HANDLE_DOC(signal.experimental_message);
  2759. }
  2760. }
  2761. for (const DocData::ArgumentDoc &argument : signal.arguments) {
  2762. const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield };
  2763. current.arguments.push_back({ argument.name, argument_type, argument.default_value });
  2764. }
  2765. if (signal.name == p_signal_name) {
  2766. result = current;
  2767. if (!is_native) {
  2768. break;
  2769. }
  2770. }
  2771. if (is_native) {
  2772. doc_signal_cache[p_class_name][signal.name] = current;
  2773. }
  2774. }
  2775. }
  2776. return result;
  2777. }
  2778. EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name) {
  2779. if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) {
  2780. return doc_theme_item_cache[p_class_name][p_theme_item_name];
  2781. }
  2782. HelpData result;
  2783. bool found = false;
  2784. const DocTools *dd = EditorHelp::get_doc_data();
  2785. HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
  2786. while (E) {
  2787. // Non-native theme items shouldn't be cached, nor translated.
  2788. const bool is_native = !E->value.is_script_doc;
  2789. for (const DocData::ThemeItemDoc &theme_item : E->value.theme_properties) {
  2790. HelpData current;
  2791. current.description = HANDLE_DOC(theme_item.description);
  2792. if (theme_item.name == p_theme_item_name) {
  2793. result = current;
  2794. found = true;
  2795. if (!is_native) {
  2796. break;
  2797. }
  2798. }
  2799. if (is_native) {
  2800. doc_theme_item_cache[p_class_name][theme_item.name] = current;
  2801. }
  2802. }
  2803. if (found || E->value.inherits.is_empty()) {
  2804. break;
  2805. }
  2806. // Check for inherited theme items.
  2807. E = dd->class_list.find(E->value.inherits);
  2808. }
  2809. return result;
  2810. }
  2811. #undef HANDLE_DOC
  2812. void EditorHelpBit::_add_type_to_title(const DocType &p_doc_type) {
  2813. _add_type_to_rt(p_doc_type.type, p_doc_type.enumeration, p_doc_type.is_bitfield, title, this, symbol_class_name);
  2814. }
  2815. void EditorHelpBit::_update_labels() {
  2816. const Ref<Font> doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts));
  2817. if (!symbol_visible_type.is_empty() || !symbol_name.is_empty()) {
  2818. title->clear();
  2819. title->push_font(doc_bold_font);
  2820. if (!symbol_visible_type.is_empty()) {
  2821. title->push_color(get_theme_color(SNAME("title_color"), SNAME("EditorHelp")));
  2822. title->add_text(symbol_visible_type);
  2823. title->pop(); // color
  2824. }
  2825. if (!symbol_visible_type.is_empty() && !symbol_name.is_empty()) {
  2826. title->add_text(" ");
  2827. }
  2828. if (!symbol_name.is_empty()) {
  2829. title->push_underline();
  2830. title->add_text(symbol_name);
  2831. title->pop(); // underline
  2832. }
  2833. title->pop(); // font
  2834. if (symbol_type == "method" || symbol_type == "signal") {
  2835. const Color symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp"));
  2836. const Color value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp"));
  2837. title->push_font(get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts)));
  2838. title->push_font_size(get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts)) * 0.9);
  2839. title->push_color(symbol_color);
  2840. title->add_text("(");
  2841. title->pop(); // color
  2842. for (int i = 0; i < help_data.arguments.size(); i++) {
  2843. const ArgumentData &argument = help_data.arguments[i];
  2844. if (i > 0) {
  2845. title->push_color(symbol_color);
  2846. title->add_text(", ");
  2847. title->pop(); // color
  2848. }
  2849. title->add_text(argument.name);
  2850. title->push_color(symbol_color);
  2851. title->add_text(": ");
  2852. title->pop(); // color
  2853. _add_type_to_title(argument.doc_type);
  2854. if (!argument.default_value.is_empty()) {
  2855. title->push_color(symbol_color);
  2856. title->add_text(" = ");
  2857. title->pop(); // color
  2858. title->push_color(value_color);
  2859. title->add_text(argument.default_value);
  2860. title->pop(); // color
  2861. }
  2862. }
  2863. title->push_color(symbol_color);
  2864. title->add_text(")");
  2865. title->pop(); // color
  2866. if (symbol_type == "method") {
  2867. title->push_color(symbol_color);
  2868. title->add_text(" -> ");
  2869. title->pop(); // color
  2870. _add_type_to_title(help_data.doc_type);
  2871. }
  2872. title->pop(); // font_size
  2873. title->pop(); // font
  2874. }
  2875. title->show();
  2876. } else {
  2877. title->hide();
  2878. }
  2879. content->clear();
  2880. bool has_prev_text = false;
  2881. if (!help_data.deprecated_message.is_empty()) {
  2882. has_prev_text = true;
  2883. Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError"));
  2884. content->add_image(error_icon, error_icon->get_width(), error_icon->get_height());
  2885. content->add_text(" ");
  2886. content->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  2887. content->push_font(doc_bold_font);
  2888. content->add_text(TTR("Deprecated:"));
  2889. content->pop(); // font
  2890. content->pop(); // color
  2891. content->add_text(" ");
  2892. _add_text_to_rt(help_data.deprecated_message, content, this, symbol_class_name);
  2893. }
  2894. if (!help_data.experimental_message.is_empty()) {
  2895. if (has_prev_text) {
  2896. content->add_newline();
  2897. content->add_newline();
  2898. }
  2899. has_prev_text = true;
  2900. Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning"));
  2901. content->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height());
  2902. content->add_text(" ");
  2903. content->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
  2904. content->push_font(doc_bold_font);
  2905. content->add_text(TTR("Experimental:"));
  2906. content->pop(); // font
  2907. content->pop(); // color
  2908. content->add_text(" ");
  2909. _add_text_to_rt(help_data.experimental_message, content, this, symbol_class_name);
  2910. }
  2911. if (!help_data.description.is_empty()) {
  2912. if (has_prev_text) {
  2913. content->add_newline();
  2914. content->add_newline();
  2915. }
  2916. has_prev_text = true;
  2917. const Color comment_color = get_theme_color(SNAME("comment_color"), SNAME("EditorHelp"));
  2918. _add_text_to_rt(help_data.description.replace("<EditorHelpBitCommentColor>", comment_color.to_html()), content, this, symbol_class_name);
  2919. }
  2920. if (is_inside_tree()) {
  2921. update_content_height();
  2922. }
  2923. }
  2924. void EditorHelpBit::_go_to_help(const String &p_what) {
  2925. EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
  2926. ScriptEditor::get_singleton()->goto_help(p_what);
  2927. emit_signal(SNAME("request_hide"));
  2928. }
  2929. void EditorHelpBit::_meta_clicked(const String &p_select) {
  2930. if (p_select.begins_with("$")) { // Enum.
  2931. const String link = p_select.substr(1);
  2932. String enum_class_name;
  2933. String enum_name;
  2934. if (CoreConstants::is_global_enum(link)) {
  2935. enum_class_name = "@GlobalScope";
  2936. enum_name = link;
  2937. } else {
  2938. const int dot_pos = link.rfind(".");
  2939. if (dot_pos >= 0) {
  2940. enum_class_name = link.left(dot_pos);
  2941. enum_name = link.substr(dot_pos + 1);
  2942. } else {
  2943. enum_class_name = symbol_class_name;
  2944. enum_name = link;
  2945. }
  2946. }
  2947. _go_to_help("class_enum:" + enum_class_name + ":" + enum_name);
  2948. } else if (p_select.begins_with("#")) { // Class.
  2949. _go_to_help("class_name:" + p_select.substr(1));
  2950. } else if (p_select.begins_with("@")) { // Member.
  2951. const int tag_end = p_select.find_char(' ');
  2952. const String tag = p_select.substr(1, tag_end - 1);
  2953. const String link = p_select.substr(tag_end + 1).lstrip(" ");
  2954. String topic;
  2955. if (tag == "method") {
  2956. topic = "class_method";
  2957. } else if (tag == "constructor") {
  2958. topic = "class_method";
  2959. } else if (tag == "operator") {
  2960. topic = "class_method";
  2961. } else if (tag == "member") {
  2962. topic = "class_property";
  2963. } else if (tag == "enum") {
  2964. topic = "class_enum";
  2965. } else if (tag == "signal") {
  2966. topic = "class_signal";
  2967. } else if (tag == "constant") {
  2968. topic = "class_constant";
  2969. } else if (tag == "annotation") {
  2970. topic = "class_annotation";
  2971. } else if (tag == "theme_item") {
  2972. topic = "class_theme_item";
  2973. } else {
  2974. return;
  2975. }
  2976. if (link.contains(".")) {
  2977. const int class_end = link.find_char('.');
  2978. _go_to_help(topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1));
  2979. } else {
  2980. _go_to_help(topic + ":" + symbol_class_name + ":" + link);
  2981. }
  2982. } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) {
  2983. OS::get_singleton()->shell_open(p_select);
  2984. } else if (p_select.begins_with("^")) { // Copy button.
  2985. DisplayServer::get_singleton()->clipboard_set(p_select.substr(1));
  2986. }
  2987. }
  2988. void EditorHelpBit::_bind_methods() {
  2989. ADD_SIGNAL(MethodInfo("request_hide"));
  2990. }
  2991. void EditorHelpBit::_notification(int p_what) {
  2992. switch (p_what) {
  2993. case NOTIFICATION_THEME_CHANGED:
  2994. _update_labels();
  2995. break;
  2996. }
  2997. }
  2998. void EditorHelpBit::parse_symbol(const String &p_symbol) {
  2999. const PackedStringArray slices = p_symbol.split("|", true, 2);
  3000. ERR_FAIL_COND_MSG(slices.size() < 3, "Invalid doc id. The expected format is 'item_type|class_name|item_name'.");
  3001. const String &item_type = slices[0];
  3002. const String &class_name = slices[1];
  3003. const String &item_name = slices[2];
  3004. String visible_type;
  3005. String name = item_name;
  3006. if (item_type == "class") {
  3007. visible_type = TTR("Class:");
  3008. name = class_name;
  3009. help_data = _get_class_help_data(class_name);
  3010. } else if (item_type == "property") {
  3011. if (name.begins_with("metadata/")) {
  3012. visible_type = TTR("Metadata:");
  3013. name = name.trim_prefix("metadata/");
  3014. } else if (class_name == "ProjectSettings" || class_name == "EditorSettings") {
  3015. visible_type = TTR("Setting:");
  3016. } else {
  3017. visible_type = TTR("Property:");
  3018. }
  3019. help_data = _get_property_help_data(class_name, item_name);
  3020. } else if (item_type == "internal_property") {
  3021. visible_type = TTR("Internal Property:");
  3022. help_data = HelpData();
  3023. help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("This property can only be set in the Inspector.") + "[/i][/color]";
  3024. } else if (item_type == "method") {
  3025. visible_type = TTR("Method:");
  3026. help_data = _get_method_help_data(class_name, item_name);
  3027. } else if (item_type == "signal") {
  3028. visible_type = TTR("Signal:");
  3029. help_data = _get_signal_help_data(class_name, item_name);
  3030. } else if (item_type == "theme_item") {
  3031. visible_type = TTR("Theme Property:");
  3032. help_data = _get_theme_item_help_data(class_name, item_name);
  3033. } else {
  3034. ERR_FAIL_MSG("Invalid tooltip type '" + item_type + "'. Valid types are 'class', 'property', 'internal_property', 'method', 'signal', and 'theme_item'.");
  3035. }
  3036. symbol_class_name = class_name;
  3037. symbol_type = item_type;
  3038. symbol_visible_type = visible_type;
  3039. symbol_name = name;
  3040. if (help_data.description.is_empty()) {
  3041. help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]";
  3042. }
  3043. if (is_inside_tree()) {
  3044. _update_labels();
  3045. }
  3046. }
  3047. void EditorHelpBit::set_custom_text(const String &p_type, const String &p_name, const String &p_description) {
  3048. symbol_class_name = String();
  3049. symbol_type = String();
  3050. symbol_visible_type = p_type;
  3051. symbol_name = p_name;
  3052. help_data = HelpData();
  3053. help_data.description = p_description;
  3054. if (is_inside_tree()) {
  3055. _update_labels();
  3056. }
  3057. }
  3058. void EditorHelpBit::set_description(const String &p_text) {
  3059. help_data.description = p_text;
  3060. if (is_inside_tree()) {
  3061. _update_labels();
  3062. }
  3063. }
  3064. void EditorHelpBit::set_content_height_limits(float p_min, float p_max) {
  3065. ERR_FAIL_COND(p_min > p_max);
  3066. content_min_height = p_min;
  3067. content_max_height = p_max;
  3068. if (is_inside_tree()) {
  3069. update_content_height();
  3070. }
  3071. }
  3072. void EditorHelpBit::update_content_height() {
  3073. float content_height = content->get_content_height();
  3074. const Ref<StyleBox> style = content->get_theme_stylebox("normal");
  3075. if (style.is_valid()) {
  3076. content_height += style->get_content_margin(SIDE_TOP) + style->get_content_margin(SIDE_BOTTOM);
  3077. }
  3078. content->set_custom_minimum_size(Size2(content->get_custom_minimum_size().x, CLAMP(content_height, content_min_height, content_max_height)));
  3079. }
  3080. EditorHelpBit::EditorHelpBit(const String &p_symbol) {
  3081. add_theme_constant_override("separation", 0);
  3082. title = memnew(RichTextLabel);
  3083. title->set_theme_type_variation("EditorHelpBitTitle");
  3084. title->set_fit_content(true);
  3085. title->set_selection_enabled(true);
  3086. //title->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip.
  3087. title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
  3088. title->hide();
  3089. add_child(title);
  3090. content_min_height = 48 * EDSCALE;
  3091. content_max_height = 360 * EDSCALE;
  3092. content = memnew(RichTextLabel);
  3093. content->set_theme_type_variation("EditorHelpBitContent");
  3094. content->set_custom_minimum_size(Size2(512 * EDSCALE, content_min_height));
  3095. content->set_selection_enabled(true);
  3096. //content->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip.
  3097. content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
  3098. add_child(content);
  3099. if (!p_symbol.is_empty()) {
  3100. parse_symbol(p_symbol);
  3101. }
  3102. }
  3103. /// EditorHelpBitTooltip ///
  3104. void EditorHelpBitTooltip::_safe_queue_free() {
  3105. if (_pushing_input > 0) {
  3106. _need_free = true;
  3107. } else {
  3108. queue_free();
  3109. }
  3110. }
  3111. void EditorHelpBitTooltip::_notification(int p_what) {
  3112. switch (p_what) {
  3113. case NOTIFICATION_WM_MOUSE_ENTER:
  3114. timer->stop();
  3115. break;
  3116. case NOTIFICATION_WM_MOUSE_EXIT:
  3117. timer->start();
  3118. break;
  3119. }
  3120. }
  3121. // Forwards non-mouse input to the parent viewport.
  3122. void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) {
  3123. if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
  3124. hide(); // Will be deleted on its timer.
  3125. } else {
  3126. const Ref<InputEventMouse> mouse_event = p_event;
  3127. if (mouse_event.is_null()) {
  3128. // GH-91652. Prevents use-after-free since `ProgressDialog` calls `Main::iteration()`.
  3129. _pushing_input++;
  3130. get_parent_viewport()->push_input(p_event);
  3131. _pushing_input--;
  3132. if (_pushing_input <= 0 && _need_free) {
  3133. queue_free();
  3134. }
  3135. }
  3136. }
  3137. }
  3138. void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) {
  3139. ERR_FAIL_NULL(p_help_bit);
  3140. EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target));
  3141. p_help_bit->connect("request_hide", callable_mp(static_cast<Window *>(tooltip), &Window::hide)); // Will be deleted on its timer.
  3142. tooltip->add_child(p_help_bit);
  3143. p_target->get_viewport()->add_child(tooltip);
  3144. p_help_bit->update_content_height();
  3145. tooltip->popup_under_cursor();
  3146. }
  3147. // Copy-paste from `Viewport::_gui_show_tooltip()`.
  3148. void EditorHelpBitTooltip::popup_under_cursor() {
  3149. Point2 mouse_pos = get_mouse_position();
  3150. Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset");
  3151. Rect2 r(mouse_pos + tooltip_offset, get_contents_minimum_size());
  3152. r.size = r.size.min(get_max_size());
  3153. Window *window = get_parent_visible_window();
  3154. Rect2i vr;
  3155. if (is_embedded()) {
  3156. vr = get_embedder()->get_visible_rect();
  3157. } else {
  3158. vr = window->get_usable_parent_rect();
  3159. }
  3160. if (r.size.x + r.position.x > vr.size.x + vr.position.x) {
  3161. // Place it in the opposite direction. If it fails, just hug the border.
  3162. r.position.x = mouse_pos.x - r.size.x - tooltip_offset.x;
  3163. if (r.position.x < vr.position.x) {
  3164. r.position.x = vr.position.x + vr.size.x - r.size.x;
  3165. }
  3166. } else if (r.position.x < vr.position.x) {
  3167. r.position.x = vr.position.x;
  3168. }
  3169. if (r.size.y + r.position.y > vr.size.y + vr.position.y) {
  3170. // Same as above.
  3171. r.position.y = mouse_pos.y - r.size.y - tooltip_offset.y;
  3172. if (r.position.y < vr.position.y) {
  3173. r.position.y = vr.position.y + vr.size.y - r.size.y;
  3174. }
  3175. } else if (r.position.y < vr.position.y) {
  3176. r.position.y = vr.position.y;
  3177. }
  3178. set_flag(Window::FLAG_NO_FOCUS, true);
  3179. popup(r);
  3180. }
  3181. EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) {
  3182. set_theme_type_variation("TooltipPanel");
  3183. timer = memnew(Timer);
  3184. timer->set_wait_time(0.2);
  3185. timer->connect("timeout", callable_mp(this, &EditorHelpBitTooltip::_safe_queue_free));
  3186. add_child(timer);
  3187. ERR_FAIL_NULL(p_target);
  3188. p_target->connect(SceneStringName(mouse_entered), callable_mp(timer, &Timer::stop));
  3189. p_target->connect(SceneStringName(mouse_exited), callable_mp(timer, &Timer::start).bind(-1));
  3190. }
  3191. #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
  3192. /// EditorHelpHighlighter ///
  3193. EditorHelpHighlighter *EditorHelpHighlighter::singleton = nullptr;
  3194. void EditorHelpHighlighter::create_singleton() {
  3195. ERR_FAIL_COND(singleton != nullptr);
  3196. singleton = memnew(EditorHelpHighlighter);
  3197. }
  3198. void EditorHelpHighlighter::free_singleton() {
  3199. ERR_FAIL_NULL(singleton);
  3200. memdelete(singleton);
  3201. singleton = nullptr;
  3202. }
  3203. EditorHelpHighlighter *EditorHelpHighlighter::get_singleton() {
  3204. return singleton;
  3205. }
  3206. EditorHelpHighlighter::HighlightData EditorHelpHighlighter::_get_highlight_data(Language p_language, const String &p_source, bool p_use_cache) {
  3207. switch (p_language) {
  3208. case LANGUAGE_GDSCRIPT:
  3209. #ifndef MODULE_GDSCRIPT_ENABLED
  3210. ERR_FAIL_V_MSG(HighlightData(), "GDScript module is disabled.");
  3211. #endif
  3212. break;
  3213. case LANGUAGE_CSHARP:
  3214. #ifndef MODULE_MONO_ENABLED
  3215. ERR_FAIL_V_MSG(HighlightData(), "Mono module is disabled.");
  3216. #endif
  3217. break;
  3218. default:
  3219. ERR_FAIL_V_MSG(HighlightData(), "Invalid parameter \"p_language\".");
  3220. }
  3221. if (p_use_cache) {
  3222. const HashMap<String, HighlightData>::ConstIterator E = highlight_data_caches[p_language].find(p_source);
  3223. if (E) {
  3224. return E->value;
  3225. }
  3226. }
  3227. text_edits[p_language]->set_text(p_source);
  3228. if (scripts[p_language].is_valid()) { // See GH-89610.
  3229. scripts[p_language]->set_source_code(p_source);
  3230. }
  3231. highlighters[p_language]->_update_cache();
  3232. HighlightData result;
  3233. int source_offset = 0;
  3234. int result_index = 0;
  3235. for (int i = 0; i < text_edits[p_language]->get_line_count(); i++) {
  3236. const Dictionary dict = highlighters[p_language]->_get_line_syntax_highlighting_impl(i);
  3237. result.resize(result.size() + dict.size());
  3238. const Variant *key = nullptr;
  3239. int prev_column = -1;
  3240. while ((key = dict.next(key)) != nullptr) {
  3241. const int column = *key;
  3242. ERR_FAIL_COND_V(column <= prev_column, HighlightData());
  3243. prev_column = column;
  3244. const Color color = dict[*key].operator Dictionary().get("color", Color());
  3245. result.write[result_index] = { source_offset + column, color };
  3246. result_index++;
  3247. }
  3248. source_offset += text_edits[p_language]->get_line(i).length() + 1; // Plus newline.
  3249. }
  3250. if (p_use_cache) {
  3251. highlight_data_caches[p_language][p_source] = result;
  3252. }
  3253. return result;
  3254. }
  3255. void EditorHelpHighlighter::highlight(RichTextLabel *p_rich_text_label, Language p_language, const String &p_source, bool p_use_cache) {
  3256. ERR_FAIL_NULL(p_rich_text_label);
  3257. const HighlightData highlight_data = _get_highlight_data(p_language, p_source, p_use_cache);
  3258. if (!highlight_data.is_empty()) {
  3259. for (int i = 1; i < highlight_data.size(); i++) {
  3260. const Pair<int, Color> &prev = highlight_data[i - 1];
  3261. const Pair<int, Color> &curr = highlight_data[i];
  3262. p_rich_text_label->push_color(prev.second);
  3263. p_rich_text_label->add_text(p_source.substr(prev.first, curr.first - prev.first));
  3264. p_rich_text_label->pop(); // color
  3265. }
  3266. const Pair<int, Color> &last = highlight_data[highlight_data.size() - 1];
  3267. p_rich_text_label->push_color(last.second);
  3268. p_rich_text_label->add_text(p_source.substr(last.first));
  3269. p_rich_text_label->pop(); // color
  3270. }
  3271. }
  3272. void EditorHelpHighlighter::reset_cache() {
  3273. const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color");
  3274. #ifdef MODULE_GDSCRIPT_ENABLED
  3275. highlight_data_caches[LANGUAGE_GDSCRIPT].clear();
  3276. text_edits[LANGUAGE_GDSCRIPT]->add_theme_color_override("font_color", text_color);
  3277. #endif
  3278. #ifdef MODULE_MONO_ENABLED
  3279. highlight_data_caches[LANGUAGE_CSHARP].clear();
  3280. text_edits[LANGUAGE_CSHARP]->add_theme_color_override("font_color", text_color);
  3281. #endif
  3282. }
  3283. EditorHelpHighlighter::EditorHelpHighlighter() {
  3284. const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color");
  3285. #ifdef MODULE_GDSCRIPT_ENABLED
  3286. TextEdit *gdscript_text_edit = memnew(TextEdit);
  3287. gdscript_text_edit->add_theme_color_override("font_color", text_color);
  3288. Ref<GDScript> gdscript;
  3289. gdscript.instantiate();
  3290. Ref<GDScriptSyntaxHighlighter> gdscript_highlighter;
  3291. gdscript_highlighter.instantiate();
  3292. gdscript_highlighter->set_text_edit(gdscript_text_edit);
  3293. gdscript_highlighter->_set_edited_resource(gdscript);
  3294. text_edits[LANGUAGE_GDSCRIPT] = gdscript_text_edit;
  3295. scripts[LANGUAGE_GDSCRIPT] = gdscript;
  3296. highlighters[LANGUAGE_GDSCRIPT] = gdscript_highlighter;
  3297. #endif
  3298. #ifdef MODULE_MONO_ENABLED
  3299. TextEdit *csharp_text_edit = memnew(TextEdit);
  3300. csharp_text_edit->add_theme_color_override("font_color", text_color);
  3301. // See GH-89610.
  3302. //Ref<CSharpScript> csharp;
  3303. //csharp.instantiate();
  3304. Ref<EditorStandardSyntaxHighlighter> csharp_highlighter;
  3305. csharp_highlighter.instantiate();
  3306. csharp_highlighter->set_text_edit(csharp_text_edit);
  3307. //csharp_highlighter->_set_edited_resource(csharp);
  3308. csharp_highlighter->_set_script_language(CSharpLanguage::get_singleton());
  3309. text_edits[LANGUAGE_CSHARP] = csharp_text_edit;
  3310. //scripts[LANGUAGE_CSHARP] = csharp;
  3311. highlighters[LANGUAGE_CSHARP] = csharp_highlighter;
  3312. #endif
  3313. }
  3314. EditorHelpHighlighter::~EditorHelpHighlighter() {
  3315. #ifdef MODULE_GDSCRIPT_ENABLED
  3316. memdelete(text_edits[LANGUAGE_GDSCRIPT]);
  3317. #endif
  3318. #ifdef MODULE_MONO_ENABLED
  3319. memdelete(text_edits[LANGUAGE_CSHARP]);
  3320. #endif
  3321. }
  3322. #endif // defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
  3323. /// FindBar ///
  3324. FindBar::FindBar() {
  3325. search_text = memnew(LineEdit);
  3326. add_child(search_text);
  3327. search_text->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
  3328. search_text->set_h_size_flags(SIZE_EXPAND_FILL);
  3329. search_text->connect("text_changed", callable_mp(this, &FindBar::_search_text_changed));
  3330. search_text->connect("text_submitted", callable_mp(this, &FindBar::_search_text_submitted));
  3331. matches_label = memnew(Label);
  3332. add_child(matches_label);
  3333. matches_label->hide();
  3334. find_prev = memnew(Button);
  3335. find_prev->set_flat(true);
  3336. add_child(find_prev);
  3337. find_prev->set_focus_mode(FOCUS_NONE);
  3338. find_prev->connect(SceneStringName(pressed), callable_mp(this, &FindBar::search_prev));
  3339. find_next = memnew(Button);
  3340. find_next->set_flat(true);
  3341. add_child(find_next);
  3342. find_next->set_focus_mode(FOCUS_NONE);
  3343. find_next->connect(SceneStringName(pressed), callable_mp(this, &FindBar::search_next));
  3344. Control *space = memnew(Control);
  3345. add_child(space);
  3346. space->set_custom_minimum_size(Size2(4, 0) * EDSCALE);
  3347. hide_button = memnew(TextureButton);
  3348. add_child(hide_button);
  3349. hide_button->set_focus_mode(FOCUS_NONE);
  3350. hide_button->set_ignore_texture_size(true);
  3351. hide_button->set_stretch_mode(TextureButton::STRETCH_KEEP_CENTERED);
  3352. hide_button->connect(SceneStringName(pressed), callable_mp(this, &FindBar::_hide_bar));
  3353. }
  3354. void FindBar::popup_search() {
  3355. show();
  3356. bool grabbed_focus = false;
  3357. if (!search_text->has_focus()) {
  3358. search_text->grab_focus();
  3359. grabbed_focus = true;
  3360. }
  3361. if (!search_text->get_text().is_empty()) {
  3362. search_text->select_all();
  3363. search_text->set_caret_column(search_text->get_text().length());
  3364. if (grabbed_focus) {
  3365. _search();
  3366. }
  3367. }
  3368. }
  3369. void FindBar::_notification(int p_what) {
  3370. switch (p_what) {
  3371. case NOTIFICATION_THEME_CHANGED: {
  3372. find_prev->set_icon(get_editor_theme_icon(SNAME("MoveUp")));
  3373. find_next->set_icon(get_editor_theme_icon(SNAME("MoveDown")));
  3374. hide_button->set_texture_normal(get_editor_theme_icon(SNAME("Close")));
  3375. hide_button->set_texture_hover(get_editor_theme_icon(SNAME("Close")));
  3376. hide_button->set_texture_pressed(get_editor_theme_icon(SNAME("Close")));
  3377. hide_button->set_custom_minimum_size(hide_button->get_texture_normal()->get_size());
  3378. matches_label->add_theme_color_override("font_color", results_count > 0 ? get_theme_color(SNAME("font_color"), SNAME("Label")) : get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  3379. } break;
  3380. case NOTIFICATION_VISIBILITY_CHANGED: {
  3381. set_process_unhandled_input(is_visible_in_tree());
  3382. } break;
  3383. }
  3384. }
  3385. void FindBar::set_rich_text_label(RichTextLabel *p_rich_text_label) {
  3386. rich_text_label = p_rich_text_label;
  3387. }
  3388. bool FindBar::search_next() {
  3389. return _search();
  3390. }
  3391. bool FindBar::search_prev() {
  3392. return _search(true);
  3393. }
  3394. bool FindBar::_search(bool p_search_previous) {
  3395. String stext = search_text->get_text();
  3396. bool keep = prev_search == stext;
  3397. bool ret = rich_text_label->search(stext, keep, p_search_previous);
  3398. prev_search = stext;
  3399. if (ret) {
  3400. _update_results_count();
  3401. } else {
  3402. results_count = 0;
  3403. }
  3404. _update_matches_label();
  3405. return ret;
  3406. }
  3407. void FindBar::_update_results_count() {
  3408. results_count = 0;
  3409. String searched = search_text->get_text();
  3410. if (searched.is_empty()) {
  3411. return;
  3412. }
  3413. String full_text = rich_text_label->get_parsed_text();
  3414. int from_pos = 0;
  3415. while (true) {
  3416. int pos = full_text.findn(searched, from_pos);
  3417. if (pos == -1) {
  3418. break;
  3419. }
  3420. results_count++;
  3421. from_pos = pos + searched.length();
  3422. }
  3423. }
  3424. void FindBar::_update_matches_label() {
  3425. if (search_text->get_text().is_empty() || results_count == -1) {
  3426. matches_label->hide();
  3427. } else {
  3428. matches_label->show();
  3429. matches_label->add_theme_color_override("font_color", results_count > 0 ? get_theme_color(SNAME("font_color"), SNAME("Label")) : get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  3430. matches_label->set_text(vformat(results_count == 1 ? TTR("%d match.") : TTR("%d matches."), results_count));
  3431. }
  3432. }
  3433. void FindBar::_hide_bar() {
  3434. if (search_text->has_focus()) {
  3435. rich_text_label->grab_focus();
  3436. }
  3437. hide();
  3438. }
  3439. void FindBar::unhandled_input(const Ref<InputEvent> &p_event) {
  3440. ERR_FAIL_COND(p_event.is_null());
  3441. Ref<InputEventKey> k = p_event;
  3442. if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
  3443. if (rich_text_label->has_focus() || is_ancestor_of(get_viewport()->gui_get_focus_owner())) {
  3444. _hide_bar();
  3445. accept_event();
  3446. }
  3447. }
  3448. }
  3449. void FindBar::_search_text_changed(const String &p_text) {
  3450. search_next();
  3451. }
  3452. void FindBar::_search_text_submitted(const String &p_text) {
  3453. if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
  3454. search_prev();
  3455. } else {
  3456. search_next();
  3457. }
  3458. }