From de4875b2556f01494a99ec50efaf56f342bafff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 11 Jun 2023 17:23:18 -0300 Subject: [PATCH] eepp: Allow to unselect selection from UIAbstractView. ecode: Updated icons ttf. Completed implementation of Build Output, now it will list Issues found when compiling in the Issues section inside Build. Tentative fix in LSPDocumentClient when parsing semantic highlight. Improved Features Health GUI. --- bin/assets/fonts/codicon.ttf | Bin 72672 -> 73452 bytes bin/assets/fonts/nonicons.ttf | Bin 104056 -> 113276 bytes bin/assets/ui/breeze.css | 7 +- include/eepp/system/fileinfo.hpp | 2 + include/eepp/ui/uiwidgetcreator.hpp | 5 +- src/eepp/system/fileinfo.cpp | 17 +- src/eepp/ui/abstract/uiabstracttableview.cpp | 14 +- src/tools/ecode/ecode.cpp | 173 +++++----- src/tools/ecode/featureshealth.cpp | 9 +- .../ecode/plugins/lsp/lspdocumentclient.cpp | 14 +- src/tools/ecode/projectbuild.cpp | 61 ++-- src/tools/ecode/projectbuild.hpp | 19 +- .../ecode/statusbuildoutputcontroller.cpp | 317 ++++++++++++++++-- .../ecode/statusbuildoutputcontroller.hpp | 42 ++- src/tools/ecode/uibuildsettings.cpp | 2 +- src/tools/ecode/widgetcommandexecuter.hpp | 17 +- 16 files changed, 518 insertions(+), 181 deletions(-) diff --git a/bin/assets/fonts/codicon.ttf b/bin/assets/fonts/codicon.ttf index bab11139af89dcf8aed0b5e21cb9e33547cc4cb4..3d1dc3d86b8887461892729305b22a54bd642c0a 100644 GIT binary patch delta 7393 zcmaLcdw3Mp`3LatySv#-AR*aJvPlRbBqTvDA>^KLhj52*zZ0%W2#|ybh=}N-;tdhO zAX17-sYO6*X=A}!+geL4Dn+E0T1pYrT019IiWDie*zd`!K7D?F{2uwtJ2RP`Idjg; zIrAPb9QCyQ%(E>kG7yk002VH3T-1EalSO?1{|+Ge{L+To>Z)IJhk@=VfD5PUYZukb zdVKFx9(#dG>bb);*Y$g@zf9fh8`rG6-090Nc)m8kd!S+El0{7)lqCb*)&TAqjf>Vb zyWW($_;>C@M$@9k+Lo)SKN$;&+QXgKnpdt~^ZMAwwm^I=KzRE0m$PCHL3k{^^EhlUmTJQSu34nGOo)EjFqQkgjC4{ zJcuGm!JT*opJ1@8!D=MQ6KKIk=`DVIDUkc|5}w5Ka+fT^J+eyX$TC?fb7i^QEDchN z!?IACTIvH`JIugL%))HULv_oef%3>iOu}SL!6Ga{E$Y#LCagp=ZpJFC!7W&eTd@we zVLfig2Hb%N?!qSAj|cD&w%}oG#Ut2;?RXSBuoI7C7ruqv_%@!vQ`n2|@R)u0E}q7- zcn&|n5AkCh!B6m0Hr~(ha~zA{*LV%T!EwBfH}EFg_n z@Nay9|KNtW#4TR&Nwjp3cuABb2}`o1N@q!vE|M-?rMqNHj^s)Yxk-9TFXQ5jN~BcEq+ABcV2KQop)yP=WsHoKaWY;e$|RX8(`35Llvy%Qs%5?`kj1h@YNSr; zWrZ}#N?9kj$=z~~Y?6EBK6yYMl+E&xY>{p9sIv1j)oS*^ObihWqd%d@50rg_|TO9Z`%DjFemCF?mEDmaW(?6*53PsFUx>GqPW9 zmkn}<+$j;+DC-f4N0r3lF?kX-coiq{Tb#fIjK?@6;8*w_p21WsmVwxVg-ApzUXVC+ zMF#$kkn9w}dAx;F_&t7yckl4v-Eg%5rN@F$$XAJG{v;xk-E zIZ}{}F+m)_K^czGGE%1Cmwf5Gt&=;tB!Z57VUXarOYS|y*Jhz=e}xutop6zHv62GD zC5kT-oEioD+gc?VjCD%ZGA>opiLqYs^@CHrj0?hb#^s9dB%BorV;LKiJjK|kWCUZA zk}AfPif=KTW`ze^@?yhdQn^I;<6uxA%$C2P_JB zfWC0-T@!mdIPA$S9A$wJ1RihbefPQMsv+w&R4UJ=d##r_e_LB*aD&LPD<6V9^= z)vYJv<_fz{I42Z4QaHa=>{8*JRP0>gyrtO9!a1eb;lg=avFn9%TCo#`^E<`v7|uJ2 z9Wxw`dltKBIA?6NbHUyk&L0%}Z8+~L_TX^-sMwdoIjh*K!}*h9{|@JzVowj}&x(CM zob!skKb-dz#{oDO6i0*zoQvEb4i0eMR~#nbe4uy;oDUU;4mf{N97N!Jq&S?w`K#i< z0_S7JAqLLh6bBMGj^eNb$5=c$M<6(DienL+zblSPa6VBSpWu9|I8wp6q&Q~5`G?}@ z1?RHjI0ol4#Ssn86~(a)&Oa4LIle(xxgZXBa6VTY`ruqs90cL~OJOhLb;W@Z&c9Xr zoqfS|;$R8qKZ?U9oEwS*D4Z`9hf)rWD#B3}Mij?a7?^`li~;u6Q?-V!^A6& z`Y=Jo@gF9naDg#F@tXi9QSs{lCQ0EVw!aB0elox$D}Fq{q$pcgCROo+0;aR#rv*%! z;ztHd7sbyFm~_Pt516ispCB+9iXS5|-6+BZze`{;6~9nmvJ}5nV7e=?9Zj|p+eA4^ zY!l@wu}#rKiEWCTlteT3RAQ^YmlD=s#PsF{iLIVKN^JGyDY3QFSJ@9>ldr_qNIxaE zMhcYJ8Yxs_Yothtt&w6Swnj?YMIiPLOO@C=EK_3duw03~!wMz#4*M&ycQ`>UnLVsC1&5_?lal-L^?YTJJ(@7~_YFeSG1!aIzA6!YNAZ38yNt=bNTvBI9%= z_8c>m*kfnf_UA(E!LyXugJ&z@-^?5(_V06*%wznDRCE0+GN0>Tkp*1;iY(^(LM2NW z7b&T+@4x*cNgbp8BS}4Djgl3NwMrTp?QtY48J8+q$7p{;avS3^C3iC}S8@-dJr~I) z#s-V+FZXiAo|xo5#wH~XFxrcdJjmFrWHaN+`E=V3{w0A+Wi_zW%$+sBSEBQ9#?Mn7AZcwt9@eU>5VZ2kxK1SZ4CEsP-sN`wJ zyHrFDaK%;=F~5MZ)kMrRU~DxJa}F3=OT_F0#?}%s4}rN~F(ZMoHAc)$U~G*MvlJLx zW5j#~#?}~t&0w}*xLpADp?O#_n}OM?nAgBOqL|^p*lH!_IxyQ6rnk)LoYn7hZat=$ z|G?~2%!FWU-4c#6+PWp#-tv6stcZO;ZA}v1V|+qMfbmHs{TZK9Vw-obVm<|9Yn$+W zMqAq?qZw^&lZ;}twM`Pk_&p`F8TZ?_j|)lQ#xsg(8O#9%`&J%QOx0ixDJE?&&nl*G zFwZF_axk_a#FP%^d4*3If1o6a@vvgT*YZ|cQcRGWt%}nTm(%)2?8Ew^5?iM~QDR%- zB_$6t{!}qXgn3zkH)MXMq=ND1N(M0gLNS+wc}1a)@s~<$?YwGhg$uDY@+&3wq{o!l z7vR@QHZZ=X#J&K(QDR?!)$_(=dizR0E4D+sH9u4zH z#f%!}tYU5r^C!hD8|Iv1z76wdg~g2L6?1Ty_Y|{nm6Y+t< z3ydEsiEA0uH7oLWZhfo}V*HyD`_?&%sXdHQO!8sc6w`ee`+H*I5A%uQ6adVpijx5_ zmlUT3VE&;vK>%~vJ|SFit^npU#aRQGD~j_6F#lAXNr1VkIHv&fxq@vl`|c3u8DRdU zU?0)zigOPz|5luZfcZjkKB8qoM&1}!eH-u5;=}~nT#8c^Xmcx0R-lbfyTxe>v_&aS zV4%&baJl77M&IdUxYa>%Mgwgf71#l^#VF2lpp8$Yh0%6x=XH!pWx?#n$U z_1Jn-{!NE^R`z_Ym#0^9ulc?1?)7}HPkJZxUe$YFA5WhleVY2bke85GnYTFaaNdV~ z{e9Q=J(2IvFUybI*Kbh2=6+`i1{FM7@J^wtFtxCta8lvA!aapQFFaRxy(qaTzo@FH zu4q%yo}w3uJ}K^5yrKBz;tM6iO6HWTE$vv^y|l7)ZRy$4Yh`20)|Ne8=9I^k=a-Kz zUsJxj{Edp96_pjcD~|O?|FZt;`ajqI{D6)FMh>VMaA3fxfinhf8x$QGG;MI%;O4>m zhWLl{9 zJaW${*QkO~vql{rbz;=n(K|+8ACo?2*O&`qCyhNa&NXh%xCh3)I6gSOY5ei=mnI}k zD4Q^A!iEX&Rt2l3RIRIeuIi(S`4hKJd}U(f{G`-L`zOau-aPsAl+jZ*PI+l+!qhoa zUz@gN+M#L3ryrUA@$?%ry3c5uv315PGd(kR%sevl(yaVh(`Id)wQttDvzun`n|*Rl z!kiOxuFhRM_eb;6=2gzSbKaHe{OUE;N2)K+FPpz<{<#HMFm}QF3o8~^EV)#ZRx_mL zxtdcoU(_bl*44f^x^70@wz|D_hwEOcJGC@_Y4g&Q&o3XleEsqh z%WtfxSg~!z>4x-%K@GbaUTnD9nAy0Yad*@Frjsk1SAN{wySct~efMWFBGG)V!MR!= zU&Cm`vU5yE7H2*Cpr8G`kgUYQ#KPj-!o(~q+(imYi+#meiMd&wq&PP#F=uoQ+9vs%aB^`?J>y>zGciz8IZ-Mn}ytH{Ud;eBV7vKXPz#UO7pbn&g6 zobv4K>g;Us>b{E?gS^X7sCC}v-2B--W_3#DfF?zv)XT`u(+@|t2oP^ z`}MM@;K5L+x%psuGwUT}|5aB@LRstqd+N^)l#dJLzS$Kc}dTiivjlJ+aZLQC_#p>r095WLcXtZtUTf_P0XIwH^}5_X5BItQ;x0j*y_#4Z+M!x)#O_IhY@$)My zoiAz;D%mUv-prh$l7b}P*DK*X$@S)@v&su|iof}DxTLfsC)evs@=Zb?bC8rFDHLw-+s51%VGC~@t7afQ326ePt5x@N}3cG!@S<&KW=^D&Ha zN$XIvBSj7!J>!w_Y`O!kgRVoaXOCw3Wr#aAX35IN#@ePeeQH)Ms#|k(aR-^|^?8>y st*%|Q=ID>(<<6)m&+6Kyn#T*na;M+7x_Qy66%DmVe;Ag&9=-to1$KywI)pI#(SPfy9eB$Y}M<>}FVttCm4>iJ%Nk9_XmxlgzIoa?&Ib^Y$&)%Zrxxr0IL z(|kpMtOYP>>YT}S;oZ923xuo$5_V3XeOHYWJ$*Hhz7#myr?z_Xw5mt8jpViGsGyc_ z1dI##h_4S(dhMKri+`B-*B`uJBM`iG_PnW+>pN6*08%>vf#q{1FRlxCS9pI(;PokU zC(o(&Url;-2qb_vkZX1GZeN%?vT+S0=w-m$al@gs<~t$0mezg!~vix0| zH=K~y0vgr@l?3|N1$q2`%69*zkl6g=H!nxvQ3T$6)rm0-2s)0Sz{h!`X#N)nIQ7n2 z=UwML6KcvEoBPj%6jpyNU8FOfmt z4ZP*UKk+vHg?I2Sj^aI><>;Qr1^fpW@jV5DAqi zi4~8;NxURTq9jQRX(_FwjigDsw3T*}A(_%%I!Y(Wl5FWJIg%@Rk}m~PD8Fml@|0|o9r7%`!MFG~zQe5;iy7#KnLbRA z*{H>A)L<0aAp^f)B_6~9d5AwDFDBwEaY>QPlNjug0a7C2GFse{f|0TX{csicz+~xx$8a0supbQ)iPlJgL9A@VUpRvga2y}wBb>yi zIEBw~8eifBK9t*WSX$#=1jB_8gyIW)g3r(b2k{dwp$Lgcz@RWZhh5T_lPuUjKjON7 zc|@o$f>Q;tpW7qf-^mFxN!^@0llhwPHRBW|T^Oe-P9r$e6rN|SR+7wEqhtZ&bS2Fh zYZWIRoEbiQ;0Ih|oT)e);mlGP%s5-gV~lf@^k@Sxhn0+BT(88A?*=7ySZqs4?D+Z>7ehFk z6kcN7tYkdn7AuJVV5Z-Rdb8(fo<66zYQov2xPHR1KP}=)3TLoX-^3bvT?_7FT#UUns8ia84_(`f$EfT>s&mQOpEzzEaEyaLy`b2RL6V z<_S3G6f*|L@;pz7$pg+eifIJSw~7e`&IN_#jQ>_lGH||AOgC`;qnLQ$TvSX!aK2Ye zG;kcnv;@ak_SgFmU)ZTb%vErHP|R9zniTUFoF5f48JtUsIStNFirEd$WyL%P=V!%? z2j_}n?t}A-Vix4=xyl1EA;S4pF*U-urkEt*{HB;L;apeP#`wFs*|HmaO-!b6{!mP- zaO`v>CRjLsDW+Pk&dSHE3nPm87bZY46T<{5=42TD>Mdqxm}ZK38YWmVW5c)=3nAVuzxG5<3(fm9S@hrV~#{Y`1k*V!JI%iS4#*CAQnTDEp9Xx+<|fm7~P=RIU=+ zQ+Y~kPvtAIJymd%4`N$hsKmCsn-bgdA|gRw@*EJpkPNER~ID!GHvUPrQwai$VG)b`(yEN7f;@l9LunK?>U zFwRx7l5w7r`x)z$tYVz6wg-sS35@LlVx0nGdw^K6z}Oxj)-Es)DpoNts}<`Rm^BJ^Sk|Jy zJplFx^pIk;17o|4Sogr#E+bYzFt*ExwGhk(g;D+mEz-LF!ZX`f#QF$ki(;h&WBZM8 zfYJ6F$wU5kTBP|V^33)g;VVYldn7JK+j}HMjJEek<}q$lthZpER@lS1UC98(XOxsM z?oblW_^guAj63b@<3Zd!c}__R<1Pg|`<_=U6lUFC~i^-%(`C zmhpSVnjgkdto~u_b;NxDm`24N0hk{Y_Xc2^6n6<=epK8qfVrf&a{%*`;vNFbW!q0Y za619!XT^;Lm@A4~3^2baZZ^Q!X+qp~fcaIy4(2t*tq7Rk6zuQwy5jZ(%o;{u;p5va& zadmO4<95Vdj-L|0E`D45X>YoBly|Ooi+8{GsP}q8%Y;4&3lml)1}7FIj!)d3cr>vo zDKaT1sXVDJX?xP~q)ROlT9mYy-C{?J8!eZ$+}HA0tAJLMe66-8dy?ytcO_qMJ*D;T z)|XPcq-;rfCFQ%6YpI^pt*Hl6Po_4tDQ>f*&E7U=(#q4`Nq46grypwDukERJ741%E zq-WG-+{o;nd8U13`%4`bblBJ7?T%p`vpSCL_-Mx?osv71ciPvfv2#-ANu7^$zLXW2 zH9l*3R#SFK_VVm~*?)F9(8YJLYkAiLIbk_vIZJXj=IqWnnsX`FlUtBGDtAHd`rKW) z@8q7%y^-h1>y%fMw>|GleqMf6{%iRs^P37*7Cc^XsGzB^qOh*;t-_{mExS$WR^M$` zwhA7d+J$1JD{sVc6jt=fBQ&8_Rl zMvpBUdtmI@aS7wrjXO3za{Tb|Ysa6QkUU}0ghwY_n7DM}CzC#zvUS;J@5&DuNb^z6LZ6?5X}teTrSciY?x^HLg)r9YR_kT}p&+^~7YHz5N8 zx!)JgHNy)Orp4vDa^upxj2W&tZ*F0JhATfeZhq1K{*=bXo_D*?pT80t>yC}R&wbvW z|9t*D8V;?==u(};awD88WCGHeAkyp|;=F~tiz_Wk@-x!%b6u2?8^_OxOUvhLiM|pV znjKx>3w1AXWrk&)FD)(2cDHNCzmgbwC^Xwu5PE2TU}kFA^IKx;?Az{!;zKbZ4G+G3 z!sWkzZA`<}52{-GxR!wX%BkGAj6o}OMTyK}6p`W@>`nqVd2sz@L*M>j0`@w-y_SKJ(8a!%MQMtB-ug#1>9IRvH$=8 diff --git a/bin/assets/fonts/nonicons.ttf b/bin/assets/fonts/nonicons.ttf index 7750e22289ea6010af1df384da6bd069b83538dc..cd5efdb8ae21f63983af323483de42a5a825fc5c 100644 GIT binary patch delta 28180 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 PushButton::icon, Menu::Item:disabled > Menu::Item::text, Menu::Item:disabled > Menu::Item::shortcut, Menu::Item:disabled > Menu::Item::icon, diff --git a/include/eepp/system/fileinfo.hpp b/include/eepp/system/fileinfo.hpp index a49e2f5e8..56d7a514c 100644 --- a/include/eepp/system/fileinfo.hpp +++ b/include/eepp/system/fileinfo.hpp @@ -48,6 +48,8 @@ class EE_API FileInfo { std::string linksTo() const; + std::string getRealPath() const; + bool exists() const; void getInfo(); diff --git a/include/eepp/ui/uiwidgetcreator.hpp b/include/eepp/ui/uiwidgetcreator.hpp index 615d69ef7..944a11476 100644 --- a/include/eepp/ui/uiwidgetcreator.hpp +++ b/include/eepp/ui/uiwidgetcreator.hpp @@ -11,8 +11,9 @@ class EE_API UIWidgetCreator { typedef std::function CustomWidgetCb; typedef std::function RegisterWidgetCb; - typedef std::map WidgetCallbackMap; - typedef std::map RegisteredWidgetCallbackMap; + typedef std::unordered_map WidgetCallbackMap; + typedef std::unordered_map + RegisteredWidgetCallbackMap; static UIWidget* createFromName( std::string widgetName ); diff --git a/src/eepp/system/fileinfo.cpp b/src/eepp/system/fileinfo.cpp index 71cb611a5..3b3393f24 100644 --- a/src/eepp/system/fileinfo.cpp +++ b/src/eepp/system/fileinfo.cpp @@ -243,21 +243,16 @@ bool FileInfo::linksToDirectory() const { std::string FileInfo::linksTo() const { #if EE_PLATFORM != EE_PLATFORM_WIN - if ( isLink() ) { - char* ch = realpath( mFilepath.c_str(), NULL ); - - if ( NULL != ch ) { - std::string tstr( ch ); - - free( ch ); - - return tstr; - } - } + if ( isLink() ) + return getRealPath(); #endif return std::string( "" ); } +std::string FileInfo::getRealPath() const { + return FileSystem::getRealPath( getFilepath() ); +} + bool FileInfo::exists() const { if ( isDirectory() ) FileSystem::dirRemoveSlashAtEnd( mFilepath ); diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index e37a0c593..adcd3fd80 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace EE { namespace UI { namespace Abstract { @@ -421,7 +422,12 @@ UITableRow* UIAbstractTableView::createRow() { rowWidget->addEventListener( Event::MouseDown, [&]( const Event* event ) { if ( !( event->asMouseEvent()->getFlags() & EE_BUTTON_LMASK ) || !isRowSelection() ) return; - getSelection().set( event->getNode()->asType()->getCurIndex() ); + auto index = event->getNode()->asType()->getCurIndex(); + if ( getUISceneNode()->getWindow()->getInput()->isControlPressed() ) { + getSelection().remove( index ); + } else { + getSelection().set( index ); + } } ); return rowWidget; } @@ -478,7 +484,11 @@ void UIAbstractTableView::bindNavigationClick( UIWidget* widget ) { onOpenModelIndex( idx, event ); } else if ( isCellSelection() && ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) ) { auto cellIdx = mouseEvent->getNode()->asType()->getCurIndex(); - getSelection().set( cellIdx ); + if ( getUISceneNode()->getWindow()->getInput()->isControlPressed() ) { + getSelection().remove( cellIdx ); + } else { + getSelection().set( cellIdx ); + } } } ) ); } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index e1fad6f6c..7a41b77c4 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -3483,12 +3483,30 @@ TableView#locate_bar_table > tableview::row:selected > tableview::cell:nth-child .theme-none > treeview::cell::text, .theme-none > listview::cell::text, .none { - color: #b26818; + color: #d48838; } Anchor.success:hover, Anchor.error:hover { color: var(--primary); } +.status_build_output_cont > SelectButton { + font-size: 11dp; + padding: 2dp 8dp 2dp 8dp; + border-radius: 0dp; + border-width: 1dp; + border-color: transparent; + transition: all 0.2; +} +.status_build_output_cont > SelectButton:hover { + border-width: 1dp; + border-color: var(--primary); +} +#build_output_issues TableView::cell.theme-warning > TableView::cell::icon { + tint: var(--theme-warning); +} +#build_output_issues TableView::cell.theme-error > TableView::cell::icon { + tint: var(--theme-error); +} @@ -3559,17 +3577,17 @@ Anchor.error:hover { - + - - - - + + + + - + @@ -3583,86 +3601,80 @@ Anchor.error:hover { - + - + )html"; UIIconTheme* iconTheme = UIIconTheme::New( "ecode" ); mMenuIconSize = mConfig.ui.fontSize.asPixels( 0, Sizef(), mDisplayDPI ); - std::unordered_map icons = { - { "document-new", 0xecc3 }, - { "document-open", 0xed70 }, - { "document-save", 0xf0b3 }, - { "document-save-as", 0xf0b3 }, - { "document-close", 0xeb99 }, - { "quit", 0xeb97 }, - { "undo", 0xea58 }, - { "redo", 0xea5a }, - { "cut", 0xf0c1 }, - { "copy", 0xecd5 }, - { "paste", 0xeb91 }, - { "edit", 0xec86 }, - { "split-horizontal", 0xf17a }, - { "split-vertical", 0xf17b }, - { "find-replace", 0xed2b }, - // { "folder", 0xed54 }, - // { "folder-open", 0xed70 }, - { "folder-add", 0xed5a }, - // { "file", 0xecc3 }, - { "file-add", 0xecc9 }, - { "file-copy", 0xecd3 }, - { "file-code", 0xecd1 }, - { "file-edit", 0xecdb }, - { "font-size", 0xed8d }, - { "delete-bin", 0xec1e }, - { "delete-text", 0xec1e }, - { "zoom-in", 0xf2db }, - { "zoom-out", 0xf2dd }, - { "zoom-reset", 0xeb47 }, - { "fullscreen", 0xed9c }, - { "keybindings", 0xee75 }, - // { "tree-expanded", 0xea50 }, - // { "tree-contracted", 0xea54 }, - { "search", 0xf0d1 }, - { "go-up", 0xea78 }, - { "ok", 0xeb7a }, - { "cancel", 0xeb98 }, - { "color-picker", 0xf13d }, - { "pixel-density", 0xed8c }, - { "go-to-line", 0xf1f8 }, - { "table-view", 0xf1de }, - { "list-view", 0xecf1 }, - { "menu-unfold", 0xef40 }, - { "menu-fold", 0xef3d }, - { "download-cloud", 0xec58 }, - { "layout-left", 0xee94 }, - { "layout-right", 0xee9b }, - { "color-scheme", 0xebd4 }, - { "global-settings", 0xedcf }, - { "folder-user", 0xed84 }, - { "help", 0xf045 }, - { "terminal", 0xf1f6 }, - { "earth", 0xec7a }, - { "arrow-down", 0xea4c }, - { "arrow-up", 0xea76 }, - { "arrow-down-s", 0xea4e }, - { "arrow-up-s", 0xea78 }, - { "arrow-right-s", 0xea6e }, - { "match-case", 0xed8d }, - { "palette", 0xefc5 }, - { "file-code", 0xecd1 }, - { "cursor-pointer", 0xec09 }, - { "drive", 0xedf8 }, - { "refresh", 0xf064 }, - { "hearth-pulse", 0xee10 }, - { "add", 0xea12 }, - { "hammer", 0xedee }, - { "eraser", 0xec9e } }; + std::unordered_map icons = { { "document-new", 0xecc3 }, + { "document-open", 0xed70 }, + { "document-save", 0xf0b3 }, + { "document-save-as", 0xf0b3 }, + { "document-close", 0xeb99 }, + { "quit", 0xeb97 }, + { "undo", 0xea58 }, + { "redo", 0xea5a }, + { "cut", 0xf0c1 }, + { "copy", 0xecd5 }, + { "paste", 0xeb91 }, + { "edit", 0xec86 }, + { "split-horizontal", 0xf17a }, + { "split-vertical", 0xf17b }, + { "find-replace", 0xed2b }, + { "folder-add", 0xed5a }, + { "file-add", 0xecc9 }, + { "file-copy", 0xecd3 }, + { "file-code", 0xecd1 }, + { "file-edit", 0xecdb }, + { "font-size", 0xed8d }, + { "delete-bin", 0xec1e }, + { "delete-text", 0xec1e }, + { "zoom-in", 0xf2db }, + { "zoom-out", 0xf2dd }, + { "zoom-reset", 0xeb47 }, + { "fullscreen", 0xed9c }, + { "keybindings", 0xee75 }, + { "search", 0xf0d1 }, + { "go-up", 0xea78 }, + { "ok", 0xeb7a }, + { "cancel", 0xeb98 }, + { "color-picker", 0xf13d }, + { "pixel-density", 0xed8c }, + { "go-to-line", 0xf1f8 }, + { "table-view", 0xf1de }, + { "list-view", 0xecf1 }, + { "menu-unfold", 0xef40 }, + { "menu-fold", 0xef3d }, + { "download-cloud", 0xec58 }, + { "layout-left", 0xee94 }, + { "layout-right", 0xee9b }, + { "color-scheme", 0xebd4 }, + { "global-settings", 0xedcf }, + { "folder-user", 0xed84 }, + { "help", 0xf045 }, + { "terminal", 0xf1f6 }, + { "earth", 0xec7a }, + { "arrow-down", 0xea4c }, + { "arrow-up", 0xea76 }, + { "arrow-down-s", 0xea4e }, + { "arrow-up-s", 0xea78 }, + { "arrow-right-s", 0xea6e }, + { "match-case", 0xed8d }, + { "palette", 0xefc5 }, + { "file-code", 0xecd1 }, + { "cursor-pointer", 0xec09 }, + { "drive", 0xedf8 }, + { "refresh", 0xf064 }, + { "hearth-pulse", 0xee10 }, + { "add", 0xea12 }, + { "hammer", 0xedee }, + { "eraser", 0xec9e } }; for ( const auto& icon : icons ) iconTheme->add( UIGlyphIcon::New( icon.first, iconFont, icon.second ) ); @@ -3783,7 +3795,9 @@ Anchor.error:hover { { "chevron-right", 0xeab6 }, { "lightbulb-autofix", 0xeb13 }, { "layout-sidebar-left-off", 0xec02 }, - { "layout-sidebar-left", 0xebf3 } }; + { "layout-sidebar-left", 0xebf3 }, + { "warning", 0xea6c }, + { "error", 0xea87 } }; for ( const auto& icon : codIcons ) iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) ); @@ -3815,6 +3829,7 @@ Anchor.error:hover { UIWidgetCreator::registerWidget( "globalsearchbar", UIGlobalSearchBar::New ); UIWidgetCreator::registerWidget( "mainlayout", UIMainLayout::New ); UIWidgetCreator::registerWidget( "statusbar", UIStatusBar::New ); + UIWidgetCreator::registerWidget( "rellayce", UIRelativeLayoutCommandExecuter::New ); mUISceneNode->loadLayoutFromString( baseUI ); mUISceneNode->bind( "main_layout", mMainLayout ); mUISceneNode->bind( "code_container", mBaseLayout ); diff --git a/src/tools/ecode/featureshealth.cpp b/src/tools/ecode/featureshealth.cpp index 6854190d2..40ba1e27c 100644 --- a/src/tools/ecode/featureshealth.cpp +++ b/src/tools/ecode/featureshealth.cpp @@ -373,15 +373,18 @@ void FeaturesHealth::displayHealth( PluginManager* pluginManager, UISceneNode* s table->setFitAllColumnsToWidget( true ); table->setModel( model ); auto healthLangInfo = win->find( "health-lang-info" ); - table->setOnSelection( [table, healthLangInfo, sceneNode]( const ModelIndex& index ) { + table->setOnSelectionChange( [table, healthLangInfo, sceneNode]() { + if ( table->getSelection().isEmpty() || nullptr == table->getModel() ) { + healthLangInfo->setVisible( false ); + return; + } + ModelIndex index = table->getSelection().first(); static const std::string none = sceneNode->i18n( "none", "None" ); static const std::string notFound = sceneNode->i18n( "not_found_in_path", "Not found in $PATH" ); static const std::string patherr = String::format( "%s: %s", I18N( "path_is", "PATH is" ), std::getenv( "PATH" ) ); - if ( nullptr == table->getModel() ) - return; HealthModel* model = static_cast( table->getModel() ); const auto& lang = model->getHealthRow( index.row() ); healthLangInfo->childsCloseAll(); diff --git a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp index f9b423089..5482bddd4 100644 --- a/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp +++ b/src/tools/ecode/plugins/lsp/lspdocumentclient.cpp @@ -285,25 +285,25 @@ void LSPDocumentClient::highlight() { start = deltaStart; } - auto& line = tokenizerLines[currentLine]; + auto* line = &tokenizerLines[currentLine]; if ( type >= 0 && type < (int)caps.legend.tokenTypes.size() ) { const auto& ltype = caps.legend.tokenTypes[type]; - line.tokens.push_back( + line->tokens.push_back( { semanticTokenTypeToSyntaxType( ltype, mDoc->getSyntaxDefinition() ), start, len } ); } else { - line.tokens.push_back( { "normal", start, len } ); + line->tokens.push_back( { "normal", start, len } ); } - line.hash = mDoc->line( currentLine ).getHash(); - line.updateSignature(); + line->hash = mDoc->line( currentLine ).getHash(); + line->updateSignature(); auto curSignature = mDoc->getHighlighter()->getTokenizedLineSignature( lastLine ); - if ( lastLinePtr && lastLinePtr->signature == curSignature ) { + if ( lastLine != currentLine && lastLinePtr && lastLinePtr->signature == curSignature ) { tokenizerLines.erase( lastLine ); } lastLine = currentLine; - lastLinePtr = &line; + lastLinePtr = line; } diff = clock.getElapsedTime(); diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 9b3ef52b5..a6a4cf491 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -57,6 +57,7 @@ static void replaceVar( ProjectBuildStep& s, const std::string& var, const std:: String::replaceAll( s.cmd, var, val ); String::replaceAll( s.args, var, val ); String::replaceAll( s.workingDir, slashDup, FileSystem::getOSSlash() ); + FileSystem::dirAddSlashAtEnd( s.workingDir ); } ProjectBuildSteps ProjectBuild::replaceVars( const ProjectBuildSteps& steps ) const { @@ -686,27 +687,33 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str auto printElapsed = [&clock, &i18n, &progressFn]() { if ( progressFn ) { progressFn( - 100, Sys::getDateTimeStr() + ": " + - String::format( - i18n( "build_elapsed_time", "Elapsed Time: %s.\n" ).toUtf8().c_str(), - clock.getElapsedTime().toString().c_str() ) ); + 100, + Sys::getDateTimeStr() + ": " + + String::format( + i18n( "build_elapsed_time", "Elapsed Time: %s.\n" ).toUtf8().c_str(), + clock.getElapsedTime().toString().c_str() ), + nullptr ); } }; if ( progressFn ) { - progressFn( 0, Sys::getDateTimeStr() + ": " + - String::format( i18n( "running_steps_for_project", - "Running steps for project %s...\n" ) - .toUtf8() - .c_str(), - buildName.c_str() ) ); + progressFn( 0, + Sys::getDateTimeStr() + ": " + + String::format( + i18n( "running_steps_for_project", "Running steps for project %s...\n" ) + .toUtf8() + .c_str(), + buildName.c_str() ), + nullptr ); if ( !buildType.empty() ) progressFn( - 0, Sys::getDateTimeStr() + ": " + - String::format( - i18n( "using_build_type", "Using build type: %s.\n" ).toUtf8().c_str(), - buildType.c_str() ) ); + 0, + Sys::getDateTimeStr() + ": " + + String::format( + i18n( "using_build_type", "Using build type: %s.\n" ).toUtf8().c_str(), + buildType.c_str() ), + nullptr ); } int c = 0; @@ -742,7 +749,8 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str Sys::getDateTimeStr() + ": " + String::format( i18n( "starting_process", "Starting %s %s\n" ).toUtf8().c_str(), - cmd.cmd.c_str(), cmd.args.c_str() ) ); + cmd.cmd.c_str(), cmd.args.c_str() ), + nullptr ); std::string buffer( 1024, '\0' ); unsigned bytesRead = 0; @@ -751,7 +759,7 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str bytesRead = mProcess->readStdOut( buffer ); std::string data( buffer.substr( 0, bytesRead ) ); if ( progressFn ) - progressFn( progress, std::move( data ) ); + progressFn( progress, std::move( data ), &cmd ); } while ( bytesRead != 0 && mProcess->isAlive() && !mShuttingDown && !mCancelBuild ); if ( mShuttingDown || mCancelBuild ) { @@ -759,7 +767,7 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str mCancelBuild = false; printElapsed(); if ( doneFn ) - doneFn( EXIT_FAILURE ); + doneFn( EXIT_FAILURE, &cmd ); return; } @@ -773,11 +781,12 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str "The process \"%s\" exited with errors.\n" ) .toUtf8() .c_str(), - cmd.cmd.c_str() ) ); + cmd.cmd.c_str() ), + nullptr ); } printElapsed(); if ( doneFn ) - doneFn( returnCode ); + doneFn( returnCode, &cmd ); return; } else { if ( progressFn ) { @@ -786,22 +795,24 @@ void ProjectBuildManager::runBuild( const std::string& buildName, const std::str "The process \"%s\" exited normally.\n" ) .toUtf8() .c_str(), - cmd.cmd.c_str() ) ); + cmd.cmd.c_str() ), + nullptr ); } } } else { printElapsed(); if ( doneFn ) - doneFn( EXIT_FAILURE ); + doneFn( EXIT_FAILURE, nullptr ); return; } c++; + if ( c == (int)res.cmds.size() ) { + printElapsed(); + if ( doneFn ) + doneFn( EXIT_SUCCESS, &cmd ); + } } - - printElapsed(); - if ( doneFn ) - doneFn( EXIT_SUCCESS ); } void ProjectBuildManager::buildSidePanelTab() { diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp index abe52794d..d2b66e1a0 100644 --- a/src/tools/ecode/projectbuild.hpp +++ b/src/tools/ecode/projectbuild.hpp @@ -95,6 +95,13 @@ struct ProjectBuildConfig { enum class ProjectOutputParserTypes { Error = 0, Warning = 1, Notice = 2 }; +struct PatternOrder { + int file{ 1 }; + int line{ 2 }; + int col{ 3 }; + int message{ 4 }; +}; + struct ProjectBuildOutputParserConfig { static std::string typeToString( ProjectOutputParserTypes type ) { switch ( type ) { @@ -110,12 +117,7 @@ struct ProjectBuildOutputParserConfig { ProjectOutputParserTypes type; std::string pattern; - struct { - int file{ 1 }; - int line{ 2 }; - int col{ 3 }; - int message{ 4 }; - } patternOrder; + PatternOrder patternOrder; }; class ProjectBuildOutputParser { @@ -223,8 +225,9 @@ struct ProjectBuildCommandsRes { bool isValid() { return errorMsg.empty(); } }; -using ProjectBuildProgressFn = std::function; -using ProjectBuildDoneFn = std::function; +using ProjectBuildProgressFn = + std::function; +using ProjectBuildDoneFn = std::function; using ProjectBuildi18nFn = std::function; diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp index e590c0902..d3eb43eda 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.cpp +++ b/src/tools/ecode/statusbuildoutputcontroller.cpp @@ -1,5 +1,6 @@ #include "statusbuildoutputcontroller.hpp" #include "ecode.hpp" +#include "widgetcommandexecuter.hpp" namespace ecode { @@ -40,9 +41,7 @@ void StatusBuildOutputController::hide() { void StatusBuildOutputController::show() { if ( nullptr == mContainer ) { mMainSplitter->updateLayout(); - mContainer = createContainer(); - mContainer->setId( "build_output" ); - mContainer->setVisible( false ); + createContainer(); } if ( !mContainer->isVisible() ) { @@ -55,7 +54,7 @@ void StatusBuildOutputController::show() { } mContainer->setParent( mMainSplitter ); mContainer->setVisible( true ); - mContainer->setFocus(); + mContainer->getFirstChild()->setFocus(); mApp->getStatusBar()->updateState(); } } @@ -90,6 +89,53 @@ UIPushButton* StatusBuildOutputController::getCleanButton( App* app ) { return nullptr; } +bool StatusBuildOutputController::searchFindAndAddStatusResult( + const std::vector& patterns, const std::string& text, + const ProjectBuildCommand* cmd ) { + LuaPattern::Range matches[12]; + for ( const auto& pattern : patterns ) { + if ( pattern.pattern.matches( text, matches ) ) { + StatusMessage status; + status.type = pattern.config.type; + + for ( int i = 0; i < (int)pattern.pattern.getNumMatches(); ++i ) { + if ( !matches[i].isValid() ) + break; + + if ( i == 0 ) { + status.output = text; + continue; + } + + std::string subtxt = text.substr( matches[i].start, matches[i].end ); + if ( pattern.config.patternOrder.message == i ) { + auto nl = subtxt.find_first_of( '\n' ); + if ( nl == std::string::npos ) { + status.message = std::move( subtxt ); + } else { + status.message = subtxt.substr( 0, nl ); + } + } else if ( pattern.config.patternOrder.file == i ) { + status.file = FileSystem::getRealPath( cmd->workingDir + subtxt ); + status.fileName = FileSystem::fileNameFromPath( status.file ); + } else if ( pattern.config.patternOrder.line == i ) { + int l; + if ( String::fromString( l, subtxt ) ) + status.line = l; + } else if ( pattern.config.patternOrder.col == i ) { + int c; + if ( String::fromString( c, subtxt ) ) + status.col = c; + } + } + + mStatusResults.emplace_back( status ); + return true; + } + } + return false; +} + void StatusBuildOutputController::runBuild( const std::string& buildName, const std::string& buildType, const ProjectBuildOutputParser& outputParser ) { @@ -99,17 +145,27 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, auto pbm = mApp->getProjectBuildManager(); show(); + showBuildOutput(); - mContainer->getDocument().reset(); - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mStatusResults.clear(); + if ( mTableIssues ) + mTableIssues->getSelection().clear(); + mBuildOutput->getDocument().reset(); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); std::vector patterns; + mPatternHolder.clear(); + mCurLineBuffer.clear(); + auto configs = { outputParser.getPresetConfig(), outputParser.getConfig() }; for ( const auto& config : configs ) { for ( const auto& parser : config ) { + mPatternHolder.push_back( { LuaPattern( parser.pattern ), parser } ); + SyntaxPattern ptn( { parser.pattern }, getProjectOutputParserTypeToString( parser.type ) ); + patterns.emplace_back( std::move( ptn ) ); } } @@ -123,9 +179,9 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, SyntaxDefinition synDef( "custom_build", {}, patterns ); - mContainer->getDocument().setSyntaxDefinition( synDef ); - mContainer->getVScrollBar()->setValue( 1.f ); - mContainer->getDocument().getHighlighter()->setMaxTokenizationLength( 2048 ); + mBuildOutput->getDocument().setSyntaxDefinition( synDef ); + mBuildOutput->getVScrollBar()->setValue( 1.f ); + mBuildOutput->getDocument().getHighlighter()->setMaxTokenizationLength( 2048 ); UIPushButton* buildButton = getBuildButton( mApp ); if ( buildButton ) @@ -140,15 +196,33 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, auto res = pbm->build( buildName, [this]( const auto& key, const auto& def ) { return mApp->i18n( key, def ); }, buildType, - [this]( auto, auto buffer ) { - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + [this]( auto, std::string buffer, const ProjectBuildCommand* cmd ) { + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); + + if ( nullptr == cmd ) + return; + + do { + auto nl = buffer.find_first_of( '\n' ); + if ( nl != std::string::npos ) { + mCurLineBuffer += buffer.substr( 0, nl ); + searchFindAndAddStatusResult( mPatternHolder, mCurLineBuffer, cmd ); + buffer = buffer.substr( nl + 1 ); + mCurLineBuffer.clear(); + } else { + mCurLineBuffer += buffer; + buffer.clear(); + } + } while ( !buffer.empty() ); }, - [this, enableCleanButton]( auto exitCode ) { + [this, enableCleanButton]( auto exitCode, const ProjectBuildCommand* cmd ) { + if ( !mCurLineBuffer.empty() && nullptr != cmd ) + searchFindAndAddStatusResult( mPatternHolder, mCurLineBuffer, cmd ); String buffer; if ( EXIT_SUCCESS == exitCode ) { @@ -159,11 +233,11 @@ void StatusBuildOutputController::runBuild( const std::string& buildName, mApp->i18n( "build_failed", "Build run with errors\n" ); } - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); UIPushButton* buildButton = getBuildButton( mApp ); @@ -192,8 +266,8 @@ void StatusBuildOutputController::runClean( const std::string& buildName, show(); - mContainer->getDocument().reset(); - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->getDocument().reset(); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); std::vector patterns; @@ -211,8 +285,8 @@ void StatusBuildOutputController::runClean( const std::string& buildName, SyntaxDefinition synDef( "custom_build", {}, patterns ); - mContainer->getDocument().setSyntaxDefinition( synDef ); - mContainer->getVScrollBar()->setValue( 1.f ); + mBuildOutput->getDocument().setSyntaxDefinition( synDef ); + mBuildOutput->getVScrollBar()->setValue( 1.f ); UIPushButton* buildButton = getBuildButton( mApp ); bool enableBuildButton = false; @@ -227,15 +301,15 @@ void StatusBuildOutputController::runClean( const std::string& buildName, auto res = pbm->clean( buildName, [this]( const auto& key, const auto& def ) { return mApp->i18n( key, def ); }, buildType, - [this]( auto, auto buffer ) { - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + [this]( auto, auto buffer, auto ) { + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); }, - [this, enableBuildButton]( auto exitCode ) { + [this, enableBuildButton]( auto exitCode, auto ) { String buffer; if ( EXIT_SUCCESS == exitCode ) { @@ -246,11 +320,11 @@ void StatusBuildOutputController::runClean( const std::string& buildName, mApp->i18n( "clean_failed", "Clean run with errors\n" ); } - mContainer->runOnMainThread( [this, buffer]() { - bool scrollToBottom = mContainer->getVScrollBar()->getValue() == 1.f; - mContainer->getDocument().textInput( buffer ); + mBuildOutput->runOnMainThread( [this, buffer]() { + bool scrollToBottom = mBuildOutput->getVScrollBar()->getValue() == 1.f; + mBuildOutput->getDocument().textInput( buffer ); if ( scrollToBottom ) - mContainer->setScrollY( mContainer->getMaxScroll().y ); + mBuildOutput->setScrollY( mBuildOutput->getMaxScroll().y ); } ); UIPushButton* cleanButton = getCleanButton( mApp ); @@ -270,11 +344,130 @@ void StatusBuildOutputController::runClean( const std::string& buildName, } UICodeEditor* StatusBuildOutputController::getContainer() { - return mContainer; + return mBuildOutput; } -UICodeEditor* StatusBuildOutputController::createContainer() { - UICodeEditor* editor = UICodeEditor::NewOpt( true, true ); +void StatusBuildOutputController::showIssues() { + mBuildOutput->setVisible( false ); + mTableIssues->setVisible( true ); + mButOutput->setSelected( false ); + mButIssues->setSelected( true ); + mTableIssues->setFocus(); +} + +void StatusBuildOutputController::showBuildOutput() { + mBuildOutput->setVisible( true ); + mTableIssues->setVisible( false ); + mButOutput->setSelected( true ); + mButIssues->setSelected( false ); + mBuildOutput->setFocus(); +} + +class StatusMessageModel : public Model { + public: + static std::shared_ptr create( std::vector& data, + UISceneNode* sceneNode ) { + return std::make_shared( data, sceneNode ); + } + + explicit StatusMessageModel( std::vector& data, UISceneNode* sceneNode ) : + mData( data ), mSceneNode( sceneNode ) {} + + virtual size_t rowCount( const ModelIndex& ) const { return mData.size(); } + + virtual size_t columnCount( const ModelIndex& ) const { return 3; } + + virtual Variant data( const ModelIndex& index, ModelRole role ) const { + eeASSERT( index.row() < (Int64)mData.size() ); + static UIIcon* errorIcon = mSceneNode->findIcon( "error" ); + static UIIcon* warnIcon = mSceneNode->findIcon( "warning" ); + if ( role == ModelRole::Display ) { + switch ( index.column() ) { + case 2: + return Variant( mData[index.row()].line ); + case 1: + return Variant( mData[index.row()].fileName.c_str() ); + case 0: + default: + return Variant( mData[index.row()].message ); + } + } else if ( role == ModelRole::Icon && index.column() == 0 ) { + return Variant( mData[index.row()].type == ProjectOutputParserTypes::Error ? errorIcon + : warnIcon ); + } else if ( role == ModelRole::Class ) { + return Variant( mData[index.row()].type == ProjectOutputParserTypes::Error + ? "theme-error" + : "theme-warning" ); + } else if ( role == ModelRole::Custom ) { + switch ( index.column() ) { + case 0: + return Variant( mData[index.row()].file.c_str() ); + case 1: + return Variant( mData[index.row()].line ); + case 2: + return Variant( mData[index.row()].col ); + } + } + return {}; + } + + virtual void update() { onModelUpdate(); } + + virtual std::string columnName( const size_t& idx ) const { + switch ( idx ) { + case 2: + return mSceneNode->i18n( "message", "Message" ); + case 1: + return mSceneNode->i18n( "file", "File" ); + case 0: + return mSceneNode->i18n( "line", "Line" ); + } + return ""; + } + + virtual bool classModelRoleEnabled() { return true; } + + protected: + std::vector& mData; + UISceneNode* mSceneNode; +}; + +void StatusBuildOutputController::onLoadDone( const Variant& lineNum, const Variant& colNum ) { + if ( mSplitter->curEditorExistsAndFocused() && lineNum.isValid() && colNum.isValid() && + lineNum.is( Variant::Type::Int64 ) && colNum.is( Variant::Type::Int64 ) ) { + TextPosition pos{ lineNum.asInt64() > 0 ? lineNum.asInt64() - 1 : 0, colNum.asInt64() }; + mSplitter->getCurEditor()->getDocument().setSelection( pos ); + mSplitter->getCurEditor()->goToLine( pos ); + } +} + +void StatusBuildOutputController::setHeaderWidth() { + auto totWidth = mTableIssues->getPixelsSize().getWidth() - + ( mTableIssues->getVerticalScrollBar()->isVisible() + ? mTableIssues->getVerticalScrollBar()->getPixelsSize().getWidth() + : 0.f ); + mTableIssues->setColumnWidth( 0, totWidth * 0.80f ); + mTableIssues->setColumnWidth( 1, totWidth * 0.15f ); + mTableIssues->setColumnWidth( 2, totWidth * 0.05f ); +} + +void StatusBuildOutputController::createContainer() { + if ( mContainer ) + return; + const auto XML = R"xml( + + + + + + + + + )xml"; + mContainer = mApp->getUISceneNode() + ->loadLayoutFromString( XML, mMainSplitter ) + ->asType(); + auto editor = mContainer->find( "build_output_output" ); editor->setLocked( true ); editor->setLineBreakingColumn( 0 ); editor->setShowLineNumber( false ); @@ -282,7 +475,57 @@ UICodeEditor* StatusBuildOutputController::createContainer() { editor->getDocument().textInput( mApp->i18n( "no_build_has_been_run", "No build has been run" ) ); editor->setScrollY( editor->getMaxScroll().y ); - return editor; + mButOutput = mContainer->find( "but_build_output_output" ); + mButIssues = mContainer->find( "but_build_output_issues" ); + mTableIssues = mContainer->find( "build_output_issues" ); + mTableIssues->setHeadersVisible( true ); + mTableIssues->setModel( StatusMessageModel::create( mStatusResults, mApp->getUISceneNode() ) ); + setHeaderWidth(); + mTableIssues->on( Event::OnSizeChange, [this]( auto ) { setHeaderWidth(); } ); + mTableIssues->on( Event::OnModelEvent, [this]( const Event* event ) { + auto modelEvent = static_cast( event ); + auto idx = modelEvent->getModelIndex(); + if ( modelEvent->getModel() && modelEvent->getModelEventType() == ModelEventType::Open && + idx.isValid() ) { + auto model = modelEvent->getModel(); + Variant vPath( model->data( idx, ModelRole::Custom ) ); + if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { + std::string path( vPath.asCStr() ); + UITab* tab = mSplitter->isDocumentOpen( path ); + Variant lineNum( model->data( model->index( modelEvent->getModelIndex().row(), 1 ), + ModelRole::Custom ) ); + Variant colNum( model->data( model->index( modelEvent->getModelIndex().row(), 2 ), + ModelRole::Custom ) ); + if ( !tab ) { + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + mApp->loadFileFromPath( + path, true, nullptr, + [&, lineNum, colNum]( UICodeEditor*, const std::string& ) { + onLoadDone( lineNum, colNum ); + } ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + onLoadDone( lineNum, colNum ); + } + } + } + } ); + mBuildOutput = editor; + mContainer->setVisible( false ); + auto cont = mContainer->getFirstChild()->asType(); + cont->setCommand( "build-output-show-build-output", [this]() { showBuildOutput(); } ); + cont->setCommand( "build-output-show-build-issues", [this]() { showIssues(); } ); + cont->getKeyBindings().addKeybind( { KEY_1, KeyMod::getDefaultModifier() }, + "build-output-show-build-output" ); + cont->getKeyBindings().addKeybind( { KEY_2, KeyMod::getDefaultModifier() }, + "build-output-show-build-issues" ); + mButOutput->onClick( [this]( auto ) { showBuildOutput(); } ); + mButIssues->onClick( [this]( auto ) { showIssues(); } ); + mButOutput->setTooltipText( + cont->getKeyBindings().getCommandKeybindString( "build-output-show-build-output" ) ); + mButIssues->setTooltipText( + cont->getKeyBindings().getCommandKeybindString( "build-output-show-build-issues" ) ); } } // namespace ecode diff --git a/src/tools/ecode/statusbuildoutputcontroller.hpp b/src/tools/ecode/statusbuildoutputcontroller.hpp index 52ea0785d..9c346c947 100644 --- a/src/tools/ecode/statusbuildoutputcontroller.hpp +++ b/src/tools/ecode/statusbuildoutputcontroller.hpp @@ -2,10 +2,13 @@ #define ECODE_STATUSBUILDOUTPUTCONTROLLER_HPP #include "projectbuild.hpp" +#include #include #include +#include #include #include +#include using namespace EE; using namespace EE::UI; @@ -15,6 +18,21 @@ namespace ecode { class App; +struct StatusMessage { + ProjectOutputParserTypes type; + String output; + String message; + std::string file; + std::string fileName; + Int64 line{ 0 }; + Int64 col{ 0 }; +}; + +struct PatternHolder { + LuaPattern pattern; + ProjectBuildOutputParserConfig config; +}; + class StatusBuildOutputController { public: StatusBuildOutputController( UISplitter* mainSplitter, UISceneNode* uiSceneNode, App* app ); @@ -33,18 +51,38 @@ class StatusBuildOutputController { UICodeEditor* getContainer(); + void showIssues(); + + void showBuildOutput(); + protected: UISplitter* mMainSplitter{ nullptr }; UISceneNode* mUISceneNode{ nullptr }; App* mApp{ nullptr }; UICodeEditorSplitter* mSplitter{ nullptr }; - UICodeEditor* mContainer{ nullptr }; - UICodeEditor* createContainer(); + UIRelativeLayout* mContainer{ nullptr }; + UICodeEditor* mBuildOutput{ nullptr }; + UISelectButton* mButOutput{ nullptr }; + UISelectButton* mButIssues{ nullptr }; + UITableView* mTableIssues{ nullptr }; + + std::vector mStatusResults; + std::vector mPatternHolder; + std::string mCurLineBuffer; + + void createContainer(); UIPushButton* getBuildButton( App* app ); UIPushButton* getCleanButton( App* app ); + + bool searchFindAndAddStatusResult( const std::vector& patterns, + const std::string& text, const ProjectBuildCommand* cmd ); + + void onLoadDone( const Variant& lineNum, const Variant& colNum ); + + void setHeaderWidth(); }; } // namespace ecode diff --git a/src/tools/ecode/uibuildsettings.cpp b/src/tools/ecode/uibuildsettings.cpp index 1bc049c43..3b55348ef 100644 --- a/src/tools/ecode/uibuildsettings.cpp +++ b/src/tools/ecode/uibuildsettings.cpp @@ -381,7 +381,7 @@ static const auto SETTINGS_PANEL_XML = R"xml( - + diff --git a/src/tools/ecode/widgetcommandexecuter.hpp b/src/tools/ecode/widgetcommandexecuter.hpp index 4e3ed317a..a36c8e729 100644 --- a/src/tools/ecode/widgetcommandexecuter.hpp +++ b/src/tools/ecode/widgetcommandexecuter.hpp @@ -44,7 +44,7 @@ class UIGlobalSearchBar : public UILinearLayout, public WidgetCommandExecuter { }; class UIMainLayout : public UIRelativeLayout, public WidgetCommandExecuter { - public: + public: static UIMainLayout* New() { return eeNew( UIMainLayout, () ); } UIMainLayout() : @@ -56,6 +56,21 @@ class UIMainLayout : public UIRelativeLayout, public WidgetCommandExecuter { } }; +class UIRelativeLayoutCommandExecuter : public UIRelativeLayout, public WidgetCommandExecuter { + public: + static UIRelativeLayoutCommandExecuter* New() { + return eeNew( UIRelativeLayoutCommandExecuter, () ); + } + + UIRelativeLayoutCommandExecuter() : + UIRelativeLayout( "rellayce" ), + WidgetCommandExecuter( getUISceneNode()->getWindow()->getInput() ) {} + + virtual Uint32 onKeyDown( const KeyEvent& event ) { + return WidgetCommandExecuter::onKeyDown( event ); + } +}; + } // namespace ecode #endif // ECODE_WIDGETCOMMANDEXECUTER_HPP