editor_help.cpp 168 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148
  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/io/json.h"
  36. #include "core/object/script_language.h"
  37. #include "core/os/keyboard.h"
  38. #include "core/string/string_builder.h"
  39. #include "core/version.h"
  40. #include "editor/doc/doc_data_compressed.gen.h"
  41. #include "editor/docks/filesystem_dock.h"
  42. #include "editor/editor_main_screen.h"
  43. #include "editor/editor_node.h"
  44. #include "editor/editor_string_names.h"
  45. #include "editor/file_system/editor_file_system.h"
  46. #include "editor/file_system/editor_paths.h"
  47. #include "editor/gui/editor_toaster.h"
  48. #include "editor/inspector/editor_property_name_processor.h"
  49. #include "editor/script/script_editor_plugin.h"
  50. #include "editor/settings/editor_settings.h"
  51. #include "editor/themes/editor_scale.h"
  52. #include "scene/gui/line_edit.h"
  53. #include "modules/modules_enabled.gen.h" // For gdscript, mono.
  54. // For syntax highlighting.
  55. #ifdef MODULE_GDSCRIPT_ENABLED
  56. #include "modules/gdscript/editor/gdscript_highlighter.h"
  57. #include "modules/gdscript/gdscript.h"
  58. #endif
  59. // For syntax highlighting.
  60. #ifdef MODULE_MONO_ENABLED
  61. #include "modules/mono/csharp_script.h"
  62. #endif
  63. #define CONTRIBUTE_URL "https://contributing.godotengine.org/en/latest/documentation/class_reference.html"
  64. #ifdef MODULE_MONO_ENABLED
  65. // Sync with the types mentioned in https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_differences.html
  66. const Vector<String> classes_with_csharp_differences = {
  67. "@GlobalScope",
  68. "String",
  69. "NodePath",
  70. "Signal",
  71. "Callable",
  72. "RID",
  73. "Basis",
  74. "Transform2D",
  75. "Transform3D",
  76. "Rect2",
  77. "Rect2i",
  78. "AABB",
  79. "Quaternion",
  80. "Projection",
  81. "Color",
  82. "Array",
  83. "Dictionary",
  84. "PackedByteArray",
  85. "PackedColorArray",
  86. "PackedFloat32Array",
  87. "PackedFloat64Array",
  88. "PackedInt32Array",
  89. "PackedInt64Array",
  90. "PackedStringArray",
  91. "PackedVector2Array",
  92. "PackedVector3Array",
  93. "PackedVector4Array",
  94. "Variant",
  95. };
  96. #endif
  97. static const char32_t nbsp_chr = 160;
  98. static const String nbsp = String::chr(nbsp_chr);
  99. static const String nbsp_equal_nbsp = nbsp + "=" + nbsp;
  100. static const String colon_nbsp = ":" + nbsp;
  101. const Vector<String> packed_array_types = {
  102. "PackedByteArray",
  103. "PackedColorArray",
  104. "PackedFloat32Array",
  105. "PackedFloat64Array",
  106. "PackedInt32Array",
  107. "PackedInt64Array",
  108. "PackedStringArray",
  109. "PackedVector2Array",
  110. "PackedVector3Array",
  111. "PackedVector4Array",
  112. };
  113. static String _replace_nbsp_with_space(const String &p_string) {
  114. return p_string.replace_char(nbsp_chr, ' ');
  115. }
  116. static String _fix_constant(const String &p_constant) {
  117. if (p_constant.strip_edges() == "4294967295") {
  118. return "0xFFFFFFFF";
  119. }
  120. if (p_constant.strip_edges() == "2147483647") {
  121. return "0x7FFFFFFF";
  122. }
  123. if (p_constant.strip_edges() == "1048575") {
  124. return "0xFFFFF";
  125. }
  126. return p_constant;
  127. }
  128. static void _add_qualifiers_to_rt(const String &p_qualifiers, RichTextLabel *p_rt) {
  129. for (const String &qualifier : p_qualifiers.split_spaces()) {
  130. String hint;
  131. if (qualifier == "vararg") {
  132. hint = TTR("This method supports a variable number of arguments.");
  133. } else if (qualifier == "virtual") {
  134. hint = TTR("This method is called by the engine.\nIt can be overridden to customize built-in behavior.");
  135. } else if (qualifier == "required") {
  136. hint = TTR("This method is required to be overridden when extending its base class.");
  137. } else if (qualifier == "const") {
  138. hint = TTR("This method has no side effects.\nIt does not modify the object in any way.");
  139. } else if (qualifier == "static") {
  140. hint = TTR("This method does not need an instance to be called.\nIt can be called directly using the class name.");
  141. } else if (qualifier == "abstract") {
  142. hint = TTR("This method must be implemented to complete the abstract class.");
  143. }
  144. p_rt->add_text(" ");
  145. if (hint.is_empty()) {
  146. p_rt->add_text(qualifier);
  147. } else {
  148. p_rt->push_hint(hint);
  149. p_rt->add_text(qualifier);
  150. p_rt->pop(); // hint
  151. }
  152. }
  153. }
  154. // Removes unnecessary prefix from `p_class_specifier` when within the `p_edited_class` context.
  155. static String _contextualize_class_specifier(const String &p_class_specifier, const String &p_edited_class) {
  156. // If this is a completely different context than the current class, then keep full path.
  157. if (!p_class_specifier.begins_with(p_edited_class)) {
  158. return p_class_specifier;
  159. }
  160. // Here equal `length()` and `begins_with()` from above implies `p_class_specifier == p_edited_class`.
  161. if (p_class_specifier.length() == p_edited_class.length()) {
  162. int rfind = p_class_specifier.rfind_char('.');
  163. if (rfind == -1) { // Single identifier.
  164. return p_class_specifier;
  165. }
  166. // Multiple specifiers: keep last one only.
  167. return p_class_specifier.substr(rfind + 1);
  168. }
  169. // They share a _name_ prefix but not a _class specifier_ prefix, e.g. `Tree` and `TreeItem`.
  170. // `begins_with()` and `length()`s being different implies `p_class_specifier.length() > p_edited_class.length()` so this is safe.
  171. if (p_class_specifier[p_edited_class.length()] != '.') {
  172. return p_class_specifier;
  173. }
  174. // Remove class specifier prefix.
  175. return p_class_specifier.substr(p_edited_class.length() + 1);
  176. }
  177. /// EditorHelp ///
  178. void EditorHelp::_update_theme_item_cache() {
  179. VBoxContainer::_update_theme_item_cache();
  180. theme_cache.text_color = get_theme_color(SNAME("text_color"), SNAME("EditorHelp"));
  181. theme_cache.title_color = get_theme_color(SNAME("title_color"), SNAME("EditorHelp"));
  182. theme_cache.headline_color = get_theme_color(SNAME("headline_color"), SNAME("EditorHelp"));
  183. theme_cache.comment_color = get_theme_color(SNAME("comment_color"), SNAME("EditorHelp"));
  184. theme_cache.symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp"));
  185. theme_cache.value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp"));
  186. theme_cache.qualifier_color = get_theme_color(SNAME("qualifier_color"), SNAME("EditorHelp"));
  187. theme_cache.type_color = get_theme_color(SNAME("type_color"), SNAME("EditorHelp"));
  188. theme_cache.override_color = get_theme_color(SNAME("override_color"), SNAME("EditorHelp"));
  189. theme_cache.doc_font = get_theme_font(SNAME("doc"), EditorStringName(EditorFonts));
  190. theme_cache.doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts));
  191. theme_cache.doc_italic_font = get_theme_font(SNAME("doc_italic"), EditorStringName(EditorFonts));
  192. theme_cache.doc_title_font = get_theme_font(SNAME("doc_title"), EditorStringName(EditorFonts));
  193. theme_cache.doc_code_font = get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts));
  194. theme_cache.doc_kbd_font = get_theme_font(SNAME("doc_keyboard"), EditorStringName(EditorFonts));
  195. theme_cache.doc_font_size = get_theme_font_size(SNAME("doc_size"), EditorStringName(EditorFonts));
  196. theme_cache.doc_title_font_size = get_theme_font_size(SNAME("doc_title_size"), EditorStringName(EditorFonts));
  197. theme_cache.doc_code_font_size = get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts));
  198. theme_cache.doc_kbd_font_size = get_theme_font_size(SNAME("doc_keyboard_size"), EditorStringName(EditorFonts));
  199. theme_cache.background_style = get_theme_stylebox(SNAME("background"), SNAME("EditorHelp"));
  200. class_desc->begin_bulk_theme_override();
  201. class_desc->add_theme_font_override("normal_font", theme_cache.doc_font);
  202. class_desc->add_theme_font_size_override("normal_font_size", theme_cache.doc_font_size);
  203. class_desc->add_theme_constant_override(SceneStringName(line_separation), get_theme_constant(SceneStringName(line_separation), SNAME("EditorHelp")));
  204. class_desc->add_theme_constant_override("table_h_separation", get_theme_constant(SNAME("table_h_separation"), SNAME("EditorHelp")));
  205. class_desc->add_theme_constant_override("table_v_separation", get_theme_constant(SNAME("table_v_separation"), SNAME("EditorHelp")));
  206. class_desc->add_theme_constant_override("text_highlight_h_padding", get_theme_constant(SNAME("text_highlight_h_padding"), SNAME("EditorHelp")));
  207. class_desc->add_theme_constant_override("text_highlight_v_padding", get_theme_constant(SNAME("text_highlight_v_padding"), SNAME("EditorHelp")));
  208. class_desc->end_bulk_theme_override();
  209. }
  210. void EditorHelp::_search(bool p_search_previous) {
  211. if (p_search_previous) {
  212. find_bar->search_prev();
  213. } else {
  214. find_bar->search_next();
  215. }
  216. }
  217. void EditorHelp::_class_desc_finished() {
  218. if (scroll_to >= 0) {
  219. class_desc->connect(SceneStringName(draw), callable_mp(class_desc, &RichTextLabel::scroll_to_paragraph).bind(scroll_to), CONNECT_ONE_SHOT | CONNECT_DEFERRED);
  220. }
  221. scroll_to = -1;
  222. }
  223. void EditorHelp::_class_list_select(const String &p_select) {
  224. _goto_desc(p_select);
  225. }
  226. void EditorHelp::_class_desc_select(const String &p_select) {
  227. if (p_select.begins_with("$")) { // Enum.
  228. const String link = p_select.substr(1);
  229. String enum_class_name;
  230. String enum_name;
  231. if (CoreConstants::is_global_enum(link)) {
  232. enum_class_name = "@GlobalScope";
  233. enum_name = link;
  234. } else {
  235. const int dot_pos = link.rfind_char('.');
  236. if (dot_pos >= 0) {
  237. enum_class_name = link.left(dot_pos);
  238. enum_name = link.substr(dot_pos + 1);
  239. } else {
  240. enum_class_name = edited_class;
  241. enum_name = link;
  242. }
  243. }
  244. emit_signal(SNAME("go_to_help"), "class_enum:" + enum_class_name + ":" + enum_name);
  245. } else if (p_select.begins_with("#")) { // Class.
  246. emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1));
  247. } else if (p_select.begins_with("@")) { // Member.
  248. const int tag_end = p_select.find_char(' ');
  249. const String tag = p_select.substr(1, tag_end - 1);
  250. const String link = p_select.substr(tag_end + 1).lstrip(" ");
  251. String topic;
  252. const HashMap<String, int> *table = nullptr;
  253. if (tag == "method") {
  254. topic = "class_method";
  255. table = &method_line;
  256. } else if (tag == "constructor") {
  257. topic = "class_method";
  258. table = &method_line;
  259. } else if (tag == "operator") {
  260. topic = "class_method";
  261. table = &method_line;
  262. } else if (tag == "member") {
  263. topic = "class_property";
  264. table = &property_line;
  265. } else if (tag == "enum") {
  266. topic = "class_enum";
  267. table = &enum_line;
  268. } else if (tag == "signal") {
  269. topic = "class_signal";
  270. table = &signal_line;
  271. } else if (tag == "constant") {
  272. topic = "class_constant";
  273. table = &constant_line;
  274. } else if (tag == "annotation") {
  275. topic = "class_annotation";
  276. table = &annotation_line;
  277. } else if (tag == "theme_item") {
  278. topic = "class_theme_item";
  279. table = &theme_property_line;
  280. } else {
  281. return;
  282. }
  283. // Case order is important here to correctly handle edge cases like `Variant.Type` in `@GlobalScope`.
  284. if (table->has(link)) {
  285. // Found in the current page.
  286. if (class_desc->is_finished()) {
  287. emit_signal(SNAME("request_save_history"));
  288. class_desc->scroll_to_paragraph((*table)[link]);
  289. } else {
  290. scroll_to = (*table)[link];
  291. }
  292. } else {
  293. // Look for link in `@GlobalScope`.
  294. if (topic == "class_enum") {
  295. const DocData::ClassDoc &cd = doc->class_list["@GlobalScope"];
  296. const String enum_link = link.trim_prefix("@GlobalScope.");
  297. for (const DocData::ConstantDoc &constant : cd.constants) {
  298. if (constant.enumeration == enum_link) {
  299. // Found in `@GlobalScope`.
  300. emit_signal(SNAME("go_to_help"), topic + ":@GlobalScope:" + enum_link);
  301. return;
  302. }
  303. }
  304. } else if (topic == "class_constant") {
  305. const DocData::ClassDoc &cd = doc->class_list["@GlobalScope"];
  306. for (const DocData::ConstantDoc &constant : cd.constants) {
  307. if (constant.name == link) {
  308. // Found in `@GlobalScope`.
  309. emit_signal(SNAME("go_to_help"), topic + ":@GlobalScope:" + link);
  310. return;
  311. }
  312. }
  313. }
  314. if (link.contains_char('.')) {
  315. const int class_end = link.rfind_char('.');
  316. emit_signal(SNAME("go_to_help"), topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1));
  317. }
  318. }
  319. } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) {
  320. OS::get_singleton()->shell_open(p_select);
  321. } else if (p_select.begins_with("^")) { // Copy button.
  322. DisplayServer::get_singleton()->clipboard_set(p_select.substr(1));
  323. EditorToaster::get_singleton()->popup_str(TTR("Code snippet copied to clipboard."), EditorToaster::SEVERITY_INFO);
  324. }
  325. }
  326. void EditorHelp::_class_desc_input(const Ref<InputEvent> &p_input) {
  327. }
  328. void EditorHelp::_class_desc_resized(bool p_force_update_theme) {
  329. // Add extra horizontal margins for better readability.
  330. // The margins increase as the width of the editor help container increases.
  331. real_t char_width = theme_cache.doc_code_font->get_char_size('x', theme_cache.doc_code_font_size).width;
  332. const int new_display_margin = MAX(30 * EDSCALE, get_parent_anchorable_rect().size.width - char_width * 120 * EDSCALE) * 0.5;
  333. if (display_margin != new_display_margin || p_force_update_theme) {
  334. display_margin = new_display_margin;
  335. Ref<StyleBox> class_desc_stylebox = theme_cache.background_style->duplicate();
  336. class_desc_stylebox->set_content_margin(SIDE_LEFT, display_margin);
  337. class_desc_stylebox->set_content_margin(SIDE_RIGHT, display_margin);
  338. class_desc->add_theme_style_override(CoreStringName(normal), class_desc_stylebox);
  339. class_desc->add_theme_style_override("focused", class_desc_stylebox);
  340. }
  341. }
  342. 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) {
  343. const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp"));
  344. if (p_type.is_empty() || p_type == "void") {
  345. p_rt->push_color(Color(type_color, 0.5));
  346. p_rt->push_hint(TTR("No return value."));
  347. p_rt->add_text("void");
  348. p_rt->pop(); // hint
  349. p_rt->pop(); // color
  350. return;
  351. }
  352. bool is_enum_type = !p_enum.is_empty();
  353. bool is_bitfield = p_is_bitfield && is_enum_type;
  354. bool can_ref = !p_type.contains_char('*') || is_enum_type;
  355. String link_t = p_type; // For links in metadata
  356. String display_t; // For display purposes.
  357. if (is_enum_type) {
  358. link_t = p_enum; // The link for enums is always the full enum description
  359. display_t = _contextualize_class_specifier(p_enum, p_class);
  360. } else {
  361. display_t = _contextualize_class_specifier(p_type, p_class);
  362. }
  363. p_rt->push_color(type_color);
  364. bool add_typed_container = false;
  365. if (can_ref) {
  366. if (link_t.ends_with("[]")) {
  367. add_typed_container = true;
  368. link_t = link_t.trim_suffix("[]");
  369. display_t = display_t.trim_suffix("[]");
  370. p_rt->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
  371. p_rt->add_text("Array");
  372. p_rt->pop(); // meta
  373. p_rt->add_text("[");
  374. } else if (link_t.begins_with("Dictionary[")) {
  375. add_typed_container = true;
  376. link_t = link_t.trim_prefix("Dictionary[").trim_suffix("]");
  377. display_t = display_t.trim_prefix("Dictionary[").trim_suffix("]");
  378. p_rt->push_meta("#Dictionary", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
  379. p_rt->add_text("Dictionary");
  380. p_rt->pop(); // meta
  381. p_rt->add_text("[");
  382. p_rt->push_meta("#" + link_t.get_slice(", ", 0), RichTextLabel::META_UNDERLINE_ON_HOVER); // class
  383. p_rt->add_text(_contextualize_class_specifier(display_t.get_slice(", ", 0), p_class));
  384. p_rt->pop(); // meta
  385. p_rt->add_text(", ");
  386. link_t = link_t.get_slice(", ", 1);
  387. display_t = _contextualize_class_specifier(display_t.get_slice(", ", 1), p_class);
  388. } else if (is_bitfield) {
  389. p_rt->push_color(Color(type_color, 0.5));
  390. p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
  391. p_rt->add_text("BitField");
  392. p_rt->pop(); // hint
  393. p_rt->add_text("[");
  394. p_rt->pop(); // color
  395. }
  396. if (is_enum_type) {
  397. p_rt->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum
  398. } else {
  399. p_rt->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class
  400. }
  401. }
  402. p_rt->add_text(display_t);
  403. if (can_ref) {
  404. p_rt->pop(); // meta
  405. if (add_typed_container) {
  406. p_rt->add_text("]");
  407. } else if (is_bitfield) {
  408. p_rt->push_color(Color(type_color, 0.5));
  409. p_rt->add_text("]");
  410. p_rt->pop(); // color
  411. }
  412. }
  413. p_rt->pop(); // color
  414. }
  415. void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) {
  416. _add_type_to_rt(p_type, p_enum, p_is_bitfield, class_desc, this, edited_class);
  417. }
  418. void EditorHelp::_add_type_icon(const String &p_type, int p_size, const String &p_fallback) {
  419. Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(p_type, p_fallback);
  420. if (icon.is_null()) {
  421. icon = EditorNode::get_singleton()->get_class_icon("Object");
  422. ERR_FAIL_COND(icon.is_null());
  423. }
  424. Vector2i size = Vector2i(icon->get_width(), icon->get_height());
  425. if (p_size > 0) {
  426. // Ensures icon scales proportionally on both axes, based on icon height.
  427. float ratio = p_size / float(size.height);
  428. size.width *= ratio;
  429. size.height *= ratio;
  430. }
  431. class_desc->add_image(icon, size.width, size.height);
  432. }
  433. // Macros for assigning the deprecated/experimental marks to class members in overview.
  434. #define DEPRECATED_DOC_TAG \
  435. class_desc->push_font(theme_cache.doc_bold_font); \
  436. class_desc->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor))); \
  437. Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError")); \
  438. class_desc->add_image(error_icon, error_icon->get_width(), error_icon->get_height()); \
  439. class_desc->add_text(String::chr(160) + TTR("Deprecated")); \
  440. class_desc->pop(); \
  441. class_desc->pop();
  442. #define EXPERIMENTAL_DOC_TAG \
  443. class_desc->push_font(theme_cache.doc_bold_font); \
  444. class_desc->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); \
  445. Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning")); \
  446. class_desc->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height()); \
  447. class_desc->add_text(String::chr(160) + TTR("Experimental")); \
  448. class_desc->pop(); \
  449. class_desc->pop();
  450. // Macros for displaying the deprecated/experimental info in class member descriptions.
  451. #define DEPRECATED_DOC_MSG(m_message, m_default_message) \
  452. Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError")); \
  453. class_desc->add_image(error_icon, error_icon->get_width(), error_icon->get_height()); \
  454. class_desc->add_text(nbsp); \
  455. class_desc->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor))); \
  456. class_desc->push_font(theme_cache.doc_bold_font); \
  457. class_desc->add_text(TTR("Deprecated:")); \
  458. class_desc->pop(); \
  459. class_desc->pop(); \
  460. class_desc->add_text(" "); \
  461. if ((m_message).is_empty()) { \
  462. class_desc->add_text(m_default_message); \
  463. } else { \
  464. _add_text(m_message); \
  465. }
  466. #define EXPERIMENTAL_DOC_MSG(m_message, m_default_message) \
  467. Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning")); \
  468. class_desc->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height()); \
  469. class_desc->add_text(nbsp); \
  470. class_desc->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); \
  471. class_desc->push_font(theme_cache.doc_bold_font); \
  472. class_desc->add_text(TTR("Experimental:")); \
  473. class_desc->pop(); \
  474. class_desc->pop(); \
  475. class_desc->add_text(" "); \
  476. if ((m_message).is_empty()) { \
  477. class_desc->add_text(m_default_message); \
  478. } else { \
  479. _add_text(m_message); \
  480. }
  481. void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview, bool p_override) {
  482. if (p_override) {
  483. method_line[p_method.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  484. }
  485. const bool is_vararg = p_method.qualifiers.contains("vararg");
  486. if (p_overview) {
  487. class_desc->push_cell();
  488. class_desc->push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, Control::TEXT_DIRECTION_AUTO, "");
  489. } else {
  490. _add_bulletpoint();
  491. }
  492. _add_type(p_method.return_type, p_method.return_enum, p_method.return_is_bitfield);
  493. if (p_overview) {
  494. class_desc->pop(); // paragraph
  495. class_desc->pop(); // cell
  496. class_desc->push_cell();
  497. } else {
  498. class_desc->add_text(" ");
  499. }
  500. const bool is_documented = p_method.is_deprecated || p_method.is_experimental || !p_method.description.strip_edges().is_empty();
  501. if (p_overview && is_documented) {
  502. class_desc->push_meta("@method " + p_method.name, RichTextLabel::META_UNDERLINE_ON_HOVER);
  503. }
  504. class_desc->push_color(theme_cache.headline_color);
  505. class_desc->add_text(p_method.name);
  506. class_desc->pop(); // color
  507. if (p_overview && is_documented) {
  508. class_desc->pop(); // meta
  509. }
  510. class_desc->push_color(theme_cache.symbol_color);
  511. class_desc->add_text("(");
  512. class_desc->pop(); // color
  513. for (int j = 0; j < p_method.arguments.size(); j++) {
  514. const DocData::ArgumentDoc &argument = p_method.arguments[j];
  515. if (j > 0) {
  516. class_desc->push_color(theme_cache.symbol_color);
  517. class_desc->add_text(", ");
  518. class_desc->pop(); // color
  519. }
  520. class_desc->push_color(theme_cache.text_color);
  521. class_desc->add_text(argument.name);
  522. class_desc->pop(); // color
  523. class_desc->push_color(theme_cache.symbol_color);
  524. class_desc->add_text(colon_nbsp);
  525. class_desc->pop(); // color
  526. _add_type(argument.type, argument.enumeration, argument.is_bitfield);
  527. if (!argument.default_value.is_empty()) {
  528. class_desc->push_color(theme_cache.symbol_color);
  529. class_desc->add_text(nbsp_equal_nbsp);
  530. class_desc->pop(); // color
  531. class_desc->push_color(theme_cache.value_color);
  532. class_desc->add_text(_fix_constant(argument.default_value));
  533. class_desc->pop(); // color
  534. }
  535. }
  536. if (is_vararg) {
  537. if (!p_method.arguments.is_empty()) {
  538. class_desc->push_color(theme_cache.symbol_color);
  539. class_desc->add_text(", ");
  540. class_desc->pop(); // color
  541. }
  542. class_desc->push_color(theme_cache.symbol_color);
  543. class_desc->add_text("...");
  544. class_desc->pop(); // color
  545. const DocData::ArgumentDoc &rest_argument = p_method.rest_argument;
  546. class_desc->push_color(theme_cache.text_color);
  547. class_desc->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
  548. class_desc->pop(); // color
  549. class_desc->push_color(theme_cache.symbol_color);
  550. class_desc->add_text(colon_nbsp);
  551. class_desc->pop(); // color
  552. if (rest_argument.type.is_empty()) {
  553. _add_type("Array");
  554. } else {
  555. _add_type(rest_argument.type, rest_argument.enumeration, rest_argument.is_bitfield);
  556. }
  557. }
  558. class_desc->push_color(theme_cache.symbol_color);
  559. class_desc->add_text(")");
  560. class_desc->pop(); // color
  561. if (!p_method.qualifiers.is_empty()) {
  562. class_desc->push_color(theme_cache.qualifier_color);
  563. _add_qualifiers_to_rt(p_method.qualifiers, class_desc);
  564. class_desc->pop(); // color
  565. }
  566. if (p_overview) {
  567. if (p_method.is_deprecated) {
  568. class_desc->add_text(" ");
  569. DEPRECATED_DOC_TAG;
  570. }
  571. if (p_method.is_experimental) {
  572. class_desc->add_text(" ");
  573. EXPERIMENTAL_DOC_TAG;
  574. }
  575. class_desc->pop(); // cell
  576. }
  577. }
  578. void EditorHelp::_add_bulletpoint() {
  579. static const char32_t prefix[3] = { 0x25CF /* filled circle */, ' ', 0 };
  580. class_desc->add_text(String(prefix));
  581. }
  582. void EditorHelp::_push_normal_font() {
  583. class_desc->push_font(theme_cache.doc_font);
  584. class_desc->push_font_size(theme_cache.doc_font_size);
  585. }
  586. void EditorHelp::_pop_normal_font() {
  587. class_desc->pop(); // font_size
  588. class_desc->pop(); // font
  589. }
  590. void EditorHelp::_push_title_font() {
  591. class_desc->push_font(theme_cache.doc_title_font);
  592. class_desc->push_font_size(theme_cache.doc_title_font_size);
  593. class_desc->push_color(theme_cache.title_color);
  594. }
  595. void EditorHelp::_pop_title_font() {
  596. class_desc->pop(); // color
  597. class_desc->pop(); // font_size
  598. class_desc->pop(); // font
  599. }
  600. void EditorHelp::_push_code_font() {
  601. class_desc->push_font(theme_cache.doc_code_font);
  602. class_desc->push_font_size(theme_cache.doc_code_font_size);
  603. }
  604. void EditorHelp::_pop_code_font() {
  605. class_desc->pop(); // font_size
  606. class_desc->pop(); // font
  607. }
  608. Error EditorHelp::_goto_desc(const String &p_class) {
  609. if (!doc->class_list.has(p_class)) {
  610. return ERR_DOES_NOT_EXIST;
  611. }
  612. select_locked = true;
  613. class_desc->show();
  614. description_line = 0;
  615. if (p_class == edited_class) {
  616. return OK; // Already there.
  617. }
  618. edited_class = p_class;
  619. _update_doc();
  620. return OK;
  621. }
  622. void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods) {
  623. class_desc->add_newline();
  624. class_desc->add_newline();
  625. static const char *titles_by_type[METHOD_TYPE_MAX] = {
  626. TTRC("Methods"),
  627. TTRC("Constructors"),
  628. TTRC("Operators"),
  629. };
  630. const String title = TTRGET(titles_by_type[p_method_type]);
  631. section_line.push_back(Pair<String, int>(title, class_desc->get_paragraph_count() - 2));
  632. _push_title_font();
  633. class_desc->add_text(title);
  634. _pop_title_font();
  635. class_desc->add_newline();
  636. class_desc->add_newline();
  637. class_desc->push_indent(1);
  638. _push_code_font();
  639. class_desc->push_table(2);
  640. class_desc->set_table_column_expand(1, true);
  641. bool any_previous = false;
  642. for (int pass = 0; pass < 2; pass++) {
  643. Vector<DocData::MethodDoc> m;
  644. for (const DocData::MethodDoc &method : p_methods) {
  645. const String &q = method.qualifiers;
  646. if ((pass == 0 && q.contains("virtual")) || (pass == 1 && !q.contains("virtual"))) {
  647. m.push_back(method);
  648. }
  649. }
  650. if (any_previous && !m.is_empty()) {
  651. class_desc->push_cell();
  652. class_desc->pop(); // cell
  653. class_desc->push_cell();
  654. class_desc->pop(); // cell
  655. }
  656. String group_prefix;
  657. for (int i = 0; i < m.size(); i++) {
  658. const String new_prefix = m[i].name.left(3);
  659. bool is_new_group = false;
  660. if (i < m.size() - 1 && new_prefix == m[i + 1].name.left(3) && new_prefix != group_prefix) {
  661. is_new_group = i > 0;
  662. group_prefix = new_prefix;
  663. } else if (!group_prefix.is_empty() && new_prefix != group_prefix) {
  664. is_new_group = true;
  665. group_prefix = "";
  666. }
  667. if (is_new_group && pass == 1) {
  668. class_desc->push_cell();
  669. class_desc->pop(); // cell
  670. class_desc->push_cell();
  671. class_desc->pop(); // cell
  672. }
  673. // For constructors always point to the first one.
  674. _add_method(m[i], true, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
  675. }
  676. any_previous = !m.is_empty();
  677. }
  678. class_desc->pop(); // table
  679. _pop_code_font();
  680. class_desc->pop(); // indent
  681. }
  682. void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods) {
  683. #define HANDLE_DOC(m_string) ((p_classdoc.is_script_doc ? (m_string) : DTR(m_string)).strip_edges())
  684. class_desc->add_newline();
  685. class_desc->add_newline();
  686. class_desc->add_newline();
  687. static const char *titles_by_type[METHOD_TYPE_MAX] = {
  688. TTRC("Method Descriptions"),
  689. TTRC("Constructor Descriptions"),
  690. TTRC("Operator Descriptions"),
  691. };
  692. const String title = TTRGET(titles_by_type[p_method_type]);
  693. section_line.push_back(Pair<String, int>(title, class_desc->get_paragraph_count() - 2));
  694. _push_title_font();
  695. class_desc->add_text(title);
  696. _pop_title_font();
  697. String link_color_text = theme_cache.title_color.to_html(false);
  698. for (int pass = 0; pass < 2; pass++) {
  699. Vector<DocData::MethodDoc> methods_filtered;
  700. for (int i = 0; i < p_methods.size(); i++) {
  701. const String &q = p_methods[i].qualifiers;
  702. if ((pass == 0 && q.contains("virtual")) || (pass == 1 && !q.contains("virtual"))) {
  703. methods_filtered.push_back(p_methods[i]);
  704. }
  705. }
  706. for (int i = 0; i < methods_filtered.size(); i++) {
  707. const DocData::MethodDoc &method = methods_filtered[i];
  708. class_desc->add_newline();
  709. class_desc->add_newline();
  710. class_desc->add_newline();
  711. _push_code_font();
  712. // For constructors always point to the first one.
  713. _add_method(method, false, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0));
  714. _pop_code_font();
  715. class_desc->add_newline();
  716. class_desc->add_newline();
  717. class_desc->push_indent(1);
  718. _push_normal_font();
  719. class_desc->push_color(theme_cache.text_color);
  720. bool has_prev_text = false;
  721. if (method.is_deprecated) {
  722. has_prev_text = true;
  723. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  724. TTRC("This method may be changed or removed in future versions."),
  725. TTRC("This constructor may be changed or removed in future versions."),
  726. TTRC("This operator may be changed or removed in future versions."),
  727. };
  728. DEPRECATED_DOC_MSG(HANDLE_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type]));
  729. }
  730. if (method.is_experimental) {
  731. if (has_prev_text) {
  732. class_desc->add_newline();
  733. class_desc->add_newline();
  734. }
  735. has_prev_text = true;
  736. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  737. TTRC("This method may be changed or removed in future versions."),
  738. TTRC("This constructor may be changed or removed in future versions."),
  739. TTRC("This operator may be changed or removed in future versions."),
  740. };
  741. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type]));
  742. }
  743. if (!method.errors_returned.is_empty()) {
  744. if (has_prev_text) {
  745. class_desc->add_newline();
  746. class_desc->add_newline();
  747. }
  748. has_prev_text = true;
  749. class_desc->add_text(TTR("Error codes returned:"));
  750. class_desc->add_newline();
  751. class_desc->push_list(0, RichTextLabel::LIST_DOTS, false);
  752. for (int j = 0; j < method.errors_returned.size(); j++) {
  753. if (j > 0) {
  754. class_desc->add_newline();
  755. }
  756. int val = method.errors_returned[j];
  757. String text = itos(val);
  758. for (int k = 0; k < CoreConstants::get_global_constant_count(); k++) {
  759. if (CoreConstants::get_global_constant_value(k) == val && CoreConstants::get_global_constant_enum(k) == SNAME("Error")) {
  760. text = CoreConstants::get_global_constant_name(k);
  761. break;
  762. }
  763. }
  764. class_desc->push_font(theme_cache.doc_bold_font);
  765. class_desc->add_text(text);
  766. class_desc->pop(); // font
  767. }
  768. class_desc->pop(); // list
  769. }
  770. const String descr = HANDLE_DOC(method.description);
  771. const bool is_documented = method.is_deprecated || method.is_experimental || !descr.is_empty();
  772. if (!descr.is_empty()) {
  773. if (has_prev_text) {
  774. class_desc->add_newline();
  775. class_desc->add_newline();
  776. }
  777. has_prev_text = true;
  778. _add_text(descr);
  779. } else if (!is_documented) {
  780. if (has_prev_text) {
  781. class_desc->add_newline();
  782. class_desc->add_newline();
  783. }
  784. has_prev_text = true;
  785. String message;
  786. if (p_classdoc.is_script_doc) {
  787. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  788. TTRC("There is currently no description for this method."),
  789. TTRC("There is currently no description for this constructor."),
  790. TTRC("There is currently no description for this operator."),
  791. };
  792. message = TTRGET(messages_by_type[p_method_type]);
  793. } else {
  794. static const char *messages_by_type[METHOD_TYPE_MAX] = {
  795. TTRC("There is currently no description for this method. Please help us by [color=$color][url=$url]contributing one[/url][/color]!"),
  796. TTRC("There is currently no description for this constructor. Please help us by [color=$color][url=$url]contributing one[/url][/color]!"),
  797. TTRC("There is currently no description for this operator. Please help us by [color=$color][url=$url]contributing one[/url][/color]!"),
  798. };
  799. message = TTRGET(messages_by_type[p_method_type]).replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text);
  800. }
  801. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  802. class_desc->add_text(" ");
  803. class_desc->push_color(theme_cache.comment_color);
  804. class_desc->append_text(message);
  805. class_desc->pop(); // color
  806. }
  807. class_desc->pop(); // color
  808. _pop_normal_font();
  809. class_desc->pop(); // indent
  810. }
  811. }
  812. #undef HANDLE_DOC
  813. }
  814. void EditorHelp::_update_doc() {
  815. if (!doc->class_list.has(edited_class)) {
  816. return;
  817. }
  818. scroll_locked = true;
  819. class_desc->clear();
  820. method_line.clear();
  821. section_line.clear();
  822. section_line.push_back(Pair<String, int>(TTR("Top"), 0));
  823. String link_color_text = theme_cache.title_color.to_html(false);
  824. DocData::ClassDoc cd = doc->class_list[edited_class]; // Make a copy, so we can sort without worrying.
  825. #define HANDLE_DOC(m_string) ((cd.is_script_doc ? (m_string) : DTR(m_string)).strip_edges())
  826. // Class name
  827. _push_title_font();
  828. class_desc->add_text(TTR("Class:") + " ");
  829. _add_type_icon(edited_class, theme_cache.doc_title_font_size, "");
  830. class_desc->add_text(nbsp);
  831. class_desc->push_color(theme_cache.headline_color);
  832. class_desc->add_text(edited_class);
  833. class_desc->pop(); // color
  834. _pop_title_font();
  835. if (cd.is_deprecated) {
  836. class_desc->add_newline();
  837. DEPRECATED_DOC_MSG(HANDLE_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions."));
  838. }
  839. if (cd.is_experimental) {
  840. class_desc->add_newline();
  841. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions."));
  842. }
  843. // Inheritance tree
  844. // Ascendents
  845. if (!cd.inherits.is_empty()) {
  846. class_desc->add_newline();
  847. _push_normal_font();
  848. class_desc->push_color(theme_cache.title_color);
  849. class_desc->add_text(TTR("Inherits:") + " ");
  850. String inherits = cd.inherits;
  851. while (!inherits.is_empty()) {
  852. _add_type_icon(inherits, theme_cache.doc_font_size, "ArrowRight");
  853. class_desc->add_text(nbsp); // Otherwise icon borrows hyperlink from `_add_type()`.
  854. _add_type(inherits);
  855. const DocData::ClassDoc *base_class_doc = doc->class_list.getptr(inherits);
  856. inherits = base_class_doc ? base_class_doc->inherits : String();
  857. if (!inherits.is_empty()) {
  858. class_desc->add_text(" < ");
  859. }
  860. }
  861. class_desc->pop(); // color
  862. _pop_normal_font();
  863. }
  864. // Descendants
  865. if ((cd.is_script_doc || ClassDB::class_exists(cd.name)) && doc->inheriting.has(cd.name)) {
  866. class_desc->add_newline();
  867. _push_normal_font();
  868. class_desc->push_color(theme_cache.title_color);
  869. class_desc->add_text(TTR("Inherited by:") + " ");
  870. for (RBSet<String, NaturalNoCaseComparator>::Element *itr = doc->inheriting[cd.name].front(); itr; itr = itr->next()) {
  871. if (itr->prev()) {
  872. class_desc->add_text(" , ");
  873. }
  874. _add_type_icon(itr->get(), theme_cache.doc_font_size, "ArrowRight");
  875. class_desc->add_text(nbsp); // Otherwise icon borrows hyperlink from `_add_type()`.
  876. _add_type(itr->get());
  877. }
  878. class_desc->pop(); // color
  879. _pop_normal_font();
  880. }
  881. bool has_description = false;
  882. // Brief description
  883. const String brief_class_descr = HANDLE_DOC(cd.brief_description);
  884. if (!brief_class_descr.is_empty()) {
  885. has_description = true;
  886. class_desc->add_newline();
  887. class_desc->add_newline();
  888. class_desc->push_indent(1);
  889. class_desc->push_font(theme_cache.doc_bold_font);
  890. class_desc->push_color(theme_cache.text_color);
  891. _add_text(brief_class_descr);
  892. class_desc->pop(); // color
  893. class_desc->pop(); // font
  894. class_desc->pop(); // indent
  895. }
  896. // Class description
  897. const String class_descr = HANDLE_DOC(cd.description);
  898. if (!class_descr.is_empty()) {
  899. has_description = true;
  900. class_desc->add_newline();
  901. class_desc->add_newline();
  902. section_line.push_back(Pair<String, int>(TTR("Description"), class_desc->get_paragraph_count() - 2));
  903. description_line = class_desc->get_paragraph_count() - 2;
  904. _push_title_font();
  905. class_desc->add_text(TTR("Description"));
  906. _pop_title_font();
  907. class_desc->add_newline();
  908. class_desc->add_newline();
  909. class_desc->push_indent(1);
  910. _push_normal_font();
  911. class_desc->push_color(theme_cache.text_color);
  912. _add_text(class_descr);
  913. class_desc->pop(); // color
  914. _pop_normal_font();
  915. class_desc->pop(); // indent
  916. }
  917. if (!has_description) {
  918. class_desc->add_newline();
  919. class_desc->add_newline();
  920. class_desc->push_indent(1);
  921. _push_normal_font();
  922. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  923. class_desc->add_text(" ");
  924. class_desc->push_color(theme_cache.comment_color);
  925. if (cd.is_script_doc) {
  926. class_desc->add_text(TTR("There is currently no description for this class."));
  927. } else {
  928. 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));
  929. }
  930. class_desc->pop(); // color
  931. _pop_normal_font();
  932. class_desc->pop(); // indent
  933. }
  934. #ifdef MODULE_MONO_ENABLED
  935. if (classes_with_csharp_differences.has(cd.name)) {
  936. class_desc->add_newline();
  937. class_desc->add_newline();
  938. const String &csharp_differences_url = vformat("%s/tutorials/scripting/c_sharp/c_sharp_differences.html", GODOT_VERSION_DOCS_URL);
  939. class_desc->push_indent(1);
  940. _push_normal_font();
  941. class_desc->push_color(theme_cache.text_color);
  942. 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));
  943. class_desc->pop(); // color
  944. _pop_normal_font();
  945. class_desc->pop(); // indent
  946. }
  947. #endif
  948. // Online tutorials
  949. if (!cd.tutorials.is_empty()) {
  950. class_desc->add_newline();
  951. class_desc->add_newline();
  952. _push_title_font();
  953. class_desc->add_text(TTR("Online Tutorials"));
  954. _pop_title_font();
  955. class_desc->add_newline();
  956. class_desc->push_indent(1);
  957. _push_code_font();
  958. class_desc->push_color(theme_cache.symbol_color);
  959. for (const DocData::TutorialDoc &tutorial : cd.tutorials) {
  960. const String link = HANDLE_DOC(tutorial.link);
  961. String link_text = HANDLE_DOC(tutorial.title);
  962. if (link_text.is_empty()) {
  963. const int sep_pos = link.find("//");
  964. if (sep_pos >= 0) {
  965. link_text = link.substr(sep_pos + 2);
  966. } else {
  967. link_text = link;
  968. }
  969. }
  970. class_desc->add_newline();
  971. _add_bulletpoint();
  972. class_desc->append_text("[url=" + link + "]" + link_text + "[/url]");
  973. }
  974. class_desc->pop(); // color
  975. _pop_code_font();
  976. class_desc->pop(); // indent
  977. }
  978. // Properties overview
  979. HashSet<String> skip_methods;
  980. bool has_properties = false;
  981. bool has_property_descriptions = false;
  982. for (const DocData::PropertyDoc &prop : cd.properties) {
  983. const bool is_documented = prop.is_deprecated || prop.is_experimental || !prop.description.strip_edges().is_empty();
  984. if (!is_documented && prop.name.begins_with("_")) {
  985. continue;
  986. }
  987. has_properties = true;
  988. if (!prop.overridden) {
  989. has_property_descriptions = true;
  990. break;
  991. }
  992. }
  993. if (has_properties) {
  994. class_desc->add_newline();
  995. class_desc->add_newline();
  996. section_line.push_back(Pair<String, int>(TTR("Properties"), class_desc->get_paragraph_count() - 2));
  997. _push_title_font();
  998. class_desc->add_text(TTR("Properties"));
  999. _pop_title_font();
  1000. class_desc->add_newline();
  1001. class_desc->add_newline();
  1002. class_desc->push_indent(1);
  1003. _push_code_font();
  1004. class_desc->push_table(4);
  1005. class_desc->set_table_column_expand(1, true);
  1006. cd.properties.sort_custom<PropertyCompare>();
  1007. bool is_generating_overridden_properties = true; // Set to false as soon as we encounter a non-overridden property.
  1008. bool overridden_property_exists = false;
  1009. for (const DocData::PropertyDoc &prop : cd.properties) {
  1010. // Ignore undocumented private.
  1011. const bool is_documented = prop.is_deprecated || prop.is_experimental || !prop.description.strip_edges().is_empty();
  1012. if (!is_documented && prop.name.begins_with("_")) {
  1013. continue;
  1014. }
  1015. if (is_generating_overridden_properties && !prop.overridden) {
  1016. is_generating_overridden_properties = false;
  1017. // No need for the extra spacing when there's no overridden property.
  1018. if (overridden_property_exists) {
  1019. class_desc->push_cell();
  1020. class_desc->pop(); // cell
  1021. class_desc->push_cell();
  1022. class_desc->pop(); // cell
  1023. class_desc->push_cell();
  1024. class_desc->pop(); // cell
  1025. class_desc->push_cell();
  1026. class_desc->pop(); // cell
  1027. }
  1028. }
  1029. property_line[prop.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1030. // Property type.
  1031. class_desc->push_cell();
  1032. class_desc->push_paragraph(HORIZONTAL_ALIGNMENT_RIGHT, Control::TEXT_DIRECTION_AUTO, "");
  1033. _add_type(prop.type, prop.enumeration, prop.is_bitfield);
  1034. class_desc->pop(); // paragraph
  1035. class_desc->pop(); // cell
  1036. bool describe = false;
  1037. if (!prop.setter.is_empty()) {
  1038. skip_methods.insert(prop.setter);
  1039. describe = true;
  1040. }
  1041. if (!prop.getter.is_empty()) {
  1042. skip_methods.insert(prop.getter);
  1043. describe = true;
  1044. }
  1045. if (is_documented) {
  1046. describe = true;
  1047. }
  1048. if (prop.overridden) {
  1049. describe = false;
  1050. }
  1051. // Property name.
  1052. class_desc->push_cell();
  1053. class_desc->push_color(theme_cache.headline_color);
  1054. if (describe) {
  1055. class_desc->push_meta("@member " + prop.name, RichTextLabel::META_UNDERLINE_ON_HOVER);
  1056. }
  1057. class_desc->add_text(prop.name);
  1058. if (describe) {
  1059. class_desc->pop(); // meta
  1060. }
  1061. class_desc->pop(); // color
  1062. class_desc->pop(); // cell
  1063. // Property value.
  1064. class_desc->push_cell();
  1065. if (!prop.default_value.is_empty()) {
  1066. if (prop.overridden) {
  1067. class_desc->push_color(theme_cache.override_color);
  1068. class_desc->add_text("[");
  1069. const String link = vformat("[url=@member %s.%s]%s[/url]", prop.overrides, prop.name, prop.overrides);
  1070. class_desc->append_text(vformat(TTR("overrides %s:"), link));
  1071. class_desc->add_text(" " + _fix_constant(prop.default_value) + "]");
  1072. class_desc->pop(); // color
  1073. overridden_property_exists = true;
  1074. } else {
  1075. class_desc->push_color(theme_cache.symbol_color);
  1076. class_desc->add_text("[" + TTR("default:") + " ");
  1077. class_desc->pop(); // color
  1078. class_desc->push_color(theme_cache.value_color);
  1079. class_desc->add_text(_fix_constant(prop.default_value));
  1080. class_desc->pop(); // color
  1081. class_desc->push_color(theme_cache.symbol_color);
  1082. class_desc->add_text("]");
  1083. class_desc->pop(); // color
  1084. }
  1085. }
  1086. class_desc->pop(); // cell
  1087. // Property setter/getter and deprecated/experimental marks.
  1088. class_desc->push_cell();
  1089. bool has_prev_text = false;
  1090. if (cd.is_script_doc && (!prop.setter.is_empty() || !prop.getter.is_empty())) {
  1091. has_prev_text = true;
  1092. class_desc->push_color(theme_cache.symbol_color);
  1093. class_desc->add_text("[" + TTR("property:") + " ");
  1094. class_desc->pop(); // color
  1095. if (!prop.setter.is_empty()) {
  1096. class_desc->push_color(theme_cache.value_color);
  1097. class_desc->add_text("setter");
  1098. class_desc->pop(); // color
  1099. }
  1100. if (!prop.getter.is_empty()) {
  1101. if (!prop.setter.is_empty()) {
  1102. class_desc->push_color(theme_cache.symbol_color);
  1103. class_desc->add_text(", ");
  1104. class_desc->pop(); // color
  1105. }
  1106. class_desc->push_color(theme_cache.value_color);
  1107. class_desc->add_text("getter");
  1108. class_desc->pop(); // color
  1109. }
  1110. class_desc->push_color(theme_cache.symbol_color);
  1111. class_desc->add_text("]");
  1112. class_desc->pop(); // color
  1113. }
  1114. if (prop.is_deprecated) {
  1115. if (has_prev_text) {
  1116. class_desc->add_text(" ");
  1117. }
  1118. has_prev_text = true;
  1119. DEPRECATED_DOC_TAG;
  1120. }
  1121. if (prop.is_experimental) {
  1122. if (has_prev_text) {
  1123. class_desc->add_text(" ");
  1124. }
  1125. has_prev_text = true;
  1126. EXPERIMENTAL_DOC_TAG;
  1127. }
  1128. class_desc->pop(); // cell
  1129. }
  1130. class_desc->pop(); // table
  1131. _pop_code_font();
  1132. class_desc->pop(); // indent
  1133. }
  1134. // Methods overview
  1135. bool sort_methods = EDITOR_GET("text_editor/help/sort_functions_alphabetically");
  1136. Vector<DocData::MethodDoc> methods;
  1137. for (const DocData::MethodDoc &method : cd.methods) {
  1138. if (skip_methods.has(method.name)) {
  1139. if (method.arguments.is_empty() /* getter */ || (method.arguments.size() == 1 && method.return_type == "void" /* setter */)) {
  1140. continue;
  1141. }
  1142. }
  1143. // Ignore undocumented non virtual private.
  1144. const bool is_documented = method.is_deprecated || method.is_experimental || !method.description.strip_edges().is_empty();
  1145. if (!is_documented && method.name.begins_with("_") && !method.qualifiers.contains("virtual")) {
  1146. continue;
  1147. }
  1148. methods.push_back(method);
  1149. }
  1150. if (!cd.constructors.is_empty()) {
  1151. if (sort_methods) {
  1152. cd.constructors.sort();
  1153. }
  1154. _update_method_list(METHOD_TYPE_CONSTRUCTOR, cd.constructors);
  1155. }
  1156. if (!methods.is_empty()) {
  1157. if (sort_methods) {
  1158. methods.sort();
  1159. }
  1160. _update_method_list(METHOD_TYPE_METHOD, methods);
  1161. }
  1162. if (!cd.operators.is_empty()) {
  1163. if (sort_methods) {
  1164. cd.operators.sort();
  1165. }
  1166. _update_method_list(METHOD_TYPE_OPERATOR, cd.operators);
  1167. }
  1168. // Theme properties
  1169. if (!cd.theme_properties.is_empty()) {
  1170. class_desc->add_newline();
  1171. class_desc->add_newline();
  1172. section_line.push_back(Pair<String, int>(TTR("Theme Properties"), class_desc->get_paragraph_count() - 2));
  1173. _push_title_font();
  1174. class_desc->add_text(TTR("Theme Properties"));
  1175. _pop_title_font();
  1176. String theme_data_type;
  1177. HashMap<String, String> data_type_names;
  1178. data_type_names["color"] = TTR("Colors");
  1179. data_type_names["constant"] = TTR("Constants");
  1180. data_type_names["font"] = TTR("Fonts");
  1181. data_type_names["font_size"] = TTR("Font Sizes");
  1182. data_type_names["icon"] = TTR("Icons");
  1183. data_type_names["style"] = TTR("Styles");
  1184. for (const DocData::ThemeItemDoc &theme_item : cd.theme_properties) {
  1185. if (theme_data_type != theme_item.data_type) {
  1186. theme_data_type = theme_item.data_type;
  1187. class_desc->add_newline();
  1188. class_desc->add_newline();
  1189. class_desc->push_indent(1);
  1190. _push_title_font();
  1191. if (data_type_names.has(theme_data_type)) {
  1192. class_desc->add_text(data_type_names[theme_data_type]);
  1193. } else {
  1194. class_desc->add_text(theme_data_type);
  1195. }
  1196. _pop_title_font();
  1197. class_desc->pop(); // indent
  1198. }
  1199. class_desc->add_newline();
  1200. class_desc->add_newline();
  1201. theme_property_line[theme_item.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1202. class_desc->push_indent(1);
  1203. // Theme item header.
  1204. _push_code_font();
  1205. _add_bulletpoint();
  1206. // Theme item object type.
  1207. _add_type(theme_item.type);
  1208. // Theme item name.
  1209. class_desc->push_color(theme_cache.headline_color);
  1210. class_desc->add_text(" ");
  1211. class_desc->add_text(theme_item.name);
  1212. class_desc->pop(); // color
  1213. // Theme item default value.
  1214. if (!theme_item.default_value.is_empty()) {
  1215. class_desc->push_color(theme_cache.symbol_color);
  1216. class_desc->add_text(" [" + TTR("default:") + " ");
  1217. class_desc->pop(); // color
  1218. class_desc->push_color(theme_cache.value_color);
  1219. class_desc->add_text(_fix_constant(theme_item.default_value));
  1220. class_desc->pop(); // color
  1221. class_desc->push_color(theme_cache.symbol_color);
  1222. class_desc->add_text("]");
  1223. class_desc->pop(); // color
  1224. }
  1225. _pop_code_font();
  1226. // Theme item description.
  1227. class_desc->push_indent(1);
  1228. _push_normal_font();
  1229. class_desc->push_color(theme_cache.comment_color);
  1230. bool has_prev_text = false;
  1231. if (theme_item.is_deprecated) {
  1232. has_prev_text = true;
  1233. DEPRECATED_DOC_MSG(HANDLE_DOC(theme_item.deprecated_message), TTR("This theme property may be changed or removed in future versions."));
  1234. }
  1235. if (theme_item.is_experimental) {
  1236. if (has_prev_text) {
  1237. class_desc->add_newline();
  1238. class_desc->add_newline();
  1239. }
  1240. has_prev_text = true;
  1241. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(theme_item.experimental_message), TTR("This theme property may be changed or removed in future versions."));
  1242. }
  1243. const String descr = HANDLE_DOC(theme_item.description);
  1244. if (!descr.is_empty()) {
  1245. if (has_prev_text) {
  1246. class_desc->add_newline();
  1247. class_desc->add_newline();
  1248. }
  1249. has_prev_text = true;
  1250. _add_text(descr);
  1251. } else if (!has_prev_text) {
  1252. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1253. class_desc->add_text(" ");
  1254. class_desc->push_color(theme_cache.comment_color);
  1255. if (cd.is_script_doc) {
  1256. class_desc->add_text(TTR("There is currently no description for this theme property."));
  1257. } else {
  1258. 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));
  1259. }
  1260. class_desc->pop(); // color
  1261. }
  1262. class_desc->pop(); // color
  1263. _pop_normal_font();
  1264. class_desc->pop(); // indent
  1265. class_desc->pop(); // indent
  1266. }
  1267. }
  1268. // Signals
  1269. if (!cd.signals.is_empty()) {
  1270. if (sort_methods) {
  1271. cd.signals.sort();
  1272. }
  1273. bool header_added = false;
  1274. for (const DocData::MethodDoc &signal : cd.signals) {
  1275. // Ignore undocumented private.
  1276. const bool is_documented = signal.is_deprecated || signal.is_experimental || !signal.description.strip_edges().is_empty();
  1277. if (!is_documented && signal.name.begins_with("_")) {
  1278. continue;
  1279. }
  1280. if (!header_added) {
  1281. header_added = true;
  1282. class_desc->add_newline();
  1283. class_desc->add_newline();
  1284. section_line.push_back(Pair<String, int>(TTR("Signals"), class_desc->get_paragraph_count() - 2));
  1285. _push_title_font();
  1286. class_desc->add_text(TTR("Signals"));
  1287. _pop_title_font();
  1288. }
  1289. class_desc->add_newline();
  1290. class_desc->add_newline();
  1291. signal_line[signal.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1292. class_desc->push_indent(1);
  1293. // Signal header.
  1294. _push_code_font();
  1295. _add_bulletpoint();
  1296. class_desc->push_color(theme_cache.headline_color);
  1297. class_desc->add_text(signal.name);
  1298. class_desc->pop(); // color
  1299. class_desc->push_color(theme_cache.symbol_color);
  1300. class_desc->add_text("(");
  1301. class_desc->pop(); // color
  1302. for (int j = 0; j < signal.arguments.size(); j++) {
  1303. const DocData::ArgumentDoc &argument = signal.arguments[j];
  1304. if (j > 0) {
  1305. class_desc->push_color(theme_cache.symbol_color);
  1306. class_desc->add_text(", ");
  1307. class_desc->pop(); // color
  1308. }
  1309. class_desc->push_color(theme_cache.text_color);
  1310. class_desc->add_text(argument.name);
  1311. class_desc->pop(); // color
  1312. class_desc->push_color(theme_cache.symbol_color);
  1313. class_desc->add_text(colon_nbsp);
  1314. class_desc->pop(); // color
  1315. _add_type(argument.type, argument.enumeration, argument.is_bitfield);
  1316. // Signals currently do not support default argument values, neither the core nor GDScript.
  1317. // This code is just for completeness.
  1318. if (!argument.default_value.is_empty()) {
  1319. class_desc->push_color(theme_cache.symbol_color);
  1320. class_desc->add_text(nbsp_equal_nbsp);
  1321. class_desc->pop(); // color
  1322. class_desc->push_color(theme_cache.value_color);
  1323. class_desc->add_text(_fix_constant(argument.default_value));
  1324. class_desc->pop(); // color
  1325. }
  1326. }
  1327. class_desc->push_color(theme_cache.symbol_color);
  1328. class_desc->add_text(")");
  1329. class_desc->pop(); // color
  1330. _pop_code_font();
  1331. class_desc->add_newline();
  1332. // Signal description.
  1333. class_desc->push_indent(1);
  1334. _push_normal_font();
  1335. class_desc->push_color(theme_cache.comment_color);
  1336. const String descr = HANDLE_DOC(signal.description);
  1337. const bool is_multiline = descr.find_char('\n') > 0;
  1338. bool has_prev_text = false;
  1339. if (signal.is_deprecated) {
  1340. has_prev_text = true;
  1341. DEPRECATED_DOC_MSG(HANDLE_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions."));
  1342. }
  1343. if (signal.is_experimental) {
  1344. if (has_prev_text) {
  1345. class_desc->add_newline();
  1346. if (is_multiline) {
  1347. class_desc->add_newline();
  1348. }
  1349. }
  1350. has_prev_text = true;
  1351. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions."));
  1352. }
  1353. if (!descr.is_empty()) {
  1354. if (has_prev_text) {
  1355. class_desc->add_newline();
  1356. if (is_multiline) {
  1357. class_desc->add_newline();
  1358. }
  1359. }
  1360. has_prev_text = true;
  1361. _add_text(descr);
  1362. } else if (!has_prev_text) {
  1363. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1364. class_desc->add_text(" ");
  1365. class_desc->push_color(theme_cache.comment_color);
  1366. if (cd.is_script_doc) {
  1367. class_desc->add_text(TTR("There is currently no description for this signal."));
  1368. } else {
  1369. 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));
  1370. }
  1371. class_desc->pop(); // color
  1372. }
  1373. class_desc->pop(); // color
  1374. _pop_normal_font();
  1375. class_desc->pop(); // indent
  1376. class_desc->pop(); // indent
  1377. }
  1378. }
  1379. // Constants and enums
  1380. if (!cd.constants.is_empty()) {
  1381. HashMap<String, Vector<DocData::ConstantDoc>> enums;
  1382. Vector<DocData::ConstantDoc> constants;
  1383. for (const DocData::ConstantDoc &constant : cd.constants) {
  1384. if (!constant.enumeration.is_empty()) {
  1385. if (!enums.has(constant.enumeration)) {
  1386. enums[constant.enumeration] = Vector<DocData::ConstantDoc>();
  1387. }
  1388. enums[constant.enumeration].push_back(constant);
  1389. } else {
  1390. // Ignore undocumented private.
  1391. const bool is_documented = constant.is_deprecated || constant.is_experimental || !constant.description.strip_edges().is_empty();
  1392. if (!is_documented && constant.name.begins_with("_")) {
  1393. continue;
  1394. }
  1395. constants.push_back(constant);
  1396. }
  1397. }
  1398. // Enums
  1399. bool has_enums = enums.size() && !cd.is_script_doc;
  1400. if (enums.size() && !has_enums) {
  1401. for (KeyValue<String, DocData::EnumDoc> &E : cd.enums) {
  1402. const bool is_documented = E.value.is_deprecated || E.value.is_experimental || !E.value.description.strip_edges().is_empty();
  1403. if (!is_documented && E.key.begins_with("_")) {
  1404. continue;
  1405. }
  1406. has_enums = true;
  1407. break;
  1408. }
  1409. }
  1410. if (has_enums) {
  1411. class_desc->add_newline();
  1412. class_desc->add_newline();
  1413. section_line.push_back(Pair<String, int>(TTR("Enumerations"), class_desc->get_paragraph_count() - 2));
  1414. _push_title_font();
  1415. class_desc->add_text(TTR("Enumerations"));
  1416. _pop_title_font();
  1417. for (KeyValue<String, Vector<DocData::ConstantDoc>> &E : enums) {
  1418. String key = E.key;
  1419. if ((key.get_slice_count(".") > 1) && (key.get_slicec('.', 0) == edited_class)) {
  1420. key = key.get_slicec('.', 1);
  1421. }
  1422. if (cd.enums.has(key)) {
  1423. const bool is_documented = cd.enums[key].is_deprecated || cd.enums[key].is_experimental || !cd.enums[key].description.strip_edges().is_empty();
  1424. if (!is_documented && cd.is_script_doc && E.key.begins_with("_")) {
  1425. continue;
  1426. }
  1427. }
  1428. class_desc->add_newline();
  1429. class_desc->add_newline();
  1430. // Enum header.
  1431. _push_code_font();
  1432. enum_line[E.key] = class_desc->get_paragraph_count() - 2;
  1433. class_desc->push_color(theme_cache.title_color);
  1434. if (E.value.size() && E.value[0].is_bitfield) {
  1435. class_desc->add_text("flags ");
  1436. } else {
  1437. class_desc->add_text("enum ");
  1438. }
  1439. class_desc->pop(); // color
  1440. class_desc->push_color(theme_cache.headline_color);
  1441. class_desc->add_text(key);
  1442. class_desc->pop(); // color
  1443. class_desc->push_color(theme_cache.symbol_color);
  1444. class_desc->add_text(":");
  1445. class_desc->pop(); // color
  1446. _pop_code_font();
  1447. // Enum description.
  1448. if (key != "@unnamed_enums" && cd.enums.has(key)) {
  1449. const String descr = HANDLE_DOC(cd.enums[key].description);
  1450. const bool is_multiline = descr.find_char('\n') > 0;
  1451. if (cd.enums[key].is_deprecated || cd.enums[key].is_experimental || !descr.is_empty()) {
  1452. class_desc->add_newline();
  1453. class_desc->push_indent(1);
  1454. _push_normal_font();
  1455. class_desc->push_color(theme_cache.text_color);
  1456. bool has_prev_text = false;
  1457. if (cd.enums[key].is_deprecated) {
  1458. has_prev_text = true;
  1459. DEPRECATED_DOC_MSG(HANDLE_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions."));
  1460. }
  1461. if (cd.enums[key].is_experimental) {
  1462. if (has_prev_text) {
  1463. class_desc->add_newline();
  1464. if (is_multiline) {
  1465. class_desc->add_newline();
  1466. }
  1467. }
  1468. has_prev_text = true;
  1469. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions."));
  1470. }
  1471. if (!descr.is_empty()) {
  1472. if (has_prev_text) {
  1473. class_desc->add_newline();
  1474. if (is_multiline) {
  1475. class_desc->add_newline();
  1476. }
  1477. }
  1478. has_prev_text = true;
  1479. _add_text(descr);
  1480. }
  1481. class_desc->pop(); // color
  1482. _pop_normal_font();
  1483. class_desc->pop(); // indent
  1484. }
  1485. }
  1486. HashMap<String, int> enum_values;
  1487. const int enum_start_line = enum_line[E.key];
  1488. bool prev_is_multiline = true; // Use a large margin for the first item.
  1489. for (const DocData::ConstantDoc &enum_value : E.value) {
  1490. const String descr = HANDLE_DOC(enum_value.description);
  1491. const bool is_multiline = descr.find_char('\n') > 0;
  1492. class_desc->add_newline();
  1493. if (prev_is_multiline || is_multiline) {
  1494. class_desc->add_newline();
  1495. }
  1496. prev_is_multiline = is_multiline;
  1497. if (cd.name == "@GlobalScope") {
  1498. enum_values[enum_value.name] = enum_start_line;
  1499. }
  1500. // Add the enum constant line to the constant_line map so we can locate it as a constant.
  1501. constant_line[enum_value.name] = class_desc->get_paragraph_count() - 2;
  1502. class_desc->push_indent(1);
  1503. // Enum value header.
  1504. _push_code_font();
  1505. _add_bulletpoint();
  1506. class_desc->push_color(theme_cache.headline_color);
  1507. class_desc->add_text(enum_value.name);
  1508. class_desc->pop(); // color
  1509. class_desc->push_color(theme_cache.symbol_color);
  1510. class_desc->add_text(nbsp_equal_nbsp);
  1511. class_desc->pop(); // color
  1512. class_desc->push_color(theme_cache.value_color);
  1513. class_desc->add_text(_fix_constant(enum_value.value));
  1514. class_desc->pop(); // color
  1515. _pop_code_font();
  1516. // Enum value description.
  1517. if (enum_value.is_deprecated || enum_value.is_experimental || !descr.is_empty()) {
  1518. class_desc->add_newline();
  1519. class_desc->push_indent(1);
  1520. _push_normal_font();
  1521. class_desc->push_color(theme_cache.comment_color);
  1522. bool has_prev_text = false;
  1523. if (enum_value.is_deprecated) {
  1524. has_prev_text = true;
  1525. DEPRECATED_DOC_MSG(HANDLE_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions."));
  1526. }
  1527. if (enum_value.is_experimental) {
  1528. if (has_prev_text) {
  1529. class_desc->add_newline();
  1530. if (is_multiline) {
  1531. class_desc->add_newline();
  1532. }
  1533. }
  1534. has_prev_text = true;
  1535. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions."));
  1536. }
  1537. if (!descr.is_empty()) {
  1538. if (has_prev_text) {
  1539. class_desc->add_newline();
  1540. if (is_multiline) {
  1541. class_desc->add_newline();
  1542. }
  1543. }
  1544. has_prev_text = true;
  1545. _add_text(descr);
  1546. }
  1547. class_desc->pop(); // color
  1548. _pop_normal_font();
  1549. class_desc->pop(); // indent
  1550. }
  1551. class_desc->pop(); // indent
  1552. }
  1553. if (cd.name == "@GlobalScope") {
  1554. enum_values_line[E.key] = enum_values;
  1555. }
  1556. }
  1557. }
  1558. // Constants
  1559. if (!constants.is_empty()) {
  1560. class_desc->add_newline();
  1561. class_desc->add_newline();
  1562. section_line.push_back(Pair<String, int>(TTR("Constants"), class_desc->get_paragraph_count() - 2));
  1563. _push_title_font();
  1564. class_desc->add_text(TTR("Constants"));
  1565. _pop_title_font();
  1566. bool prev_is_multiline = true; // Use a large margin for the first item.
  1567. for (const DocData::ConstantDoc &constant : constants) {
  1568. const String descr = HANDLE_DOC(constant.description);
  1569. const bool is_multiline = descr.find_char('\n') > 0;
  1570. class_desc->add_newline();
  1571. if (prev_is_multiline || is_multiline) {
  1572. class_desc->add_newline();
  1573. }
  1574. prev_is_multiline = is_multiline;
  1575. constant_line[constant.name] = class_desc->get_paragraph_count() - 2;
  1576. class_desc->push_indent(1);
  1577. // Constant header.
  1578. _push_code_font();
  1579. if (constant.value.begins_with("Color(") && constant.value.ends_with(")")) {
  1580. String stripped = constant.value.remove_char(' ').replace("Color(", "").remove_char(')');
  1581. PackedFloat64Array color = stripped.split_floats(",");
  1582. if (color.size() >= 3) {
  1583. class_desc->push_color(Color(color[0], color[1], color[2]));
  1584. _add_bulletpoint();
  1585. class_desc->pop(); // color
  1586. }
  1587. } else {
  1588. _add_bulletpoint();
  1589. }
  1590. class_desc->push_color(theme_cache.headline_color);
  1591. class_desc->add_text(constant.name);
  1592. class_desc->pop(); // color
  1593. class_desc->push_color(theme_cache.symbol_color);
  1594. class_desc->add_text(nbsp_equal_nbsp);
  1595. class_desc->pop(); // color
  1596. class_desc->push_color(theme_cache.value_color);
  1597. class_desc->add_text(_fix_constant(constant.value));
  1598. class_desc->pop(); // color
  1599. _pop_code_font();
  1600. // Constant description.
  1601. if (constant.is_deprecated || constant.is_experimental || !descr.is_empty()) {
  1602. class_desc->add_newline();
  1603. class_desc->push_indent(1);
  1604. _push_normal_font();
  1605. class_desc->push_color(theme_cache.comment_color);
  1606. bool has_prev_text = false;
  1607. if (constant.is_deprecated) {
  1608. has_prev_text = true;
  1609. DEPRECATED_DOC_MSG(HANDLE_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions."));
  1610. }
  1611. if (constant.is_experimental) {
  1612. if (has_prev_text) {
  1613. class_desc->add_newline();
  1614. if (is_multiline) {
  1615. class_desc->add_newline();
  1616. }
  1617. }
  1618. has_prev_text = true;
  1619. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions."));
  1620. }
  1621. if (!descr.is_empty()) {
  1622. if (has_prev_text) {
  1623. class_desc->add_newline();
  1624. if (is_multiline) {
  1625. class_desc->add_newline();
  1626. }
  1627. }
  1628. has_prev_text = true;
  1629. _add_text(descr);
  1630. }
  1631. class_desc->pop(); // color
  1632. _pop_normal_font();
  1633. class_desc->pop(); // indent
  1634. }
  1635. class_desc->pop(); // indent
  1636. }
  1637. }
  1638. }
  1639. // Annotations
  1640. if (!cd.annotations.is_empty()) {
  1641. if (sort_methods) {
  1642. cd.annotations.sort();
  1643. }
  1644. class_desc->add_newline();
  1645. class_desc->add_newline();
  1646. section_line.push_back(Pair<String, int>(TTR("Annotations"), class_desc->get_paragraph_count() - 2));
  1647. _push_title_font();
  1648. class_desc->add_text(TTR("Annotations"));
  1649. _pop_title_font();
  1650. for (const DocData::MethodDoc &annotation : cd.annotations) {
  1651. class_desc->add_newline();
  1652. class_desc->add_newline();
  1653. annotation_line[annotation.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description.
  1654. class_desc->push_indent(1);
  1655. // Annotation header.
  1656. _push_code_font();
  1657. _add_bulletpoint();
  1658. class_desc->push_color(theme_cache.headline_color);
  1659. class_desc->add_text(annotation.name);
  1660. class_desc->pop(); // color
  1661. if (!annotation.arguments.is_empty()) {
  1662. class_desc->push_color(theme_cache.symbol_color);
  1663. class_desc->add_text("(");
  1664. class_desc->pop(); // color
  1665. for (int j = 0; j < annotation.arguments.size(); j++) {
  1666. const DocData::ArgumentDoc &argument = annotation.arguments[j];
  1667. if (j > 0) {
  1668. class_desc->push_color(theme_cache.symbol_color);
  1669. class_desc->add_text(", ");
  1670. class_desc->pop(); // color
  1671. }
  1672. class_desc->push_color(theme_cache.text_color);
  1673. class_desc->add_text(argument.name);
  1674. class_desc->pop(); // color
  1675. class_desc->push_color(theme_cache.symbol_color);
  1676. class_desc->add_text(colon_nbsp);
  1677. class_desc->pop(); // color
  1678. _add_type(argument.type, argument.enumeration, argument.is_bitfield);
  1679. if (!argument.default_value.is_empty()) {
  1680. class_desc->push_color(theme_cache.symbol_color);
  1681. class_desc->add_text(nbsp_equal_nbsp);
  1682. class_desc->pop(); // color
  1683. class_desc->push_color(theme_cache.value_color);
  1684. class_desc->add_text(_fix_constant(argument.default_value));
  1685. class_desc->pop(); // color
  1686. }
  1687. }
  1688. if (annotation.qualifiers.contains("vararg")) {
  1689. if (!annotation.arguments.is_empty()) {
  1690. class_desc->push_color(theme_cache.symbol_color);
  1691. class_desc->add_text(", ");
  1692. class_desc->pop(); // color
  1693. }
  1694. class_desc->push_color(theme_cache.symbol_color);
  1695. class_desc->add_text("...");
  1696. class_desc->pop(); // color
  1697. const DocData::ArgumentDoc &rest_argument = annotation.rest_argument;
  1698. class_desc->push_color(theme_cache.text_color);
  1699. class_desc->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
  1700. class_desc->pop(); // color
  1701. class_desc->push_color(theme_cache.symbol_color);
  1702. class_desc->add_text(colon_nbsp);
  1703. class_desc->pop(); // color
  1704. if (rest_argument.type.is_empty()) {
  1705. _add_type("Array");
  1706. } else {
  1707. _add_type(rest_argument.type, rest_argument.enumeration, rest_argument.is_bitfield);
  1708. }
  1709. }
  1710. class_desc->push_color(theme_cache.symbol_color);
  1711. class_desc->add_text(")");
  1712. class_desc->pop(); // color
  1713. }
  1714. if (!annotation.qualifiers.is_empty()) {
  1715. class_desc->push_color(theme_cache.qualifier_color);
  1716. _add_qualifiers_to_rt(annotation.qualifiers, class_desc);
  1717. class_desc->pop(); // color
  1718. }
  1719. _pop_code_font();
  1720. class_desc->add_newline();
  1721. // Annotation description.
  1722. class_desc->push_indent(1);
  1723. _push_normal_font();
  1724. class_desc->push_color(theme_cache.comment_color);
  1725. const String descr = HANDLE_DOC(annotation.description);
  1726. if (!descr.is_empty()) {
  1727. _add_text(descr);
  1728. } else {
  1729. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1730. class_desc->add_text(" ");
  1731. class_desc->push_color(theme_cache.comment_color);
  1732. if (cd.is_script_doc) {
  1733. class_desc->add_text(TTR("There is currently no description for this annotation."));
  1734. } else {
  1735. 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));
  1736. }
  1737. class_desc->pop(); // color
  1738. }
  1739. class_desc->pop(); // color
  1740. _pop_normal_font();
  1741. class_desc->pop(); // indent
  1742. class_desc->pop(); // indent
  1743. }
  1744. }
  1745. // Property descriptions
  1746. if (has_property_descriptions) {
  1747. class_desc->add_newline();
  1748. class_desc->add_newline();
  1749. class_desc->add_newline();
  1750. section_line.push_back(Pair<String, int>(TTR("Property Descriptions"), class_desc->get_paragraph_count() - 2));
  1751. _push_title_font();
  1752. class_desc->add_text(TTR("Property Descriptions"));
  1753. _pop_title_font();
  1754. for (const DocData::PropertyDoc &prop : cd.properties) {
  1755. if (prop.overridden) {
  1756. continue;
  1757. }
  1758. // Ignore undocumented private.
  1759. const bool is_documented = prop.is_deprecated || prop.is_experimental || !prop.description.strip_edges().is_empty();
  1760. if (!is_documented && prop.name.begins_with("_")) {
  1761. continue;
  1762. }
  1763. class_desc->add_newline();
  1764. class_desc->add_newline();
  1765. class_desc->add_newline();
  1766. property_line[prop.name] = class_desc->get_paragraph_count() - 2;
  1767. class_desc->push_table(2);
  1768. class_desc->set_table_column_expand(1, true);
  1769. class_desc->push_cell();
  1770. _push_code_font();
  1771. _add_bulletpoint();
  1772. _add_type(prop.type, prop.enumeration, prop.is_bitfield);
  1773. _pop_code_font();
  1774. class_desc->pop(); // cell
  1775. class_desc->push_cell();
  1776. _push_code_font();
  1777. class_desc->push_color(theme_cache.headline_color);
  1778. class_desc->add_text(prop.name);
  1779. class_desc->pop(); // color
  1780. if (!prop.default_value.is_empty()) {
  1781. class_desc->push_color(theme_cache.symbol_color);
  1782. class_desc->add_text(" [" + TTR("default:") + " ");
  1783. class_desc->pop(); // color
  1784. class_desc->push_color(theme_cache.value_color);
  1785. class_desc->add_text(_fix_constant(prop.default_value));
  1786. class_desc->pop(); // color
  1787. class_desc->push_color(theme_cache.symbol_color);
  1788. class_desc->add_text("]");
  1789. class_desc->pop(); // color
  1790. }
  1791. if (cd.is_script_doc && (!prop.setter.is_empty() || !prop.getter.is_empty())) {
  1792. class_desc->push_color(theme_cache.symbol_color);
  1793. class_desc->add_text(" [" + TTR("property:") + " ");
  1794. class_desc->pop(); // color
  1795. if (!prop.setter.is_empty()) {
  1796. class_desc->push_color(theme_cache.value_color);
  1797. class_desc->add_text("setter");
  1798. class_desc->pop(); // color
  1799. }
  1800. if (!prop.getter.is_empty()) {
  1801. if (!prop.setter.is_empty()) {
  1802. class_desc->push_color(theme_cache.symbol_color);
  1803. class_desc->add_text(", ");
  1804. class_desc->pop(); // color
  1805. }
  1806. class_desc->push_color(theme_cache.value_color);
  1807. class_desc->add_text("getter");
  1808. class_desc->pop(); // color
  1809. }
  1810. class_desc->push_color(theme_cache.symbol_color);
  1811. class_desc->add_text("]");
  1812. class_desc->pop(); // color
  1813. }
  1814. _pop_code_font();
  1815. class_desc->pop(); // cell
  1816. // Script doc doesn't have setter, getter.
  1817. if (!cd.is_script_doc) {
  1818. HashMap<String, DocData::MethodDoc> method_map;
  1819. for (int j = 0; j < methods.size(); j++) {
  1820. method_map[methods[j].name] = methods[j];
  1821. }
  1822. if (!prop.setter.is_empty()) {
  1823. class_desc->push_cell();
  1824. class_desc->pop(); // cell
  1825. class_desc->push_cell();
  1826. _push_code_font();
  1827. class_desc->push_color(theme_cache.text_color);
  1828. if (method_map[prop.setter].arguments.size() > 1) {
  1829. // Setters with additional arguments are exposed in the method list, so we link them here for quick access.
  1830. class_desc->push_meta("@method " + prop.setter);
  1831. class_desc->add_text(prop.setter + TTR("(value)"));
  1832. class_desc->pop(); // meta
  1833. } else {
  1834. class_desc->add_text(prop.setter + TTR("(value)"));
  1835. }
  1836. class_desc->pop(); // color
  1837. class_desc->push_color(theme_cache.comment_color);
  1838. class_desc->add_text(" setter");
  1839. class_desc->pop(); // color
  1840. _pop_code_font();
  1841. class_desc->pop(); // cell
  1842. method_line[prop.setter] = property_line[prop.name];
  1843. }
  1844. if (!prop.getter.is_empty()) {
  1845. class_desc->push_cell();
  1846. class_desc->pop(); // cell
  1847. class_desc->push_cell();
  1848. _push_code_font();
  1849. class_desc->push_color(theme_cache.text_color);
  1850. if (!method_map[prop.getter].arguments.is_empty()) {
  1851. // Getters with additional arguments are exposed in the method list, so we link them here for quick access.
  1852. class_desc->push_meta("@method " + prop.getter);
  1853. class_desc->add_text(prop.getter + "()");
  1854. class_desc->pop(); // meta
  1855. } else {
  1856. class_desc->add_text(prop.getter + "()");
  1857. }
  1858. class_desc->pop(); // color
  1859. class_desc->push_color(theme_cache.comment_color);
  1860. class_desc->add_text(" getter");
  1861. class_desc->pop(); // color
  1862. _pop_code_font();
  1863. class_desc->pop(); // cell
  1864. method_line[prop.getter] = property_line[prop.name];
  1865. }
  1866. }
  1867. class_desc->pop(); // table
  1868. class_desc->add_newline();
  1869. class_desc->add_newline();
  1870. class_desc->push_indent(1);
  1871. _push_normal_font();
  1872. class_desc->push_color(theme_cache.text_color);
  1873. bool has_prev_text = false;
  1874. if (prop.is_deprecated) {
  1875. has_prev_text = true;
  1876. DEPRECATED_DOC_MSG(HANDLE_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions."));
  1877. }
  1878. if (prop.is_experimental) {
  1879. if (has_prev_text) {
  1880. class_desc->add_newline();
  1881. class_desc->add_newline();
  1882. }
  1883. has_prev_text = true;
  1884. EXPERIMENTAL_DOC_MSG(HANDLE_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions."));
  1885. }
  1886. const String descr = HANDLE_DOC(prop.description);
  1887. if (!descr.is_empty()) {
  1888. if (has_prev_text) {
  1889. class_desc->add_newline();
  1890. class_desc->add_newline();
  1891. }
  1892. has_prev_text = true;
  1893. _add_text(descr);
  1894. } else if (!has_prev_text) {
  1895. class_desc->add_image(get_editor_theme_icon(SNAME("Error")));
  1896. class_desc->add_text(" ");
  1897. class_desc->push_color(theme_cache.comment_color);
  1898. if (cd.is_script_doc) {
  1899. class_desc->add_text(TTR("There is currently no description for this property."));
  1900. } else {
  1901. 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));
  1902. }
  1903. class_desc->pop(); // color
  1904. }
  1905. // Add copy note to built-in properties returning `Packed*Array`.
  1906. if (!cd.is_script_doc && packed_array_types.has(prop.type)) {
  1907. class_desc->add_newline();
  1908. class_desc->add_newline();
  1909. // See also `EditorHelpBit::parse_symbol()` and `doc/tools/make_rst.py`.
  1910. _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));
  1911. }
  1912. class_desc->pop(); // color
  1913. _pop_normal_font();
  1914. class_desc->pop(); // indent
  1915. }
  1916. }
  1917. // Constructor descriptions
  1918. if (!cd.constructors.is_empty()) {
  1919. _update_method_descriptions(cd, METHOD_TYPE_CONSTRUCTOR, cd.constructors);
  1920. }
  1921. // Method descriptions
  1922. if (!methods.is_empty()) {
  1923. _update_method_descriptions(cd, METHOD_TYPE_METHOD, methods);
  1924. }
  1925. // Operator descriptions
  1926. if (!cd.operators.is_empty()) {
  1927. _update_method_descriptions(cd, METHOD_TYPE_OPERATOR, cd.operators);
  1928. }
  1929. // Allow the document to be scrolled slightly below the end.
  1930. class_desc->add_newline();
  1931. class_desc->add_newline();
  1932. // Free the scroll.
  1933. scroll_locked = false;
  1934. #undef HANDLE_DOC
  1935. }
  1936. void EditorHelp::_request_help(const String &p_string) {
  1937. Error err = _goto_desc(p_string);
  1938. if (err == OK) {
  1939. EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
  1940. }
  1941. }
  1942. void EditorHelp::_help_callback(const String &p_topic) {
  1943. Vector<String> parts;
  1944. {
  1945. int from = 0;
  1946. int buffer_start = 0;
  1947. while (true) {
  1948. const int pos = p_topic.find_char(':', from);
  1949. if (pos < 0) {
  1950. parts.push_back(p_topic.substr(buffer_start));
  1951. break;
  1952. }
  1953. if (pos + 1 < p_topic.length() && p_topic[pos + 1] == ':') {
  1954. // `::` used in built-in scripts.
  1955. from = pos + 2;
  1956. } else {
  1957. parts.push_back(p_topic.substr(buffer_start, pos - buffer_start));
  1958. from = pos + 1;
  1959. buffer_start = from;
  1960. }
  1961. }
  1962. }
  1963. const String what = parts[0]; // `parts` is always non-empty.
  1964. const String clss = (parts.size() > 1) ? parts[1] : String();
  1965. const String name = (parts.size() > 2) ? parts[2] : String();
  1966. _request_help(clss); // First go to class.
  1967. int line = 0;
  1968. if (what == "class_desc") {
  1969. line = description_line;
  1970. } else if (what == "class_signal") {
  1971. if (signal_line.has(name)) {
  1972. line = signal_line[name];
  1973. }
  1974. } else if (what == "class_method" || what == "class_method_desc") {
  1975. if (method_line.has(name)) {
  1976. line = method_line[name];
  1977. }
  1978. } else if (what == "class_property") {
  1979. if (property_line.has(name)) {
  1980. line = property_line[name];
  1981. }
  1982. } else if (what == "class_enum") {
  1983. if (enum_line.has(name)) {
  1984. line = enum_line[name];
  1985. }
  1986. } else if (what == "class_theme_item") {
  1987. if (theme_property_line.has(name)) {
  1988. line = theme_property_line[name];
  1989. }
  1990. } else if (what == "class_constant") {
  1991. if (constant_line.has(name)) {
  1992. line = constant_line[name];
  1993. }
  1994. } else if (what == "class_annotation") {
  1995. if (annotation_line.has(name)) {
  1996. line = annotation_line[name];
  1997. }
  1998. } else if (what == "class_global") { // Deprecated.
  1999. if (constant_line.has(name)) {
  2000. line = constant_line[name];
  2001. } else if (method_line.has(name)) {
  2002. line = method_line[name];
  2003. } else {
  2004. HashMap<String, HashMap<String, int>>::Iterator iter = enum_values_line.begin();
  2005. while (true) {
  2006. if (iter->value.has(name)) {
  2007. line = iter->value[name];
  2008. break;
  2009. } else if (iter == enum_values_line.last()) {
  2010. break;
  2011. } else {
  2012. ++iter;
  2013. }
  2014. }
  2015. }
  2016. }
  2017. if (class_desc->is_finished()) {
  2018. class_desc->scroll_to_paragraph(line);
  2019. } else {
  2020. scroll_to = line;
  2021. }
  2022. }
  2023. static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) {
  2024. bool is_native = false;
  2025. {
  2026. const DocData::ClassDoc *E = EditorHelp::get_doc(p_class);
  2027. if (E && !E->is_script_doc) {
  2028. is_native = true;
  2029. }
  2030. }
  2031. const bool using_space_indent = int(EDITOR_GET("text_editor/behavior/indent/type")) == 1;
  2032. const int indent_size = MAX(1, int(EDITOR_GET("text_editor/behavior/indent/size")));
  2033. const Ref<Font> doc_font = p_owner_node->get_theme_font(SNAME("doc"), EditorStringName(EditorFonts));
  2034. const Ref<Font> doc_bold_font = p_owner_node->get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts));
  2035. const Ref<Font> doc_italic_font = p_owner_node->get_theme_font(SNAME("doc_italic"), EditorStringName(EditorFonts));
  2036. const Ref<Font> doc_code_font = p_owner_node->get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts));
  2037. const Ref<Font> doc_kbd_font = p_owner_node->get_theme_font(SNAME("doc_keyboard"), EditorStringName(EditorFonts));
  2038. const int doc_font_size = p_owner_node->get_theme_font_size(SNAME("doc_size"), EditorStringName(EditorFonts));
  2039. const int doc_code_font_size = p_owner_node->get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts));
  2040. const int doc_kbd_font_size = p_owner_node->get_theme_font_size(SNAME("doc_keyboard_size"), EditorStringName(EditorFonts));
  2041. const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp"));
  2042. const Color code_color = p_owner_node->get_theme_color(SNAME("code_color"), SNAME("EditorHelp"));
  2043. const Color kbd_color = p_owner_node->get_theme_color(SNAME("kbd_color"), SNAME("EditorHelp"));
  2044. const Color code_dark_color = Color(code_color, 0.8);
  2045. const Color link_color = p_owner_node->get_theme_color(SNAME("link_color"), SNAME("EditorHelp"));
  2046. const Color link_method_color = p_owner_node->get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
  2047. const Color link_property_color = link_color.lerp(p_owner_node->get_theme_color(SNAME("accent_color"), EditorStringName(Editor)), 0.25);
  2048. const Color link_annotation_color = link_color.lerp(p_owner_node->get_theme_color(SNAME("accent_color"), EditorStringName(Editor)), 0.5);
  2049. const Color code_bg_color = p_owner_node->get_theme_color(SNAME("code_bg_color"), SNAME("EditorHelp"));
  2050. const Color kbd_bg_color = p_owner_node->get_theme_color(SNAME("kbd_bg_color"), SNAME("EditorHelp"));
  2051. const Color param_bg_color = p_owner_node->get_theme_color(SNAME("param_bg_color"), SNAME("EditorHelp"));
  2052. String bbcode = p_bbcode.dedent().remove_chars("\r").strip_edges();
  2053. // Select the correct code examples.
  2054. switch ((int)EDITOR_GET("text_editor/help/class_reference_examples")) {
  2055. case 0: // GDScript
  2056. bbcode = bbcode.replace("[gdscript", "[codeblock lang=gdscript"); // Tag can have extra arguments.
  2057. bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
  2058. for (int pos = bbcode.find("[csharp"); pos != -1; pos = bbcode.find("[csharp")) {
  2059. int end_pos = bbcode.find("[/csharp]");
  2060. if (end_pos == -1) {
  2061. WARN_PRINT("Unclosed [csharp] block or parse fail in code (search for tag errors)");
  2062. break;
  2063. }
  2064. bbcode = bbcode.left(pos) + bbcode.substr(end_pos + 9); // 9 is length of "[/csharp]".
  2065. while (bbcode[pos] == '\n') {
  2066. bbcode = bbcode.left(pos) + bbcode.substr(pos + 1);
  2067. }
  2068. }
  2069. break;
  2070. case 1: // C#
  2071. bbcode = bbcode.replace("[csharp", "[codeblock lang=csharp"); // Tag can have extra arguments.
  2072. bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
  2073. for (int pos = bbcode.find("[gdscript"); pos != -1; pos = bbcode.find("[gdscript")) {
  2074. int end_pos = bbcode.find("[/gdscript]");
  2075. if (end_pos == -1) {
  2076. WARN_PRINT("Unclosed [gdscript] block or parse fail in code (search for tag errors)");
  2077. break;
  2078. }
  2079. bbcode = bbcode.left(pos) + bbcode.substr(end_pos + 11); // 11 is length of "[/gdscript]".
  2080. while (bbcode[pos] == '\n') {
  2081. bbcode = bbcode.left(pos) + bbcode.substr(pos + 1);
  2082. }
  2083. }
  2084. break;
  2085. case 2: // GDScript and C#
  2086. bbcode = bbcode.replace("[csharp", "[b]C#:[/b]\n[codeblock lang=csharp"); // Tag can have extra arguments.
  2087. bbcode = bbcode.replace("[gdscript", "[b]GDScript:[/b]\n[codeblock lang=gdscript"); // Tag can have extra arguments.
  2088. bbcode = bbcode.replace("[/csharp]", "[/codeblock]");
  2089. bbcode = bbcode.replace("[/gdscript]", "[/codeblock]");
  2090. break;
  2091. }
  2092. // Remove codeblocks (they would be printed otherwise).
  2093. bbcode = bbcode.replace("[codeblocks]\n", "");
  2094. bbcode = bbcode.replace("\n[/codeblocks]", "");
  2095. bbcode = bbcode.replace("[codeblocks]", "");
  2096. bbcode = bbcode.replace("[/codeblocks]", "");
  2097. // Remove `\n` here because `\n` is replaced by `\n\n` later.
  2098. // Will be compensated when parsing `[/codeblock]`.
  2099. bbcode = bbcode.replace("[/codeblock]\n", "[/codeblock]");
  2100. List<String> tag_stack;
  2101. int pos = 0;
  2102. while (pos < bbcode.length()) {
  2103. int brk_pos = bbcode.find_char('[', pos);
  2104. if (brk_pos < 0) {
  2105. brk_pos = bbcode.length();
  2106. }
  2107. if (brk_pos > pos) {
  2108. p_rt->add_text(bbcode.substr(pos, brk_pos - pos).replace("\n", "\n\n"));
  2109. }
  2110. if (brk_pos == bbcode.length()) {
  2111. break; // Nothing else to add.
  2112. }
  2113. int brk_end = bbcode.find_char(']', brk_pos + 1);
  2114. if (brk_end == -1) {
  2115. p_rt->add_text(bbcode.substr(brk_pos).replace("\n", "\n\n"));
  2116. break;
  2117. }
  2118. const String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
  2119. if (tag.begins_with("/")) {
  2120. bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1);
  2121. if (!tag_ok) {
  2122. p_rt->add_text("[");
  2123. pos = brk_pos + 1;
  2124. continue;
  2125. }
  2126. tag_stack.pop_front();
  2127. pos = brk_end + 1;
  2128. if (tag == "/img") {
  2129. // Nothing to do.
  2130. } else if (tag == "/url") {
  2131. p_rt->pop(); // meta
  2132. p_rt->pop(); // color
  2133. p_rt->add_text(nbsp);
  2134. p_rt->add_image(p_owner_node->get_editor_theme_icon(SNAME("ExternalLink")), 0, doc_font_size, link_color);
  2135. } else {
  2136. p_rt->pop();
  2137. }
  2138. } 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 ")) {
  2139. const int tag_end = tag.find_char(' ');
  2140. const String link_tag = tag.left(tag_end);
  2141. const String link_target = tag.substr(tag_end + 1).lstrip(" ");
  2142. Color target_color = link_color;
  2143. RichTextLabel::MetaUnderline underline_mode = RichTextLabel::META_UNDERLINE_ON_HOVER;
  2144. if (link_tag == "method" || link_tag == "constructor" || link_tag == "operator") {
  2145. target_color = link_method_color;
  2146. } else if (link_tag == "member" || link_tag == "signal" || link_tag == "theme_item") {
  2147. target_color = link_property_color;
  2148. } else if (link_tag == "annotation") {
  2149. target_color = link_annotation_color;
  2150. } else {
  2151. // Better visibility for constants, enums, etc.
  2152. underline_mode = RichTextLabel::META_UNDERLINE_ALWAYS;
  2153. }
  2154. // Use monospace font to make clickable references
  2155. // easier to distinguish from inline code and other text.
  2156. p_rt->push_font(doc_code_font);
  2157. p_rt->push_font_size(doc_code_font_size);
  2158. p_rt->push_color(target_color);
  2159. p_rt->push_meta("@" + link_tag + " " + link_target, underline_mode);
  2160. if (link_tag == "member" &&
  2161. ((!link_target.contains_char('.') && (p_class == "ProjectSettings" || p_class == "EditorSettings")) ||
  2162. link_target.begins_with("ProjectSettings.") || link_target.begins_with("EditorSettings."))) {
  2163. // Special formatting for both ProjectSettings and EditorSettings.
  2164. String prefix;
  2165. if (link_target.begins_with("EditorSettings.")) {
  2166. prefix = "(" + TTR("Editor") + ") ";
  2167. }
  2168. const String setting_name = link_target.trim_prefix("ProjectSettings.").trim_prefix("EditorSettings.");
  2169. PackedStringArray setting_sections;
  2170. for (const String &section : setting_name.split("/", false)) {
  2171. setting_sections.append(EditorPropertyNameProcessor::get_singleton()->process_name(section, EditorPropertyNameProcessor::get_settings_style()));
  2172. }
  2173. p_rt->push_bold();
  2174. p_rt->add_text(prefix + String(" > ").join(setting_sections));
  2175. p_rt->pop(); // bold
  2176. } else {
  2177. p_rt->add_text(link_target + (link_tag == "method" ? "()" : ""));
  2178. }
  2179. p_rt->pop(); // meta
  2180. p_rt->pop(); // color
  2181. p_rt->pop(); // font_size
  2182. p_rt->pop(); // font
  2183. pos = brk_end + 1;
  2184. } else if (tag.begins_with("param ")) {
  2185. const int tag_end = tag.find_char(' ');
  2186. const String param_name = tag.substr(tag_end + 1).lstrip(" ");
  2187. // Use monospace font with translucent background color to make code easier to distinguish from other text.
  2188. p_rt->push_font(doc_code_font);
  2189. p_rt->push_font_size(doc_code_font_size);
  2190. p_rt->push_bgcolor(param_bg_color);
  2191. p_rt->push_color(code_color);
  2192. p_rt->add_text(param_name);
  2193. p_rt->pop(); // color
  2194. p_rt->pop(); // bgcolor
  2195. p_rt->pop(); // font_size
  2196. p_rt->pop(); // font
  2197. pos = brk_end + 1;
  2198. } else if (tag == p_class) {
  2199. // Use a bold font when class reference tags are in their own page.
  2200. p_rt->push_font(doc_bold_font);
  2201. p_rt->add_text(tag);
  2202. p_rt->pop(); // font
  2203. pos = brk_end + 1;
  2204. } else if (EditorHelp::has_doc(tag)) {
  2205. // Use a monospace font for class reference tags such as [Node2D] or [SceneTree].
  2206. p_rt->push_font(doc_code_font);
  2207. p_rt->push_font_size(doc_code_font_size);
  2208. p_rt->push_color(type_color);
  2209. p_rt->push_meta("#" + tag, RichTextLabel::META_UNDERLINE_ON_HOVER);
  2210. p_rt->add_text(tag);
  2211. p_rt->pop(); // meta
  2212. p_rt->pop(); // color
  2213. p_rt->pop(); // font_size
  2214. p_rt->pop(); // font
  2215. pos = brk_end + 1;
  2216. } else if (tag == "b") {
  2217. // Use bold font.
  2218. p_rt->push_font(doc_bold_font);
  2219. pos = brk_end + 1;
  2220. tag_stack.push_front(tag);
  2221. } else if (tag == "i") {
  2222. // Use italics font.
  2223. p_rt->push_font(doc_italic_font);
  2224. pos = brk_end + 1;
  2225. tag_stack.push_front(tag);
  2226. } else if (tag == "code" || tag.begins_with("code ")) {
  2227. int end_pos = bbcode.find("[/code]", brk_end + 1);
  2228. if (end_pos < 0) {
  2229. end_pos = bbcode.length();
  2230. }
  2231. // Use monospace font with darkened background color to make code easier to distinguish from other text.
  2232. p_rt->push_font(doc_code_font);
  2233. p_rt->push_font_size(doc_code_font_size);
  2234. p_rt->push_bgcolor(code_bg_color);
  2235. p_rt->push_color(code_color.lerp(p_owner_node->get_theme_color(SNAME("error_color"), EditorStringName(Editor)), 0.6));
  2236. p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)));
  2237. p_rt->pop(); // color
  2238. p_rt->pop(); // bgcolor
  2239. p_rt->pop(); // font_size
  2240. p_rt->pop(); // font
  2241. pos = end_pos + 7; // `len("[/code]")`.
  2242. } else if (tag == "codeblock" || tag.begins_with("codeblock ")) {
  2243. int end_pos = bbcode.find("[/codeblock]", brk_end + 1);
  2244. if (end_pos < 0) {
  2245. end_pos = bbcode.length();
  2246. }
  2247. const String codeblock_text = bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)).strip_edges();
  2248. String codeblock_copy_text = codeblock_text;
  2249. if (using_space_indent) {
  2250. // Replace the code block's tab indentation with spaces.
  2251. StringBuilder builder;
  2252. PackedStringArray text_lines = codeblock_copy_text.split("\n");
  2253. for (const String &line : text_lines) {
  2254. const String stripped_line = line.dedent();
  2255. const int tab_count = line.length() - stripped_line.length();
  2256. if (builder.num_strings_appended() > 0) {
  2257. builder.append("\n");
  2258. }
  2259. if (tab_count > 0) {
  2260. builder.append(String(" ").repeat(tab_count * indent_size) + stripped_line);
  2261. } else {
  2262. builder.append(line);
  2263. }
  2264. }
  2265. codeblock_copy_text = builder.as_string();
  2266. }
  2267. String lang;
  2268. const PackedStringArray args = tag.trim_prefix("codeblock").split(" ", false);
  2269. for (int i = args.size() - 1; i >= 0; i--) {
  2270. if (args[i].begins_with("lang=")) {
  2271. lang = args[i].trim_prefix("lang=");
  2272. break;
  2273. }
  2274. }
  2275. // Use monospace font with darkened background color to make code easier to distinguish from other text.
  2276. // Use a single-column table with cell row background color instead of `[bgcolor]`.
  2277. // This makes the background color highlight cover the entire block, rather than individual lines.
  2278. p_rt->push_font(doc_code_font);
  2279. p_rt->push_font_size(doc_code_font_size);
  2280. p_rt->push_table(2);
  2281. p_rt->push_cell();
  2282. p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99));
  2283. p_rt->set_cell_padding(Rect2(10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE));
  2284. p_rt->push_color(code_dark_color);
  2285. bool codeblock_printed = false;
  2286. #ifdef MODULE_GDSCRIPT_ENABLED
  2287. if (!codeblock_printed && (lang.is_empty() || lang == "gdscript")) {
  2288. EditorHelpHighlighter::get_singleton()->highlight(p_rt, EditorHelpHighlighter::LANGUAGE_GDSCRIPT, codeblock_text, is_native);
  2289. codeblock_printed = true;
  2290. }
  2291. #endif
  2292. #ifdef MODULE_MONO_ENABLED
  2293. if (!codeblock_printed && lang == "csharp") {
  2294. EditorHelpHighlighter::get_singleton()->highlight(p_rt, EditorHelpHighlighter::LANGUAGE_CSHARP, codeblock_text, is_native);
  2295. codeblock_printed = true;
  2296. }
  2297. #endif
  2298. if (!codeblock_printed) {
  2299. p_rt->add_text(codeblock_text);
  2300. codeblock_printed = true;
  2301. }
  2302. p_rt->pop(); // color
  2303. p_rt->pop(); // cell
  2304. // Copy codeblock button.
  2305. p_rt->push_cell();
  2306. p_rt->set_cell_row_background_color(code_bg_color, Color(code_bg_color, 0.99));
  2307. p_rt->set_cell_padding(Rect2(0, 10 * EDSCALE, 0, 10 * EDSCALE));
  2308. p_rt->set_cell_size_override(Vector2(1, 1), Vector2(10, 10) * EDSCALE);
  2309. p_rt->push_meta("^" + codeblock_copy_text, RichTextLabel::META_UNDERLINE_ON_HOVER);
  2310. 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."));
  2311. p_rt->pop(); // meta
  2312. p_rt->pop(); // cell
  2313. p_rt->pop(); // table
  2314. p_rt->pop(); // font_size
  2315. p_rt->pop(); // font
  2316. pos = end_pos + 12; // `len("[/codeblock]")`.
  2317. // Compensate for `\n` removed before the loop.
  2318. if (pos < bbcode.length()) {
  2319. p_rt->add_newline();
  2320. }
  2321. } else if (tag == "kbd") {
  2322. int end_pos = bbcode.find("[/kbd]", brk_end + 1);
  2323. if (end_pos < 0) {
  2324. end_pos = bbcode.length();
  2325. }
  2326. // Use keyboard font with custom color and background color.
  2327. p_rt->push_font(doc_kbd_font);
  2328. p_rt->push_font_size(doc_kbd_font_size);
  2329. p_rt->push_bgcolor(kbd_bg_color);
  2330. p_rt->push_color(kbd_color);
  2331. p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)));
  2332. p_rt->pop(); // color
  2333. p_rt->pop(); // bgcolor
  2334. p_rt->pop(); // font_size
  2335. p_rt->pop(); // font
  2336. pos = end_pos + 6; // `len("[/kbd]")`.
  2337. } else if (tag == "center") {
  2338. // Align to center.
  2339. p_rt->push_paragraph(HORIZONTAL_ALIGNMENT_CENTER, Control::TEXT_DIRECTION_AUTO, "");
  2340. pos = brk_end + 1;
  2341. tag_stack.push_front(tag);
  2342. } else if (tag == "br") {
  2343. // Force a line break.
  2344. p_rt->add_newline();
  2345. pos = brk_end + 1;
  2346. } else if (tag == "u") {
  2347. // Use underline.
  2348. p_rt->push_underline();
  2349. pos = brk_end + 1;
  2350. tag_stack.push_front(tag);
  2351. } else if (tag == "s") {
  2352. // Use strikethrough.
  2353. p_rt->push_strikethrough();
  2354. pos = brk_end + 1;
  2355. tag_stack.push_front(tag);
  2356. } else if (tag == "lb") {
  2357. p_rt->add_text("[");
  2358. pos = brk_end + 1;
  2359. } else if (tag == "rb") {
  2360. p_rt->add_text("]");
  2361. pos = brk_end + 1;
  2362. } else if (tag == "url" || tag.begins_with("url=")) {
  2363. String url;
  2364. if (tag.begins_with("url=")) {
  2365. url = tag.substr(4);
  2366. } else {
  2367. int end = bbcode.find_char('[', brk_end);
  2368. if (end == -1) {
  2369. end = bbcode.length();
  2370. }
  2371. url = bbcode.substr(brk_end + 1, end - brk_end - 1);
  2372. }
  2373. p_rt->push_color(link_color);
  2374. p_rt->push_meta(url, RichTextLabel::META_UNDERLINE_ON_HOVER, url + "\n\n" + TTR("Click to open in browser."));
  2375. pos = brk_end + 1;
  2376. tag_stack.push_front("url");
  2377. } else if (tag.begins_with("img")) {
  2378. int width = 0;
  2379. int height = 0;
  2380. bool size_in_percent = false;
  2381. if (tag.length() > 4) {
  2382. Vector<String> subtags = tag.substr(4).split(" ");
  2383. HashMap<String, String> bbcode_options;
  2384. for (int i = 0; i < subtags.size(); i++) {
  2385. const String &expr = subtags[i];
  2386. int value_pos = expr.find_char('=');
  2387. if (value_pos > -1) {
  2388. bbcode_options[expr.left(value_pos)] = expr.substr(value_pos + 1).unquote();
  2389. }
  2390. }
  2391. HashMap<String, String>::Iterator width_option = bbcode_options.find("width");
  2392. if (width_option) {
  2393. width = width_option->value.to_int();
  2394. if (width_option->value.ends_with("%")) {
  2395. size_in_percent = true;
  2396. }
  2397. }
  2398. HashMap<String, String>::Iterator height_option = bbcode_options.find("height");
  2399. if (height_option) {
  2400. height = height_option->value.to_int();
  2401. if (height_option->value.ends_with("%")) {
  2402. size_in_percent = true;
  2403. }
  2404. }
  2405. }
  2406. int end = bbcode.find_char('[', brk_end);
  2407. if (end == -1) {
  2408. end = bbcode.length();
  2409. }
  2410. String image_path = bbcode.substr(brk_end + 1, end - brk_end - 1);
  2411. 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);
  2412. pos = end;
  2413. tag_stack.push_front("img");
  2414. } else if (tag.begins_with("color=")) {
  2415. String col = tag.substr(6);
  2416. Color color = Color::from_string(col, Color());
  2417. p_rt->push_color(color);
  2418. pos = brk_end + 1;
  2419. tag_stack.push_front("color");
  2420. } else if (tag.begins_with("font=")) {
  2421. String font_path = tag.substr(5);
  2422. Ref<Font> font = ResourceLoader::load(font_path, "Font");
  2423. if (font.is_valid()) {
  2424. p_rt->push_font(font);
  2425. } else {
  2426. p_rt->push_font(doc_font);
  2427. }
  2428. pos = brk_end + 1;
  2429. tag_stack.push_front("font");
  2430. } else {
  2431. p_rt->add_text("["); // Ignore.
  2432. pos = brk_pos + 1;
  2433. }
  2434. }
  2435. // Close unclosed tags.
  2436. for (const String &tag : tag_stack) {
  2437. if (tag != "img") {
  2438. p_rt->pop();
  2439. }
  2440. }
  2441. }
  2442. void EditorHelp::_add_text(const String &p_bbcode) {
  2443. _add_text_to_rt(p_bbcode, class_desc, this, edited_class);
  2444. }
  2445. void EditorHelp::_wait_for_thread(Thread &p_thread) {
  2446. if (p_thread.is_started()) {
  2447. p_thread.wait_to_finish();
  2448. }
  2449. }
  2450. void EditorHelp::_compute_doc_version_hash() {
  2451. uint32_t version_hash = Engine::get_singleton()->get_version_info().hash();
  2452. 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);
  2453. }
  2454. String EditorHelp::get_cache_full_path() {
  2455. return EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("editor_doc_cache-%d.%d.res", GODOT_VERSION_MAJOR, GODOT_VERSION_MINOR));
  2456. }
  2457. String EditorHelp::get_script_doc_cache_full_path() {
  2458. return EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_script_doc_cache.res");
  2459. }
  2460. DocTools *EditorHelp::get_doc_data() {
  2461. _wait_for_thread();
  2462. return doc;
  2463. }
  2464. bool EditorHelp::has_doc(const String &p_class_name) {
  2465. return get_doc(p_class_name) != nullptr;
  2466. }
  2467. DocData::ClassDoc *EditorHelp::get_doc(const String &p_class_name) {
  2468. return get_doc_data()->class_list.getptr(p_class_name);
  2469. }
  2470. void EditorHelp::add_doc(const DocData::ClassDoc &p_class_doc) {
  2471. if (!_script_docs_loaded.is_set()) {
  2472. _docs_to_add.push_back(p_class_doc);
  2473. return;
  2474. }
  2475. get_doc_data()->add_doc(p_class_doc);
  2476. }
  2477. void EditorHelp::remove_doc(const String &p_class_name) {
  2478. if (!_script_docs_loaded.is_set()) {
  2479. _docs_to_remove.push_back(p_class_name);
  2480. return;
  2481. }
  2482. DocTools *dt = get_doc_data();
  2483. if (dt->has_doc(p_class_name)) {
  2484. dt->remove_doc(p_class_name);
  2485. }
  2486. }
  2487. void EditorHelp::remove_script_doc_by_path(const String &p_path) {
  2488. if (!_script_docs_loaded.is_set()) {
  2489. _docs_to_remove_by_path.push_back(p_path);
  2490. return;
  2491. }
  2492. get_doc_data()->remove_script_doc_by_path(p_path);
  2493. }
  2494. void EditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) {
  2495. if (!ext_doc) {
  2496. ext_doc = memnew(DocTools);
  2497. }
  2498. ext_doc->load_xml(p_buffer, p_size);
  2499. if (doc) {
  2500. doc->load_xml(p_buffer, p_size);
  2501. }
  2502. }
  2503. void EditorHelp::remove_class(const String &p_class) {
  2504. if (ext_doc && ext_doc->has_doc(p_class)) {
  2505. ext_doc->remove_doc(p_class);
  2506. }
  2507. if (doc && doc->has_doc(p_class)) {
  2508. remove_doc(p_class);
  2509. }
  2510. }
  2511. void EditorHelp::_load_doc_thread(void *p_udata) {
  2512. bool use_script_cache = (bool)p_udata;
  2513. Ref<Resource> cache_res = ResourceLoader::load(get_cache_full_path());
  2514. if (cache_res.is_valid() && cache_res->get_meta("version_hash", "") == doc_version_hash) {
  2515. Array classes = cache_res->get_meta("classes", Array());
  2516. for (int i = 0; i < classes.size(); i++) {
  2517. doc->add_doc(DocData::ClassDoc::from_dict(classes[i]));
  2518. }
  2519. if (use_script_cache) {
  2520. callable_mp_static(&EditorHelp::load_script_doc_cache).call_deferred();
  2521. }
  2522. // Extensions' docs are not cached. Generate them now (on the main thread).
  2523. callable_mp_static(&EditorHelp::_gen_extensions_docs).call_deferred();
  2524. } else {
  2525. // We have to go back to the main thread to start from scratch, bypassing any possibly existing cache.
  2526. callable_mp_static(&EditorHelp::generate_doc).call_deferred(false, use_script_cache);
  2527. }
  2528. OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
  2529. }
  2530. void EditorHelp::_gen_doc_thread(void *p_udata) {
  2531. DocTools compdoc;
  2532. compdoc.load_compressed(_doc_data_compressed, _doc_data_compressed_size, _doc_data_uncompressed_size);
  2533. doc->merge_from(compdoc); // Ensure all is up to date.
  2534. Ref<Resource> cache_res;
  2535. cache_res.instantiate();
  2536. cache_res->set_meta("version_hash", doc_version_hash);
  2537. Array classes;
  2538. for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {
  2539. if (ClassDB::class_exists(E.value.name)) {
  2540. ClassDB::APIType api = ClassDB::get_api_type(E.value.name);
  2541. if (api == ClassDB::API_EXTENSION || api == ClassDB::API_EDITOR_EXTENSION) {
  2542. continue;
  2543. }
  2544. }
  2545. classes.push_back(DocData::ClassDoc::to_dict(E.value));
  2546. }
  2547. cache_res->set_meta("classes", classes);
  2548. Error err = ResourceSaver::save(cache_res, get_cache_full_path(), ResourceSaver::FLAG_COMPRESS);
  2549. if (err) {
  2550. ERR_PRINT("Cannot save editor help cache (" + get_cache_full_path() + ").");
  2551. }
  2552. // Load script docs after native ones are cached so native cache doesn't contain script docs.
  2553. bool use_script_cache = (bool)p_udata;
  2554. if (use_script_cache) {
  2555. callable_mp_static(&EditorHelp::load_script_doc_cache).call_deferred();
  2556. }
  2557. OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
  2558. }
  2559. void EditorHelp::_gen_extensions_docs() {
  2560. doc->generate((DocTools::GENERATE_FLAG_SKIP_BASIC_TYPES | DocTools::GENERATE_FLAG_EXTENSION_CLASSES_ONLY));
  2561. // Append extra doc data, as it gets overridden by the generation step.
  2562. if (ext_doc) {
  2563. doc->merge_from(*ext_doc);
  2564. }
  2565. }
  2566. static void _load_script_doc_cache(bool p_changes) {
  2567. EditorHelp::load_script_doc_cache();
  2568. }
  2569. void EditorHelp::load_script_doc_cache() {
  2570. if (!ProjectSettings::get_singleton()->is_project_loaded()) {
  2571. print_verbose("Skipping loading script doc cache since no project is open.");
  2572. return;
  2573. }
  2574. if (EditorNode::is_cmdline_mode()) {
  2575. return;
  2576. }
  2577. _wait_for_thread();
  2578. if (!ResourceLoader::exists(get_script_doc_cache_full_path())) {
  2579. print_verbose("Script documentation cache not found. Regenerating it may take a while for projects with many scripts.");
  2580. regenerate_script_doc_cache();
  2581. return;
  2582. }
  2583. if (EditorFileSystem::get_singleton()->is_scanning()) {
  2584. // This is assuming EditorFileSystem is performing first scan. We must wait until it is done.
  2585. EditorFileSystem::get_singleton()->connect(SNAME("sources_changed"), callable_mp_static(_load_script_doc_cache), CONNECT_ONE_SHOT);
  2586. return;
  2587. }
  2588. worker_thread.start(_load_script_doc_cache_thread, nullptr);
  2589. }
  2590. void EditorHelp::_process_postponed_docs() {
  2591. for (const String &class_name : _docs_to_remove) {
  2592. doc->remove_doc(class_name);
  2593. }
  2594. for (const String &path : _docs_to_remove_by_path) {
  2595. doc->remove_script_doc_by_path(path);
  2596. }
  2597. for (const DocData::ClassDoc &cd : _docs_to_add) {
  2598. doc->add_doc(cd);
  2599. }
  2600. _docs_to_add.clear();
  2601. _docs_to_remove.clear();
  2602. _docs_to_remove_by_path.clear();
  2603. }
  2604. void EditorHelp::_load_script_doc_cache_thread(void *p_udata) {
  2605. ERR_FAIL_COND_MSG(!ProjectSettings::get_singleton()->is_project_loaded(), "Error: cannot load script doc cache without a project.");
  2606. ERR_FAIL_COND_MSG(!ResourceLoader::exists(get_script_doc_cache_full_path()), "Error: cannot load script doc cache from inexistent file.");
  2607. Ref<Resource> script_doc_cache_res = ResourceLoader::load(get_script_doc_cache_full_path(), "", ResourceFormatLoader::CACHE_MODE_IGNORE);
  2608. if (script_doc_cache_res.is_null()) {
  2609. print_verbose("Script doc cache is corrupted. Regenerating it instead.");
  2610. _delete_script_doc_cache();
  2611. callable_mp_static(EditorHelp::regenerate_script_doc_cache).call_deferred();
  2612. return;
  2613. }
  2614. Array classes = script_doc_cache_res->get_meta("classes", Array());
  2615. for (const Dictionary dict : classes) {
  2616. doc->add_doc(DocData::ClassDoc::from_dict(dict));
  2617. }
  2618. // Protect from race condition in other threads reading / this thread writing to _docs_to_add/remove/etc.
  2619. _script_docs_loaded.set();
  2620. // Deal with docs likely added from EditorFileSystem's scans while the cache was loading in EditorHelp::worker_thread.
  2621. _process_postponed_docs();
  2622. // Always delete the doc cache after successful load since most uses of editor will change a script, invalidating cache.
  2623. _delete_script_doc_cache();
  2624. }
  2625. // Helper method to deal with "sources_changed" signal having a parameter.
  2626. static void _regenerate_script_doc_cache(bool p_changes) {
  2627. EditorHelp::regenerate_script_doc_cache();
  2628. }
  2629. void EditorHelp::regenerate_script_doc_cache() {
  2630. if (EditorFileSystem::get_singleton()->is_scanning()) {
  2631. // Wait until EditorFileSystem scanning is complete to use updated filesystem structure.
  2632. EditorFileSystem::get_singleton()->connect(SNAME("sources_changed"), callable_mp_static(_regenerate_script_doc_cache), CONNECT_ONE_SHOT);
  2633. return;
  2634. }
  2635. _wait_for_thread(worker_thread);
  2636. _wait_for_thread(loader_thread);
  2637. loader_thread.start(_regen_script_doc_thread, EditorFileSystem::get_singleton()->get_filesystem());
  2638. }
  2639. // Runs on worker_thread since it writes to DocData.
  2640. void EditorHelp::_finish_regen_script_doc_thread(void *p_udata) {
  2641. loader_thread.wait_to_finish();
  2642. _process_postponed_docs();
  2643. _script_docs_loaded.set();
  2644. OS::get_singleton()->benchmark_end_measure("EditorHelp", "Generate Script Documentation");
  2645. }
  2646. // Runs on loader_thread since _reload_scripts_documentation calls ResourceLoader::load().
  2647. // Avoids deadlocks of worker_thread needing main thread for load task dispatching, but main thread waiting on worker_thread.
  2648. void EditorHelp::_regen_script_doc_thread(void *p_udata) {
  2649. OS::get_singleton()->benchmark_begin_measure("EditorHelp", "Generate Script Documentation");
  2650. EditorFileSystemDirectory *dir = static_cast<EditorFileSystemDirectory *>(p_udata);
  2651. _script_docs_loaded.set_to(false);
  2652. // Ignore changes from filesystem scan since script docs will be now.
  2653. _docs_to_add.clear();
  2654. _docs_to_remove.clear();
  2655. _docs_to_remove_by_path.clear();
  2656. _reload_scripts_documentation(dir);
  2657. // All ResourceLoader::load() calls are done, so we can no longer deadlock with main thread.
  2658. // Switch to back to worker_thread from loader_thread to resynchronize access to DocData.
  2659. worker_thread.start(_finish_regen_script_doc_thread, nullptr);
  2660. }
  2661. void EditorHelp::_reload_scripts_documentation(EditorFileSystemDirectory *p_dir) {
  2662. // Recursively force compile all scripts, which should generate their documentation.
  2663. for (int i = 0; i < p_dir->get_subdir_count(); i++) {
  2664. _reload_scripts_documentation(p_dir->get_subdir(i));
  2665. }
  2666. for (int i = 0; i < p_dir->get_file_count(); i++) {
  2667. if (ClassDB::is_parent_class(p_dir->get_file_type(i), SNAME("Script"))) {
  2668. Ref<Script> scr = ResourceLoader::load(p_dir->get_file_path(i));
  2669. if (scr.is_valid()) {
  2670. for (const DocData::ClassDoc &cd : scr->get_documentation()) {
  2671. _docs_to_add.push_back(cd);
  2672. }
  2673. }
  2674. }
  2675. }
  2676. }
  2677. void EditorHelp::_delete_script_doc_cache() {
  2678. if (FileAccess::exists(get_script_doc_cache_full_path())) {
  2679. DirAccess::remove_file_or_error(ProjectSettings::get_singleton()->globalize_path(get_script_doc_cache_full_path()));
  2680. }
  2681. }
  2682. void EditorHelp::save_script_doc_cache() {
  2683. if (!_script_docs_loaded.is_set()) {
  2684. print_verbose("Script docs haven't been properly loaded or regenerated, so don't save them to disk.");
  2685. return;
  2686. }
  2687. Ref<Resource> cache_res;
  2688. cache_res.instantiate();
  2689. Array classes;
  2690. for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) {
  2691. if (E.value.is_script_doc) {
  2692. classes.push_back(DocData::ClassDoc::to_dict(E.value));
  2693. }
  2694. }
  2695. cache_res->set_meta("classes", classes);
  2696. Error err = ResourceSaver::save(cache_res, get_script_doc_cache_full_path(), ResourceSaver::FLAG_COMPRESS);
  2697. ERR_FAIL_COND_MSG(err != OK, vformat("Cannot save script documentation cache in %s.", get_script_doc_cache_full_path()));
  2698. }
  2699. void EditorHelp::generate_doc(bool p_use_cache, bool p_use_script_cache) {
  2700. doc_generation_count++;
  2701. OS::get_singleton()->benchmark_begin_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));
  2702. // In case not the first attempt.
  2703. _wait_for_thread();
  2704. if (!doc) {
  2705. doc = memnew(DocTools);
  2706. }
  2707. if (doc_version_hash.is_empty()) {
  2708. _compute_doc_version_hash();
  2709. }
  2710. if (p_use_cache && FileAccess::exists(get_cache_full_path())) {
  2711. worker_thread.start(_load_doc_thread, (void *)p_use_script_cache);
  2712. } else {
  2713. print_verbose("Regenerating editor help cache");
  2714. doc->generate();
  2715. worker_thread.start(_gen_doc_thread, (void *)p_use_script_cache);
  2716. }
  2717. }
  2718. void EditorHelp::_toggle_files_pressed() {
  2719. ScriptEditor::get_singleton()->toggle_files_panel();
  2720. update_toggle_files_button();
  2721. }
  2722. void EditorHelp::_notification(int p_what) {
  2723. switch (p_what) {
  2724. case NOTIFICATION_POSTINITIALIZE: {
  2725. // Requires theme to be up to date.
  2726. _class_desc_resized(false);
  2727. } break;
  2728. case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
  2729. bool need_update = false;
  2730. if (EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/help")) {
  2731. need_update = true;
  2732. }
  2733. #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED)
  2734. if (!need_update && EditorSettings::get_singleton()->check_changed_settings_in_group("text_editor/theme/highlighting")) {
  2735. need_update = true;
  2736. }
  2737. #endif
  2738. if (!need_update) {
  2739. break;
  2740. }
  2741. [[fallthrough]];
  2742. }
  2743. case NOTIFICATION_READY: {
  2744. _wait_for_thread();
  2745. _update_doc();
  2746. } break;
  2747. case NOTIFICATION_THEME_CHANGED: {
  2748. if (is_inside_tree()) {
  2749. if (is_visible_in_tree()) {
  2750. _update_doc();
  2751. } else {
  2752. update_pending = true;
  2753. }
  2754. _class_desc_resized(true);
  2755. }
  2756. update_toggle_files_button();
  2757. } break;
  2758. case NOTIFICATION_VISIBILITY_CHANGED: {
  2759. if (update_pending && is_visible_in_tree()) {
  2760. _update_doc();
  2761. }
  2762. update_toggle_files_button();
  2763. } break;
  2764. case NOTIFICATION_TRANSLATION_CHANGED: {
  2765. if (!is_ready()) {
  2766. break;
  2767. }
  2768. if (is_visible_in_tree()) {
  2769. _update_doc();
  2770. } else {
  2771. update_pending = true;
  2772. }
  2773. [[fallthrough]];
  2774. }
  2775. case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
  2776. update_toggle_files_button();
  2777. } break;
  2778. }
  2779. }
  2780. void EditorHelp::go_to_help(const String &p_help) {
  2781. _wait_for_thread();
  2782. _help_callback(p_help);
  2783. }
  2784. void EditorHelp::go_to_class(const String &p_class) {
  2785. _wait_for_thread();
  2786. _goto_desc(p_class);
  2787. }
  2788. void EditorHelp::update_doc() {
  2789. _wait_for_thread();
  2790. ERR_FAIL_COND(!doc->class_list.has(edited_class));
  2791. ERR_FAIL_COND(!doc->class_list[edited_class].is_script_doc);
  2792. _update_doc();
  2793. }
  2794. void EditorHelp::cleanup_doc() {
  2795. _wait_for_thread();
  2796. memdelete(doc);
  2797. doc = nullptr;
  2798. }
  2799. Vector<Pair<String, int>> EditorHelp::get_sections() {
  2800. _wait_for_thread();
  2801. Vector<Pair<String, int>> sections;
  2802. for (int i = 0; i < section_line.size(); i++) {
  2803. sections.push_back(Pair<String, int>(section_line[i].first, i));
  2804. }
  2805. return sections;
  2806. }
  2807. void EditorHelp::scroll_to_section(int p_section_index) {
  2808. _wait_for_thread();
  2809. int line = section_line[p_section_index].second;
  2810. if (class_desc->is_finished()) {
  2811. class_desc->scroll_to_paragraph(line);
  2812. } else {
  2813. scroll_to = line;
  2814. }
  2815. }
  2816. void EditorHelp::popup_search() {
  2817. _wait_for_thread();
  2818. find_bar->popup_search();
  2819. }
  2820. String EditorHelp::get_class() {
  2821. return edited_class;
  2822. }
  2823. void EditorHelp::search_again(bool p_search_previous) {
  2824. _search(p_search_previous);
  2825. }
  2826. int EditorHelp::get_scroll() const {
  2827. return class_desc->get_v_scroll_bar()->get_value();
  2828. }
  2829. void EditorHelp::set_scroll(int p_scroll) {
  2830. class_desc->get_v_scroll_bar()->set_value(p_scroll);
  2831. }
  2832. void EditorHelp::update_toggle_files_button() {
  2833. if (is_layout_rtl()) {
  2834. toggle_files_button->set_button_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_files_panel_toggled() ? SNAME("Forward") : SNAME("Back")));
  2835. } else {
  2836. toggle_files_button->set_button_icon(get_editor_theme_icon(ScriptEditor::get_singleton()->is_files_panel_toggled() ? SNAME("Back") : SNAME("Forward")));
  2837. }
  2838. toggle_files_button->set_tooltip_text(vformat("%s (%s)", TTR("Toggle Files Panel"), ED_GET_SHORTCUT("script_editor/toggle_files_panel")->get_as_text()));
  2839. }
  2840. void EditorHelp::_bind_methods() {
  2841. ClassDB::bind_method("_class_list_select", &EditorHelp::_class_list_select);
  2842. ClassDB::bind_method("_request_help", &EditorHelp::_request_help);
  2843. ClassDB::bind_method("_search", &EditorHelp::_search);
  2844. ClassDB::bind_method("_help_callback", &EditorHelp::_help_callback);
  2845. ADD_SIGNAL(MethodInfo("go_to_help"));
  2846. ADD_SIGNAL(MethodInfo("request_save_history"));
  2847. }
  2848. void EditorHelp::init_gdext_pointers() {
  2849. GDExtensionEditorHelp::editor_help_load_xml_buffer = &EditorHelp::load_xml_buffer;
  2850. GDExtensionEditorHelp::editor_help_remove_class = &EditorHelp::remove_class;
  2851. }
  2852. EditorHelp::EditorHelp() {
  2853. set_custom_minimum_size(Size2(150 * EDSCALE, 0));
  2854. class_desc = memnew(RichTextLabel);
  2855. class_desc->set_tab_size(8);
  2856. add_child(class_desc);
  2857. class_desc->set_threaded(true);
  2858. class_desc->set_v_size_flags(SIZE_EXPAND_FILL);
  2859. class_desc->connect(SceneStringName(finished), callable_mp(this, &EditorHelp::_class_desc_finished));
  2860. class_desc->connect("meta_clicked", callable_mp(this, &EditorHelp::_class_desc_select));
  2861. class_desc->connect(SceneStringName(gui_input), callable_mp(this, &EditorHelp::_class_desc_input));
  2862. class_desc->connect(SceneStringName(resized), callable_mp(this, &EditorHelp::_class_desc_resized).bind(false));
  2863. // Added second so it opens at the bottom so it won't offset the entire widget.
  2864. find_bar = memnew(FindBar);
  2865. add_child(find_bar);
  2866. find_bar->hide();
  2867. find_bar->set_rich_text_label(class_desc);
  2868. status_bar = memnew(HBoxContainer);
  2869. add_child(status_bar);
  2870. status_bar->set_h_size_flags(SIZE_EXPAND_FILL);
  2871. status_bar->set_custom_minimum_size(Size2(0, 24 * EDSCALE));
  2872. toggle_files_button = memnew(Button);
  2873. toggle_files_button->set_theme_type_variation(SceneStringName(FlatButton));
  2874. toggle_files_button->set_accessibility_name(TTRC("Scripts"));
  2875. toggle_files_button->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  2876. toggle_files_button->connect(SceneStringName(pressed), callable_mp(this, &EditorHelp::_toggle_files_pressed));
  2877. status_bar->add_child(toggle_files_button);
  2878. class_desc->set_selection_enabled(true);
  2879. class_desc->set_context_menu_enabled(true);
  2880. class_desc->set_selection_modifier(callable_mp_static(_replace_nbsp_with_space));
  2881. class_desc->hide();
  2882. }
  2883. /// EditorHelpBit ///
  2884. #define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges())
  2885. EditorHelpBit::HelpData EditorHelpBit::_get_class_help_data(const StringName &p_class_name) {
  2886. if (doc_class_cache.has(p_class_name)) {
  2887. return doc_class_cache[p_class_name];
  2888. }
  2889. HelpData result;
  2890. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  2891. if (class_doc) {
  2892. // Non-native class shouldn't be cached, nor translated.
  2893. const bool is_native = !class_doc->is_script_doc;
  2894. const String brief_description = HANDLE_DOC(class_doc->brief_description);
  2895. const String long_description = HANDLE_DOC(class_doc->description);
  2896. if (!brief_description.is_empty()) {
  2897. result.description += "[b]" + brief_description + "[/b]";
  2898. }
  2899. if (!long_description.is_empty()) {
  2900. if (!result.description.is_empty()) {
  2901. result.description += '\n';
  2902. }
  2903. result.description += long_description;
  2904. }
  2905. if (class_doc->is_deprecated) {
  2906. if (class_doc->deprecated_message.is_empty()) {
  2907. result.deprecated_message = TTR("This class may be changed or removed in future versions.");
  2908. } else {
  2909. result.deprecated_message = HANDLE_DOC(class_doc->deprecated_message);
  2910. }
  2911. }
  2912. if (class_doc->is_experimental) {
  2913. if (class_doc->experimental_message.is_empty()) {
  2914. result.experimental_message = TTR("This class may be changed or removed in future versions.");
  2915. } else {
  2916. result.experimental_message = HANDLE_DOC(class_doc->experimental_message);
  2917. }
  2918. }
  2919. if (is_native) {
  2920. doc_class_cache[p_class_name] = result;
  2921. }
  2922. }
  2923. return result;
  2924. }
  2925. EditorHelpBit::HelpData EditorHelpBit::_get_enum_help_data(const StringName &p_class_name, const StringName &p_enum_name) {
  2926. if (doc_enum_cache.has(p_class_name) && doc_enum_cache[p_class_name].has(p_enum_name)) {
  2927. return doc_enum_cache[p_class_name][p_enum_name];
  2928. }
  2929. HelpData result;
  2930. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  2931. if (class_doc) {
  2932. // Non-native enums shouldn't be cached, nor translated.
  2933. const bool is_native = !class_doc->is_script_doc;
  2934. for (const KeyValue<String, DocData::EnumDoc> &kv : class_doc->enums) {
  2935. const StringName enum_name = kv.key;
  2936. const DocData::EnumDoc &enum_doc = kv.value;
  2937. HelpData current;
  2938. current.description = HANDLE_DOC(enum_doc.description);
  2939. if (enum_doc.is_deprecated) {
  2940. if (enum_doc.deprecated_message.is_empty()) {
  2941. current.deprecated_message = TTR("This enumeration may be changed or removed in future versions.");
  2942. } else {
  2943. current.deprecated_message = HANDLE_DOC(enum_doc.deprecated_message);
  2944. }
  2945. }
  2946. if (enum_doc.is_experimental) {
  2947. if (enum_doc.experimental_message.is_empty()) {
  2948. current.experimental_message = TTR("This enumeration may be changed or removed in future versions.");
  2949. } else {
  2950. current.experimental_message = HANDLE_DOC(enum_doc.experimental_message);
  2951. }
  2952. }
  2953. if (enum_name == p_enum_name) {
  2954. result = current;
  2955. if (!is_native) {
  2956. break;
  2957. }
  2958. }
  2959. if (is_native) {
  2960. doc_enum_cache[p_class_name][enum_name] = current;
  2961. }
  2962. }
  2963. }
  2964. return result;
  2965. }
  2966. EditorHelpBit::HelpData EditorHelpBit::_get_constant_help_data(const StringName &p_class_name, const StringName &p_constant_name) {
  2967. if (doc_constant_cache.has(p_class_name) && doc_constant_cache[p_class_name].has(p_constant_name)) {
  2968. return doc_constant_cache[p_class_name][p_constant_name];
  2969. }
  2970. HelpData result;
  2971. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  2972. if (class_doc) {
  2973. // Non-native constants shouldn't be cached, nor translated.
  2974. const bool is_native = !class_doc->is_script_doc;
  2975. for (const DocData::ConstantDoc &constant : class_doc->constants) {
  2976. HelpData current;
  2977. current.description = HANDLE_DOC(constant.description);
  2978. if (constant.is_deprecated) {
  2979. if (constant.deprecated_message.is_empty()) {
  2980. current.deprecated_message = TTR("This constant may be changed or removed in future versions.");
  2981. } else {
  2982. current.deprecated_message = HANDLE_DOC(constant.deprecated_message);
  2983. }
  2984. }
  2985. if (constant.is_experimental) {
  2986. if (constant.experimental_message.is_empty()) {
  2987. current.experimental_message = TTR("This constant may be changed or removed in future versions.");
  2988. } else {
  2989. current.experimental_message = HANDLE_DOC(constant.experimental_message);
  2990. }
  2991. }
  2992. current.doc_type = { constant.type, constant.enumeration, constant.is_bitfield };
  2993. if (constant.is_value_valid) {
  2994. current.value = constant.value;
  2995. }
  2996. if (constant.name == p_constant_name) {
  2997. result = current;
  2998. if (!is_native) {
  2999. break;
  3000. }
  3001. }
  3002. if (is_native) {
  3003. doc_constant_cache[p_class_name][constant.name] = current;
  3004. }
  3005. }
  3006. }
  3007. return result;
  3008. }
  3009. EditorHelpBit::HelpData EditorHelpBit::_get_property_help_data(const StringName &p_class_name, const StringName &p_property_name) {
  3010. if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) {
  3011. return doc_property_cache[p_class_name][p_property_name];
  3012. }
  3013. HelpData result;
  3014. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  3015. if (class_doc) {
  3016. // Non-native properties shouldn't be cached, nor translated.
  3017. const bool is_native = !class_doc->is_script_doc;
  3018. for (const DocData::PropertyDoc &property : class_doc->properties) {
  3019. HelpData current;
  3020. current.description = HANDLE_DOC(property.description);
  3021. if (property.is_deprecated) {
  3022. if (property.deprecated_message.is_empty()) {
  3023. current.deprecated_message = TTR("This property may be changed or removed in future versions.");
  3024. } else {
  3025. current.deprecated_message = HANDLE_DOC(property.deprecated_message);
  3026. }
  3027. }
  3028. if (property.is_experimental) {
  3029. if (property.experimental_message.is_empty()) {
  3030. current.experimental_message = TTR("This property may be changed or removed in future versions.");
  3031. } else {
  3032. current.experimental_message = HANDLE_DOC(property.experimental_message);
  3033. }
  3034. }
  3035. current.doc_type = { property.type, property.enumeration, property.is_bitfield };
  3036. current.value = property.default_value;
  3037. String enum_class_name;
  3038. String enum_name;
  3039. if (CoreConstants::is_global_enum(property.enumeration)) {
  3040. enum_class_name = "@GlobalScope";
  3041. enum_name = property.enumeration;
  3042. } else {
  3043. const int dot_pos = property.enumeration.rfind_char('.');
  3044. if (dot_pos >= 0) {
  3045. enum_class_name = property.enumeration.left(dot_pos);
  3046. enum_name = property.enumeration.substr(dot_pos + 1);
  3047. }
  3048. }
  3049. if (!enum_class_name.is_empty() && !enum_name.is_empty()) {
  3050. // Classes can use enums from other classes, so check from which it came.
  3051. const DocData::ClassDoc *enum_class = EditorHelp::get_doc(enum_class_name);
  3052. if (enum_class) {
  3053. const String enum_prefix = EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " ";
  3054. for (DocData::ConstantDoc constant : enum_class->constants) {
  3055. // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
  3056. if (constant.enumeration == enum_name && !constant.name.ends_with("_MAX")) {
  3057. // Prettify the enum value display, so that "<ENUM_NAME>_<ITEM>" becomes "Item".
  3058. const String item_name = EditorPropertyNameProcessor::get_singleton()->process_name(constant.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED).trim_prefix(enum_prefix);
  3059. String item_descr = HANDLE_DOC(constant.description);
  3060. if (item_descr.is_empty()) {
  3061. item_descr = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]";
  3062. }
  3063. current.description += vformat("\n[b]%s:[/b] %s", item_name, item_descr);
  3064. }
  3065. }
  3066. current.description = current.description.lstrip("\n");
  3067. }
  3068. }
  3069. if (property.name == p_property_name) {
  3070. result = current;
  3071. if (!is_native) {
  3072. break;
  3073. }
  3074. }
  3075. if (is_native) {
  3076. doc_property_cache[p_class_name][property.name] = current;
  3077. }
  3078. }
  3079. }
  3080. return result;
  3081. }
  3082. EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name) {
  3083. if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) {
  3084. return doc_theme_item_cache[p_class_name][p_theme_item_name];
  3085. }
  3086. HelpData result;
  3087. bool found = false;
  3088. DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  3089. while (class_doc) {
  3090. // Non-native theme items shouldn't be cached, nor translated.
  3091. const bool is_native = !class_doc->is_script_doc;
  3092. for (const DocData::ThemeItemDoc &theme_item : class_doc->theme_properties) {
  3093. HelpData current;
  3094. current.description = HANDLE_DOC(theme_item.description);
  3095. if (theme_item.is_deprecated) {
  3096. if (theme_item.deprecated_message.is_empty()) {
  3097. current.deprecated_message = TTR("This theme property may be changed or removed in future versions.");
  3098. } else {
  3099. current.deprecated_message = HANDLE_DOC(theme_item.deprecated_message);
  3100. }
  3101. }
  3102. if (theme_item.is_experimental) {
  3103. if (theme_item.experimental_message.is_empty()) {
  3104. current.experimental_message = TTR("This theme property may be changed or removed in future versions.");
  3105. } else {
  3106. current.experimental_message = HANDLE_DOC(theme_item.experimental_message);
  3107. }
  3108. }
  3109. current.doc_type = { theme_item.type, String(), false };
  3110. current.value = theme_item.default_value;
  3111. if (theme_item.name == p_theme_item_name) {
  3112. result = current;
  3113. found = true;
  3114. if (!is_native) {
  3115. break;
  3116. }
  3117. }
  3118. if (is_native) {
  3119. doc_theme_item_cache[p_class_name][theme_item.name] = current;
  3120. }
  3121. }
  3122. if (found || class_doc->inherits.is_empty()) {
  3123. break;
  3124. }
  3125. // Check for inherited theme items.
  3126. class_doc = EditorHelp::get_doc(class_doc->inherits);
  3127. }
  3128. return result;
  3129. }
  3130. EditorHelpBit::HelpData EditorHelpBit::_get_method_help_data(const StringName &p_class_name, const StringName &p_method_name) {
  3131. if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) {
  3132. return doc_method_cache[p_class_name][p_method_name];
  3133. }
  3134. HelpData result;
  3135. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  3136. if (class_doc) {
  3137. // Non-native methods shouldn't be cached, nor translated.
  3138. const bool is_native = !class_doc->is_script_doc;
  3139. for (const DocData::MethodDoc &method : class_doc->methods) {
  3140. HelpData current;
  3141. current.description = HANDLE_DOC(method.description);
  3142. if (method.is_deprecated) {
  3143. if (method.deprecated_message.is_empty()) {
  3144. current.deprecated_message = TTR("This method may be changed or removed in future versions.");
  3145. } else {
  3146. current.deprecated_message = HANDLE_DOC(method.deprecated_message);
  3147. }
  3148. }
  3149. if (method.is_experimental) {
  3150. if (method.experimental_message.is_empty()) {
  3151. current.experimental_message = TTR("This method may be changed or removed in future versions.");
  3152. } else {
  3153. current.experimental_message = HANDLE_DOC(method.experimental_message);
  3154. }
  3155. }
  3156. current.doc_type = { method.return_type, method.return_enum, method.return_is_bitfield };
  3157. for (const DocData::ArgumentDoc &argument : method.arguments) {
  3158. const DocType argument_doc_type = { argument.type, argument.enumeration, argument.is_bitfield };
  3159. current.arguments.push_back({ argument.name, argument_doc_type, argument.default_value });
  3160. }
  3161. current.qualifiers = method.qualifiers;
  3162. const DocData::ArgumentDoc &rest_argument = method.rest_argument;
  3163. const DocType rest_argument_doc_type = { rest_argument.type, rest_argument.enumeration, rest_argument.is_bitfield };
  3164. current.rest_argument = { rest_argument.name, rest_argument_doc_type, rest_argument.default_value };
  3165. if (method.name == p_method_name) {
  3166. result = current;
  3167. if (!is_native) {
  3168. break;
  3169. }
  3170. }
  3171. if (is_native) {
  3172. doc_method_cache[p_class_name][method.name] = current;
  3173. }
  3174. }
  3175. }
  3176. return result;
  3177. }
  3178. EditorHelpBit::HelpData EditorHelpBit::_get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name) {
  3179. if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) {
  3180. return doc_signal_cache[p_class_name][p_signal_name];
  3181. }
  3182. HelpData result;
  3183. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  3184. if (class_doc) {
  3185. // Non-native signals shouldn't be cached, nor translated.
  3186. const bool is_native = !class_doc->is_script_doc;
  3187. for (const DocData::MethodDoc &signal : class_doc->signals) {
  3188. HelpData current;
  3189. current.description = HANDLE_DOC(signal.description);
  3190. if (signal.is_deprecated) {
  3191. if (signal.deprecated_message.is_empty()) {
  3192. current.deprecated_message = TTR("This signal may be changed or removed in future versions.");
  3193. } else {
  3194. current.deprecated_message = HANDLE_DOC(signal.deprecated_message);
  3195. }
  3196. }
  3197. if (signal.is_experimental) {
  3198. if (signal.experimental_message.is_empty()) {
  3199. current.experimental_message = TTR("This signal may be changed or removed in future versions.");
  3200. } else {
  3201. current.experimental_message = HANDLE_DOC(signal.experimental_message);
  3202. }
  3203. }
  3204. for (const DocData::ArgumentDoc &argument : signal.arguments) {
  3205. const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield };
  3206. current.arguments.push_back({ argument.name, argument_type, argument.default_value });
  3207. }
  3208. current.qualifiers = signal.qualifiers;
  3209. if (signal.name == p_signal_name) {
  3210. result = current;
  3211. if (!is_native) {
  3212. break;
  3213. }
  3214. }
  3215. if (is_native) {
  3216. doc_signal_cache[p_class_name][signal.name] = current;
  3217. }
  3218. }
  3219. }
  3220. return result;
  3221. }
  3222. EditorHelpBit::HelpData EditorHelpBit::_get_annotation_help_data(const StringName &p_class_name, const StringName &p_annotation_name) {
  3223. if (doc_annotation_cache.has(p_class_name) && doc_annotation_cache[p_class_name].has(p_annotation_name)) {
  3224. return doc_annotation_cache[p_class_name][p_annotation_name];
  3225. }
  3226. HelpData result;
  3227. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(p_class_name);
  3228. if (class_doc) {
  3229. // Non-native annotations shouldn't be cached, nor translated.
  3230. const bool is_native = !class_doc->is_script_doc;
  3231. for (const DocData::MethodDoc &annotation : class_doc->annotations) {
  3232. HelpData current;
  3233. current.description = HANDLE_DOC(annotation.description);
  3234. if (annotation.is_deprecated) {
  3235. if (annotation.deprecated_message.is_empty()) {
  3236. current.deprecated_message = TTR("This annotation may be changed or removed in future versions.");
  3237. } else {
  3238. current.deprecated_message = HANDLE_DOC(annotation.deprecated_message);
  3239. }
  3240. }
  3241. if (annotation.is_experimental) {
  3242. if (annotation.experimental_message.is_empty()) {
  3243. current.experimental_message = TTR("This annotation may be changed or removed in future versions.");
  3244. } else {
  3245. current.experimental_message = HANDLE_DOC(annotation.experimental_message);
  3246. }
  3247. }
  3248. for (const DocData::ArgumentDoc &argument : annotation.arguments) {
  3249. const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield };
  3250. current.arguments.push_back({ argument.name, argument_type, argument.default_value });
  3251. }
  3252. current.qualifiers = annotation.qualifiers;
  3253. if (annotation.name == p_annotation_name) {
  3254. result = current;
  3255. if (!is_native) {
  3256. break;
  3257. }
  3258. }
  3259. if (is_native) {
  3260. doc_annotation_cache[p_class_name][annotation.name] = current;
  3261. }
  3262. }
  3263. }
  3264. return result;
  3265. }
  3266. #undef HANDLE_DOC
  3267. void EditorHelpBit::_add_type_to_title(const DocType &p_doc_type) {
  3268. _add_type_to_rt(p_doc_type.type, p_doc_type.enumeration, p_doc_type.is_bitfield, title, this, symbol_class_name);
  3269. }
  3270. void EditorHelpBit::_update_labels() {
  3271. const Ref<Font> doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts));
  3272. if (!symbol_type.is_empty() || !symbol_name.is_empty()) {
  3273. title->clear();
  3274. title->push_font(doc_bold_font);
  3275. if (!symbol_type.is_empty()) {
  3276. title->push_color(get_theme_color(SNAME("title_color"), SNAME("EditorHelp")));
  3277. title->add_text(symbol_type);
  3278. title->pop(); // color
  3279. }
  3280. if (!symbol_type.is_empty() && !symbol_name.is_empty()) {
  3281. title->add_text(" ");
  3282. }
  3283. if (!symbol_name.is_empty()) {
  3284. if (!symbol_doc_link.is_empty()) {
  3285. title->push_meta(symbol_doc_link, RichTextLabel::META_UNDERLINE_ON_HOVER);
  3286. }
  3287. if (use_class_prefix && !symbol_class_name.is_empty() && symbol_hint != SYMBOL_HINT_INHERITANCE) {
  3288. title->add_text(symbol_class_name + ".");
  3289. }
  3290. title->add_text(symbol_name);
  3291. if (!symbol_doc_link.is_empty()) {
  3292. title->pop(); // meta
  3293. }
  3294. }
  3295. title->pop(); // font
  3296. const Color text_color = get_theme_color(SNAME("text_color"), SNAME("EditorHelp"));
  3297. const Color symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp"));
  3298. const Color value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp"));
  3299. const Color qualifier_color = get_theme_color(SNAME("qualifier_color"), SNAME("EditorHelp"));
  3300. const Ref<Font> doc_source = get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts));
  3301. const int doc_source_size = get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts));
  3302. switch (symbol_hint) {
  3303. case SYMBOL_HINT_NONE: {
  3304. // Nothing to do.
  3305. } break;
  3306. case SYMBOL_HINT_INHERITANCE: {
  3307. const DocData::ClassDoc *class_doc = EditorHelp::get_doc(symbol_class_name);
  3308. String inherits = class_doc ? class_doc->inherits : String();
  3309. if (!inherits.is_empty()) {
  3310. title->push_font(doc_source);
  3311. title->push_font_size(doc_source_size * 0.9);
  3312. while (!inherits.is_empty()) {
  3313. title->push_color(symbol_color);
  3314. title->add_text(" <" + nbsp);
  3315. title->pop(); // color
  3316. _add_type_to_title({ inherits, String(), false });
  3317. const DocData::ClassDoc *base_class_doc = EditorHelp::get_doc(inherits);
  3318. inherits = base_class_doc ? base_class_doc->inherits : String();
  3319. }
  3320. title->pop(); // font_size
  3321. title->pop(); // font
  3322. }
  3323. } break;
  3324. case SYMBOL_HINT_ASSIGNABLE: {
  3325. const bool has_type = !help_data.doc_type.type.is_empty();
  3326. const bool has_value = !help_data.value.is_empty();
  3327. if (has_type || has_value) {
  3328. title->push_font(doc_source);
  3329. title->push_font_size(doc_source_size * 0.9);
  3330. if (has_type) {
  3331. title->push_color(symbol_color);
  3332. title->add_text(colon_nbsp);
  3333. title->pop(); // color
  3334. _add_type_to_title(help_data.doc_type);
  3335. }
  3336. if (has_value) {
  3337. title->push_color(symbol_color);
  3338. title->add_text(nbsp_equal_nbsp);
  3339. title->pop(); // color
  3340. title->push_color(value_color);
  3341. title->add_text(_fix_constant(help_data.value));
  3342. title->pop(); // color
  3343. }
  3344. title->pop(); // font_size
  3345. title->pop(); // font
  3346. }
  3347. } break;
  3348. case SYMBOL_HINT_SIGNATURE: {
  3349. title->push_font(doc_source);
  3350. title->push_font_size(doc_source_size * 0.9);
  3351. title->push_color(symbol_color);
  3352. title->add_text("(");
  3353. title->pop(); // color
  3354. for (int i = 0; i < help_data.arguments.size(); i++) {
  3355. const ArgumentData &argument = help_data.arguments[i];
  3356. if (i > 0) {
  3357. title->push_color(symbol_color);
  3358. title->add_text(", ");
  3359. title->pop(); // color
  3360. }
  3361. title->push_color(text_color);
  3362. title->add_text(argument.name);
  3363. title->pop(); // color
  3364. title->push_color(symbol_color);
  3365. title->add_text(colon_nbsp);
  3366. title->pop(); // color
  3367. _add_type_to_title(argument.doc_type);
  3368. if (!argument.default_value.is_empty()) {
  3369. title->push_color(symbol_color);
  3370. title->add_text(nbsp_equal_nbsp);
  3371. title->pop(); // color
  3372. title->push_color(value_color);
  3373. title->add_text(_fix_constant(argument.default_value));
  3374. title->pop(); // color
  3375. }
  3376. }
  3377. if (help_data.qualifiers.contains("vararg")) {
  3378. if (!help_data.arguments.is_empty()) {
  3379. title->push_color(symbol_color);
  3380. title->add_text(", ");
  3381. title->pop(); // color
  3382. }
  3383. title->push_color(symbol_color);
  3384. title->add_text("...");
  3385. title->pop(); // color
  3386. const ArgumentData &rest_argument = help_data.rest_argument;
  3387. title->push_color(text_color);
  3388. title->add_text(rest_argument.name.is_empty() ? "args" : rest_argument.name);
  3389. title->pop(); // color
  3390. title->push_color(symbol_color);
  3391. title->add_text(colon_nbsp);
  3392. title->pop(); // color
  3393. if (rest_argument.doc_type.type.is_empty()) {
  3394. _add_type_to_title({ "Array", "", false });
  3395. } else {
  3396. _add_type_to_title(rest_argument.doc_type);
  3397. }
  3398. }
  3399. title->push_color(symbol_color);
  3400. title->add_text(")");
  3401. title->pop(); // color
  3402. if (!help_data.doc_type.type.is_empty()) {
  3403. title->push_color(symbol_color);
  3404. title->add_text(" ->" + nbsp);
  3405. title->pop(); // color
  3406. _add_type_to_title(help_data.doc_type);
  3407. }
  3408. if (!help_data.qualifiers.is_empty()) {
  3409. title->push_color(qualifier_color);
  3410. _add_qualifiers_to_rt(help_data.qualifiers, title);
  3411. title->pop(); // color
  3412. }
  3413. title->pop(); // font_size
  3414. title->pop(); // font
  3415. } break;
  3416. }
  3417. title->show();
  3418. } else {
  3419. title->hide();
  3420. }
  3421. content->clear();
  3422. bool has_prev_text = false;
  3423. if (!help_data.deprecated_message.is_empty()) {
  3424. has_prev_text = true;
  3425. Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError"));
  3426. content->add_image(error_icon, error_icon->get_width(), error_icon->get_height());
  3427. content->add_text(nbsp);
  3428. content->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  3429. content->push_font(doc_bold_font);
  3430. content->add_text(TTR("Deprecated:"));
  3431. content->pop(); // font
  3432. content->pop(); // color
  3433. content->add_text(" ");
  3434. _add_text_to_rt(help_data.deprecated_message, content, this, symbol_class_name);
  3435. }
  3436. if (!help_data.experimental_message.is_empty()) {
  3437. if (has_prev_text) {
  3438. content->add_newline();
  3439. content->add_newline();
  3440. }
  3441. has_prev_text = true;
  3442. Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning"));
  3443. content->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height());
  3444. content->add_text(nbsp);
  3445. content->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
  3446. content->push_font(doc_bold_font);
  3447. content->add_text(TTR("Experimental:"));
  3448. content->pop(); // font
  3449. content->pop(); // color
  3450. content->add_text(" ");
  3451. _add_text_to_rt(help_data.experimental_message, content, this, symbol_class_name);
  3452. }
  3453. if (!help_data.description.is_empty()) {
  3454. if (has_prev_text) {
  3455. content->add_newline();
  3456. content->add_newline();
  3457. }
  3458. has_prev_text = true;
  3459. const Color comment_color = get_theme_color(SNAME("comment_color"), SNAME("EditorHelp"));
  3460. _add_text_to_rt(help_data.description.replace("<EditorHelpBitCommentColor>", comment_color.to_html()), content, this, symbol_class_name);
  3461. }
  3462. if (!help_data.resource_path.is_empty()) {
  3463. if (has_prev_text) {
  3464. content->add_newline();
  3465. content->add_newline();
  3466. }
  3467. has_prev_text = true;
  3468. const String ext = help_data.resource_path.get_extension();
  3469. const bool is_dir = ext.is_empty();
  3470. const bool is_valid = is_dir || EditorFileSystem::get_singleton()->get_valid_extensions().has(ext);
  3471. if (!is_dir && is_valid) {
  3472. content->push_meta("open-res:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER);
  3473. content->add_image(get_editor_theme_icon(SNAME("Load")));
  3474. content->add_text(nbsp + TTR("Open"));
  3475. content->pop(); // meta
  3476. content->add_newline();
  3477. }
  3478. if (is_valid) {
  3479. content->push_meta("show:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER);
  3480. content->add_image(get_editor_theme_icon(SNAME("Filesystem")));
  3481. content->add_text(nbsp + TTR("Show in FileSystem"));
  3482. content->pop(); // meta
  3483. } else {
  3484. content->push_meta("open-file:" + help_data.resource_path, RichTextLabel::META_UNDERLINE_ON_HOVER);
  3485. content->add_image(get_editor_theme_icon(SNAME("Filesystem")));
  3486. content->add_text(nbsp + TTR("Open in File Manager"));
  3487. content->pop(); // meta
  3488. }
  3489. }
  3490. if (is_inside_tree()) {
  3491. update_content_height();
  3492. }
  3493. }
  3494. void EditorHelpBit::_go_to_url(const String &p_what) {
  3495. Vector<String> parts;
  3496. {
  3497. int from = 0;
  3498. int buffer_start = 0;
  3499. while (true) {
  3500. const int pos = p_what.find_char(':', from);
  3501. if (pos < 0) {
  3502. parts.push_back(p_what.substr(buffer_start));
  3503. break;
  3504. }
  3505. if (pos + 1 < p_what.length() && p_what[pos + 1] == ':') {
  3506. // `::` used in built-in scripts.
  3507. from = pos + 2;
  3508. } else {
  3509. parts.push_back(p_what.substr(buffer_start, pos - buffer_start));
  3510. from = pos + 1;
  3511. buffer_start = from;
  3512. }
  3513. }
  3514. }
  3515. const String what = parts[0]; // `parts` is always non-empty.
  3516. const String clss = (parts.size() > 1) ? parts[1].to_lower() : String();
  3517. const String name = (parts.size() > 2) ? parts[2].to_lower().replace_chars("/_", '-') : String();
  3518. String section = "";
  3519. if (what == "class_desc") {
  3520. section = "#description";
  3521. } else if (what == "class_signal") {
  3522. section = vformat("#class-%s-signal-%s", clss, name);
  3523. } else if (what == "class_method" || what == "class_method_desc") {
  3524. section = vformat("#class-%s-method-%s", clss, name);
  3525. } else if (what == "class_property") {
  3526. section = vformat("#class-%s-property-%s", clss, name);
  3527. } else if (what == "class_enum") {
  3528. section = vformat("#enum-%s-%s", clss, name);
  3529. } else if (what == "class_theme_item") {
  3530. section = vformat("#class-%s-theme-%s", clss, name);
  3531. } else if (what == "class_constant") {
  3532. section = vformat("#class-%s-constant-%s", clss, name);
  3533. } else if (what == "class_annotation") {
  3534. section = vformat("#%s", clss);
  3535. }
  3536. String doc_url = clss.is_empty() ? String(GODOT_VERSION_DOCS_URL "/") : vformat(GODOT_VERSION_DOCS_URL "/classes/class_%s.html%s", clss, section);
  3537. OS::get_singleton()->shell_open(doc_url);
  3538. }
  3539. void EditorHelpBit::_go_to_help(const String &p_what) {
  3540. if (ScriptEditor::get_singleton()) {
  3541. EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
  3542. ScriptEditor::get_singleton()->goto_help(p_what);
  3543. } else {
  3544. _go_to_url(p_what);
  3545. }
  3546. emit_signal(SNAME("request_hide"));
  3547. }
  3548. void EditorHelpBit::_meta_clicked(const String &p_select) {
  3549. if (p_select.begins_with("$")) { // Enum.
  3550. const String link = p_select.substr(1);
  3551. String enum_class_name;
  3552. String enum_name;
  3553. if (CoreConstants::is_global_enum(link)) {
  3554. enum_class_name = "@GlobalScope";
  3555. enum_name = link;
  3556. } else {
  3557. const int dot_pos = link.rfind_char('.');
  3558. if (dot_pos >= 0) {
  3559. enum_class_name = link.left(dot_pos);
  3560. enum_name = link.substr(dot_pos + 1);
  3561. } else {
  3562. enum_class_name = symbol_class_name;
  3563. enum_name = link;
  3564. }
  3565. }
  3566. _go_to_help("class_enum:" + enum_class_name + ":" + enum_name);
  3567. } else if (p_select.begins_with("#")) { // Class.
  3568. _go_to_help("class_name:" + p_select.substr(1));
  3569. } else if (p_select.begins_with("@")) { // Member.
  3570. const int tag_end = p_select.find_char(' ');
  3571. const String tag = p_select.substr(1, tag_end - 1);
  3572. const String link = p_select.substr(tag_end + 1).lstrip(" ");
  3573. String topic;
  3574. if (tag == "method") {
  3575. topic = "class_method";
  3576. } else if (tag == "constructor") {
  3577. topic = "class_method";
  3578. } else if (tag == "operator") {
  3579. topic = "class_method";
  3580. } else if (tag == "member") {
  3581. topic = "class_property";
  3582. } else if (tag == "enum") {
  3583. topic = "class_enum";
  3584. } else if (tag == "signal") {
  3585. topic = "class_signal";
  3586. } else if (tag == "constant") {
  3587. topic = "class_constant";
  3588. } else if (tag == "annotation") {
  3589. topic = "class_annotation";
  3590. } else if (tag == "theme_item") {
  3591. topic = "class_theme_item";
  3592. } else {
  3593. return;
  3594. }
  3595. if (topic == "class_enum") {
  3596. const String enum_link = link.trim_prefix("@GlobalScope.");
  3597. if (CoreConstants::is_global_enum(enum_link)) {
  3598. _go_to_help(topic + ":@GlobalScope:" + enum_link);
  3599. return;
  3600. }
  3601. } else if (topic == "class_constant") {
  3602. if (CoreConstants::is_global_constant(link)) {
  3603. _go_to_help(topic + ":@GlobalScope:" + link);
  3604. return;
  3605. }
  3606. }
  3607. if (link.contains_char('.')) {
  3608. const int class_end = link.rfind_char('.');
  3609. _go_to_help(topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1));
  3610. } else {
  3611. _go_to_help(topic + ":" + symbol_class_name + ":" + link);
  3612. }
  3613. } else if (p_select.begins_with("open-file:")) {
  3614. String path = ProjectSettings::get_singleton()->globalize_path(p_select.trim_prefix("open-file:"));
  3615. OS::get_singleton()->shell_show_in_file_manager(path, true);
  3616. } else if (p_select.begins_with("open-res:")) {
  3617. EditorNode::get_singleton()->load_scene_or_resource(p_select.trim_prefix("open-res:"));
  3618. } else if (p_select.begins_with("show:")) {
  3619. FileSystemDock::get_singleton()->navigate_to_path(p_select.trim_prefix("show:"));
  3620. } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) {
  3621. OS::get_singleton()->shell_open(p_select);
  3622. } else if (p_select.begins_with("^")) { // Copy button.
  3623. DisplayServer::get_singleton()->clipboard_set(p_select.substr(1));
  3624. }
  3625. }
  3626. void EditorHelpBit::_bind_methods() {
  3627. ADD_SIGNAL(MethodInfo("request_hide"));
  3628. }
  3629. void EditorHelpBit::_notification(int p_what) {
  3630. switch (p_what) {
  3631. case NOTIFICATION_THEME_CHANGED:
  3632. _update_labels();
  3633. break;
  3634. }
  3635. }
  3636. void EditorHelpBit::parse_symbol(const String &p_symbol, const String &p_prologue) {
  3637. const PackedStringArray slices = p_symbol.split("|", true, 3);
  3638. ERR_FAIL_COND_MSG(slices.size() < 3, R"(Invalid doc id: The expected format is "item_type|class_name|item_name[|item_data]".)");
  3639. const String &item_type = slices[0];
  3640. const String &class_name = slices[1];
  3641. const String &item_name = slices[2];
  3642. Dictionary item_data;
  3643. if (slices.size() > 3) {
  3644. item_data = JSON::parse_string(slices[3]);
  3645. }
  3646. symbol_doc_link = String();
  3647. symbol_class_name = class_name;
  3648. symbol_type = String();
  3649. symbol_name = item_name;
  3650. symbol_hint = SYMBOL_HINT_NONE;
  3651. help_data = HelpData();
  3652. if (item_type == "class") {
  3653. symbol_doc_link = vformat("#%s", class_name);
  3654. symbol_type = TTR("Class");
  3655. symbol_name = class_name;
  3656. symbol_hint = SYMBOL_HINT_INHERITANCE;
  3657. help_data = _get_class_help_data(class_name);
  3658. } else if (item_type == "enum") {
  3659. symbol_doc_link = vformat("$%s.%s", class_name, item_name);
  3660. symbol_type = TTR("Enumeration");
  3661. help_data = _get_enum_help_data(class_name, item_name);
  3662. } else if (item_type == "constant") {
  3663. symbol_doc_link = vformat("@constant %s.%s", class_name, item_name);
  3664. symbol_type = TTR("Constant");
  3665. symbol_hint = SYMBOL_HINT_ASSIGNABLE;
  3666. help_data = _get_constant_help_data(class_name, item_name);
  3667. } else if (item_type == "property") {
  3668. if (item_name.begins_with("metadata/")) {
  3669. symbol_type = TTR("Metadata");
  3670. symbol_name = item_name.trim_prefix("metadata/");
  3671. } else if (class_name == "ProjectSettings" || class_name == "EditorSettings") {
  3672. symbol_doc_link = vformat("@member %s.%s", class_name, item_name);
  3673. symbol_type = TTR("Setting");
  3674. symbol_hint = SYMBOL_HINT_ASSIGNABLE;
  3675. } else {
  3676. symbol_doc_link = vformat("@member %s.%s", class_name, item_name);
  3677. symbol_type = TTR("Property");
  3678. symbol_hint = SYMBOL_HINT_ASSIGNABLE;
  3679. }
  3680. help_data = _get_property_help_data(class_name, item_name);
  3681. // Add copy note to built-in properties returning `Packed*Array`.
  3682. const DocData::ClassDoc *cd = EditorHelp::get_doc(class_name);
  3683. if (cd && !cd->is_script_doc && packed_array_types.has(help_data.doc_type.type)) {
  3684. if (!help_data.description.is_empty()) {
  3685. help_data.description += "\n";
  3686. }
  3687. // See also `EditorHelp::_update_doc()` and `doc/tools/make_rst.py`.
  3688. help_data.description += 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."), help_data.doc_type.type);
  3689. }
  3690. } else if (item_type == "internal_property") {
  3691. symbol_type = TTR("Internal Property");
  3692. help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("This property can only be set in the Inspector.") + "[/i][/color]";
  3693. } else if (item_type == "theme_item") {
  3694. symbol_doc_link = vformat("@theme_item %s.%s", class_name, item_name);
  3695. symbol_type = TTR("Theme Property");
  3696. symbol_hint = SYMBOL_HINT_ASSIGNABLE;
  3697. help_data = _get_theme_item_help_data(class_name, item_name);
  3698. } else if (item_type == "method") {
  3699. symbol_doc_link = vformat("@method %s.%s", class_name, item_name);
  3700. symbol_type = TTR("Method");
  3701. symbol_hint = SYMBOL_HINT_SIGNATURE;
  3702. help_data = _get_method_help_data(class_name, item_name);
  3703. } else if (item_type == "signal") {
  3704. symbol_doc_link = vformat("@signal %s.%s", class_name, item_name);
  3705. symbol_type = TTR("Signal");
  3706. symbol_hint = SYMBOL_HINT_SIGNATURE;
  3707. help_data = _get_signal_help_data(class_name, item_name);
  3708. } else if (item_type == "annotation") {
  3709. symbol_doc_link = vformat("@annotation %s.%s", class_name, item_name);
  3710. symbol_type = TTR("Annotation");
  3711. symbol_hint = SYMBOL_HINT_SIGNATURE;
  3712. help_data = _get_annotation_help_data(class_name, item_name);
  3713. } else if (item_type == "local_constant" || item_type == "local_variable") {
  3714. symbol_type = (item_type == "local_constant") ? TTR("Local Constant") : TTR("Local Variable");
  3715. symbol_hint = SYMBOL_HINT_ASSIGNABLE;
  3716. help_data.description = item_data.get("description", "").operator String().strip_edges();
  3717. if (item_data.get("is_deprecated", false)) {
  3718. const String deprecated_message = item_data.get("deprecated_message", "").operator String().strip_edges();
  3719. if (deprecated_message.is_empty()) {
  3720. if (item_type == "local_constant") {
  3721. help_data.deprecated_message = TTR("This constant may be changed or removed in future versions.");
  3722. } else {
  3723. help_data.deprecated_message = TTR("This variable may be changed or removed in future versions.");
  3724. }
  3725. } else {
  3726. help_data.deprecated_message = deprecated_message;
  3727. }
  3728. }
  3729. if (item_data.get("is_experimental", false)) {
  3730. const String experimental_message = item_data.get("experimental_message", "").operator String().strip_edges();
  3731. if (experimental_message.is_empty()) {
  3732. if (item_type == "local_constant") {
  3733. help_data.experimental_message = TTR("This constant may be changed or removed in future versions.");
  3734. } else {
  3735. help_data.experimental_message = TTR("This variable may be changed or removed in future versions.");
  3736. }
  3737. } else {
  3738. help_data.experimental_message = experimental_message;
  3739. }
  3740. }
  3741. help_data.doc_type.type = item_data.get("doc_type", "");
  3742. help_data.doc_type.enumeration = item_data.get("enumeration", "");
  3743. help_data.doc_type.is_bitfield = item_data.get("is_bitfield", false);
  3744. help_data.value = item_data.get("value", "");
  3745. } else if (item_type == "resource") {
  3746. String path = item_name.simplify_path();
  3747. const bool is_uid = path.begins_with("uid://");
  3748. if (is_uid) {
  3749. if (ResourceUID::get_singleton()->has_id(ResourceUID::get_singleton()->text_to_id(path))) {
  3750. path = ResourceUID::uid_to_path(path);
  3751. } else {
  3752. path = "";
  3753. }
  3754. }
  3755. help_data.resource_path = path;
  3756. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
  3757. if (da->file_exists(path)) {
  3758. help_data.doc_type.type = ResourceLoader::get_resource_type(path);
  3759. if (help_data.doc_type.type.is_empty()) {
  3760. const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false);
  3761. symbol_type = textfile_ext.has(path.get_extension()) ? TTR("Text File") : TTR("File");
  3762. } else {
  3763. symbol_type = TTR("Resource");
  3764. symbol_hint = SYMBOL_HINT_ASSIGNABLE;
  3765. if (is_uid) {
  3766. help_data.description = vformat("%s: [color=<EditorHelpBitCommentColor>]%s[/color]", TTR("Path"), path);
  3767. }
  3768. }
  3769. symbol_name = path.get_file();
  3770. } else if (!is_uid && da->dir_exists(path)) {
  3771. symbol_type = TTR("Directory");
  3772. symbol_name = path;
  3773. } else {
  3774. help_data.resource_path = "";
  3775. symbol_name = "";
  3776. if (is_uid) {
  3777. symbol_type = TTR("Invalid UID");
  3778. help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("This UID does not point to any valid Resource.") + "[/i][/color]";
  3779. } else {
  3780. symbol_type = TTR("Invalid path");
  3781. help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("This path does not exist.") + "[/i][/color]";
  3782. }
  3783. }
  3784. } else {
  3785. ERR_FAIL_MSG("Invalid doc id: Unknown item type " + item_type.quote() + ".");
  3786. }
  3787. // Do not add links for custom or undocumented symbols.
  3788. if (symbol_class_name.is_empty() || (help_data.description.is_empty() && help_data.deprecated_message.is_empty() && help_data.experimental_message.is_empty())) {
  3789. symbol_doc_link = String();
  3790. }
  3791. if (!p_prologue.is_empty()) {
  3792. if (help_data.description.is_empty()) {
  3793. help_data.description = p_prologue;
  3794. } else {
  3795. help_data.description = p_prologue + "\n" + help_data.description;
  3796. }
  3797. }
  3798. if (help_data.description.is_empty() && item_type != "resource") {
  3799. help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]";
  3800. }
  3801. if (is_inside_tree()) {
  3802. _update_labels();
  3803. }
  3804. }
  3805. void EditorHelpBit::set_custom_text(const String &p_type, const String &p_name, const String &p_description) {
  3806. symbol_doc_link = String();
  3807. symbol_class_name = String();
  3808. symbol_type = p_type;
  3809. symbol_name = p_name;
  3810. symbol_hint = SYMBOL_HINT_NONE;
  3811. help_data = HelpData();
  3812. help_data.description = p_description;
  3813. if (is_inside_tree()) {
  3814. _update_labels();
  3815. }
  3816. }
  3817. void EditorHelpBit::set_content_height_limits(float p_min, float p_max) {
  3818. ERR_FAIL_COND(p_min > p_max);
  3819. content_min_height = p_min;
  3820. content_max_height = p_max;
  3821. if (is_inside_tree()) {
  3822. update_content_height();
  3823. }
  3824. }
  3825. void EditorHelpBit::update_content_height() {
  3826. float content_height = content->get_content_height();
  3827. const Ref<StyleBox> style = content->get_theme_stylebox(CoreStringName(normal));
  3828. if (style.is_valid()) {
  3829. content_height += style->get_content_margin(SIDE_TOP) + style->get_content_margin(SIDE_BOTTOM);
  3830. }
  3831. content->set_custom_minimum_size(Size2(content->get_custom_minimum_size().x, CLAMP(content_height, content_min_height, content_max_height)));
  3832. }
  3833. EditorHelpBit::EditorHelpBit(const String &p_symbol, const String &p_prologue, bool p_use_class_prefix, bool p_allow_selection, bool p_in_tooltip) {
  3834. add_theme_constant_override("separation", 0);
  3835. title = memnew(RichTextLabel);
  3836. title->set_theme_type_variation(p_in_tooltip ? "EditorHelpBitTooltipTitle" : "EditorHelpBitTitle");
  3837. title->set_custom_minimum_size(Size2(640 * EDSCALE, 0)); // GH-93031. Set the minimum width even if `fit_content` is true.
  3838. title->set_fit_content(true);
  3839. title->set_selection_enabled(p_allow_selection);
  3840. title->set_context_menu_enabled(p_allow_selection);
  3841. title->set_selection_modifier(callable_mp_static(_replace_nbsp_with_space));
  3842. title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
  3843. title->hide();
  3844. add_child(title);
  3845. content_min_height = 48 * EDSCALE;
  3846. content_max_height = 360 * EDSCALE;
  3847. content = memnew(RichTextLabel);
  3848. content->set_theme_type_variation(p_in_tooltip ? "EditorHelpBitTooltipContent" : "EditorHelpBitContent");
  3849. content->set_custom_minimum_size(Size2(640 * EDSCALE, content_min_height));
  3850. content->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  3851. content->set_selection_enabled(p_allow_selection);
  3852. content->set_context_menu_enabled(p_allow_selection);
  3853. content->set_selection_modifier(callable_mp_static(_replace_nbsp_with_space));
  3854. content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked));
  3855. add_child(content);
  3856. use_class_prefix = p_use_class_prefix;
  3857. if (!p_symbol.is_empty()) {
  3858. parse_symbol(p_symbol, p_prologue);
  3859. } else if (!p_prologue.is_empty()) {
  3860. set_custom_text(String(), String(), p_prologue);
  3861. }
  3862. }
  3863. /// EditorHelpBitTooltip ///
  3864. bool EditorHelpBitTooltip::_is_tooltip_visible = false;
  3865. Control *EditorHelpBitTooltip::_make_invisible_control() {
  3866. Control *control = memnew(Control);
  3867. control->set_visible(false);
  3868. return control;
  3869. }
  3870. void EditorHelpBitTooltip::_start_timer() {
  3871. if (timer->is_inside_tree() && timer->is_stopped()) {
  3872. timer->start();
  3873. }
  3874. }
  3875. void EditorHelpBitTooltip::_target_gui_input(const Ref<InputEvent> &p_event) {
  3876. // Only scrolling is not checked in `NOTIFICATION_INTERNAL_PROCESS`.
  3877. const Ref<InputEventMouseButton> mb = p_event;
  3878. if (mb.is_valid()) {
  3879. switch (mb->get_button_index()) {
  3880. case MouseButton::WHEEL_UP:
  3881. case MouseButton::WHEEL_DOWN:
  3882. case MouseButton::WHEEL_LEFT:
  3883. case MouseButton::WHEEL_RIGHT:
  3884. queue_free();
  3885. break;
  3886. default:
  3887. break;
  3888. }
  3889. }
  3890. }
  3891. void EditorHelpBitTooltip::_notification(int p_what) {
  3892. switch (p_what) {
  3893. case NOTIFICATION_ENTER_TREE:
  3894. _is_tooltip_visible = true;
  3895. _enter_tree_time = OS::get_singleton()->get_ticks_msec();
  3896. break;
  3897. case NOTIFICATION_EXIT_TREE:
  3898. _is_tooltip_visible = false;
  3899. break;
  3900. case NOTIFICATION_WM_MOUSE_ENTER:
  3901. _is_mouse_inside_tooltip = true;
  3902. timer->stop();
  3903. break;
  3904. case NOTIFICATION_WM_MOUSE_EXIT:
  3905. _is_mouse_inside_tooltip = false;
  3906. _start_timer();
  3907. break;
  3908. case NOTIFICATION_INTERNAL_PROCESS:
  3909. // A workaround to hide the tooltip since the window does not receive keyboard events
  3910. // with `FLAG_POPUP` and `FLAG_NO_FOCUS` flags, so we can't use `_input_from_window()`.
  3911. if (is_inside_tree()) {
  3912. if (Input::get_singleton()->is_action_just_pressed(SNAME("ui_cancel"), true)) {
  3913. queue_free();
  3914. get_parent_viewport()->set_input_as_handled();
  3915. } else if (Input::get_singleton()->is_anything_pressed_except_mouse()) {
  3916. queue_free();
  3917. } else if (!Input::get_singleton()->get_mouse_button_mask().is_empty()) {
  3918. if (!_is_mouse_inside_tooltip) {
  3919. queue_free();
  3920. }
  3921. } else if (!Input::get_singleton()->get_last_mouse_velocity().is_zero_approx()) {
  3922. if (!_is_mouse_inside_tooltip && OS::get_singleton()->get_ticks_msec() - _enter_tree_time > 350) {
  3923. _start_timer();
  3924. }
  3925. }
  3926. }
  3927. break;
  3928. }
  3929. }
  3930. Control *EditorHelpBitTooltip::make_tooltip(Control *p_target, const String &p_symbol, const String &p_prologue, bool p_use_class_prefix) {
  3931. ERR_FAIL_NULL_V(p_target, _make_invisible_control());
  3932. // Show the custom tooltip only if it is not already visible.
  3933. // The viewport will retrigger `make_custom_tooltip()` every few seconds
  3934. // because the return control is not visible even if the custom tooltip is displayed.
  3935. if (_is_tooltip_visible || Input::get_singleton()->is_anything_pressed()) {
  3936. return _make_invisible_control();
  3937. }
  3938. EditorHelpBit *help_bit = memnew(EditorHelpBit(p_symbol, p_prologue, p_use_class_prefix, false, true));
  3939. EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target));
  3940. help_bit->connect("request_hide", callable_mp(static_cast<Node *>(tooltip), &Node::queue_free));
  3941. tooltip->add_child(help_bit);
  3942. p_target->add_child(tooltip);
  3943. help_bit->update_content_height();
  3944. tooltip->popup_under_cursor();
  3945. return _make_invisible_control();
  3946. }
  3947. // Copy-paste from `Viewport::_gui_show_tooltip()`.
  3948. void EditorHelpBitTooltip::popup_under_cursor() {
  3949. Point2 mouse_pos = get_mouse_position();
  3950. Point2 tooltip_offset = GLOBAL_GET_CACHED(Point2, "display/mouse_cursor/tooltip_position_offset");
  3951. Rect2 r(mouse_pos + tooltip_offset, get_contents_minimum_size());
  3952. r.size = r.size.min(get_max_size());
  3953. Window *window = get_parent_visible_window();
  3954. Rect2i vr;
  3955. if (is_embedded()) {
  3956. vr = get_embedder()->get_visible_rect();
  3957. } else {
  3958. vr = window->get_usable_parent_rect();
  3959. }
  3960. if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SELF_FITTING_WINDOWS) || is_embedded()) {
  3961. if (r.size.x + r.position.x > vr.size.x + vr.position.x) {
  3962. // Place it in the opposite direction. If it fails, just hug the border.
  3963. r.position.x = mouse_pos.x - r.size.x - tooltip_offset.x;
  3964. if (r.position.x < vr.position.x) {
  3965. r.position.x = vr.position.x + vr.size.x - r.size.x;
  3966. }
  3967. } else if (r.position.x < vr.position.x) {
  3968. r.position.x = vr.position.x;
  3969. }
  3970. if (r.size.y + r.position.y > vr.size.y + vr.position.y) {
  3971. // Same as above.
  3972. r.position.y = mouse_pos.y - r.size.y - tooltip_offset.y;
  3973. if (r.position.y < vr.position.y) {
  3974. r.position.y = vr.position.y + vr.size.y - r.size.y;
  3975. }
  3976. } else if (r.position.y < vr.position.y) {
  3977. r.position.y = vr.position.y;
  3978. }
  3979. }
  3980. // When `FLAG_POPUP` is false, it prevents the editor from losing focus when displaying the tooltip.
  3981. // This way, clicks and double-clicks are still available outside the tooltip.
  3982. set_flag(Window::FLAG_POPUP, false);
  3983. set_flag(Window::FLAG_NO_FOCUS, true);
  3984. popup(r);
  3985. }
  3986. EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) {
  3987. ERR_FAIL_NULL(p_target);
  3988. set_theme_type_variation("TooltipPanel");
  3989. timer = memnew(Timer);
  3990. timer->set_wait_time(0.25);
  3991. timer->connect("timeout", callable_mp(static_cast<Node *>(this), &Node::queue_free));
  3992. add_child(timer);
  3993. p_target->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorHelpBitTooltip::_start_timer));
  3994. p_target->connect(SceneStringName(gui_input), callable_mp(this, &EditorHelpBitTooltip::_target_gui_input));
  3995. set_process_internal(true);
  3996. }
  3997. /// EditorHelpHighlighter ///
  3998. EditorHelpHighlighter *EditorHelpHighlighter::singleton = nullptr;
  3999. void EditorHelpHighlighter::create_singleton() {
  4000. ERR_FAIL_COND(singleton != nullptr);
  4001. singleton = memnew(EditorHelpHighlighter);
  4002. }
  4003. void EditorHelpHighlighter::free_singleton() {
  4004. ERR_FAIL_NULL(singleton);
  4005. memdelete(singleton);
  4006. singleton = nullptr;
  4007. }
  4008. EditorHelpHighlighter *EditorHelpHighlighter::get_singleton() {
  4009. return singleton;
  4010. }
  4011. EditorHelpHighlighter::HighlightData EditorHelpHighlighter::_get_highlight_data(Language p_language, const String &p_source, bool p_use_cache) {
  4012. switch (p_language) {
  4013. case LANGUAGE_GDSCRIPT:
  4014. #ifndef MODULE_GDSCRIPT_ENABLED
  4015. ERR_FAIL_V_MSG(HighlightData(), "GDScript module is disabled.");
  4016. #endif
  4017. break;
  4018. case LANGUAGE_CSHARP:
  4019. #ifndef MODULE_MONO_ENABLED
  4020. ERR_FAIL_V_MSG(HighlightData(), "Mono module is disabled.");
  4021. #endif
  4022. break;
  4023. default:
  4024. ERR_FAIL_V_MSG(HighlightData(), "Invalid parameter \"p_language\".");
  4025. }
  4026. if (p_use_cache) {
  4027. const HashMap<String, HighlightData>::ConstIterator E = highlight_data_caches[p_language].find(p_source);
  4028. if (E) {
  4029. return E->value;
  4030. }
  4031. }
  4032. text_edits[p_language]->set_text(p_source);
  4033. if (scripts[p_language].is_valid()) { // See GH-89610.
  4034. scripts[p_language]->set_source_code(p_source);
  4035. }
  4036. highlighters[p_language]->_update_cache();
  4037. HighlightData result;
  4038. int source_offset = 0;
  4039. int result_index = 0;
  4040. for (int i = 0; i < text_edits[p_language]->get_line_count(); i++) {
  4041. const Dictionary dict = highlighters[p_language]->_get_line_syntax_highlighting_impl(i);
  4042. result.resize(result.size() + dict.size());
  4043. const Variant *key = nullptr;
  4044. int prev_column = -1;
  4045. while ((key = dict.next(key)) != nullptr) {
  4046. const int column = *key;
  4047. ERR_FAIL_COND_V(column <= prev_column, HighlightData());
  4048. prev_column = column;
  4049. const Color color = dict[*key].operator Dictionary().get("color", Color());
  4050. result.write[result_index] = { source_offset + column, color };
  4051. result_index++;
  4052. }
  4053. source_offset += text_edits[p_language]->get_line(i).length() + 1; // Plus newline.
  4054. }
  4055. if (p_use_cache) {
  4056. highlight_data_caches[p_language][p_source] = result;
  4057. }
  4058. return result;
  4059. }
  4060. void EditorHelpHighlighter::highlight(RichTextLabel *p_rich_text_label, Language p_language, const String &p_source, bool p_use_cache) {
  4061. ERR_FAIL_NULL(p_rich_text_label);
  4062. const HighlightData highlight_data = _get_highlight_data(p_language, p_source, p_use_cache);
  4063. if (!highlight_data.is_empty()) {
  4064. for (int i = 1; i < highlight_data.size(); i++) {
  4065. const Pair<int, Color> &prev = highlight_data[i - 1];
  4066. const Pair<int, Color> &curr = highlight_data[i];
  4067. p_rich_text_label->push_color(prev.second);
  4068. p_rich_text_label->add_text(p_source.substr(prev.first, curr.first - prev.first));
  4069. p_rich_text_label->pop(); // color
  4070. }
  4071. const Pair<int, Color> &last = highlight_data[highlight_data.size() - 1];
  4072. p_rich_text_label->push_color(last.second);
  4073. p_rich_text_label->add_text(p_source.substr(last.first));
  4074. p_rich_text_label->pop(); // color
  4075. }
  4076. }
  4077. void EditorHelpHighlighter::reset_cache() {
  4078. const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color");
  4079. #ifdef MODULE_GDSCRIPT_ENABLED
  4080. highlight_data_caches[LANGUAGE_GDSCRIPT].clear();
  4081. text_edits[LANGUAGE_GDSCRIPT]->add_theme_color_override(SceneStringName(font_color), text_color);
  4082. #endif
  4083. #ifdef MODULE_MONO_ENABLED
  4084. highlight_data_caches[LANGUAGE_CSHARP].clear();
  4085. text_edits[LANGUAGE_CSHARP]->add_theme_color_override(SceneStringName(font_color), text_color);
  4086. #endif
  4087. }
  4088. EditorHelpHighlighter::EditorHelpHighlighter() {
  4089. const Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color");
  4090. #ifdef MODULE_GDSCRIPT_ENABLED
  4091. TextEdit *gdscript_text_edit = memnew(TextEdit);
  4092. gdscript_text_edit->add_theme_color_override(SceneStringName(font_color), text_color);
  4093. Ref<GDScript> gdscript;
  4094. gdscript.instantiate();
  4095. Ref<GDScriptSyntaxHighlighter> gdscript_highlighter;
  4096. gdscript_highlighter.instantiate();
  4097. gdscript_highlighter->set_text_edit(gdscript_text_edit);
  4098. gdscript_highlighter->_set_edited_resource(gdscript);
  4099. text_edits[LANGUAGE_GDSCRIPT] = gdscript_text_edit;
  4100. scripts[LANGUAGE_GDSCRIPT] = gdscript;
  4101. highlighters[LANGUAGE_GDSCRIPT] = gdscript_highlighter;
  4102. #endif
  4103. #ifdef MODULE_MONO_ENABLED
  4104. TextEdit *csharp_text_edit = memnew(TextEdit);
  4105. csharp_text_edit->add_theme_color_override(SceneStringName(font_color), text_color);
  4106. // See GH-89610.
  4107. //Ref<CSharpScript> csharp;
  4108. //csharp.instantiate();
  4109. Ref<EditorStandardSyntaxHighlighter> csharp_highlighter;
  4110. csharp_highlighter.instantiate();
  4111. csharp_highlighter->set_text_edit(csharp_text_edit);
  4112. //csharp_highlighter->_set_edited_resource(csharp);
  4113. csharp_highlighter->_set_script_language(CSharpLanguage::get_singleton());
  4114. text_edits[LANGUAGE_CSHARP] = csharp_text_edit;
  4115. //scripts[LANGUAGE_CSHARP] = csharp;
  4116. highlighters[LANGUAGE_CSHARP] = csharp_highlighter;
  4117. #endif
  4118. }
  4119. EditorHelpHighlighter::~EditorHelpHighlighter() {
  4120. #ifdef MODULE_GDSCRIPT_ENABLED
  4121. memdelete(text_edits[LANGUAGE_GDSCRIPT]);
  4122. #endif
  4123. #ifdef MODULE_MONO_ENABLED
  4124. memdelete(text_edits[LANGUAGE_CSHARP]);
  4125. #endif
  4126. }
  4127. /// FindBar ///
  4128. FindBar::FindBar() {
  4129. search_text = memnew(LineEdit);
  4130. search_text->set_keep_editing_on_text_submit(true);
  4131. add_child(search_text);
  4132. search_text->set_placeholder(TTR("Search"));
  4133. search_text->set_tooltip_text(TTR("Search"));
  4134. search_text->set_accessibility_name(TTRC("Search Documentation"));
  4135. search_text->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
  4136. search_text->set_h_size_flags(SIZE_EXPAND_FILL);
  4137. search_text->connect(SceneStringName(text_changed), callable_mp(this, &FindBar::_search_text_changed));
  4138. search_text->connect(SceneStringName(text_submitted), callable_mp(this, &FindBar::_search_text_submitted));
  4139. matches_label = memnew(Label);
  4140. add_child(matches_label);
  4141. matches_label->set_focus_mode(FOCUS_ACCESSIBILITY);
  4142. matches_label->hide();
  4143. find_prev = memnew(Button);
  4144. find_prev->set_theme_type_variation(SceneStringName(FlatButton));
  4145. find_prev->set_disabled(results_count < 1);
  4146. find_prev->set_tooltip_text(TTR("Previous Match"));
  4147. add_child(find_prev);
  4148. find_prev->set_focus_mode(FOCUS_ACCESSIBILITY);
  4149. find_prev->connect(SceneStringName(pressed), callable_mp(this, &FindBar::search_prev));
  4150. find_next = memnew(Button);
  4151. find_next->set_theme_type_variation(SceneStringName(FlatButton));
  4152. find_next->set_disabled(results_count < 1);
  4153. find_next->set_tooltip_text(TTR("Next Match"));
  4154. add_child(find_next);
  4155. find_next->set_focus_mode(FOCUS_ACCESSIBILITY);
  4156. find_next->connect(SceneStringName(pressed), callable_mp(this, &FindBar::search_next));
  4157. hide_button = memnew(Button);
  4158. hide_button->set_theme_type_variation(SceneStringName(FlatButton));
  4159. hide_button->set_tooltip_text(TTR("Hide"));
  4160. hide_button->set_focus_mode(FOCUS_ACCESSIBILITY);
  4161. hide_button->connect(SceneStringName(pressed), callable_mp(this, &FindBar::_hide_bar));
  4162. hide_button->set_v_size_flags(SIZE_EXPAND_FILL);
  4163. add_child(hide_button);
  4164. }
  4165. void FindBar::popup_search() {
  4166. show();
  4167. bool grabbed_focus = false;
  4168. if (!search_text->has_focus()) {
  4169. search_text->grab_focus();
  4170. grabbed_focus = true;
  4171. }
  4172. if (!search_text->get_text().is_empty()) {
  4173. search_text->select_all();
  4174. search_text->set_caret_column(search_text->get_text().length());
  4175. if (grabbed_focus) {
  4176. rich_text_label->deselect();
  4177. results_count_to_current = 0;
  4178. _search();
  4179. }
  4180. }
  4181. }
  4182. void FindBar::_notification(int p_what) {
  4183. switch (p_what) {
  4184. case NOTIFICATION_THEME_CHANGED: {
  4185. find_prev->set_button_icon(get_editor_theme_icon(SNAME("MoveUp")));
  4186. find_next->set_button_icon(get_editor_theme_icon(SNAME("MoveDown")));
  4187. hide_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
  4188. matches_label->add_theme_color_override(SceneStringName(font_color), results_count > 0 ? get_theme_color(SceneStringName(font_color), SNAME("Label")) : get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  4189. } break;
  4190. case NOTIFICATION_VISIBILITY_CHANGED: {
  4191. set_process_input(is_visible_in_tree());
  4192. } break;
  4193. }
  4194. }
  4195. void FindBar::set_rich_text_label(RichTextLabel *p_rich_text_label) {
  4196. rich_text_label = p_rich_text_label;
  4197. }
  4198. bool FindBar::search_next() {
  4199. return _search();
  4200. }
  4201. bool FindBar::search_prev() {
  4202. return _search(true);
  4203. }
  4204. bool FindBar::_search(bool p_search_previous) {
  4205. String stext = search_text->get_text();
  4206. bool keep = prev_search == stext;
  4207. bool ret = rich_text_label->search(stext, keep, p_search_previous);
  4208. prev_search = stext;
  4209. if (!keep) {
  4210. results_count_to_current = 0;
  4211. }
  4212. if (ret) {
  4213. _update_results_count(p_search_previous);
  4214. } else {
  4215. results_count = 0;
  4216. results_count_to_current = 0;
  4217. }
  4218. if (results_count == 1) {
  4219. rich_text_label->scroll_to_selection();
  4220. }
  4221. _update_matches_label();
  4222. return ret;
  4223. }
  4224. void FindBar::_update_results_count(bool p_search_previous) {
  4225. results_count = 0;
  4226. String searched = search_text->get_text();
  4227. if (searched.is_empty()) {
  4228. return;
  4229. }
  4230. String full_text = rich_text_label->get_parsed_text();
  4231. int from_pos = 0;
  4232. while (true) {
  4233. int pos = full_text.findn(searched, from_pos);
  4234. if (pos == -1) {
  4235. break;
  4236. }
  4237. results_count++;
  4238. from_pos = pos + searched.length();
  4239. }
  4240. results_count_to_current += (p_search_previous) ? -1 : 1;
  4241. if (results_count_to_current > results_count) {
  4242. results_count_to_current = results_count_to_current - results_count;
  4243. } else if (results_count_to_current <= 0) {
  4244. results_count_to_current = results_count;
  4245. }
  4246. }
  4247. void FindBar::_update_matches_label() {
  4248. if (search_text->get_text().is_empty() || results_count == -1) {
  4249. matches_label->hide();
  4250. } else {
  4251. matches_label->show();
  4252. matches_label->add_theme_color_override(SceneStringName(font_color), results_count > 0 ? get_theme_color(SceneStringName(font_color), SNAME("Label")) : get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  4253. if (results_count == 0) {
  4254. matches_label->set_text(TTR("No match"));
  4255. } else if (results_count_to_current == 0) {
  4256. matches_label->set_text(vformat(TTRN("%d match", "%d matches", results_count), results_count));
  4257. } else {
  4258. matches_label->set_text(vformat(TTRN("%d of %d match", "%d of %d matches", results_count), results_count_to_current, results_count));
  4259. }
  4260. }
  4261. find_prev->set_disabled(results_count < 1);
  4262. find_next->set_disabled(results_count < 1);
  4263. }
  4264. void FindBar::_hide_bar() {
  4265. if (search_text->has_focus()) {
  4266. rich_text_label->grab_focus();
  4267. }
  4268. hide();
  4269. }
  4270. // Implemented in input(..) as the LineEdit consumes the Escape pressed key.
  4271. void FindBar::input(const Ref<InputEvent> &p_event) {
  4272. ERR_FAIL_COND(p_event.is_null());
  4273. Ref<InputEventKey> k = p_event;
  4274. if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
  4275. Control *focus_owner = get_viewport()->gui_get_focus_owner();
  4276. if (rich_text_label->has_focus() || (focus_owner && is_ancestor_of(focus_owner))) {
  4277. _hide_bar();
  4278. accept_event();
  4279. }
  4280. }
  4281. }
  4282. void FindBar::_search_text_changed(const String &p_text) {
  4283. search_next();
  4284. }
  4285. void FindBar::_search_text_submitted(const String &p_text) {
  4286. if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
  4287. search_prev();
  4288. } else {
  4289. search_next();
  4290. }
  4291. }