From d5db929b3fb0a4fcaea6a512948e69f5f9470a53 Mon Sep 17 00:00:00 2001 From: Patrick Cleaveliln Date: Sat, 5 Jul 2025 20:42:24 +0000 Subject: [PATCH] remove plugin support --- .devcontainer/devcontainer.json | 15 + .../odin-feature/devcontainer-feature.json | 11 + .devcontainer/odin-feature/install.sh | 19 + Makefile | 14 +- liblua5.4.a | Bin 297072 -> 0 bytes plugin-rs-bindings/Cargo.lock | 7 - plugin-rs-bindings/Cargo.toml | 8 - plugin-rs-bindings/src/lib.rs | 781 ----------- plugins/default_view/init.lua | 96 -- plugins/grep/Cargo.lock | 334 ----- plugins/grep/Cargo.toml | 14 - plugins/grep/rust-toolchain.toml | 3 - plugins/grep/src/lib.rs | 449 ------ plugins/highlighter/src/plugin.odin | 423 ------ plugins/lua/view.lua | 526 ------- rust-toolchain | 1 - src/core/core.odin | 373 +++-- src/core/file_buffer.odin | 32 +- src/lua/lua.odin | 1244 ----------------- src/lua/lua.odin~ | 693 --------- src/main.odin | 1140 ++------------- src/plugin/plugin.odin | 451 ------ src/ui/imm.odin | 729 ---------- src/ui/ui.odin | 28 +- todo.md | 50 +- 25 files changed, 453 insertions(+), 6988 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/odin-feature/devcontainer-feature.json create mode 100644 .devcontainer/odin-feature/install.sh delete mode 100755 liblua5.4.a delete mode 100644 plugin-rs-bindings/Cargo.lock delete mode 100644 plugin-rs-bindings/Cargo.toml delete mode 100644 plugin-rs-bindings/src/lib.rs delete mode 100644 plugins/default_view/init.lua delete mode 100644 plugins/grep/Cargo.lock delete mode 100644 plugins/grep/Cargo.toml delete mode 100644 plugins/grep/rust-toolchain.toml delete mode 100644 plugins/grep/src/lib.rs delete mode 100644 plugins/highlighter/src/plugin.odin delete mode 100644 plugins/lua/view.lua delete mode 100755 rust-toolchain delete mode 100644 src/lua/lua.odin delete mode 100644 src/lua/lua.odin~ delete mode 100644 src/plugin/plugin.odin delete mode 100644 src/ui/imm.odin diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..5e05819 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +{ + "image": "mcr.microsoft.com/devcontainers/cpp:ubuntu", + "customizations": { + "vscode": { + "extensions": [ + "danielgavin.ols" + ] + } + }, + "features": { + "./odin-feature": { + "version": "latest" + } + } +} \ No newline at end of file diff --git a/.devcontainer/odin-feature/devcontainer-feature.json b/.devcontainer/odin-feature/devcontainer-feature.json new file mode 100644 index 0000000..c87344c --- /dev/null +++ b/.devcontainer/odin-feature/devcontainer-feature.json @@ -0,0 +1,11 @@ +{ + "name": "Odin Compiler", + "id": "odin", + "version": "1.0.0", + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ], + "containerEnv": { + "ODIN_ROOT": "/usr/local/share/odin" + } +} \ No newline at end of file diff --git a/.devcontainer/odin-feature/install.sh b/.devcontainer/odin-feature/install.sh new file mode 100644 index 0000000..4697550 --- /dev/null +++ b/.devcontainer/odin-feature/install.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -e + +git clone --branch "dev-2024-12" "https://github.com/odin-lang/Odin.git" + +cd Odin + +mkdir -p /usr/local/share/odin +cp -r ./base /usr/local/share/odin/ +cp -r ./core /usr/local/share/odin/ +cp -r ./vendor /usr/local/share/odin/ + +make release + +cp ./odin /usr/local/bin/ + +echo "Installing SDL2 & SDL2_ttf" +apt update +apt --yes --force-yes install libsdl2-dev libsdl2-ttf-dev diff --git a/Makefile b/Makefile index 5b58e46..9fcfd75 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,5 @@ all: editor -editor: src/*.odin # grep odin_highlighter - # odin build src/ -out:bin/editor.o -build-mode:obj -debug -lld - # dsymutil bin/editor.o -o bin/editor.dSYM - ../Odin/odin build src/ -out:bin/editor -debug -linker:lld -extra-linker-flags:"-L./" - -odin_highlighter: plugins/highlighter/src/*.odin - ../Odin/odin build plugins/highlighter/src/ -build-mode:dll -no-entry-point -out:bin/highlighter - -grep: plugins/grep/src/*.rs - nightly-cargo b --manifest-path=plugins/grep/Cargo.toml - cp plugins/grep/target/debug/libgrep_plugin.dylib bin/ +editor: src/**/*.odin + mkdir -p bin + odin build src/ -out:bin/editor -debug \ No newline at end of file diff --git a/liblua5.4.a b/liblua5.4.a deleted file mode 100755 index b9f953ef6cf498378f9230b3a4dc7796877c0f4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297072 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

tKUe%L@eddODDfAHzeM~3@xLMdH^u+nd7SUJ z1)nVbTJej;-zfgs;-4#irTA6i*NDGO{Gj*|@#EsxiT^|KFBShX@f*ayTKwz8zg7HJ z@$VP^M_c!AN2B11b2z?P@Z3tqKM`!n$Kty~;?EGdv%{t4o*7Jsey`wpR1rc!%h ztLX_J%l>Y`9YUYKh~vi=v)?cDVc2wfDq!>JDS}<6r|&5Cdyi(nP12Pd$#{>HlYu#d zo~;YncSwFyxr|pzxiyl09PcT0ZrrCx1^a{Pd_uT|3MIZs+PO>WT`u*o znzazDN8rseg&cWsg;V@z+Sch4ckkJQd za(R7%b)qE4Dz9JCy(a#9;(sB2-cLAx<>&nRu{NXU0KsLqF+Nmq=IxA+7TkRoubc z2)Aw=ZPw=CHE&lJ$!1HF{H)r6xW?(BH%YQt`geIa>TB;vXk|q4-w5l1v#FiCq51`KAl5oM1dxaOr1^vn+n>*XcP_aJJwDg7a^|IIj0aFM~~<_qo+e4=0+rRe-u2@VKeBY3Zk>BWL~+ZmTwIF0dHf=kmGZx-yx zWLzn@_yESb&?Lv&$9Zy@Ztdq91zY>N4#C#`Zcwnb&zlr%?f0_tI6rIOw??qFuiGkk z=Dd+qH62{Bo%3m3&VHfbihRaa`#mcdTkV|^Z0Y6R0;YdQ()S6By4df9($8 zTg85f;QZB$Eq!WU!`SNo;gcDkE&TI~7@s6~W;rRGl0ZrVDG8(`kdi=30x1ckB#@Fo zN&+bfq$H4%KuQ8B38W;Dl0ZrVDG8(`kdi=30x1ckB#@FoN&+bfq$H4%KuQ8B38W;D zl0ZrVDG8(`kdi=30x1ckB#@FoN&+bfq$H4%!2e4LASHp61X2=6NgySGlmt=|NJ$_ifs_PN5=coPC4rO#QW8i>ASHp61X2=6 zNgySGlmt=|NJ$_ifs_PN5=coPC4rO#QW8i>ASHp61X2=6NgySGlmt=|NJ$_ifs_PN z5=coPC4rO#{{N7`yoD>b-fgVgdi>gO_W5hKX06-0blujY)`c^+toy;b&1*NW-Fn>G zdV5AVyL{a_YwK+YoV{-AVe7W6-MVPq)_LoHul%`0*`i@iQqJP|r=*%|O4alFb|6;~HdsUqKSYsY7tvVc>R)BUL z5sOF*J1xjf=(b=epoe*{;DqY;OGIL<3`#_R$HK6{pqn!s6&$XL=utQ(I2zVNXTpLp zE$=CTF;?aJG-~Qj5-_#1kswCCZnF-{cx}<3Hf)WDR>JY1D3mgsfw)U$s6(CVa$}=Y zJ(+pBQ~hjSu~U6`$T?2+yTeX%s(&1|-l=|-UEx&MA9bavgIMpAH z-t1J*9$Vs6|8$f))%%OKIMttW&UdOWmzFx!{khbuPW8qLztbJt8TLBWM+IfhP#7c9 z8&#hcsCjPniBYFJ^bXUd?lHG}BT;`Ss9rRy@w3oT^_(rDhV5Z>eOgS_r~B1i=>c`+ zTu&TAY?;f#e{e30cIVt3u1L@ytX6-T8xL=H1>#b}mGxm`ByB*pU^Tu3NP?$UBK+(3Tf6cL}?n{+Twf;ycTP*Af`rYbTvkF!gLrcA4 zVrBYKy6- zW~sR`^?+T(ZZ?V^`x;x-A+A#nfB&SdBJ}*Bq<%9BWrCmlG+3HhR^#-L*qqI}1I1|6z7D ztSK-MjcK)EOhxMFvx06{OuanUuKF8Dd}P$`OvB(sFMKse&EBp)N(-vLX23*yt1(ah zcy>VDFgqH9&8}9D&h|;@>Dg|wC2?f={Oqbw2oqgUy*yj$_4aIKYHj!;%}VeD1UPy% zl?atoP3a-^-YmOn{;^?bQ*v9nT@77BvIKW%phbhpyZ| zi_Aq&21cm5Vh)U%dK>a?xQX~0ZXtIZu7VB*t79Q4{LNXeSUjrkgdiJk12u!=>r|U1 zx;ph>h9#NKjDXbj7a4B#LPny#BN-yHKV*bM)WV1w%do3nWC?lQuB&h-xgB?rTYoQA zBq5Y{GxVr>9+lYJM)bD(bc%LzyB;83ERg<_!ScESUC{Xuk@~ee1{d@DKzdz;+;cgSrU*5TihJMccWI)r7#O>@!huR(TQzav8TD7jMSUlweoy7qur zC_=I7i7aKSWn+HV0WmdvP{602&Vn|7fzr~jdWpu=YX=0qb+L%ITHTc83htypOBOm) zJ&<+wM)fGTcY_;d=HFEJkox3+X^Y=>kTN}9^{)f8!MW=|Wv*41AE<6Z^g4C@fzfq! z>Q02$Z&SA&Sf^SJLTkUIyqdovx4j-e^KQn}GY69Tcu{gxJqVum%P1b}4Of*npx!w! z3G{!c}HaJ$q1eBgUrs9YhA}dl051uSe~LRO{3P3U9da#*Yl!EfI^qdp*U+h&k&(l@7Z61ck4V3ptGr8S2k~?-Qxx;swMm((UMTR4HQAFe2 z|5!PD$$m ze$3m~%?tWr_U}SvTE>YuuHAi~Qn+mbjARSR%4qgK&GV_2gE46w>{g#0OxrcRGx*6N zA$9K|E|14g!}^UwbZl+3TD@}!T2OC8Uabw}PBhp|+UTjP4t9s)kYf`}z?3G{Ur7v& zaH0A^56^^;9t;)LMcjy-Mm5Xi)x_9;4Y^}YV7Fl!VWv+GX2VGv%NGxcxZGZJk6XQu zw8M8&#+cif^V&l~q>K#i0DaFrlwZfs$Q{vc^Fx#(tSvER_{<>@Y+pIWD@f4|I`Y+S zM+b#l9wxW{5pug9Me2;Gx_N#~JuyF`-k4t<3GLu@#lHD|Ol4S6G#;wg8`mEiR`(z3 zR{uJb^jVh>s=81;I==?M>VQJKI)bEk)6~lZ(%;&{VtX1Rlhk@3dE`cBUB9^2ED{?Tr+;k{GTJo!S^Z0ra$PKqLdpC zqZwjOs`a<>XXBL>a)YK5rnjLl)5FGw@0^U zL;JNV>ZM6F`*Un^WG9O)Uzku54&$iyf8Oa~rw+KSj}`*v$6h z?1)#lJ*0jK&XL=Rr|AxINADy~W^2z@kAS=nQD{{wxr6tT+uTNO@6V_`Rjx?HPm7>z zpSt-7w2qXg`v};-*N=cg#Qa!r&@{8}2y9rw>hdF%=~P!AQAOuD>bfJKxA#$IjSnC* z8|>KQN1$%BHm5vaiCBxMXOE1k zz9V6!{&-{n>vC5Ood8^Q6sp>ghP3rprlHiG!I-O#N96}cdSf_&ipSI!sOs*kh>Rhp z*Q=99V)Cv>+NnlL+jb4Pt=Ez}@F*D5X?VKgbx!r-QBn2EQL#{{%3rM}kMcnnPW8o6 zvJZBu`USNl9JfB2M*Jlv)2OmDBWVa_e6scei#2 zwA=LtvcOvKy#)~$c2Tsw`TGL)rxw(B>s-8M`f>r9aw682s^RE(%(u!Lb*pQRj)kOw z%}0l60e|b!=ms#EV6@jPr(LX-}!{IhL%V$6FOs&mN;ii>&^9 z3})$nrBj8zAh#8+GC>$UMpAzXj=h@J^>59wkKAGHPHDH}D~cJap9Oc~GIHCmAa|fa z$6PxLInq|=@nc!Nx?y+rAr_)&0xjKpQ6N}NdOCJ&jSHgx>#Dz*q@Hr(ooHt7h+g-ETkRI zuNV5%Ul+p8shbzk42knhtWxe1s(Uks3jvsCpmHQ@@H298g=)2^O3`?mmYo^!0 z)b$%8?EWpeqwj0?19H1Z!8#k0(Y;H2>bWHWZ#1fITIyAgF6E-tt4q-$+VKu9^?Ur= z)w@gStafB+w8rm?1=RRbjB&kTbgHM8&_alg0X|rQ9o1zSXjvc@)9WpYeGhb6Nq!FM zo-2ro_3uYZ)Th9cz;>8x#M-Dsu3Ad{-?Vg7Sp9t|T6Z;(o35cmu^Jp6+`E)_;}3v1 zdL0o*ZzLjZ(WwjimSR;G$BZ@whOt|Sq4lTaPTocC#NFif-jiYY)Sto8crW2W?M`X8 z{ywS-PUQmXmRzkVx993pl)H1Ga(B-Ld)GbW_Q6#L;Vd8zN0^x0pi@ugxl~_XSbdd; zMai;i_5QLQdM9!9aysQh*W>kxx^6iQ{o9sPJD*q%)4m+5y~dzqENgZb6bc$In*`Aj^Fc40t~!={hj%8 zH0o5ZP{n(xxV_I)aWVDIab7GQtD@@t0%fyuo;*%6X-Q<#2KkM@NGTg%B6n1~{lB7c z`^)4`Xt&`N3U|IrZu0=Sjc<`VqTN26so~^D{W|}mxSw?CF;u8&NHf1nBs$U9n~!0A z+X}7aI5xd(1@*KyQm3w7AqsLGN$ovK)%iPe+ka1P+bFpsACf!q2Oa+rxy^qechAS< zHhfBM+uwA|XXMsTqTD%EF6=_@DsYD)c=t!U%byis+KyMNhYI{5^;kiz3rm|P3&K!$ zSG89?Q-If8FBV93UMXO;c%y(8H|m`NjKP0X-aV~z;0``aZo`lcUpp7!F`Vt)wI~ww3=;ZyD?F|CLuR9`!JIO> z&D}<`*<@yq8w2JZqsx}H$E<8HN038Gcvm@6~(rmY$=JPk&_u^=XEj~C>u6w_8MchA*0jYWbQ?(cC+3-X=cR+jQPz*Z(6H>?(>=ZEQ|4Y{H}aS?=C7&?HyFjE_BJDs zH<9M4NBisNHrUEX%#z+&`^>ufZ|*Th?A^0Q?d`LB?L%g-trvYYl2$ihuQvi~8q&IE z1y(t_(2skJZuD=rtq}!eW=|UBE6Ya_UDkm17nk&)HAOwPu34Q%zGGFZ(K2_! zh9*_^8GDU>Gp`}NrgUqY5$i!8b=WGp%!;i8M!h*~4$K-qbk!c)uu;)u44OUWkgdfW zGg{0EquV}|w$CUlDamUzJAT-R)*3yt7i`UFH@5CJN7250MguC;YK)+2g-r-FLUSr= zwwCNhLmQ0BA*0P+UQ^Ow#@f*TH5HCs-4J@WeavVw#?U!aMnxWkva1;?)^6@_VbQ;+egGNSqMR~iia>}S#8S5|`O;o-W343fE zvs-MAA}C2sY}__(ZY5nUYBToP+R(Ro?NEojabs63ysNGcnHRSh&9>%)8f+zd%~+tK zrouA}A(l^=HHC#0^+w?uG>W>rqzh`>0_m?QEG&$5*_zDVX_K~^Ht5{AIc#gjFxq2I zm_x=uT18PyTItq$V@**-xZBo(k<)CBU_?!TH=|+R9m(I z(ULxOz#enNJ_4amKok?Ra^&f=RdiE%#yGmM%LcKUqqDkDt8v?2d%xLZZ#6P9+Hqc> zwqsFnAWkp4+-xAu#io>SX$|8G<}uC*G2RZ3=$^|M9|6lkc>WcPuQ-Hp&sB^UWi!rb zVtmaK#$7iuE_N`kZ(+RmaK=;jFkUU`d%76EA@wPLg7NKA-kPTvA1C~UPcwd2_?w<# zJPw;p^=*2CahBA-U+_V~-}olee|9wUH~xE zlNfK7`WLTZe5%N+@D#>p9K-xwf-k`7E9JNQRHp9`d6pM3E|dP5TFdxknwGzuVW#`_Y0oCo$+?*kFrY`W9OsWH*hKAOQk=Gf5dp{ zVN5UnDdUGk-lKwlFY;P*2h+y{Pqs4t0d$PyS59x*@HjFV@4KJzT@J=;9%SrFXWaY{ z<0C{L%J9OBo)1LcS$Jti&kguc{*!_)mi``moav8A|Caom@hj3lyYWJeo`1^to&O8Q zj)k0GyoI!X7yY+SGR~LrKJg{vVaYEKuYKq#5_wL|f?)8JS$H<% zPsE;-pU?OkB9AUF<0{ejy*|bliGJh-89y)e>lgf%l;0R)`j$o9zR5bqw+mjdlW~{m zXT9Lv4(8uQ9f{{b;cxmO<9|r|vvx5~m-?)_gmD)<(uXydGVVNfdj1WM=%a!^ zmi)#9pCjXEH{R#bbFAp+nBXC?r_FfNN6#bDzFxuC%lPiTiRl3cm*=>d@pq+v%LI=| zd3}QCOL-H!nZI80o4SSZI+0J_PZ%F^BnRyq)pY(x2UUB}os= zp_WG(UQW_8Dg6@`?3esI1<#lMUx3$`^t|fe^hNhFHif@k@V}(I1#L_Z2+p{V@k3Hx z?0&}ImGnb`@0ic|SF|&IrRbOA0me7U_}wb_agnF$V0xd}yEegV5l#J{|1i^?V&BUJ zUoP$2^$62*g}?1l#^(rqK=3~na(>;NO#e#yukbO(yG4K71^+?Xm;E@?dn|nuJXhqA z`E#Z}Dg8Mp_x9Uaae_i^cS?~{~eUpOEk^UU$WB!|@ ze!E^`e7?-@MK3eHO6*&^;IG7fjR^i+`YWrS`L7iFS0ngFNk1ldqm;kk73NxBxz z>&4zQ3I2xI@9bBZKgS{R5=`$`N#E*z&GZXIe!YUf2O801157_j>YMo*+G6zY6y<_J}^k1eeNq$oz=uy`sMrf=8wQx&^-h*;4td#+bif)+aH+ zk4b)Ag3lLwpZOQ&KTqhp1Yae%@Dry0RmRJJ;A=#maz17HFQk51e`EZa%x{(SZytF5 zEc#md8RIfZpY?afE}5^g1*eNX?)if0A1~teb<;$N=XueeA_IcJ6B2#z5PX!R_n1uI zA@fZ@aIMIDuZ`(nN&dU-j9-!bcBe7ES?ql&Ev)ct68SnZ8DA~*;sY4}*s2dL1n@kI z5A~1zV8;1UzXgXfK2Y%d1&oiB@xStD#_6IzO~*1W7kSk<7(XugS1x4yo}@2b#MmeH zC$^aJTG4M$F5`^FT)#%ax663y5PZCY>4SoA6n&c%{F&hFJkIa8g4YOcl>D{|{*jEg z2Enz`p4R1@p3FMQt0JHA4w0{C1>;KvPYHg`T8|eny<2eq@r>`6{C1tdxN5@Suo!0 z>he2pV0w|{SJuq9Sn#0WFUj6R=i6UkdM@NEy+!yl(4U0Y2wo2RPPpkv=Kmh_n{dam zjNMpo5f1-dS5KYqVBGvUW0rNHSxBr=Oq2TZs<8MfQ{eNM6AI1;yJB0pE zj0eKa|6=+Zg6Dt2c%7ur{EYEJ!GoVNzER}m_=55OI5@wiKQdk?@+jQP_)EdN{vcTD z8~BLvm8c8VCri>-FJZj$W2T=Y=^f943D4s~cRa)RXM#O~f47j+cRa`RYDr%q>F*GF zc`wsXlm43QVSKCP*CqKiV(d`;=KqT6zry~O@IE;|8kGKZWHEh0Q_X_@_=v#^Ke+Tm|(Q7i9f0^Jtf*+Lp2Inz-o|M-_MjOxXq&{1v zKDn^Z6l{4{lhyswz6EbGK3Q<{JB&-Q9w7db_ZZ(J`H#N9_&nk7c#H8rrGAyYjOPix zTk@+BoG<0?6g(p77YQB~{s9?p-BLc@)9U(EyutZ5!B$iK!hhiQz9i$j=^e&j3O(;b z#x$)HfBx?npJd5H(tBWEh@SI%rqlm`5Uv-DFC1uGF8QB~YUuplW&U|4FMIzFi+Mot9mczG8^+38K%LS91Hml=e1X&iHAes~<7mfn=0^ z@(RW^f_F7AzCrZAS@4Zv9_AyW=X5h35&g;j1><`}e@iZ7JSO99>Zgo97kTFYgz-lX&d;%j@f>My zcN^pLMV>7pKab#jw=?~9X-{4~;~z@-!_uBBqFZ+v}NrwaBaa7RD{2?{+CaAm#59`QYD7b$*r7U-wJ@ja<#?uMznK zg#UHSl~n%9o0&ech;hSqWQ@m<{>|_+zEJSQ7REopd`syo&Si{$5z%;S1>?VnJcopT zjg;Rf`CYP@`E$a|Uw|?xzjim{!>#(s`Su5gF}<;h>30ZyLijI|@w;~`(;cG!qr%@H z{Zk_R4@vpO!hc#0=da{E`zV>;V&`#wFG+hcf55m)aNzrlA8>H`;!4I>;e3kP(;Q1XE=HDpxDzFX} z#e@GP(fMWhnEtTHbKJ$aQsmj-Vf=mRuQKW1d!@Zf`ZrhTmC~QT5q-_Pkkh{^ctM=; zKGFB~DC5_q{tZsXw=d@Ubqjt?>KDF<=?_VNW}nR%|I4G>Q*r_0XQjL>k=J?B-~G}b z7fb){_Cct4K9};w<$c{vLhq9Ibw3w8ApEVCet0?kA%dF(|6Tg0UidE-dse@V`M(nT zxrz)go*oC+f1C_6p8I4xjSGK;LM=B zF8F!s54%!lZD#%0CG*Fay#E>#d3O$Q`d^4XWxUMz-%@_1;5MN*3C6#{>;75wD)aAw zNBX?xHO99Je~-|cq(7#F{}ak1!Izu?gmF`6yE{{WpSFO)$PZjmvi&z~doH`e$qo;{nkxbqHhp zmzb{q6fIQn{8idBXk)xfaAq3gNwFV$493(_YS-N5)n!8wAz5`CS&p6M?Mu9y7(D*Ks|olJjB9=KI7*_zose}=Sq9SFEFNKVroyl;6dT96I?3tTUp8cHd!y!{EG3n zWPCJNG2SidCtqZoCib!3#rQ2Lzftn9miG6a$Ml~{`^$oS!{p z)n^0q|4HOE|1`z{(f@A2anaw-)0zIZ*w4xm#^09u=1(zxRmN9YoNh<;@)!{PTPphADE)P~=u_9ZT;Az2pA`yTF6|pT zhv_}io(@SrA??lB!t|i5$2*1pQAyt~_+ZJuUihDp_6$gVjnW^Z>p8zg!apheCrJO> z&tUq1*pu$>Gd}fDmPgMI7;l&U?s77|R^(m(J;rWHzov$9o}};dGxkgR8o^Cc-}dJr zXgmcnzjRMB{~6L>6>AyKmhn@#o$-1pFI&zp>Kt6(aWB&!m-g*(GftEKbBI2lB=u|b zF+E4*l@nmRQ_`2b#JETFzyCtU=Lv50Fuq>e7bp_CbIuZ)Cs;J?H|0A@h7q#us_K75$XRy zd7pfHI@f>Xk4(Qy>`TpCjIWjY7XFp-a-o;dK?|M_mT>xBd4Ev@I=TaY{2UBcd8TKF<&vxvOuYmp~)B9%NAI!kc8RdCq)bH6D_z1`^IsdE~_+f}8 znSL$gnT+kwhh#hjc_rha8Tq|~^+z(jZAN`RME@t#Z=F%zCg^7}{mvQuPh#zvOn1(x z-?uRSlIed$Hz(s0XUOxx8Tm&s-jn&?m{DF2^e>tICG0^mJ|6ZZ884gB-_95Yv*Wir&51!H9KhEf{_h;ZmGwT0a*u&)Ze0xUy-eMwLe*O)6 zn9P5~jPhTc!JjsRz7^v?IsG@6?vD?gflr^I?KXcW$qap{m?6)f z%&7mFGvqN0dzf7Rt{M4%e@1_uJEQ*(no-`?8T3WOd%m>N%`WfSWfn$IA%`@s_n}I85)W@8me<#e4?{{YC*8?X0#}l82 z@S%SZBYXBZK0n8Y{-uikjp`TpJc&;?K2PEEOMITjrw5;B@Oc&=`uC|`eCT~T{p-_< z_|U&F(KPcbd|t+t7c@>{u<3s=Q^BO+%uR3qwGlK40SV6+Vh= zB|hy4KZ4Jr_;lj)7(OQAQ?ma5Ue*rf#Pxe2TsW{(IXzW$&mk_?!Sy)!#xmVZ=X8f~ zU6{UgkbK0H;xbNrx01dhNwCC;Z@JR9yKy;Y6!&!Lpf41u3V9I6u4cn`MDd++{<63; zOjq;J6*#zUK{@?lmp>BKLEP|G9iR(|G}0Gvgqa6-qBwP^8ebuF*Kip1bmA+y9!6B( z#J7==udZzX--NWj_o!QB-Py$q8=Yak7l>#Zow&RPw_k8L%ohg{SdUUDI)JZH5-!C> zp)L=7*_Saj9@QX3UvPwj5blrERSpo)xw~-{##*}SCX^le9!a-+T- zlCEDO&ZOvsMu-HX7vZK=>eeV-x$0z>?_Z^S*5S%kTr>j;>cN31U6+8bVLR7HLbw%= zxNzetu9wl5uBT< zLHD@UJB2oTy!z%g%}@HoDG}mJqny!g{;)e#TT6G^(S^FWKT-#&x_*7r9Y#%EtH zjeJl_opiB>e9f98lD=uq5p>Ow#i6T2UqUB^(D%JEPvRTw1kXq9@MU%iR!j%Wr-Pd% zsJ~av7ZH+;fIyP(CZzQL6^D*<;wBUN?z^TZh1Vq$w$SjE%OHrcBpFxjjNuAepOuO3 z3Xv<~}i9r|&M6ur?jmt!mtp31g3YCMc!?<{uXa0iazTeD5FE9%;g9 zT9fsCN*qz{-2d7SN?lH=tP4M!bgh8C5rjyjV6ZZB^BiB~T(whQu3+UyH;R;>2{vuD zoOD$hg0PMf@>zkIy-GKdfRhZ9uYg>M@S*d)+Y0FVq@M?x{g7hgaM4)o?D2rNmxM;f$&BIMD(?kM@u_E=%NB1abmd#GOUdOsQhZ1deChp!L zC|$4bjN<-*)k22xD$FwoV6-8xINisD3}GwvT^XD*nsk2@#hi}oabdWpSx%Q_1*e%o z;&Cj@C?h!;27WTsSHvVoByP{6VmIh%Xqrq4FIO^htw_a$?6Cq!SBj}#?*4G%dI!3d zgZN9GbeqLAMkA&%-4uzrFPScvvEv%{=`^KD;dNG6PcA;(#zpew%bKlgsj1xcV*e7? zfFVcg-gRva`3iPoMZ9&*sZ-y#%{4E^%)0-5a!wy+YkjjEB|9$xC}+OFUFR%8-FUj? zLvE|lMftHn=0xPD7rM!*^ek*e{MV)SFdw)J(eI}Dfvy6gOiQ6k zF}VXos*#K!J`7Q7MXFP*Lz9wNWVxLbqbp%y+?z%A#eygiT4UYm=}fGqwe_bfKy{Cy zgD6yO-CNEpNV$)k=7G8}s-iD3#{j~eH@M$Y$LXHeAy`UnO|e8S2;you+*C=;@i0hqFQQHW1}j_)6Rs~zq}i1A6$S92gjNRh zvjbcOeG4iD&%r(0`rbdS92`QlGZ8vRg0#AXg}~*hi5vf?e+s}^E4QdqE_8D^`T7cR zt@S!W?^^WDdQ|RuaCnIiOFfKXgp#U^T?Jl4>P*PREqVmwQqQZP0^Ke|<+AiG^-Xcg7+}ioxm)*O2|fZOxRu+`2<)MW?M8 zNMyS|6o;}R2Uuv_?_A)=y>bC;Sf-eY3n5p%RKDswgOCZ{!B_Xs@ zuJ4icK2=O>wEp!54x$oTI9j>mUVZhhE@!_8ovM~tu}^kYyC{U2KZd1}zZzl8`e70^ zvI?;86EEVG0AIV!8x{HgbdikmW#9~EmamsC7w4Aeun9n##B7prVMPXy% z#ngb1D;BEtyHC_%ZZq9g&u!Kr6u1S6crnMaj)koIskUbXHrOaw8cAb%cHafU>5d zHT8V#8zhL?LoO@j>eYp5-ozNx8w~-d4fvR#Wo zhdrYJZWFGOgHgtS;r(Y4L=mS!UKq!6sVU`l8gGlWM(bL@jwCK6Wd@j;#BHWr0Tghe zE&&;kmh$avMALdY4Oxo>(@f}uGI%3YIo>ej6J%t1`rws0#yhH3zs((;O%z0gCl{oeY~6 zE@z>2NV22;Mvx}J#Lv(G(a(3t@7Z8YLxx!=r9GcV!cO_c1TwdvV!MD-3|wZ0(Fwa+ z#bL4-dU>Lg7dxjf7PbTeYeb3U5JnJ$b)Iwj%1+`f#<+sXmn(*KYMt?P3NhEy5vVO4 z0ZvC0(~^hoL!FM=z&!iKOkeWp#BVxK?P9pASO*{_=;?HrL+AlaCUQ9*RSj#}{V>aI z?Yi|Oo`iwnOByM{+R|(4W^3%|>o@5VL1IDslkQulm@U{@$Ss3v0tvr#M_q)fkm5b^2@N_C4k1Rma2(Na*~l`acL3!qOVsQcRjNJr|2 zvjn==jYhSEKscQ@33Tr_TQyultD8#4i`G57S_*zlHMICeGqB>d!pLIN{*czgNmRX{ zwyxLH8DVd+AAKg)bx&r|7C1Qq8z>JZG^q}%pgCsZ#Fe;I>L$&yUzCoqF3X+fNsiG4 zCFRS<$Vm}et`^qDleWBML$su5;kr4gnzZiH1k{Lkj~1dcAZ#v~s`tLWPyov*?u;`b zQ+>5DRWo_buDgmOtgHU@i7s9BKYeC%qK3(HPmt)Olu!lTqflV|i$Bh&_47VXpZv}b z_Ne$>DcyD8(kHJ{EY8UBD@#PY!7fe=n)pwr-BP$w&;^#CqFqzp@8hvYs) z3o-$x{Q5AzfEaZ>aje3mEzU%chMhKJOrdEd2_~Px>gzJO2pot}&&j{k4U!_LSct|yBZ^F2QWWd}m}H%-OW6qdmTx?|YueVTrgI3rhzw@R|{4`xcSYUDz>Y(YMtnYNTs78Wiqmw$Fe8zJHY(%K<%7IYI7TS_;s9l#P3bg53z<~AP5+o0bt?bh7>&WiFZB^%I9_R_qa@r4)FgQkEaXSr>m6j9}sXuv{ zOs(cP61Ds`nX?*#r?*PdYJL%h={f1w@vywGe%g;kW4#8owpUmWyLaMu4s{+h{`o>z zVx~4FAs!sl5v4>=TntPJ^x&hFkub4=Ns6FMQN;c+`b2yGsC6^A*c9#0L#6K@v+lp6 zw1Q1n8K)7`KQG1dNW$nSR8IFI#PK!1s!1uZCY`?K-KmW=L|8zRFI|sIEV@vdQ>+M5 z$mI7snhP(^=x%jNKnJjiA6}4MhJIr}OD$9>@diP2^AdXhjVJ%QCuuhQ*d0%p`X?N% zJvqgqBt3U3Cmp!qWmYJNSTb?6o7D7Ty#uy>%!5*_QeF5)Xrhp`1yOnlVp~1U zqE&=CX*y~X+RDFgK+~4YCit}jo#XzHUdHlI^$-zT7T5kimBO*wTqcg6afCwW>9hvI z`!p}|)h3A#EwFn-1Rq>rs!GrhO1z84yrqr)Birek+jw3pKsH3hIPR(nU2^% z_GZ5rtc;TA+H@yl-GHQs4O%g*2q+2F2E(2M)#Cfahh)y54pyL^)^BcL{YF2`iPaW5 z3O>8w-=yCx(l1d^LfQqQVNNSRi@8vQ3=U%He(iuVJ>EN6N;*#I{!-PRU z`zJ?N^uHu^_yH0U65dtvuaQ8XCCc*gk|XhxKg6n6-1^FMt(hFs@zarXL%DRL4&wX? zZ?Lo~Yu}I66Cm}x4q%p}mgumjildTN9tC)}8HSO--kWyGIs{?S>Lp1rO5;GfB6Lzf zO0A)O>%|w9Gc}gD241)+&uIqT{ek^{tBv?znP_FEuaDOluSt^enfkzhUefDQ%M-H% zew={#&y+&}>jz9IZoN}}WQJL`Ko9w~17n%N<%pHz5=r=XW+<1$uTXL^WVFDGjZN*& zAdWyd-I)+J|GbJ+Ci%BjI7(Cv{IxiK#fhJvj)2RzXnp!$?Ok~|RBam{YrMsfVX|+p zeH+TYlr__vB~zi)Ft!;>#yU*4NKtl)5i`P2YO%HFV z`o8}7{_Xkax$oz`?{lu_KIcB?InQ%H=Z63~0bC)()~Ay7&n^apZ7|v;fn&!0Z`=Wn7gXnC(xXCuNgQvoXLkmU{*Vd$?=|Ao;fClWg-X2?jQ{TQF zfdOr3kOm{F7%0UI4d4;_6D$Vsn&-b8Re?4n(6~JXz_3;a%=|n7fZBhQ)v-N|0M5kJ zb{5~30LZ*&Y&zrYW+ZF^^(#P!!QTNf14-&zGSD5McKAVCf>`m<63kn_6lBQ%cVUU% zkpmG3bVF5B^?<6DDlni2f;Br#BGvkiJ9ltsBHNMtayx6U=!R!Pg(}N^+Qm@Ql}kIe z|AipOR8?t6Hoo0(Wp@gel{Jew?}|ipuL~kpyKjA1d(1?Mx?Abs*E-|T$U4~D>&%Lza@{{z#vD4{M0BVV}j&ebi6m%0yc=J{|gsXeRd{ z|7gjD22E-YhOW?Z4k}7dsrb_5Y2klfsOc3k>SUKxl_H$u9BJ(XXHX{1kUDyhfOX{@ zZmKo77hiPU{K^<=Z9AlJpoNcEYC$NBkB>@Fi0XLqR{xeH#&LmoaU+fP?&A2sD{rx zgwn2sujxj%ycWR2ZMpqt#7+7G@gV7?V8OWMB2m8U>XP@?doUK3;Z}*IuD*71BlxZ> z@b*}h>1?yl=4xlG+23f3*BN-_k9FGZFP$CgyEXW*(%?bH1FD&w{k!ag9)z4Tmy*9N z4*CWq9{Zjt5-SV$ALW7HP*&`Ft_vFChrA>a|SVc#5n^Ns#6hby>AmlIK*Zh654s-yHO zfj&%z2B1pkOg!$LJUrI-TB&2)3U_%yv2Hn7RXU%gzd!Sfr#a^{K7hLSxLElME&$*0NfOA^6VCPQ`5g^%+uIq@%uqn3Qo zB4f0S*KO2olCjzvQ)UBqMeXo%Q`wM*(p>``w8xRgm$gS@2S=S9tqOSh$;9+}n3E2f zD`nX>u#j*AWkvya7Cd+QTDNE$Inn4d69&>Ta&G%VKQ;5I#Gtx=VL~4j%39IlIZx!S z{T3C<>ksQrcw1DZ;>HGzE>JM#mmhEKB6WzMZDu&9qoxc&Wml%kmBJ|M5224c{pl7K z0yy_mCAZtWeFY;l~bv^Oo zn#229a96Oqu?P|F+Dn{AIDE0rxrh52rI#NO=qtUv6hFIE-L$?jBRGV}JvQt~Si7e5 znag>(6q^A3pijfndOf>t3i&k%6Dm3{^}j}BpA_6&pb$W&C1Dyp{rnv5<&SiEILfg`>y!eo7zMVU57u`>oFta>mY^Qr-RfJwG3z*0hGm1KA0_-(@-XM zAucAvn)%qi?&3P^>FK#J2dquJoz>oF^l+i1=BZ6j6q5TuCDM&sDpm3=f_I!w0mtV;MOemSbP)_WY@EWxo~eBrE3FDzLg@EfYD zC`1Rj(Y4_Mp@?<=>izdC`6>IfI?A83vpF0PE~{kH8R;U<38ZK)N}Y%n{=9K~@mKc$ zuY8X&oacO(weg=DnIg-U8s)cvQ!;DAGK%C|md?@MPE@yf(PgpR#FpWP=H+>{bc4pc#)HNzH%g84= zsPfi3!fQ{=Y;Vj(BbUr3;oN31q|dGfQqS6obllp!ExKwH>8pnA&rQzCH+xG@?cKNf zxaHQ1*H2JfTnaDTW3RQ1pt9D44As6l-IUqXz=P9`5qH#CJLCdhOYg#Hax}N4jsin9 f#-I(@90hFG7~9RDfsHo5%Wn_-_P}os03P@cD$MQf diff --git a/plugin-rs-bindings/Cargo.lock b/plugin-rs-bindings/Cargo.lock deleted file mode 100644 index 730d5d6..0000000 --- a/plugin-rs-bindings/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "plugin_rs_bindings" -version = "0.1.0" diff --git a/plugin-rs-bindings/Cargo.toml b/plugin-rs-bindings/Cargo.toml deleted file mode 100644 index 8030a20..0000000 --- a/plugin-rs-bindings/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "plugin_rs_bindings" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/plugin-rs-bindings/src/lib.rs b/plugin-rs-bindings/src/lib.rs deleted file mode 100644 index e875a36..0000000 --- a/plugin-rs-bindings/src/lib.rs +++ /dev/null @@ -1,781 +0,0 @@ -use std::{ - borrow::Cow, - ffi::{c_char, c_void, CStr, CString}, - fmt::write, - path::Path, -}; - -#[macro_export] -macro_rules! Closure { - (($($arg: ident: $type: ty),+) => $body: expr) => { - { - extern "C" fn f($($arg: $type),+) { - $body - } - f - } - }; - (($($arg: ident: $type: ty),+) -> $return_type: ty => $body: expr) => { - { - extern "C" fn f($($arg: $type),+) -> $return_type { - $body - } - f - } - }; -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct InputMap { - internal: *const std::ffi::c_void, -} - -#[repr(C)] -#[derive(Debug)] -pub struct BufferIndex { - pub slice_index: isize, - pub content_index: isize, -} - -#[repr(C)] -#[derive(Debug)] -pub struct Cursor { - pub col: isize, - pub line: isize, - pub index: BufferIndex, -} - -#[repr(C)] -#[derive(Debug)] -struct InternalBufferIter { - cursor: Cursor, - buffer: *const c_void, - hit_end: bool, -} - -#[repr(C)] -pub struct IterateResult { - pub char: u8, - pub should_continue: bool, -} - -#[repr(C)] -#[derive(Debug)] -pub struct BufferInput { - bytes: *const u8, - length: isize, -} - -impl BufferInput { - pub fn try_as_str(&self) -> Option> { - if self.bytes.is_null() { - None - } else { - let slice = unsafe { std::slice::from_raw_parts(self.bytes, self.length as usize) }; - - Some(String::from_utf8_lossy(slice)) - } - } -} - -#[repr(C)] -#[derive(Debug)] -pub struct BufferInfo { - pub buffer: Buffer, - pub file_path: *const i8, - pub input: BufferInput, - - pub cursor: Cursor, - - pub glyph_buffer_width: isize, - pub glyph_buffer_height: isize, - pub top_line: isize, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Buffer { - internal: *const c_void, -} - -impl Buffer { - pub fn null() -> Buffer { - Buffer { - internal: std::ptr::null(), - } - } -} - -#[repr(C)] -pub struct BufferVTable { - pub get_num_buffers: extern "C" fn() -> isize, - get_buffer_info: extern "C" fn(buffer: Buffer) -> BufferInfo, - pub get_buffer_info_from_index: extern "C" fn(buffer_index: isize) -> BufferInfo, - pub color_char_at: extern "C" fn( - buffer: *const c_void, - start_cursor: Cursor, - end_cursor: Cursor, - palette_index: i32, - ), - pub set_current_buffer: extern "C" fn(buffer_index: isize), - - open_buffer: extern "C" fn(path: *const u8, line: isize, col: isize), - open_virtual_buffer: extern "C" fn() -> *const c_void, - free_virtual_buffer: extern "C" fn(buffer: Buffer), -} - -impl BufferVTable { - pub fn get_buffer_info(&self, buffer: Buffer) -> Option { - if buffer.internal.is_null() { - None - } else { - Some((self.get_buffer_info)(buffer)) - } - } - pub fn open_buffer(&self, path: impl AsRef, line: i32, col: i32) { - let Ok(c_str) = CString::new(path.as_ref().as_os_str().as_encoded_bytes()) else { - eprintln!("grep plugin failed to open buffer"); - return; - }; - (self.open_buffer)(c_str.as_ptr() as *const u8, line as isize, col as isize); - } - pub fn open_virtual_buffer(&self) -> Buffer { - Buffer { - internal: (self.open_virtual_buffer)(), - } - } - pub fn free_virtual_buffer(&self, buffer: Buffer) { - (self.free_virtual_buffer)(buffer); - } -} - -#[repr(C)] -pub struct IteratorVTable { - get_current_buffer_iterator: extern "C" fn() -> InternalBufferIter, - get_buffer_iterator: extern "C" fn(buffer: *const c_void) -> InternalBufferIter, - get_char_at_iter: extern "C" fn(it: *const InternalBufferIter) -> u8, - get_buffer_list_iter: extern "C" fn(prev_buffer: *const isize) -> isize, - - iterate_buffer: extern "C" fn(it: *mut InternalBufferIter) -> IterateResult, - iterate_buffer_reverse: extern "C" fn(it: *mut InternalBufferIter) -> IterateResult, - iterate_buffer_until: extern "C" fn(it: *mut InternalBufferIter, until_proc: *const c_void), - iterate_buffer_until_reverse: - extern "C" fn(it: *mut InternalBufferIter, until_proc: *const c_void), - iterate_buffer_peek: extern "C" fn(it: *mut InternalBufferIter) -> IterateResult, - - pub until_line_break: *const c_void, - pub until_single_quote: *const c_void, - pub until_double_quote: *const c_void, - pub until_end_of_word: *const c_void, -} - -#[repr(C)] -pub struct UiInteraction { - pub hovering: bool, - pub clicked: bool, -} - -#[repr(C)] -struct InternalUiSemanticSize { - kind: isize, - value: isize, -} - -#[repr(isize)] -pub enum UiAxis { - Horizontal = 0, - Vertical, -} - -pub enum UiSemanticSize { - FitText, - Exact(isize), - ChildrenSum, - Fill, - PercentOfParent(isize), -} - -impl From for InternalUiSemanticSize { - fn from(value: UiSemanticSize) -> Self { - let (kind, value) = match value { - UiSemanticSize::FitText => (0, 0), - UiSemanticSize::Exact(value) => (1, value), - UiSemanticSize::ChildrenSum => (2, 0), - UiSemanticSize::Fill => (3, 0), - UiSemanticSize::PercentOfParent(value) => (4, value), - }; - - Self { kind, value } - } -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct UiContext(*const c_void); - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct UiBox(*const c_void); - -type UiPushParentProc = extern "C" fn(ui_context: UiContext, ui_box: UiBox); -type UiPopParentProc = extern "C" fn(ui_context: UiContext); -type UiFloatingProc = - extern "C" fn(ui_context: UiContext, label: *const i8, pos: [isize; 2]) -> UiBox; -type UiRectProc = extern "C" fn( - ui_context: UiContext, - label: *const i8, - border: bool, - border: bool, - axis: UiAxis, - size: [InternalUiSemanticSize; 2], -) -> UiBox; -type UiSimpleProc = extern "C" fn(ui_context: UiContext, label: *const i8) -> UiInteraction; -type UiBufferProc = extern "C" fn(ui_context: UiContext, buffer: Buffer, show_line_numbers: bool); - -#[repr(C)] -pub struct UiVTable { - ui_context: UiContext, - - push_parent: UiPushParentProc, - pop_parent: UiPopParentProc, - - spacer: UiSimpleProc, - floating: UiFloatingProc, - rect: UiRectProc, - - button: UiSimpleProc, - label: UiSimpleProc, - - buffer: UiBufferProc, - buffer_from_index: UiBufferProc, -} - -impl UiVTable { - pub fn push_parent(&self, ui_box: UiBox) { - (self.push_parent)(self.ui_context, ui_box); - } - pub fn pop_parent(&self) { - (self.pop_parent)(self.ui_context); - } - - pub fn spacer(&self, label: &CStr) -> UiInteraction { - (self.spacer)(self.ui_context, label.as_ptr()) - } - - pub fn push_rect( - &self, - label: &CStr, - show_background: bool, - show_border: bool, - axis: UiAxis, - horizontal_size: UiSemanticSize, - vertical_size: UiSemanticSize, - inner: impl FnOnce(&UiVTable), - ) { - let rect = (self.rect)( - self.ui_context, - label.as_ptr(), - show_background, - show_border, - axis, - [horizontal_size.into(), vertical_size.into()], - ); - self.push_parent(rect); - - inner(self); - - self.pop_parent(); - } - - pub fn push_floating(&self, label: &CStr, x: isize, y: isize, inner: impl FnOnce(&UiVTable)) { - let floating = (self.floating)(self.ui_context, label.as_ptr(), [x, y]); - self.push_parent(floating); - - inner(self); - - self.pop_parent(); - } - - pub fn label(&self, label: &CStr) -> UiInteraction { - (self.label)(self.ui_context, label.as_ptr()) - } - pub fn button(&self, label: &CStr) -> UiInteraction { - (self.button)(self.ui_context, label.as_ptr()) - } - - pub fn buffer(&self, buffer: Buffer, show_line_numbers: bool) { - (self.buffer)(self.ui_context, buffer, show_line_numbers) - } -} - -type OnColorBufferProc = extern "C" fn(plugin: Plugin, buffer: *const c_void); -type OnHookProc = extern "C" fn(plugin: Plugin, buffer: Buffer); -type InputGroupProc = extern "C" fn(plugin: Plugin, input_map: InputMap); -type InputActionProc = extern "C" fn(plugin: Plugin); -type WindowDrawProc = extern "C" fn(plugin: Plugin, window: *const c_void); -type WindowFreeProc = extern "C" fn(plugin: Plugin, window: *const c_void); -type WindowGetBufferProc = extern "C" fn(plugin: Plugin, window: *const c_void) -> Buffer; -#[repr(C)] -pub struct Plugin { - state: *const c_void, - - pub iter_table: IteratorVTable, - pub buffer_table: BufferVTable, - pub ui_table: UiVTable, - - pub register_hook: extern "C" fn(hook: Hook, on_hook: OnHookProc), - pub register_highlighter: - extern "C" fn(extension: *const c_char, on_color_buffer: OnColorBufferProc), - - pub register_input_group: - extern "C" fn(input_map: InputMap, key: Key, register_group: InputGroupProc), - pub register_input: extern "C" fn( - input_map: InputMap, - key: Key, - input_action: InputActionProc, - description: *const u8, - ), - - pub create_window: extern "C" fn( - user_data: *const c_void, - register_group: InputGroupProc, - draw_proc: WindowDrawProc, - free_window_proc: WindowFreeProc, - get_buffer_proc: *const (), - ) -> *const c_void, - get_window: extern "C" fn() -> *const c_void, - - pub request_window_close: extern "C" fn(), - pub get_screen_width: extern "C" fn() -> isize, - pub get_screen_height: extern "C" fn() -> isize, - pub get_font_width: extern "C" fn() -> isize, - pub get_font_height: extern "C" fn() -> isize, - get_current_directory: extern "C" fn() -> *const c_char, - pub enter_insert_mode: extern "C" fn(), - - pub draw_rect: extern "C" fn(x: i32, y: i32, width: i32, height: i32, color: PaletteColor), - pub draw_text: extern "C" fn(text: *const c_char, x: f32, y: f32, color: PaletteColor), - pub draw_buffer_from_index: extern "C" fn( - buffer_index: isize, - x: isize, - y: isize, - glyph_buffer_width: isize, - glyph_buffer_height: isize, - show_line_numbers: bool, - ), - pub draw_buffer: extern "C" fn( - buffer: Buffer, - x: isize, - y: isize, - glyph_buffer_width: isize, - glyph_buffer_height: isize, - show_line_numbers: bool, - ), -} - -pub struct BufferIter { - iter: InternalBufferIter, - iter_table: IteratorVTable, -} - -impl BufferIter { - pub fn new(plugin: Plugin, buffer: Buffer) -> Self { - let buffer_info = (plugin.buffer_table.get_buffer_info)(buffer); - - Self { - iter: InternalBufferIter { - cursor: buffer_info.cursor, - buffer: buffer.internal, - hit_end: false, - }, - iter_table: plugin.iter_table, - } - } -} - -impl Iterator for BufferIter { - type Item = char; - - fn next(&mut self) -> Option { - let iter_ptr = (&mut self.iter) as *mut InternalBufferIter; - - let result = (self.iter_table.iterate_buffer)(iter_ptr); - if result.should_continue { - Some(result.char as char) - } else { - None - } - } -} - -pub struct BufferListIter { - index: isize, - next_fn: extern "C" fn(prev_buffer: *const isize) -> isize, -} - -impl From<&Plugin> for BufferListIter { - fn from(value: &Plugin) -> Self { - BufferListIter { - index: 0, - next_fn: value.iter_table.get_buffer_list_iter, - } - } -} - -impl Iterator for BufferListIter { - type Item = isize; - - fn next(&mut self) -> Option { - if self.index == -1 { - return None; - } - - Some((self.next_fn)(&mut self.index)) - } -} - -impl Plugin { - pub fn get_current_directory(&self) -> Cow { - unsafe { - let c_str = CStr::from_ptr((self.get_current_directory)()); - - c_str.to_string_lossy() - } - } - - /// # Safety - /// If `W` is not the same type as given in `self.create_window`, it will result in undefined - /// behavior. `W` can also be a different type if another plugin has created a window. - pub unsafe fn get_window<'a, W>(&self) -> Option<&'a mut W> { - let window_ptr = (self.get_window)() as *mut W; - - if window_ptr.is_null() { - None - } else { - let window = Box::from_raw(window_ptr); - Some(Box::leak(window)) - } - } - - pub fn create_window( - &self, - window: W, - register_group: InputGroupProc, - draw_proc: WindowDrawProc, - free_window_proc: WindowFreeProc, - get_buffer_proc: Option, - ) { - let boxed = Box::new(window); - (self.create_window)( - Box::into_raw(boxed) as *const std::ffi::c_void, - register_group, - draw_proc, - free_window_proc, - if let Some(proc) = get_buffer_proc { - proc as *const () - } else { - std::ptr::null() - }, - ); - } - pub fn register_hook(&self, hook: Hook, on_hook: OnHookProc) { - (self.register_hook)(hook, on_hook) - } - pub fn register_input_group( - &self, - input_map: Option, - key: Key, - register_group: InputGroupProc, - ) { - let input_map = match input_map { - Some(input_map) => input_map, - None => InputMap { - internal: std::ptr::null(), - }, - }; - - (self.register_input_group)(input_map, key, register_group); - } -} - -#[repr(i32)] -pub enum Hook { - BufferInput, -} - -#[repr(i32)] -pub enum Key { - UNKNOWN = 0, - Enter = 13, - ESCAPE = 27, - BACKSPACE = 8, - TAB = 9, - Space = 32, - EXCLAIM = 33, - QUOTEDBL = 34, - HASH = 35, - PERCENT = 37, - DOLLAR = 36, - AMPERSAND = 38, - QUOTE = 39, - LEFTPAREN = 40, - RIGHTPAREN = 41, - ASTERISK = 42, - PLUS = 43, - COMMA = 44, - MINUS = 45, - PERIOD = 46, - SLASH = 47, - NUM0 = 48, - NUM1 = 49, - NUM2 = 50, - NUM3 = 51, - NUM4 = 52, - NUM5 = 53, - NUM6 = 54, - NUM7 = 55, - NUM8 = 56, - NUM9 = 57, - COLON = 58, - SEMICOLON = 59, - LESS = 60, - EQUAL = 61, - GREATER = 62, - QUESTION = 63, - AT = 64, - LEFTBRACKET = 91, - BACKSLASH = 92, - RIGHTBRACKET = 93, - CARET = 94, - UNDERSCORE = 95, - BACKQUOTE = 96, - A = 97, - B = 98, - C = 99, - D = 100, - E = 101, - F = 102, - G = 103, - H = 104, - I = 105, - J = 106, - K = 107, - L = 108, - M = 109, - N = 110, - O = 111, - P = 112, - Q = 113, - R = 114, - S = 115, - T = 116, - U = 117, - V = 118, - W = 119, - X = 120, - Y = 121, - Z = 122, - CAPSLOCK = 1073741881, - F1 = 1073741882, - F2 = 1073741883, - F3 = 1073741884, - F4 = 1073741885, - F5 = 1073741886, - F6 = 1073741887, - F7 = 1073741888, - F8 = 1073741889, - F9 = 1073741890, - F10 = 1073741891, - F11 = 1073741892, - F12 = 1073741893, - PRINTSCREEN = 1073741894, - SCROLLLOCK = 1073741895, - PAUSE = 1073741896, - INSERT = 1073741897, - HOME = 1073741898, - PAGEUP = 1073741899, - DELETE = 127, - END = 1073741901, - PAGEDOWN = 1073741902, - RIGHT = 1073741903, - LEFT = 1073741904, - DOWN = 1073741905, - UP = 1073741906, - NUMLOCKCLEAR = 1073741907, - KpDivide = 1073741908, - KpMultiply = 1073741909, - KpMinus = 1073741910, - KpPlus = 1073741911, - KpEnter = 1073741912, - Kp1 = 1073741913, - Kp2 = 1073741914, - Kp3 = 1073741915, - Kp4 = 1073741916, - Kp5 = 1073741917, - Kp6 = 1073741918, - Kp7 = 1073741919, - Kp8 = 1073741920, - Kp9 = 1073741921, - Kp0 = 1073741922, - KpPeriod = 1073741923, - APPLICATION = 1073741925, - POWER = 1073741926, - KpEquals = 1073741927, - F13 = 1073741928, - F14 = 1073741929, - F15 = 1073741930, - F16 = 1073741931, - F17 = 1073741932, - F18 = 1073741933, - F19 = 1073741934, - F20 = 1073741935, - F21 = 1073741936, - F22 = 1073741937, - F23 = 1073741938, - F24 = 1073741939, - EXECUTE = 1073741940, - HELP = 1073741941, - MENU = 1073741942, - SELECT = 1073741943, - STOP = 1073741944, - AGAIN = 1073741945, - UNDO = 1073741946, - CUT = 1073741947, - COPY = 1073741948, - PASTE = 1073741949, - FIND = 1073741950, - MUTE = 1073741951, - VOLUMEUP = 1073741952, - VOLUMEDOWN = 1073741953, - KpComma = 1073741957, - KpEqualsas400 = 1073741958, - ALTERASE = 1073741977, - SYSREQ = 1073741978, - CANCEL = 1073741979, - CLEAR = 1073741980, - PRIOR = 1073741981, - RETURN2 = 1073741982, - SEPARATOR = 1073741983, - OUT = 1073741984, - OPER = 1073741985, - CLEARAGAIN = 1073741986, - CRSEL = 1073741987, - EXSEL = 1073741988, - Kp00 = 1073742000, - Kp000 = 1073742001, - THOUSANDSSEPARATOR = 1073742002, - DECIMALSEPARATOR = 1073742003, - CURRENCYUNIT = 1073742004, - CURRENCYSUBUNIT = 1073742005, - KpLeftparen = 1073742006, - KpRightparen = 1073742007, - KpLeftbrace = 1073742008, - KpRightbrace = 1073742009, - KpTab = 1073742010, - KpBackspace = 1073742011, - KpA = 1073742012, - KpB = 1073742013, - KpC = 1073742014, - KpD = 1073742015, - KpE = 1073742016, - KpF = 1073742017, - KpXor = 1073742018, - KpPower = 1073742019, - KpPercent = 1073742020, - KpLess = 1073742021, - KpGreater = 1073742022, - KpAmpersand = 1073742023, - KpDblampersand = 1073742024, - KpVerticalbar = 1073742025, - KpDblverticalbar = 1073742026, - KpColon = 1073742027, - KpHash = 1073742028, - KpSpace = 1073742029, - KpAt = 1073742030, - KpExclam = 1073742031, - KpMemstore = 1073742032, - KpMemrecall = 1073742033, - KpMemclear = 1073742034, - KpMemadd = 1073742035, - KpMemsubtract = 1073742036, - KpMemmultiply = 1073742037, - KpMemdivide = 1073742038, - KpPlusminus = 1073742039, - KpClear = 1073742040, - KpClearentry = 1073742041, - KpBinary = 1073742042, - KpOctal = 1073742043, - KpDecimal = 1073742044, - KpHexadecimal = 1073742045, - LCTRL = 1073742048, - LSHIFT = 1073742049, - LALT = 1073742050, - LGUI = 1073742051, - RCTRL = 1073742052, - RSHIFT = 1073742053, - RALT = 1073742054, - RGUI = 1073742055, - MODE = 1073742081, - AUDIONEXT = 1073742082, - AUDIOPREV = 1073742083, - AUDIOSTOP = 1073742084, - AUDIOPLAY = 1073742085, - AUDIOMUTE = 1073742086, - MEDIASELECT = 1073742087, - WWW = 1073742088, - MAIL = 1073742089, - CALCULATOR = 1073742090, - COMPUTER = 1073742091, - AcSearch = 1073742092, - AcHome = 1073742093, - AcBack = 1073742094, - AcForward = 1073742095, - AcStop = 1073742096, - AcRefresh = 1073742097, - AcBookmarks = 1073742098, - BRIGHTNESSDOWN = 1073742099, - BRIGHTNESSUP = 1073742100, - DISPLAYSWITCH = 1073742101, - KBDILLUMTOGGLE = 1073742102, - KBDILLUMDOWN = 1073742103, - KBDILLUMUP = 1073742104, - EJECT = 1073742105, - SLEEP = 1073742106, - APP1 = 1073742107, - APP2 = 1073742108, - AUDIOREWIND = 1073742109, - AUDIOFASTFORWARD = 1073742110, -} - -#[repr(i32)] -pub enum PaletteColor { - Background, - Foreground, - - Background1, - Background2, - Background3, - Background4, - - Foreground1, - Foreground2, - Foreground3, - Foreground4, - - Red, - Green, - Yellow, - Blue, - Purple, - Aqua, - Gray, - - BrightRed, - BrightGreen, - BrightYellow, - BrightBlue, - BrightPurple, - BrightAqua, - BrightGray, -} diff --git a/plugins/default_view/init.lua b/plugins/default_view/init.lua deleted file mode 100644 index 58d8045..0000000 --- a/plugins/default_view/init.lua +++ /dev/null @@ -1,96 +0,0 @@ -local M = {} - -M.version = "0.1" -M.name = "Default_View" -M.namespace = "nl_spacegirl_plugin_Default" - -M.BufferListPanel = { - num_clicks = 0 -} - -M.SomeOtherPanel = { - num_clicks_2 = 0 -} - -function M.BufferListPanel.new() - local p = {} - setmetatable(p, {__index = M.BufferListPanel}) - return p -end - -function M.BufferListPanel:render(ctx) - -- if UI.button(ctx, "Number of Clicks "..self.num_clicks).clicked then - -- self.num_clicks = self.num_clicks + 1 - -- end -end - -function M.SomeOtherPanel.new() - local p = {} - setmetatable(p, {__index = M.SomeOtherPanel}) - return p -end - -function M.SomeOtherPanel:render(ctx) - UI_New.open_element(ctx, "Number of Clicks", { - dir = UI_New.LeftToRight, - kind = {UI_New.Exact(128), UI_New.Exact(32)}, - }) - UI_New.close_element(ctx) - - UI_New.open_element(ctx, "Whatever man", { - dir = UI_New.LeftToRight, - kind = {UI_New.Fit, UI_New.Exact(32)}, - }) - UI_New.close_element(ctx) -end - -function M.open_file_search_window() - local input = { - {Editor.Key.Enter, "Open File", function() Editor.log("this should open a file") end} - } - - Editor.spawn_floating_window(input, function(ctx) - UI.push_parent(ctx, UI.push_rect(ctx, "window", true, true, UI.Vertical, UI.Fill, UI.ChildrenSum)) - UI.label(ctx, "eventually this will be a window where you can search through a bunch of files 1") - UI.label(ctx, "eventually this will be a window where you can search through a bunch of files 2") - UI.label(ctx, "eventually this will be a window where you can search through a bunch of files 3") - UI.label(ctx, "eventually this will be a window where you can search through a bunch of files 4") - UI.label(ctx, "eventually this will be a window where you can search through a bunch of files 5") - UI.label(ctx, "eventually this will be a window where you can search through a bunch of files 6") - UI.label(ctx, "eventually this will be a window where you can search through a bunch of files 7") - UI.pop_parent(ctx) - end) -end - -function M.OnLoad() - Editor.log("default view loaded") - Editor.log(nl_spacegirl_plugin_Default_Legacy_View['namespace']) - - local a = M.BufferListPanel.new() - local b = M.BufferListPanel.new() - - print(M.BufferListPanel) - print(a) - print(b) - - Editor.register_key_group({ - {Editor.Key.Space, "", { - {Editor.Key.F, "Open File", M.open_file_search_window}, - {Editor.Key.J, "New Panel", function() - Editor.run_command("nl.spacegirl.editor.core", "Open New Panel", "BufferListPanel") - end}, - {Editor.Key.K, "Some Other Panel", function() - Editor.run_command("nl.spacegirl.editor.core", "Open New Panel", "SomeOtherPanel") - end} - }}, - }) - - Editor.register_panel("BufferList", "BufferListPanel") - Editor.register_panel("aksjdhflkasjdf", "SomeOtherPanel") -end - -function M.view_render(cx) - UI.label(cx, "Look its a me, a plugin") -end - -return M diff --git a/plugins/grep/Cargo.lock b/plugins/grep/Cargo.lock deleted file mode 100644 index 144e9a4..0000000 --- a/plugins/grep/Cargo.lock +++ /dev/null @@ -1,334 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] - -[[package]] -name = "bstr" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "encoding_rs_io" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83" -dependencies = [ - "encoding_rs", -] - -[[package]] -name = "globset" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "grep" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2b024ec1e686cb64d78beb852030b0e632af93817f1ed25be0173af0e94939" -dependencies = [ - "grep-cli", - "grep-matcher", - "grep-printer", - "grep-regex", - "grep-searcher", -] - -[[package]] -name = "grep-cli" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea40788c059ab8b622c4d074732750bfb3bd2912e2dd58eabc11798a4d5ad725" -dependencies = [ - "bstr", - "globset", - "libc", - "log", - "termcolor", - "winapi-util", -] - -[[package]] -name = "grep-matcher" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02" -dependencies = [ - "memchr", -] - -[[package]] -name = "grep-printer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743c12a03c8aee38b6e5bd0168d8ebb09345751323df4a01c56e792b1f38ceb2" -dependencies = [ - "bstr", - "grep-matcher", - "grep-searcher", - "log", - "serde", - "serde_json", - "termcolor", -] - -[[package]] -name = "grep-regex" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d" -dependencies = [ - "bstr", - "grep-matcher", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "grep-searcher" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54" -dependencies = [ - "bstr", - "encoding_rs", - "encoding_rs_io", - "grep-matcher", - "log", - "memchr", - "memmap2", -] - -[[package]] -name = "grep_plugin" -version = "0.1.0" -dependencies = [ - "grep", - "plugin_rs_bindings", - "termcolor", - "walkdir", -] - -[[package]] -name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "libc" -version = "0.2.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - -[[package]] -name = "memmap2" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fd3a57831bf88bc63f8cebc0cf956116276e97fef3966103e96416209f7c92" -dependencies = [ - "libc", -] - -[[package]] -name = "plugin_rs_bindings" -version = "0.1.0" - -[[package]] -name = "proc-macro2" -version = "1.0.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "regex-automata" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "ryu" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "serde" -version = "1.0.194" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.194" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.110" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "2.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "termcolor" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "walkdir" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/plugins/grep/Cargo.toml b/plugins/grep/Cargo.toml deleted file mode 100644 index d9c7c43..0000000 --- a/plugins/grep/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "grep_plugin" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -crate-type = ["cdylib"] - -[dependencies] -grep = "0.3.1" -termcolor = "1.4.0" -walkdir = "2.4.0" -plugin_rs_bindings = { path = "../../plugin-rs-bindings" } diff --git a/plugins/grep/rust-toolchain.toml b/plugins/grep/rust-toolchain.toml deleted file mode 100644 index 98d1e3f..0000000 --- a/plugins/grep/rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -[toolchain] -channel = "nightly-2024-01-24" - diff --git a/plugins/grep/src/lib.rs b/plugins/grep/src/lib.rs deleted file mode 100644 index fd03b6f..0000000 --- a/plugins/grep/src/lib.rs +++ /dev/null @@ -1,449 +0,0 @@ -use std::{ - error::Error, - ffi::{CString, OsString}, - path::Path, - str::FromStr, - sync::mpsc::{Receiver, Sender}, - thread, -}; - -use grep::{ - regex::RegexMatcherBuilder, - searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError}, -}; -use plugin_rs_bindings::{Buffer, Closure, Hook, InputMap, Key, Plugin, UiAxis, UiSemanticSize}; -use std::sync::mpsc::channel; -use walkdir::WalkDir; - -#[derive(Debug)] -pub enum SimpleSinkError { - StandardError, - NoLine, - BadString, -} - -impl SinkError for SimpleSinkError { - fn error_message(message: T) -> Self { - eprintln!("{message}"); - - Self::StandardError - } -} - -#[derive(Debug)] -struct Match { - text: Vec, - path: String, - line_number: Option, - column: u64, -} -impl Match { - fn from_sink_match_with_path( - value: &grep::searcher::SinkMatch<'_>, - path: Option, - ) -> Result { - let line = value - .lines() - .next() - .ok_or(SimpleSinkError::NoLine)? - .to_vec(); - let column = value.bytes_range_in_buffer().len() as u64; - - Ok(Self { - text: line, - path: path.unwrap_or_default(), - line_number: value.line_number(), - column, - }) - } -} - -#[derive(Default, Debug)] -struct SimpleSink { - current_path: Option, - matches: Vec, -} -impl Sink for SimpleSink { - type Error = SimpleSinkError; - - fn matched( - &mut self, - _searcher: &grep::searcher::Searcher, - mat: &grep::searcher::SinkMatch<'_>, - ) -> Result { - self.matches.push(Match::from_sink_match_with_path( - mat, - self.current_path.clone(), - )?); - - Ok(true) - } -} - -fn search(pattern: &str, paths: &[OsString]) -> Result> { - let matcher = RegexMatcherBuilder::new() - .case_smart(true) - .fixed_strings(true) - .build(pattern)?; - let mut searcher = SearcherBuilder::new() - .binary_detection(BinaryDetection::quit(b'\x00')) - .line_number(true) - .build(); - - let mut sink = SimpleSink::default(); - for path in paths { - for result in WalkDir::new(path).into_iter().filter_entry(|dent| { - if dent.file_type().is_dir() - && (dent.path().ends_with("target") || dent.path().ends_with(".git")) - { - return false; - } - - true - }) { - let dent = match result { - Ok(dent) => dent, - Err(err) => { - eprintln!("{}", err); - continue; - } - }; - if !dent.file_type().is_file() { - continue; - } - sink.current_path = Some(dent.path().to_string_lossy().into()); - - let result = searcher.search_path(&matcher, dent.path(), &mut sink); - - if let Err(err) = result { - eprintln!("{}: {:?}", dent.path().display(), err); - } - } - } - - Ok(sink) -} - -enum Message { - Search((String, Vec)), - Quit, -} - -struct GrepWindow { - sink: Option, - selected_match: usize, - top_index: usize, - input_buffer: Option, - - tx: Sender, - rx: Receiver, -} - -#[no_mangle] -pub extern "C" fn OnInitialize(plugin: Plugin) { - println!("Grep Plugin Initialized"); - plugin.register_hook(Hook::BufferInput, on_buffer_input); - plugin.register_input_group( - None, - Key::Space, - Closure!((plugin: Plugin, input_map: InputMap) => { - (plugin.register_input)( - input_map, - Key::R, - Closure!((plugin: Plugin) => { - let (window_tx, thread_rx) = channel(); - let (thread_tx, window_rx) = channel(); - create_search_thread(thread_tx, thread_rx); - - let window = GrepWindow { - selected_match: 0, - top_index: 0, - input_buffer: Some(plugin.buffer_table.open_virtual_buffer()), - sink: None, - tx: window_tx, - rx: window_rx, - }; - - plugin.create_window(window, Closure!((plugin: Plugin, input_map: InputMap) => { - (plugin.enter_insert_mode)(); - - (plugin.register_input)(input_map, Key::I, Closure!((plugin: Plugin) => { - (plugin.enter_insert_mode)() - }), "\0".as_ptr()); - (plugin.register_input)(input_map, Key::Enter, Closure!((plugin: Plugin) => { - if let Some(window) = unsafe { plugin.get_window::() } { - match &window.sink { - Some(sink) => if window.selected_match < sink.matches.len() { - let mat = unsafe { &sink.matches.get_unchecked(window.selected_match) }; - plugin.buffer_table.open_buffer(&mat.path, (mat.line_number.unwrap_or(1)-1) as i32, 0); - (plugin.request_window_close)(); - }, - None => {}, - } - } - }), "move selection up\0".as_ptr()); - (plugin.register_input)(input_map, Key::K, Closure!((plugin: Plugin) => { - if let Some(window) = unsafe { plugin.get_window::() } { - - if window.selected_match > 0 { - window.selected_match -= 1; - - if window.selected_match < window.top_index { - window.top_index = window.selected_match; - } - } else { - window.selected_match = match &window.sink { - Some(sink) => sink.matches.len()-1, - None => 0, - }; - - window.top_index = window.selected_match; - } - } - }), "move selection up\0".as_ptr()); - (plugin.register_input)(input_map, Key::J, Closure!((plugin: Plugin) => { - if let Some(window) = unsafe { plugin.get_window::() } { - let screen_height = (plugin.get_screen_height)() as i32; - let font_height = (plugin.get_font_height)() as i32; - let height = screen_height - screen_height / 4; - let max_mats_to_draw = (height - font_height * 2) / (font_height) - 1; - - let match_count = match &window.sink { - Some(sink) => sink.matches.len(), - None => 0, - }; - - if match_count > 0 { - let index_threshold = std::cmp::max(max_mats_to_draw-4, 0) as usize; - - if window.selected_match < match_count-1 { - window.selected_match += 1; - - if window.selected_match - window.top_index > index_threshold { - window.top_index += 1; - } - } else { - window.selected_match = 0; - window.top_index = 0; - } - } - } - }), "move selection down\0".as_ptr()); - }), draw_window, free_window, Some(Closure!((_plugin: Plugin, window: *const std::ffi::c_void) -> Buffer => { - let window = Box::leak(unsafe { Box::::from_raw(window as *mut GrepWindow) }); - - if let Some(buffer) = window.input_buffer { - return buffer; - } else { - return Buffer::null(); - } - }))); - }), - "Open Grep Window\0".as_ptr(), - ); - }), - ); -} - -fn create_search_thread(tx: Sender, rx: Receiver) { - thread::spawn(move || { - while let Ok(message) = rx.recv() { - match message { - Message::Search((pattern, paths)) => { - if let Ok(sink) = search(&pattern, &paths) { - if let Err(err) = tx.send(sink) { - eprintln!("error getting grep results: {err:?}"); - return; - } - } - } - Message::Quit => return, - } - } - }); -} - -#[no_mangle] -pub extern "C" fn OnExit(_plugin: Plugin) { - println!("Grep Plugin Exiting"); -} - -extern "C" fn draw_window(plugin: Plugin, window: *const std::ffi::c_void) { - let window = Box::leak(unsafe { Box::::from_raw(window as *mut GrepWindow) }); - - let screen_height = (plugin.get_screen_height)() as i32; - - let font_height = (plugin.get_font_height)() as i32; - - let height = screen_height - screen_height / 4; - - let dir = plugin.get_current_directory(); - let directory = Path::new(dir.as_ref()); - - plugin - .ui_table - .push_floating(c"grep canvas", 0, 0, |ui_table| { - // TODO: make some primitive that centers a Box - ui_table.spacer(c"left spacer"); - - ui_table.push_rect( - c"centered container", - false, - false, - UiAxis::Vertical, - UiSemanticSize::PercentOfParent(75), - UiSemanticSize::Fill, - |ui_table| { - ui_table.spacer(c"top spacer"); - ui_table.push_rect( - c"grep window", - true, - true, - UiAxis::Vertical, - UiSemanticSize::Fill, - UiSemanticSize::PercentOfParent(75), - |ui_table| { - if let Ok(sink) = window.rx.try_recv() { - window.sink = Some(sink); - } - - ui_table.push_rect( - c"results list", - false, - false, - UiAxis::Vertical, - UiSemanticSize::Fill, - UiSemanticSize::Fill, - |ui_table| match &window.sink { - Some(sink) if !sink.matches.is_empty() => { - let num_mats_to_draw = std::cmp::min( - (sink.matches.len() - window.top_index) as i32, - (height - font_height) / (font_height), - ); - - for (i, mat) in - sink.matches[window.top_index..].iter().enumerate() - { - let index = i + window.top_index; - if i as i32 >= num_mats_to_draw { - break; - } - - let path = Path::new(&mat.path); - let relative_file_path = path - .strip_prefix(directory) - .unwrap_or(path) - .to_str() - .unwrap_or(""); - - let matched_text = String::from_utf8_lossy(&mat.text); - let text = match mat.line_number { - Some(line_number) => format!( - "{}:{}:{}: {}", - relative_file_path, - line_number, - mat.column, - matched_text - ), - None => format!( - "{}:{}: {}", - relative_file_path, mat.column, matched_text - ), - }; - - if index == window.selected_match { - // TODO: don't use button here, but apply a style - // to `label` - ui_table.button( - &CString::new(text).expect("valid text"), - ); - } else { - ui_table.label( - &CString::new(text).expect("valid text"), - ); - } - } - } - Some(_) | None => { - ui_table.spacer(c"top spacer"); - ui_table.push_rect( - c"centered text container", - false, - false, - UiAxis::Horizontal, - UiSemanticSize::Fill, - UiSemanticSize::Fill, - |ui_table| { - ui_table.spacer(c"left spacer"); - ui_table.label(c"no results"); - ui_table.spacer(c"right spacer"); - }, - ); - ui_table.spacer(c"bottom spacer"); - } - }, - ); - - ui_table.push_rect( - c"grep window", - true, - false, - UiAxis::Vertical, - UiSemanticSize::Fill, - UiSemanticSize::Exact(font_height as isize), - |ui_table| { - if let Some(buffer) = window.input_buffer { - ui_table.buffer(buffer, false); - } - }, - ); - }, - ); - ui_table.spacer(c"bottom spacer"); - }, - ); - ui_table.spacer(c"right spacer"); - }); -} - -extern "C" fn on_buffer_input(plugin: Plugin, buffer: Buffer) { - // NOTE(pcleavelin): this is super jank, because another plugin could have a window open when - // this gets called, however its fine here because we aren't manipulating any data, and a check - // is made between the buffer pointers which will only be correct if its our window. - if let Some(window) = unsafe { plugin.get_window::() } { - if window.input_buffer == Some(buffer) { - window.selected_match = 0; - window.top_index = 0; - - if let Some(buffer_info) = plugin.buffer_table.get_buffer_info(buffer) { - if let Some(input) = buffer_info.input.try_as_str() { - let directory = OsString::from_str(plugin.get_current_directory().as_ref()); - - match directory { - Ok(dir) => { - if let Err(err) = window - .tx - .send(Message::Search((input.to_string(), vec![dir]))) - { - eprintln!("failed to grep: {err:?}"); - } - } - Err(_) => { - eprintln!("failed to parse directory"); - } - }; - } - } - } - } -} - -extern "C" fn free_window(plugin: Plugin, window: *const std::ffi::c_void) { - let mut window = unsafe { Box::::from_raw(window as *mut GrepWindow) }; - let _ = window.tx.send(Message::Quit); - - if let Some(buffer) = window.input_buffer { - plugin.buffer_table.free_virtual_buffer(buffer); - window.input_buffer = None; - } -} diff --git a/plugins/highlighter/src/plugin.odin b/plugins/highlighter/src/plugin.odin deleted file mode 100644 index 6fe77fe..0000000 --- a/plugins/highlighter/src/plugin.odin +++ /dev/null @@ -1,423 +0,0 @@ -// The default syntax highlighter plugin for Odin & Rust -package highlighter; - -import "base:runtime" -import "core:fmt" - -import p "../../../src/plugin" - -Plugin :: p.Plugin; -Iterator :: p.Iterator; -BufferIter :: p.BufferIter; -BufferIndex :: p.BufferIndex; - -@export -OnInitialize :: proc "c" (plugin: Plugin) { - context = runtime.default_context(); - fmt.println("builtin highlighter plugin initialized!"); - - plugin.register_highlighter(".odin", color_buffer_odin); - plugin.register_highlighter(".rs", color_buffer_rust); -} - -@export -OnExit :: proc "c" () { - context = runtime.default_context(); - fmt.println("Goodbye from the Odin Highlighter Plugin!"); -} - -@export -OnDraw :: proc "c" (plugin: Plugin) { - context = runtime.default_context(); -} - -iterate_buffer :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { - result := iter_funcs.iterate_buffer(it); - - return result.char, it.cursor.index, result.should_continue; -} - -iterate_buffer_reverse :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { - result := iter_funcs.iterate_buffer_reverse(it); - - return result.char, it.cursor.index, result.should_continue; -} - -iterate_buffer_until :: proc(plugin: Plugin, it: ^BufferIter, until_proc: rawptr) { - plugin.iter.iterate_buffer_until(it, until_proc); -} - -iterate_buffer_peek :: proc(plugin: Plugin, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { - result := plugin.iter.iterate_buffer_peek(it); - - return result.char, it.cursor.index, result.should_continue; -} - -is_odin_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> (matches: bool) { - keywords := []string { - "using", - "transmute", - "cast", - "distinct", - "opaque", - "where", - "struct", - "enum", - "union", - "bit_field", - "bit_set", - "if", - "when", - "else", - "do", - "for", - "switch", - "case", - "continue", - "break", - "size_of", - "offset_of", - "type_info_of", - "typeid_of", - "type_of", - "align_of", - "or_return", - "or_else", - "inline", - "no_inline", - "string", - "cstring", - "bool", - "b8", - "b16", - "b32", - "b64", - "rune", - "any", - "rawptr", - "f16", - "f32", - "f64", - "f16le", - "f16be", - "f32le", - "f32be", - "f64le", - "f64be", - "u8", - "u16", - "u32", - "u64", - "u128", - "u16le", - "u32le", - "u64le", - "u128le", - "u16be", - "u32be", - "u64be", - "u128be", - "uint", - "uintptr", - "i8", - "i16", - "i32", - "i64", - "i128", - "i16le", - "i32le", - "i64le", - "i128le", - "i16be", - "i32be", - "i64be", - "i128be", - "int", - "complex", - "complex32", - "complex64", - "complex128", - "quaternion", - "quaternion64", - "quaternion128", - "quaternion256", - "matrix", - "typeid", - "true", - "false", - "nil", - "dynamic", - "map", - "proc", - "in", - "notin", - "not_in", - "import", - "export", - "foreign", - "const", - "package", - "return", - "defer", - }; - - for keyword in keywords { - it := start; - keyword_index := 0; - - for character in iterate_buffer(plugin.iter, &it) { - if character != keyword[keyword_index] { - break; - } - - keyword_index += 1; - if keyword_index >= len(keyword)-1 && it == end { - if plugin.iter.get_char_at_iter(&it) == keyword[keyword_index] { - matches = true; - } - - break; - } else if keyword_index >= len(keyword)-1 { - break; - } else if it == end { - break; - } - } - - if matches { - break; - } - } - - return; -} - -is_rust_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> (matches: bool) { - keywords := []string { - "as", - "break", - "const", - "continue", - "crate", - "else", - "enum", - "extern", - "false", - "fn", - "for", - "if", - "impl", - "in", - "let", - "loop", - "match", - "mod", - "move", - "mut", - "pub", - "ref", - "return", - "self", - "Self", - "static", - "struct", - "super", - "trait", - "true", - "type", - "unsafe", - "use", - "where", - "while", - "u8", - "i8", - "u16", - "i16", - "u32", - "i32", - "u64", - "i64", - "bool", - "usize", - "isize", - "str", - "String", - "Option", - "Result", - }; - - for keyword in keywords { - it := start; - keyword_index := 0; - - for character in iterate_buffer(plugin.iter, &it) { - if character != keyword[keyword_index] { - break; - } - - keyword_index += 1; - if keyword_index >= len(keyword)-1 && it == end { - if plugin.iter.get_char_at_iter(&it) == keyword[keyword_index] { - matches = true; - } - - break; - } else if keyword_index >= len(keyword)-1 { - break; - } else if it == end { - break; - } - } - - if matches { - break; - } - } - - return; -} - -// TODO: split logic into single line coloring, and multi-line coloring. -// single line coloring can be done directly on the glyph buffer -// (with some edge cases, literally, the edge of the screen) -color_buffer_odin :: proc "c" (plugin: Plugin, buffer: rawptr) { - context = runtime.default_context(); - - buffer := plugin.buffer.get_buffer_info(buffer); - - start_it := plugin.iter.get_buffer_iterator(buffer.buffer); - it := plugin.iter.get_buffer_iterator(buffer.buffer); - - for character in iterate_buffer(plugin.iter, &it) { - if it.cursor.line > buffer.glyph_buffer_height && (it.cursor.line - buffer.top_line) > buffer.glyph_buffer_height { - break; - } - - if character == '/' { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - - character, _, succ := iterate_buffer(plugin.iter, &it); - if !succ { break; } - - if character == '/' { - iterate_buffer_until(plugin, &it, plugin.iter.until_line_break); - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 9); - } else if character == '*' { - // TODO: block comments - } - } else if character == '\'' { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - - // jump into the quoted text - iterate_buffer_until(plugin, &it, plugin.iter.until_single_quote); - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12); - - iterate_buffer(plugin.iter, &it); - } else if character == '"' { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - - // jump into the quoted text - iterate_buffer_until(plugin, &it, plugin.iter.until_double_quote); - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12); - - iterate_buffer(plugin.iter, &it); - } else if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '_' { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - it = start_it; - - iterate_buffer_until(plugin, &it, plugin.iter.until_end_of_word); - - if is_odin_keyword(plugin, start_it, it) { - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 13); - - iterate_buffer(plugin.iter, &it); - } else if character, _, cond := iterate_buffer_peek(plugin, &it); cond { - if character == '(' { - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 11); - iterate_buffer(plugin.iter, &it); - } - } else { - break; - } - } - } -} - -color_buffer_rust :: proc "c" (plugin: Plugin, buffer: rawptr) { - context = runtime.default_context(); - - buffer := plugin.buffer.get_buffer_info(buffer); - - start_it := plugin.iter.get_buffer_iterator(buffer.buffer); - it := plugin.iter.get_buffer_iterator(buffer.buffer); - - for character in iterate_buffer(plugin.iter, &it) { - if it.cursor.line > buffer.glyph_buffer_height && (it.cursor.line - buffer.top_line) > buffer.glyph_buffer_height { - break; - } - - if character == '/' { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - - character, _, succ := iterate_buffer(plugin.iter, &it); - if !succ { break; } - - if character == '/' { - iterate_buffer_until(plugin, &it, plugin.iter.until_line_break); - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 9); - } else if character == '*' { - // TODO: block comments - } - } else if character == '\'' && false { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - - // jump into the quoted text - iterate_buffer_until(plugin, &it, plugin.iter.until_single_quote); - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12); - - iterate_buffer(plugin.iter, &it); - } else if character == '"' { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - - // jump into the quoted text - iterate_buffer_until(plugin, &it, plugin.iter.until_double_quote); - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12); - - iterate_buffer(plugin.iter, &it); - } else if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '_' { - start_it = it; - // need to go back one character because `it` is on the next character - iterate_buffer_reverse(plugin.iter, &start_it); - it = start_it; - - iterate_buffer_until(plugin, &it, plugin.iter.until_end_of_word); - - if is_rust_keyword(plugin, start_it, it) { - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 13); - - iterate_buffer(plugin.iter, &it); - } else if character, _, cond := iterate_buffer_peek(plugin, &it); cond { - if character == '(' || character == '<' || character == '!' { - plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 11); - iterate_buffer(plugin.iter, &it); - } - } else { - break; - } - } - } -} diff --git a/plugins/lua/view.lua b/plugins/lua/view.lua deleted file mode 100644 index a017a15..0000000 --- a/plugins/lua/view.lua +++ /dev/null @@ -1,526 +0,0 @@ -local M = {} - -M.version = "0.1" -M.name = "Legacy_View" -M.namespace = "nl_spacegirl_plugin_Default" - -local BufferSearchOpen = false -local BufferSearchOpenElapsed = 0 - -local CommandSearchOpen = false -local CommandSearchOpenElapsed = 0 -local CommandList = {} - -local LogWindowOpen = false -local LogWindowOpenElapsed = 0 - -local CurrentPreviewBufferIndex = Editor.get_current_buffer_index() -local BufferSearchIndex = 0 - -local SideBarSmoothedWidth = 128 -local SideBarWidth = 128 -local SideBarClosed = false - -local ActiveCodeView = nil -local CodeViews = {} - -local MovingTab = nil -local MovingTabDest = nil -local MovingTabInBetween = false - -local LastMouseX = 0 -local LastMouseY = 0 - -function buffer_list_iter(start) - local idx = start - return function () - buffer_info = Editor.buffer_info_from_index(idx) - idx = idx + 1 - - return buffer_info, idx-1 - end -end - -function centered(ctx, label, axis, width, height, body) - UI.push_parent(ctx, UI.push_rect(ctx, label, false, false, UI.Horizontal, UI.Fill, UI.Fill)) - UI.spacer(ctx, "left spacer") - UI.push_parent(ctx, UI.push_rect(ctx, "halfway centered", false, false, UI.Vertical, width, UI.Fill)) - UI.spacer(ctx, "top spacer") - UI.push_parent(ctx, UI.push_rect(ctx, "centered container", false, false, axis, UI.Fill, height)) - body() - UI.pop_parent(ctx) - UI.spacer(ctx, "bottom spacer") - UI.pop_parent(ctx) - UI.spacer(ctx, "right spacer") - UI.pop_parent(ctx) -end - -function list_iter(start, list) - local idx = start - - return function() - local value = list[idx] - idx = idx + 1 - return value, idx-1 - end -end - -function list(ctx, label, selection_index, list, render_func) - list_with_iter(ctx, label, selection_index, list_iter(selection_index, list), render_func) -end - -function list_with_iter(ctx, label, selection_index, list_iter, render_func) - local num_items = 10 - - UI.push_parent(ctx, UI.push_rect(ctx, label, true, true, UI.Vertical, UI.Fill, UI.Fill)) - for data, i in list_iter do - render_func(ctx, data, i == selection_index) - end - UI.pop_parent(ctx) -end - -function lerp(from, to, rate) - return (1 - rate) * from + rate*to -end - -function remove_buffer_from_code_view(code_view_index, file_path) - if code_view_index ~= nil and CodeViews[code_view_index] ~= nil then - CodeViews[code_view_index].tabs[file_path] = nil - k,v = pairs(CodeViews[code_view_index].tabs)(CodeViews[code_view_index].tabs) - CodeViews[code_view_index].current_tab = k - end -end - -function add_buffer_to_code_view(code_view_index, file_path, buffer_index) - if code_view_index == nil then - code_view_index = 1 - ActiveCodeView = 1 - end - - -- A new code view is being created - if CodeViews[code_view_index] == nil then - CodeViews[code_view_index] = {} - CodeViews[code_view_index].tabs = {} - CodeViews[code_view_index].width = UI.Fill - end - - ActiveCodeView = code_view_index - - CodeViews[code_view_index].tabs[file_path] = {} - CodeViews[code_view_index].tabs[file_path].buffer_index = buffer_index - CodeViews[code_view_index].current_tab = file_path -end - -function ui_sidemenu(ctx) - if SideBarClosed then - SideBarSmoothedWidth = lerp(SideBarSmoothedWidth, 0, 0.3) - else - SideBarSmoothedWidth = lerp(SideBarSmoothedWidth, SideBarWidth, 0.3) - end - - side_menu, _ = UI.push_box(ctx, "side menu", {"Scrollable"}, UI.Vertical, UI.Exact(SideBarSmoothedWidth), UI.Fill) - UI.push_parent(ctx, side_menu) - UI.push_rect(ctx, "padded top open files", false, false, UI.Horizontal, UI.Fill, UI.Exact(8)) - UI.push_parent(ctx, UI.push_rect(ctx, "padded open files", false, false, UI.Horizontal, UI.Fill, UI.ChildrenSum)) - UI.push_rect(ctx, "padded top open files", false, false, UI.Horizontal, UI.Exact(8), UI.Fill) - UI.label(ctx, "Open Files") - UI.pop_parent(ctx) - UI.push_rect(ctx, "padded bottom open files", false, false, UI.Horizontal, UI.Fill, UI.Exact(8)) - - for buffer_info, i in buffer_list_iter(0) do - button_container = UI.push_rect(ctx, "button container"..i, false, false, UI.Horizontal, UI.Fill, UI.ChildrenSum) - UI.push_parent(ctx, button_container) - flags = {"Clickable", "Hoverable", "DrawText"} - if i == current_buffer_index then - table.insert(flags, 1, "DrawBackground") - end - - if UI.advanced_button(ctx, " x ", flags, UI.FitText, UI.FitText).clicked then - Editor.log("hahah, you can't close buffers yet silly") - if ActiveCodeView ~= nil then - Editor.set_current_buffer_from_index(i) - add_buffer_to_code_view(ActiveCodeView+1, buffer_info.file_path, i) - end - end - - tab_button_interaction = UI.advanced_button(ctx, " "..buffer_info.file_path.." ", flags, UI.Fill, UI.FitText) - if tab_button_interaction.clicked then - Editor.set_current_buffer_from_index(i) - add_buffer_to_code_view(ActiveCodeView, buffer_info.file_path, i) - end - if tab_button_interaction.hovering then - CurrentPreviewBufferIndex = i - end - UI.pop_parent(ctx) - end - UI.spacer(ctx, "below buffers spacer") - - UI.pop_parent(ctx) -end - -function ui_code_view(ctx, code_view_index) - local code_view = CodeViews[code_view_index] - local is_tab_dest = MovingTab ~= nil and ActiveCodeView ~= code_view_index - - code_view_rect, code_view_interaction = UI.push_rect(ctx, code_view_index.." code view", ActiveCodeView ~= code_view_index, true, UI.Vertical, code_view.width, UI.Fill) - - UI.push_parent(ctx, code_view_rect) - tab_dest_flags = {} - if is_tab_dest then tab_dest_flags = {"Hoverable"} end - - tab_dest_region, tab_dest_interaction = UI.push_box(ctx, "code view tab dest", tab_dest_flags, UI.Vertical, UI.Fill, UI.Fill) - UI.push_parent(ctx, tab_dest_region) - if is_tab_dest then - if tab_dest_interaction.hovering then - MovingTabDest = code_view_index - elseif MovingTabDest == code_view_index then - MovingTabDest = nil - end - end - - UI.push_parent(ctx, UI.push_box(ctx, "tabs", {}, UI.Horizontal, UI.Fill, UI.ChildrenSum)) - for k,v in pairs(code_view.tabs) do - show_border = k ~= code_view.current_tab - background = show_border - flags = {"Clickable", "DrawText"} - if show_border then - table.insert(flags, 1, "DrawBorder") - table.insert(flags, 1, "Hoverable") - end - - UI.push_parent(ctx, UI.push_rect(ctx, k.." tab container", background, false, UI.Horizontal, UI.ChildrenSum, UI.ChildrenSum)) - tab_button = UI.advanced_button(ctx, " "..k.." ", flags, UI.FitText, UI.Exact(32)) - if tab_button.clicked or tab_button.dragging then - ActiveCodeView = code_view_index - code_view.current_tab = k - - Editor.set_current_buffer_from_index(v["buffer_index"]) - end - - if tab_button.dragging then - if MovingTab == nil then - MovingTab = {} - MovingTab["code_view_index"] = code_view_index - MovingTab["tab"] = k - end - - UI.push_parent(ctx, UI.push_floating(ctx, "dragging tab", x-(96/2), y-(32/2))) - UI.advanced_button(ctx, " "..k.." ", {"DrawText", "DrawBorder", "DrawBackground"}, UI.FitText, UI.Exact(32)) - UI.pop_parent(ctx) - elseif MovingTab ~= nil and MovingTab["code_view_index"] == code_view_index and MovingTab["tab"] == k then - if MovingTabDest ~= nil then - if MovingTabInBetween then - remove_buffer_from_code_view(code_view_index, k) - - table.insert(CodeViews, MovingTabDest+1, nil) - add_buffer_to_code_view(MovingTabDest+1, k, v["buffer_index"]) - else - add_buffer_to_code_view(MovingTabDest, k, v["buffer_index"]) - remove_buffer_from_code_view(code_view_index, k) - - MovingTabDest = nil - end - end - - MovingTab = nil - end - UI.pop_parent(ctx) - end - UI.pop_parent(ctx) - - current_tab = code_view.current_tab - if code_view.tabs[current_tab] ~= nil then - buffer_index = code_view.tabs[current_tab].buffer_index - - UI.buffer(ctx, buffer_index) - end - - UI.pop_parent(ctx) - UI.pop_parent(ctx) - - return code_view_interaction -end - -function M.render_ui_window(ctx) - current_buffer_index = Editor.get_current_buffer_index() - x,y = UI.get_mouse_pos(ctx) - delta_x = LastMouseX - x - delta_y = LastMouseY - y - - numFrames = 7 - CurrentPreviewBufferIndex = current_buffer_index - - if not SideBarClosed or SideBarSmoothedWidth > 2 then - ui_sidemenu(ctx) - end - - side_bar_interaction = UI.advanced_button(ctx, "side menu grab handle", {"DrawBorder", "Hoverable", "Clickable"}, UI.Exact(16), UI.Fill) - if side_bar_interaction.clicked then - if SideBarClosed then - SideBarClosed = false - - if SideBarWidth < 32 then - SideBarWidth = 128 - end - else - SideBarClosed = true - end - end - if side_bar_interaction.dragging then - SideBarWidth = x-8 - - if SideBarWidth < 32 then - SideBarClosed = true - SideBarWidth = 0 - elseif SideBarWidth > 128 then - SideBarClosed = false - end - - if not SideBarClosed then - SideBarWidth = math.max(SideBarWidth, 128) - end - end - - for k,v in ipairs(CodeViews) do - code_view_interaction = ui_code_view(ctx, k) - - if next(CodeViews, k) ~= nil then - interaction = UI.advanced_button(ctx, k.."code view grab handle", {"DrawBorder", "Hoverable", "Clickable"}, UI.Exact(16), UI.Fill) - if interaction.dragging then - local width = math.max(32, x - code_view_interaction.box_pos.x) - v.width = UI.Exact(width) - elseif interaction.clicked then - v.width = UI.Fill - elseif MovingTab ~= nil and interaction.hovering then - MovingTabInBetween = true - MovingTabDest = k - elseif MovingTabDest == k and MovingTabInBetween then - MovingTabInBetween = false - MovingTabDest = nil - end - else - v.width = UI.Fill - end - end - - for k,v in ipairs(CodeViews) do - if next(v.tabs) == nil then - table.remove(CodeViews, k) - - if ActiveCodeView > k then - ActiveCodeView = ActiveCodeView - 1 - end - end - end - - -- render_buffer_search(ctx) - -- render_command_search(ctx) - render_log_window(ctx) - - LastMouseX = x - LastMouseY = y -end - -function M.open_buffer_search_window(ctx) - -- if BufferSearchOpen or BufferSearchOpenElapsed > 0 then - -- if BufferSearchOpen and BufferSearchOpenElapsed < numFrames then - -- BufferSearchOpenElapsed = BufferSearchOpenElapsed + 1 - -- elseif not BufferSearchOpen and BufferSearchOpenElapsed > 0 then - -- BufferSearchOpenElapsed = BufferSearchOpenElapsed - 1 - -- end - -- end - - -- if BufferSearchOpen or BufferSearchOpenElapsed > 0 then - -- window_percent = 75 - -- if BufferSearchOpenElapsed > 0 then - -- window_percent = ((BufferSearchOpenElapsed/numFrames) * 75) - -- end - - local input = { - {Editor.Key.Escape, "Close Window", ( - function () - Editor.request_window_close() - BufferSearchOpen = false - end - )}, - {Editor.Key.Enter, "Switch to Buffer", ( - function () - buffer_info = Editor.buffer_info_from_index(BufferSearchIndex) - add_buffer_to_code_view(ActiveCodeView, buffer_info.file_path, BufferSearchIndex) - - Editor.set_current_buffer_from_index(BufferSearchIndex) - Editor.request_window_close() - BufferSearchOpen = false - end - )}, - -- TODO: don't scroll past buffers - {Editor.Key.K, "Move Selection Up", (function () BufferSearchIndex = BufferSearchIndex - 1 end)}, - {Editor.Key.J, "Move Selection Down", (function () BufferSearchIndex = BufferSearchIndex + 1 end)}, - } - - Editor.spawn_floating_window(input, function(ctx) - -- UI.push_parent(ctx, UI.push_floating(ctx, "buffer search canvas", 0, 0)) - -- centered(ctx, "buffer search window", UI.Horizontal, UI.PercentOfParent(window_percent), UI.PercentOfParent(window_percent), ( - -- function () - list_with_iter(ctx, "buffer list", BufferSearchIndex, buffer_list_iter(BufferSearchIndex), - function(ctx, buffer_info, is_selected) - flags = {"DrawText"} - - if is_selected then - table.insert(flags, 1, "DrawBorder") - end - - interaction = UI.advanced_button(ctx, " "..buffer_info.file_path.." ", flags, UI.Fill, UI.FitText) - end - ) - UI.buffer(ctx, BufferSearchIndex) - - -- UI.push_parent(ctx, UI.push_rect(ctx, "window", true, true, UI.Horizontal, UI.Fill, UI.Fill)) - -- UI.push_parent(ctx, UI.push_rect(ctx, "buffer list", false, false, UI.Vertical, UI.Fill, UI.Fill)) - -- for buffer_info, i in buffer_list_iter() do - -- flags = {"DrawText"} - -- - -- if i == BufferSearchIndex then - -- table.insert(flags, 1, "DrawBorder") - -- end - -- interaction = UI.advanced_button(ctx, " "..buffer_info.file_path.." ", flags, UI.Fill, UI.FitText) - -- end - -- UI.pop_parent(ctx) - -- UI.buffer(ctx, BufferSearchIndex) - -- UI.pop_parent(ctx) - -- end - -- )) - -- UI.pop_parent(ctx) - end) - -- end -end - -function M.open_command_palette() - -- if CommandSearchOpen or CommandSearchOpenElapsed > 0 then - -- if CommandSearchOpen and CommandSearchOpenElapsed < numFrames then - -- CommandSearchOpenElapsed = CommandSearchOpenElapsed + 1 - -- elseif not CommandSearchOpen and CommandSearchOpenElapsed > 0 then - -- CommandSearchOpenElapsed = CommandSearchOpenElapsed - 1 - -- end - -- end - - -- if CommandSearchOpen or CommandSearchOpenElapsed > 0 then - -- window_percent_width = 75 - -- window_percent_height = 25 - -- if CommandSearchOpenElapsed > 0 then - -- window_percent_width = ((CommandSearchOpenElapsed/numFrames) * 75) - -- window_percent_height = ((CommandSearchOpenElapsed/numFrames) * 25) - -- end - - -- UI.push_parent(ctx, UI.push_floating(ctx, "buffer search canvas", 0, 0)) - -- centered(ctx, "command search window", UI.Horizontal, UI.PercentOfParent(window_percent_width), UI.PercentOfParent(window_percent_height), - -- function () - local input = { - {Editor.Key.Escape, "Close Window", ( - function () - Editor.request_window_close() - CommandSearchOpen = false - end - )}, - {Editor.Key.Enter, "Run Command", ( - function () - if CommandList[CommandSearchIndex] ~= nil then - Editor.run_command("nl.spacegirl.editor.core", CommandList[CommandSearchIndex]["name"]) - CommandList = {} - - Editor.request_window_close() - CommandSearchOpen = false - end - end - )}, - -- TODO: don't scroll past selections - {Editor.Key.K, "Move Selection Up", (function () CommandSearchIndex = CommandSearchIndex - 1 end)}, - {Editor.Key.J, "Move Selection Down", (function () CommandSearchIndex = CommandSearchIndex + 1 end)}, - } - - Editor.spawn_floating_window(input, function(ctx) - list(ctx, "command list", CommandSearchIndex, CommandList, - function(ctx, cmd, is_selected) - flags = {"DrawText"} - - if is_selected then - table.insert(flags, 1, "DrawBorder") - end - - interaction = UI.advanced_button(ctx, " "..cmd.name..": "..cmd.description.." ", flags, UI.Fill, UI.FitText) - end - ) - end) - -- ) - -- UI.pop_parent(ctx) - -- end -end - -function render_log_window(ctx) - if Editor.get_current_buffer_index() ~= -2 then - LogWindowOpen = false - end - - if LogWindowOpen or LogWindowOpenElapsed > 0 then - if LogWindowOpen and LogWindowOpenElapsed < numFrames then - LogWindowOpenElapsed = LogWindowOpenElapsed + 1 - elseif not LogWindowOpen and LogWindowOpenElapsed > 0 then - LogWindowOpenElapsed = LogWindowOpenElapsed - 1 - end - end - - if LogWindowOpen or LogWindowOpenElapsed > 0 then - window_percent = 75 - if LogWindowOpenElapsed > 0 then - window_percent = ((LogWindowOpenElapsed/numFrames) * 75) - end - - UI.push_parent(ctx, UI.push_floating(ctx, "log window canvas", 0, 0)) - centered(ctx, "log window", UI.Horizontal, UI.PercentOfParent(window_percent), UI.PercentOfParent(window_percent), ( - function () - UI.push_parent(ctx, UI.push_rect(ctx, "window", true, true, UI.Horizontal, UI.Fill, UI.Fill)) - -- -2 is the log buffer - UI.buffer(ctx, -2) - UI.pop_parent(ctx) - end - )) - UI.pop_parent(ctx) - end -end - -function handle_buffer_input() -end - -function M.OnLoad() - Editor.log("Legacy View plugin loaded") - - Editor.register_key_group({ - {Editor.Key.Backtick, "Open Editor Logs", (function () - if not LogWindowOpen then - LogWindowOpen = true - Editor.set_current_buffer_from_index(-2) - else - LogWindowOpen = false - local code_view = CodeViews[ActiveCodeView] - Editor.set_current_buffer_from_index(code_view.tabs[code_view.current_tab]["buffer_index"]) - end - end)}, - {Editor.Key.Space, "", { - {Editor.Key.P, "Command Palette", - (function () - CommandSearchOpen = true - CommandSearchIndex = 1 - - CommandList = Editor.query_command_group("nl.spacegirl.editor.core") - M.open_command_palette() - end), - }, - {Editor.Key.B, "Buffer Search", M.open_buffer_search_window} - }} - }) - - Editor.register_hook(Editor.Hook.OnDraw, M.render_ui_window) - Editor.register_hook(Editor.Hook.OnBufferInput, handle_buffer_input) -end - -return M diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100755 index 2bf5ad0..0000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -stable diff --git a/src/core/core.odin b/src/core/core.odin index 009f76a..34f3e6c 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -8,54 +8,13 @@ import "core:log" import "vendor:sdl2" import lua "vendor:lua/5.4" -import "../plugin" - Mode :: enum { Normal, Insert, Visual, } -Window :: struct { - input_map: InputActions, - draw: plugin.WindowDrawProc, - free_user_data: plugin.WindowFreeProc, - get_buffer: plugin.WindowGetBufferProc, - - // TODO: create hook for when mode changes happen - - user_data: rawptr, -} -NewWindow :: struct { - input_map: InputActions, - lua_draw_proc: i32, -} -request_window_close :: proc(state: ^State) { - state.should_close_window = true; -} - -close_window_and_free :: proc(state: ^State) { - if state.window != nil { - if state.window.free_user_data != nil { - state.window.free_user_data(state.plugin_vtable, state.window.user_data); - } - - delete_input_actions(&state.window.input_map); - free(state.window); - - state.window = nil; - } - - if window, ok := &state.new_window.(NewWindow); ok { - delete_input_actions(&window.input_map); - state.new_window = nil - } - - state.current_input_map = &state.input_map.mode[.Normal]; -} - -LuaHookRef :: i32; EditorCommandList :: map[string][dynamic]EditorCommand; State :: struct { ctx: runtime.Context, @@ -82,10 +41,6 @@ State :: struct { log_buffer: FileBuffer, - window: ^Window, - new_window: Maybe(NewWindow), - should_close_window: bool, - input_map: InputMap, current_input_map: ^InputActions, @@ -95,13 +50,6 @@ State :: struct { active_panels: [128]Maybe(Panel), panel_catalog: [dynamic]PanelId, - - plugins: [dynamic]plugin.Interface, - new_plugins: [dynamic]plugin.NewInterface, - plugin_vtable: plugin.Plugin, - highlighters: map[string]plugin.OnColorBufferProc, - hooks: map[plugin.Hook][dynamic]plugin.OnHookProc, - lua_hooks: map[plugin.Hook][dynamic]LuaHookRef, } EditorCommand :: struct { @@ -120,26 +68,13 @@ EditorCommandArgument :: union #no_nil { i32 } -PanelId :: union #no_nil { - LuaPanelId, +PanelId :: union { LibPanelId, } -Panel :: union #no_nil { - LuaPanel, +Panel :: union { LibPanel, } - -LuaPanelId :: struct { - id: string, - name: string, -} -LuaPanel :: struct { - panel_id: LuaPanelId, - index: i32, - render_ref: i32 -} - // TODO LibPanelId :: struct {} LibPanel :: struct {} @@ -164,30 +99,8 @@ buffer_from_index :: proc(state: ^State, buffer_index: int) -> ^FileBuffer { return &state.buffers[buffer_index]; } -add_hook :: proc(state: ^State, hook: plugin.Hook, hook_proc: plugin.OnHookProc) { - if _, exists := state.hooks[hook]; !exists { - state.hooks[hook] = make([dynamic]plugin.OnHookProc); - } - - runtime.append(&state.hooks[hook], hook_proc); -} - -add_lua_hook :: proc(state: ^State, hook: plugin.Hook, hook_ref: LuaHookRef) { - if _, exists := state.lua_hooks[hook]; !exists { - state.lua_hooks[hook] = make([dynamic]LuaHookRef); - log.info("added lua hook", hook) - } - - runtime.append(&state.lua_hooks[hook], hook_ref); -} - -LuaEditorAction :: struct { - fn_ref: i32, - maybe_input_map: InputActions, -}; -PluginEditorAction :: proc "c" (plugin: plugin.Plugin); EditorAction :: proc(state: ^State); -InputGroup :: union {LuaEditorAction, PluginEditorAction, EditorAction, InputActions} +InputGroup :: union {EditorAction, InputActions} Action :: struct { action: InputGroup, description: string, @@ -196,8 +109,8 @@ InputMap :: struct { mode: map[Mode]InputActions, } InputActions :: struct { - key_actions: map[plugin.Key]Action, - ctrl_key_actions: map[plugin.Key]Action, + key_actions: map[Key]Action, + ctrl_key_actions: map[Key]Action, } new_input_map :: proc() -> InputMap { @@ -217,8 +130,8 @@ new_input_map :: proc() -> InputMap { new_input_actions :: proc() -> InputActions { input_actions := InputActions { - key_actions = make(map[plugin.Key]Action), - ctrl_key_actions = make(map[plugin.Key]Action), + key_actions = make(map[Key]Action), + ctrl_key_actions = make(map[Key]Action), } return input_actions; @@ -234,22 +147,7 @@ delete_input_actions :: proc(input_map: ^InputActions) { delete(input_map.ctrl_key_actions); } -// NOTE(pcleavelin): might be a bug in the compiler where it can't coerce -// `EditorAction` to `InputGroup` when given as a proc parameter, that is why there -// are two functions -register_plugin_key_action_single :: proc(input_map: ^InputActions, key: plugin.Key, action: PluginEditorAction, description: string = "") { - if ok := key in input_map.key_actions; ok { - // TODO: log that key is already registered - log.error("plugin key already registered with single action", key); - } - - input_map.key_actions[key] = Action { - action = action, - description = description, - }; -} - -register_key_action_single :: proc(input_map: ^InputActions, key: plugin.Key, action: EditorAction, description: string = "") { +register_key_action_single :: proc(input_map: ^InputActions, key: Key, action: EditorAction, description: string = "") { if ok := key in input_map.key_actions; ok { // TODO: log that key is already registered log.error("key already registered with single action", key); @@ -261,7 +159,7 @@ register_key_action_single :: proc(input_map: ^InputActions, key: plugin.Key, ac }; } -register_key_action_group :: proc(input_map: ^InputActions, key: plugin.Key, input_group: InputGroup, description: string = "") { +register_key_action_group :: proc(input_map: ^InputActions, key: Key, input_group: InputGroup, description: string = "") { if ok := key in input_map.key_actions; ok { // TODO: log that key is already registered fmt.eprintln("key already registered with single action", key); @@ -273,7 +171,7 @@ register_key_action_group :: proc(input_map: ^InputActions, key: plugin.Key, inp }; } -register_ctrl_key_action_single :: proc(input_map: ^InputActions, key: plugin.Key, action: EditorAction, description: string = "") { +register_ctrl_key_action_single :: proc(input_map: ^InputActions, key: Key, action: EditorAction, description: string = "") { if ok := key in input_map.key_actions; ok { // TODO: log that key is already registered log.error("key already registered with single action", key); @@ -285,7 +183,7 @@ register_ctrl_key_action_single :: proc(input_map: ^InputActions, key: plugin.Ke }; } -register_ctrl_key_action_group :: proc(input_map: ^InputActions, key: plugin.Key, input_group: InputGroup, description: string = "") { +register_ctrl_key_action_group :: proc(input_map: ^InputActions, key: Key, input_group: InputGroup, description: string = "") { if ok := key in input_map.key_actions; ok { // TODO: log that key is already registered log.error("key already registered with single action", key); @@ -297,7 +195,7 @@ register_ctrl_key_action_group :: proc(input_map: ^InputActions, key: plugin.Key }; } -register_key_action :: proc{register_plugin_key_action_single, register_key_action_single, register_key_action_group}; +register_key_action :: proc{register_key_action_single, register_key_action_group}; register_ctrl_key_action :: proc{register_ctrl_key_action_single, register_ctrl_key_action_group}; register_editor_command :: proc(command_list: ^EditorCommandList, command_group, name, description: string, action: EditorAction) { @@ -427,10 +325,245 @@ where intrinsics.type_is_struct(T) { return } -register_panel_lua :: proc(state: ^State, name: string, id: string) { - append(&state.panel_catalog, LuaPanelId { - id = id, - name = name, - }) +Key :: enum { + UNKNOWN = 0, + ENTER = 13, + ESCAPE = 27, + BACKSPACE = 8, + TAB = 9, + SPACE = 32, + EXCLAIM = 33, + QUOTEDBL = 34, + HASH = 35, + PERCENT = 37, + DOLLAR = 36, + AMPERSAND = 38, + QUOTE = 39, + LEFTPAREN = 40, + RIGHTPAREN = 41, + ASTERISK = 42, + PLUS = 43, + COMMA = 44, + MINUS = 45, + PERIOD = 46, + SLASH = 47, + NUM0 = 48, + NUM1 = 49, + NUM2 = 50, + NUM3 = 51, + NUM4 = 52, + NUM5 = 53, + NUM6 = 54, + NUM7 = 55, + NUM8 = 56, + NUM9 = 57, + COLON = 58, + SEMICOLON = 59, + LESS = 60, + EQUAL = 61, + GREATER = 62, + QUESTION = 63, + AT = 64, + LEFTBRACKET = 91, + BACKSLASH = 92, + RIGHTBRACKET = 93, + CARET = 94, + UNDERSCORE = 95, + BACKQUOTE = 96, + A = 97, + B = 98, + C = 99, + D = 100, + E = 101, + F = 102, + G = 103, + H = 104, + I = 105, + J = 106, + K = 107, + L = 108, + M = 109, + N = 110, + O = 111, + P = 112, + Q = 113, + R = 114, + S = 115, + T = 116, + U = 117, + V = 118, + W = 119, + X = 120, + Y = 121, + Z = 122, + CAPSLOCK = 1073741881, + F1 = 1073741882, + F2 = 1073741883, + F3 = 1073741884, + F4 = 1073741885, + F5 = 1073741886, + F6 = 1073741887, + F7 = 1073741888, + F8 = 1073741889, + F9 = 1073741890, + F10 = 1073741891, + F11 = 1073741892, + F12 = 1073741893, + PRINTSCREEN = 1073741894, + SCROLLLOCK = 1073741895, + PAUSE = 1073741896, + INSERT = 1073741897, + HOME = 1073741898, + PAGEUP = 1073741899, + DELETE = 127, + END = 1073741901, + PAGEDOWN = 1073741902, + RIGHT = 1073741903, + LEFT = 1073741904, + DOWN = 1073741905, + UP = 1073741906, + NUMLOCKCLEAR = 1073741907, + KP_DIVIDE = 1073741908, + KP_MULTIPLY = 1073741909, + KP_MINUS = 1073741910, + KP_PLUS = 1073741911, + KP_ENTER = 1073741912, + KP_1 = 1073741913, + KP_2 = 1073741914, + KP_3 = 1073741915, + KP_4 = 1073741916, + KP_5 = 1073741917, + KP_6 = 1073741918, + KP_7 = 1073741919, + KP_8 = 1073741920, + KP_9 = 1073741921, + KP_0 = 1073741922, + KP_PERIOD = 1073741923, + APPLICATION = 1073741925, + POWER = 1073741926, + KP_EQUALS = 1073741927, + F13 = 1073741928, + F14 = 1073741929, + F15 = 1073741930, + F16 = 1073741931, + F17 = 1073741932, + F18 = 1073741933, + F19 = 1073741934, + F20 = 1073741935, + F21 = 1073741936, + F22 = 1073741937, + F23 = 1073741938, + F24 = 1073741939, + EXECUTE = 1073741940, + HELP = 1073741941, + MENU = 1073741942, + SELECT = 1073741943, + STOP = 1073741944, + AGAIN = 1073741945, + UNDO = 1073741946, + CUT = 1073741947, + COPY = 1073741948, + PASTE = 1073741949, + FIND = 1073741950, + MUTE = 1073741951, + VOLUMEUP = 1073741952, + VOLUMEDOWN = 1073741953, + KP_COMMA = 1073741957, + KP_EQUALSAS400 = 1073741958, + ALTERASE = 1073741977, + SYSREQ = 1073741978, + CANCEL = 1073741979, + CLEAR = 1073741980, + PRIOR = 1073741981, + RETURN2 = 1073741982, + SEPARATOR = 1073741983, + OUT = 1073741984, + OPER = 1073741985, + CLEARAGAIN = 1073741986, + CRSEL = 1073741987, + EXSEL = 1073741988, + KP_00 = 1073742000, + KP_000 = 1073742001, + THOUSANDSSEPARATOR = 1073742002, + DECIMALSEPARATOR = 1073742003, + CURRENCYUNIT = 1073742004, + CURRENCYSUBUNIT = 1073742005, + KP_LEFTPAREN = 1073742006, + KP_RIGHTPAREN = 1073742007, + KP_LEFTBRACE = 1073742008, + KP_RIGHTBRACE = 1073742009, + KP_TAB = 1073742010, + KP_BACKSPACE = 1073742011, + KP_A = 1073742012, + KP_B = 1073742013, + KP_C = 1073742014, + KP_D = 1073742015, + KP_E = 1073742016, + KP_F = 1073742017, + KP_XOR = 1073742018, + KP_POWER = 1073742019, + KP_PERCENT = 1073742020, + KP_LESS = 1073742021, + KP_GREATER = 1073742022, + KP_AMPERSAND = 1073742023, + KP_DBLAMPERSAND = 1073742024, + KP_VERTICALBAR = 1073742025, + KP_DBLVERTICALBAR = 1073742026, + KP_COLON = 1073742027, + KP_HASH = 1073742028, + KP_SPACE = 1073742029, + KP_AT = 1073742030, + KP_EXCLAM = 1073742031, + KP_MEMSTORE = 1073742032, + KP_MEMRECALL = 1073742033, + KP_MEMCLEAR = 1073742034, + KP_MEMADD = 1073742035, + KP_MEMSUBTRACT = 1073742036, + KP_MEMMULTIPLY = 1073742037, + KP_MEMDIVIDE = 1073742038, + KP_PLUSMINUS = 1073742039, + KP_CLEAR = 1073742040, + KP_CLEARENTRY = 1073742041, + KP_BINARY = 1073742042, + KP_OCTAL = 1073742043, + KP_DECIMAL = 1073742044, + KP_HEXADECIMAL = 1073742045, + LCTRL = 1073742048, + LSHIFT = 1073742049, + LALT = 1073742050, + LGUI = 1073742051, + RCTRL = 1073742052, + RSHIFT = 1073742053, + RALT = 1073742054, + RGUI = 1073742055, + MODE = 1073742081, + AUDIONEXT = 1073742082, + AUDIOPREV = 1073742083, + AUDIOSTOP = 1073742084, + AUDIOPLAY = 1073742085, + AUDIOMUTE = 1073742086, + MEDIASELECT = 1073742087, + WWW = 1073742088, + MAIL = 1073742089, + CALCULATOR = 1073742090, + COMPUTER = 1073742091, + AC_SEARCH = 1073742092, + AC_HOME = 1073742093, + AC_BACK = 1073742094, + AC_FORWARD = 1073742095, + AC_STOP = 1073742096, + AC_REFRESH = 1073742097, + AC_BOOKMARKS = 1073742098, + BRIGHTNESSDOWN = 1073742099, + BRIGHTNESSUP = 1073742100, + DISPLAYSWITCH = 1073742101, + KBDILLUMTOGGLE = 1073742102, + KBDILLUMDOWN = 1073742103, + KBDILLUMUP = 1073742104, + EJECT = 1073742105, + SLEEP = 1073742106, + APP1 = 1073742107, + APP2 = 1073742108, + AUDIOREWIND = 1073742109, + AUDIOFASTFORWARD = 1073742110, } - diff --git a/src/core/file_buffer.odin b/src/core/file_buffer.odin index 17f0562..e6d3a49 100644 --- a/src/core/file_buffer.odin +++ b/src/core/file_buffer.odin @@ -11,7 +11,6 @@ import "base:runtime" import "core:strings" import "../theme" -import "../plugin" ScrollDir :: enum { Up, @@ -752,32 +751,6 @@ next_buffer :: proc(state: ^State, prev_buffer: ^int) -> int { return index; } -into_buffer_info :: proc(state: ^State, buffer: ^FileBuffer) -> plugin.BufferInfo { - return plugin.BufferInfo { - buffer = buffer, - input = plugin.BufferInput { - bytes = raw_data(buffer.input_buffer), - length = len(buffer.input_buffer), - }, - cursor = plugin.Cursor { - col = buffer.cursor.col, - line = buffer.cursor.line, - index = plugin.BufferIndex { - slice_index = buffer.cursor.index.slice_index, - content_index = buffer.cursor.index.content_index, - } - }, - file_path = strings.clone_to_cstring(buffer.file_path, context.temp_allocator), - glyph_buffer_width = buffer.glyph_buffer_width, - glyph_buffer_height = buffer.glyph_buffer_height, - top_line = buffer.top_line, - }; -} -into_buffer_info_from_index :: proc(state: ^State, buffer_index: int) -> plugin.BufferInfo { - buffer := buffer_from_index(state, buffer_index); - return into_buffer_info(state, buffer); -} - free_file_buffer :: proc(buffer: ^FileBuffer) { delete(buffer.original_content); delete(buffer.added_content); @@ -876,9 +849,8 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) { draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, show_line_numbers: bool = true) { update_glyph_buffer(buffer); - if highlighter, exists := state.highlighters[buffer.extension]; exists { - highlighter(state.plugin_vtable, buffer); - } + + // TODO: syntax highlighting padding := 0; if show_line_numbers { diff --git a/src/lua/lua.odin b/src/lua/lua.odin deleted file mode 100644 index ce93bd3..0000000 --- a/src/lua/lua.odin +++ /dev/null @@ -1,1244 +0,0 @@ -package lua - -import "core:os" -import "core:fmt" -import "core:strings" -import "core:log" -import "core:path/filepath" - -import core "../core" -import plugin "../plugin" -import ui "../ui" - -import lua "vendor:lua/5.4" - -state: ^core.State = nil - -new_state :: proc(_state: ^core.State) { - state = _state - - state.L = lua.L_newstate(); - lua.L_openlibs(state.L); - - bbb: [^]lua.L_Reg; - editor_lib := [?]lua.L_Reg { - lua.L_Reg { - "quit", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - - state.should_close = true; - return i32(lua.OK); - } - }, - lua.L_Reg { - "log", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - text := string(lua.L_checkstring(L, 1)); - log.info("[LUA]:", text); - - return i32(lua.OK); - } - }, - lua.L_Reg { - "register_hook", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - hook := lua.L_checkinteger(L, 1); - - lua.L_checktype(L, 2, i32(lua.TFUNCTION)); - lua.pushvalue(L, 2); - fn_ref := lua.L_ref(L, i32(lua.REGISTRYINDEX)); - - core.add_lua_hook(state, plugin.Hook(hook), fn_ref); - - return i32(lua.OK); - } - }, - lua.L_Reg { - "register_key_group", - register_key_group, - }, - lua.L_Reg { - "register_panel", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - panel_name := strings.clone(string(lua.L_checkstring(L, 1))); - panel_identifier := strings.clone(string(lua.L_checkstring(L, 2))); - core.register_panel_lua(state, panel_name, panel_identifier) - - return i32(lua.OK) - } - }, - lua.L_Reg { - "spawn_floating_window", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - core.close_window_and_free(state); - - window_input_map := core.new_input_actions(); - lua.L_checktype(L, 1, i32(lua.TTABLE)); - table_to_action(L, 1, &window_input_map); - - lua.L_checktype(L, 2, i32(lua.TFUNCTION)); - lua.pushvalue(L, 2); - fn_ref := lua.L_ref(L, i32(lua.REGISTRYINDEX)); - - state.new_window = core.NewWindow { - input_map = window_input_map, - lua_draw_proc = fn_ref - } - state.current_input_map = &(&state.new_window.?).input_map - - return i32(lua.OK); - } - }, - lua.L_Reg { - "request_window_close", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - core.request_window_close(state); - - return i32(lua.OK); - } - }, - lua.L_Reg { - "get_current_buffer_index", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.pushinteger(L, lua.Integer(state.current_buffer)); - - return 1; - } - }, - lua.L_Reg { - "set_current_buffer_from_index", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - buffer_index := int(lua.L_checkinteger(L, 1)); - if buffer_index != -2 && (buffer_index < 0 || buffer_index >= len(state.buffers)) { - return i32(lua.ERRRUN); - } else { - state.current_buffer = buffer_index; - } - - return i32(lua.OK); - } - }, - lua.L_Reg { - "buffer_info_from_index", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - buffer_index := int(lua.L_checkinteger(L, 1)); - if buffer_index < 0 || buffer_index >= len(state.buffers) { - lua.pushnil(L); - } else { - push_lua_buffer_info :: proc(L: ^lua.State, buffer: ^core.FileBuffer) { - lua.newtable(L); - { - lua.pushlightuserdata(L, buffer); - lua.setfield(L, -2, "buffer"); - - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(buffer.cursor.col)); - lua.setfield(L, -2, "col"); - - lua.pushinteger(L, lua.Integer(buffer.cursor.line)); - lua.setfield(L, -2, "line"); - } - lua.setfield(L, -2, "cursor"); - - lua.pushstring(L, strings.clone_to_cstring(buffer.file_path, context.temp_allocator)); - lua.setfield(L, -2, "full_file_path"); - - relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator) - lua.pushstring(L, strings.clone_to_cstring(relative_file_path, context.temp_allocator)); - lua.setfield(L, -2, "file_path"); - } - } - - push_lua_buffer_info(L, core.buffer_from_index(state, buffer_index)); - } - - return 1; - } - }, - lua.L_Reg { - "query_command_group", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - group := lua.L_checkstring(L, 1); - cmds := core.query_editor_commands_by_group(&state.commands, string(group), context.temp_allocator); - - lua.newtable(L); - { - for cmd, i in cmds { - lua.newtable(L); - { - lua.pushstring(L, strings.clone_to_cstring(cmd.name, context.temp_allocator)); - lua.setfield(L, -2, "name"); - - lua.pushstring(L, strings.clone_to_cstring(cmd.description, context.temp_allocator)); - lua.setfield(L, -2, "description"); - } - lua.rawseti(L, -2, lua.Integer(i+1)); - } - } - - return 1; - } - }, - lua.L_Reg { - "run_command", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - group := lua.L_checkstring(L, 1); - name := lua.L_checkstring(L, 2); - - num_args := lua.gettop(state.L) - for i in 0..<(num_args-2) { - if lua.isstring(state.L, 3) { - value := lua.L_checkstring(state.L, 3) - lua.pop(L, 3); - - core.push_command_arg(state, string(value)) - } else if lua.isinteger(state.L, 3) { - value := lua.L_checkinteger(state.L, 3) - lua.pop(L, 3); - - core.push_command_arg(state, i32(value)) - } else { - log.error("expected string or integer for command arguments") - return 0; - } - } - - core.run_command(state, string(group), string(name)); - - return 1; - } - } - }; - bbb = raw_data(editor_lib[:]); - - get_lua_semantic_size :: proc(L: ^lua.State, index: i32) -> ui.SemanticSize { - if lua.istable(L, index) { - lua.rawgeti(L, index, 1); - semantic_kind := ui.SemanticSizeKind(lua.tointeger(L, -1)); - lua.pop(L, 1); - - lua.rawgeti(L, index, 2); - semantic_value := int(lua.tointeger(L, -1)); - lua.pop(L, 1); - - return {semantic_kind, semantic_value}; - } else { - semantic_kind := ui.SemanticSizeKind(lua.L_checkinteger(L, index)); - return {semantic_kind, 0}; - } - } - - get_size_kind :: proc(L: ^lua.State, index: i32) -> ui.UI_Size_Kind { - lua.rawgeti(L, index, 1); - k := lua.tointeger(L, -1); - lua.pop(L, 1); - - lua.rawgeti(L, index, 2); - v := lua.tointeger(L, -1); - lua.pop(L, 1); - - log.info("k", k, "v", v) - - switch k { - case 1: return ui.Exact(v) - case 2: return ui.Fit{} - case 3: return ui.Grow{} - } - - return nil - } - - push_lua_semantic_size_table :: proc(L: ^lua.State, size: ui.SemanticSize) { - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(i32(size.kind))); - lua.rawseti(L, -2, 1); - - lua.pushinteger(L, lua.Integer(size.value)); - lua.rawseti(L, -2, 2); - } - } - - push_lua_box_interaction :: proc(L: ^lua.State, interaction: ui.Interaction) { - lua.newtable(L); - { - lua.pushboolean(L, b32(interaction.clicked)); - lua.setfield(L, -2, "clicked"); - - lua.pushboolean(L, b32(interaction.hovering)); - lua.setfield(L, -2, "hovering"); - - lua.pushboolean(L, b32(interaction.dragging)); - lua.setfield(L, -2, "dragging"); - - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(interaction.box_pos.x)); - lua.setfield(L, -2, "x"); - - lua.pushinteger(L, lua.Integer(interaction.box_pos.y)); - lua.setfield(L, -2, "y"); - } - lua.setfield(L, -2, "box_pos"); - - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(interaction.box_size.x)); - lua.setfield(L, -2, "x"); - - lua.pushinteger(L, lua.Integer(interaction.box_size.y)); - lua.setfield(L, -2, "y"); - } - lua.setfield(L, -2, "box_size"); - } - } - - ui_lib := [?]lua.L_Reg { - lua.L_Reg { - "get_mouse_pos", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - lua.pushinteger(L, lua.Integer(ui_ctx.mouse_x)); - lua.pushinteger(L, lua.Integer(ui_ctx.mouse_y)); - - return 2; - } - }, - lua.L_Reg { - "Exact", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - value := lua.L_checknumber(L, 1); - push_lua_semantic_size_table(L, { ui.SemanticSizeKind.Exact, int(value) }); - - return 1; - } - }, - lua.L_Reg { - "PercentOfParent", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - value := lua.L_checknumber(L, 1); - push_lua_semantic_size_table(L, { ui.SemanticSizeKind.PercentOfParent, int(value) }); - - return 1; - } - }, - lua.L_Reg { - "push_parent", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - lua.L_checktype(L, 2, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 2); - box := transmute(^ui.Box)lua.touserdata(L, -1); - if box == nil { return i32(lua.ERRRUN); } - - ui.push_parent(ui_ctx, box); - return i32(lua.OK); - } - }, - lua.L_Reg { - "pop_parent", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - ui.pop_parent(ui_ctx); - return i32(lua.OK); - } - }, - lua.L_Reg { - "push_floating", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - x := int(lua.L_checkinteger(L, 3)); - y := int(lua.L_checkinteger(L, 4)); - - box, interaction := ui.push_floating(ui_ctx, strings.clone(string(label), context.temp_allocator), {x,y}); - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction); - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "push_box", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - flags, err := ui_flags(L, 3); - axis := ui.Axis(lua.L_checkinteger(L, 4)); - - semantic_width := get_lua_semantic_size(L, 5); - semantic_height := get_lua_semantic_size(L, 6); - - box, interaction := ui.push_box(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, axis, { semantic_width, semantic_height }); - - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction) - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "_box_interaction", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - lua.L_checktype(L, 2, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 2); - box := transmute(^ui.Box)lua.touserdata(L, -1); - if box == nil { return i32(lua.ERRRUN); } - - interaction := ui.test_box(ui_ctx, box); - push_lua_box_interaction(L, interaction) - return 1; - } - }, - lua.L_Reg { - "push_box", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - flags, err := ui_flags(L, 3); - axis := ui.Axis(lua.L_checkinteger(L, 4)); - - semantic_width := get_lua_semantic_size(L, 5); - semantic_height := get_lua_semantic_size(L, 6); - - box, interaction := ui.push_box(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, axis, { semantic_width, semantic_height }); - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction) - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "push_rect", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - background := bool(lua.toboolean(L, 3)); - border := bool(lua.toboolean(L, 4)); - axis := ui.Axis(lua.L_checkinteger(L, 5)); - - semantic_width := get_lua_semantic_size(L, 6); - semantic_height := get_lua_semantic_size(L, 7); - - box, interaction := ui.push_rect(ui_ctx, strings.clone(string(label), context.temp_allocator), background, border, axis, { semantic_width, semantic_height }); - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction) - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "spacer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - - interaction := ui.spacer(ui_ctx, strings.clone(string(label), context.temp_allocator), semantic_size = {{.Fill, 0}, {.Fill, 0}}); - - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "label", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - - interaction := ui.label(ui_ctx, strings.clone(string(label), context.temp_allocator)); - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "button", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - - interaction := ui.button(ui_ctx, strings.clone(string(label), context.temp_allocator)); - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "advanced_button", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - flags, err := ui_flags(L, 3); - - semantic_width := get_lua_semantic_size(L, 4); - semantic_height := get_lua_semantic_size(L, 5); - - interaction := ui.advanced_button(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, { semantic_width, semantic_height }); - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "buffer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - buffer_index := int(lua.L_checkinteger(L, 2)); - - if buffer_index != -2 && (buffer_index < 0 || buffer_index >= len(state.buffers)) { - return i32(lua.ERRRUN); - } - - ui_file_buffer(ui_ctx, core.buffer_from_index(state, buffer_index)); - - return i32(lua.OK); - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "log_buffer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - ui_file_buffer(ui_ctx, &state.log_buffer); - - return i32(lua.OK); - } - - return i32(lua.ERRRUN); - } - } - }; - - ui_lib_new := [?]lua.L_Reg { - lua.L_Reg { - "Exact", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - value := lua.L_checknumber(L, 1); - lua.newtable(L); - { - // Size Kind 1 = Exact - lua.pushinteger(L, lua.Integer(1)); - lua.rawseti(L, -2, 1); - - lua.pushinteger(L, lua.Integer(value)); - lua.rawseti(L, -2, 2); - } - - return 1; - } - }, - lua.L_Reg { - "close_element", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)) - lua.pushvalue(L, 1) - ui_state := transmute(^ui.State)lua.touserdata(L, -1) - if ui_state == nil { return i32(lua.ERRRUN); } - - ui.close_element(ui_state); - return i32(lua.OK); - } - }, - lua.L_Reg { - "open_element", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)) - lua.pushvalue(L, 1) - ui_state := transmute(^ui.State)lua.touserdata(L, -1) - - if ui_state != nil { - label := lua.L_checkstring(L, 2) - - if !lua.istable(state.L, 3) { - log.error("expected table for element layout"); - - // TODO: maybe call `ui.open_element` anyway to not break everything - - return i32(lua.ERRRUN); - } - - dir: ui.UI_Direction - kind: [2]ui.UI_Size_Kind - - lua.getfield(state.L, 3, "kind"); - top := lua.gettop(state.L) - - if (lua.isnil(state.L, top)) { - lua.pop(state.L, top) - } else if lua.istable(state.L, top) { - kind.x = get_size_kind(state.L, top) - kind.y = get_size_kind(state.L, top) - } else { - log.error("expected table for 'kind' field in element layout") - } - lua.pop(state.L, 1) - - lua.getfield(state.L, 3, "dir"); - if (lua.isnil(state.L, -1)) { - lua.pop(state.L, 1) - } else if lua.isinteger(state.L, -1) { - dir = ui.UI_Direction(lua.tointeger(state.L, -1)) - lua.pop(state.L, 1) - } else { - log.error("expected integer for 'dir' field in element layout") - return i32(lua.ERRRUN); - } - lua.pop(state.L, 1) - - ui.open_element(ui_state, strings.clone(string(label), context.temp_allocator), ui.UI_Layout { dir=dir, kind=kind }) - - return i32(lua.OK) - } - - return i32(lua.ERRRUN) - } - }, - lua.L_Reg { - "buffer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - buffer_index := int(lua.L_checkinteger(L, 2)); - - if buffer_index != -2 && (buffer_index < 0 || buffer_index >= len(state.buffers)) { - return i32(lua.ERRRUN); - } - - ui_file_buffer(ui_ctx, core.buffer_from_index(state, buffer_index)); - - return i32(lua.OK); - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "log_buffer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - ui_file_buffer(ui_ctx, &state.log_buffer); - - return i32(lua.OK); - } - - return i32(lua.ERRRUN); - } - } - }; - - // TODO: generate this from the plugin.Key enum - lua.newtable(state.L); - { - lua.newtable(state.L); - lua.pushinteger(state.L, lua.Integer(plugin.Key.B)); - lua.setfield(state.L, -2, "B"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.F)); - lua.setfield(state.L, -2, "F"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.T)); - lua.setfield(state.L, -2, "T"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.Y)); - lua.setfield(state.L, -2, "Y"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.P)); - lua.setfield(state.L, -2, "P"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.M)); - lua.setfield(state.L, -2, "M"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.K)); - lua.setfield(state.L, -2, "K"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.J)); - lua.setfield(state.L, -2, "J"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.Q)); - lua.setfield(state.L, -2, "Q"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.BACKQUOTE)); - lua.setfield(state.L, -2, "Backtick"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.ESCAPE)); - lua.setfield(state.L, -2, "Escape"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.ENTER)); - lua.setfield(state.L, -2, "Enter"); - - lua.pushinteger(state.L, lua.Integer(plugin.Key.SPACE)); - lua.setfield(state.L, -2, "Space"); - } - lua.setfield(state.L, -2, "Key"); - - { - lua.newtable(state.L); - lua.pushinteger(state.L, lua.Integer(plugin.Hook.BufferInput)); - lua.setfield(state.L, -2, "OnBufferInput"); - lua.pushinteger(state.L, lua.Integer(plugin.Hook.Draw)); - lua.setfield(state.L, -2, "OnDraw"); - } - lua.setfield(state.L, -2, "Hook"); - - lua.L_setfuncs(state.L, bbb, 0); - lua.setglobal(state.L, "Editor"); - - lua.newtable(state.L); - { - lua.pushinteger(state.L, lua.Integer(ui.Axis.Horizontal)); - lua.setfield(state.L, -2, "Horizontal"); - lua.pushinteger(state.L, lua.Integer(ui.Axis.Vertical)); - lua.setfield(state.L, -2, "Vertical"); - push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.Fill, 0 }); - lua.setfield(state.L, -2, "Fill"); - push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.ChildrenSum, 0 }); - lua.setfield(state.L, -2, "ChildrenSum"); - push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.FitText, 0 }); - lua.setfield(state.L, -2, "FitText"); - - lua.L_setfuncs(state.L, raw_data(&ui_lib), 0); - lua.setglobal(state.L, "UI"); - } - - lua.newtable(state.L); - { - lua.pushinteger(state.L, lua.Integer(ui.UI_Direction.LeftToRight)); - lua.setfield(state.L, -2, "LeftToRight"); - // push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.ChildrenSum, 0 }); - // lua.setfield(state.L, -2, "ChildrenSum"); - lua.newtable(state.L); - { - // Size Kind 1 = Exact - lua.pushinteger(state.L, lua.Integer(2)); - lua.rawseti(state.L, -2, 1); - - lua.pushinteger(state.L, lua.Integer(0)); - lua.rawseti(state.L, -2, 2); - } - lua.setfield(state.L, -2, "Fit"); - - lua.L_setfuncs(state.L, raw_data(&ui_lib_new), 0); - lua.setglobal(state.L, "UI_New"); - } -} - -close :: proc(L: ^lua.State) { - lua.close(L) -} - - -@(private) -register_key_group ::proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TTABLE)); - table_to_action(L, 1, state.current_input_map); - - return i32(lua.OK); -} - -@(private) -table_to_action :: proc(L: ^lua.State, index: i32, input_map: ^core.InputActions) { - lua.len(L, index); - key_group_len := lua.tointeger(L, -1); - lua.pop(L, 1); - - for i in 1..=key_group_len { - lua.rawgeti(L, index, i); - defer lua.pop(L, 1); - - lua.rawgeti(L, -1, 1); - key:= plugin.Key(lua.tointeger(L, -1)); - lua.pop(L, 1); - - lua.rawgeti(L, -1, 2); - desc := strings.clone(string(lua.tostring(L, -1))); - lua.pop(L, 1); - - switch lua.rawgeti(L, -1, 3) { - case i32(lua.TTABLE): - if action, exists := input_map.key_actions[key]; exists { - switch value in action.action { - case core.LuaEditorAction: - log.warn("Plugin attempted to register input group on existing key action (added from Lua)"); - case core.PluginEditorAction: - log.warn("Plugin attempted to register input group on existing key action (added from Plugin)"); - case core.EditorAction: - log.warn("Plugin attempted to register input group on existing key action"); - case core.InputActions: - input_map := &(&input_map.key_actions[key]).action.(core.InputActions); - table_to_action(L, lua.gettop(L), input_map); - } - } else { - core.register_key_action(input_map, key, core.new_input_actions(), desc); - table_to_action(L, lua.gettop(L), &((&input_map.key_actions[key]).action.(core.InputActions))); - } - lua.pop(L, 1); - - case i32(lua.TFUNCTION): - fn_ref := lua.L_ref(L, i32(lua.REGISTRYINDEX)); - - if lua.rawgeti(L, -1, 4) == i32(lua.TTABLE) { - maybe_input_map := core.new_input_actions(); - table_to_action(L, lua.gettop(L), &maybe_input_map); - - core.register_key_action_group(input_map, key, core.LuaEditorAction { fn_ref, maybe_input_map }, desc); - } else { - core.register_key_action_group(input_map, key, core.LuaEditorAction { fn_ref, core.InputActions {} }, desc); - } - - case: - lua.pop(L, 1); - } - } -} - -load_plugins :: proc(state: ^core.State, dir: string) { - filepath.walk(filepath.join({ os.get_current_directory(), dir }), walk_plugins, transmute(rawptr)state); - log.info("done walking") - - for plugin in state.new_plugins { - // FIXME: check if the global actually exists - lua.getglobal(state.L, fmt.ctprintf("%s_%s", plugin.namespace, plugin.name)); - - lua.getfield(state.L, -1, "OnLoad"); - if (lua.isnil(state.L, -1)) { - lua.pop(state.L, 2) - log.warn("plugin", plugin.name, "doesn't have an 'OnLoad' function") - - continue - } - - if lua.pcall(state.L, 0, 0, 0) == i32(lua.OK) { - lua.pop(state.L, lua.gettop(state.L)); - } else { - err := lua.tostring(state.L, lua.gettop(state.L)); - lua.pop(state.L, lua.gettop(state.L)); - lua.pop(state.L, 1); - - log.error("failed to initialize plugin (OnLoad):", err); - } - - } -} - -walk_plugins :: proc(info: os.File_Info, in_err: os.Errno, state: rawptr) -> (err: os.Errno, skip_dir: bool) { - state := cast(^core.State)state; - - relative_file_path, rel_error := filepath.rel(state.directory, info.fullpath); - extension := filepath.ext(info.fullpath); - - if extension == ".lua" { - log.info("attempting to load", relative_file_path) - - if plugin, ok := load_plugin(state, info.fullpath); ok { - append(&state.new_plugins, plugin); - - if rel_error == .None { - log.info("Loaded", relative_file_path); - } else { - log.info("Loaded", info.fullpath); - } - } else { - log.error("failed to load") - } - } - - return in_err, skip_dir; -} - -load_plugin :: proc(state: ^core.State, path: string) -> (lua_plugin: plugin.NewInterface, ok: bool) { - if lua.L_dofile(state.L, strings.clone_to_cstring(path, allocator = context.temp_allocator)) != i32(lua.OK) { - err := lua.tostring(state.L, lua.gettop(state.L)) - lua.pop(state.L, lua.gettop(state.L)) - - log.error("failed to load lua plugin:", err) - return - } - - lua.getfield(state.L, -1, "name"); - if (lua.isnil(state.L, -1)) { - lua.pop(state.L, 2) - - log.error("no name for lua plugin") - return - } - name := strings.clone(string(lua.tostring(state.L, -1))) - lua.pop(state.L, 1) - - lua.getfield(state.L, -1, "version"); - if (lua.isnil(state.L, -1)) { - lua.pop(state.L, 2) - return - } - version := strings.clone(string(lua.tostring(state.L, -1))) - lua.pop(state.L, 1) - - lua.getfield(state.L, -1, "namespace"); - if (lua.isnil(state.L, -1)) { - lua.pop(state.L, 2) - return - } - namespace := strings.clone(string(lua.tostring(state.L, -1))) - lua.pop(state.L, 1) - - // Add plugin to lua globals - lua.setglobal(state.L, fmt.caprintf("%s_%s", namespace, name)) - - return plugin.NewInterface { - name = name, - version = version, - namespace = namespace, - }, true -} - -ui_flags :: proc(L: ^lua.State, index: i32) -> (bit_set[ui.Flag], bool) { - lua.L_checktype(L, index, i32(lua.TTABLE)); - lua.len(L, index); - array_len := lua.tointeger(L, -1); - lua.pop(L, 1); - - flags: bit_set[ui.Flag] - - for i in 1..=array_len { - lua.rawgeti(L, index, i); - defer lua.pop(L, 1); - - flag := lua.tostring(L, -1); - switch flag { - case "Clickable": flags |= {.Clickable} - case "Hoverable": flags |= {.Hoverable} - case "Scrollable": flags |= {.Scrollable} - case "DrawText": flags |= {.DrawText} - case "DrawBorder": flags |= {.DrawBorder} - case "DrawBackground": flags |= {.DrawBackground} - case "RoundedBorder": flags |= {.RoundedBorder} - case "Floating": flags |= {.Floating} - case "CustomDrawFunc": flags |= {.CustomDrawFunc} - } - } - - return flags, true -} - -run_editor_action :: proc(state: ^core.State, key: plugin.Key, action: core.LuaEditorAction, location := #caller_location) { - lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(action.fn_ref)); - if lua.pcall(state.L, 0, 0, 0) != i32(lua.OK) { - err := lua.tostring(state.L, lua.gettop(state.L)); - lua.pop(state.L, lua.gettop(state.L)); - - log.error(err, location); - } else { - lua.pop(state.L, lua.gettop(state.L)); - } - - if action.maybe_input_map.key_actions != nil { - ptr_action := &(&state.current_input_map.key_actions[key]).action.(core.LuaEditorAction) - state.current_input_map = (&ptr_action.maybe_input_map) - } -} - -run_ui_function :: proc(state: ^core.State, ui_context: ^ui.Context, fn_ref: i32) { - lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(fn_ref)); - lua.pushlightuserdata(state.L, ui_context); - if lua.pcall(state.L, 1, 0, 0) != i32(lua.OK) { - err := lua.tostring(state.L, lua.gettop(state.L)); - lua.pop(state.L, lua.gettop(state.L)); - - log.error(err); - } else { - lua.pop(state.L, lua.gettop(state.L)); - } -} - -run_panel_render :: proc(state: ^core.State, ui_context: ^ui.Context, panel_index: i32, fn_ref: i32) { - lua.getglobal(state.L, "__core_panels") - - lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(fn_ref)); - - lua.pushinteger(state.L, lua.Integer(panel_index)); - lua.gettable(state.L, -3); - - lua.pushlightuserdata(state.L, ui_context); - if lua.pcall(state.L, 2, 0, 0) != i32(lua.OK) { - err := lua.tostring(state.L, lua.gettop(state.L)); - lua.pop(state.L, lua.gettop(state.L)); - - log.error(err); - } else { - lua.pop(state.L, lua.gettop(state.L)); - } -} - -run_panel_render_new :: proc(state: ^core.State, ui_state: ^ui.State, panel_index: i32, fn_ref: i32) { - lua.getglobal(state.L, "__core_panels") - - lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(fn_ref)); - - lua.pushinteger(state.L, lua.Integer(panel_index)); - lua.gettable(state.L, -3); - - lua.pushlightuserdata(state.L, ui_state); - if lua.pcall(state.L, 2, 0, 0) != i32(lua.OK) { - err := lua.tostring(state.L, lua.gettop(state.L)); - lua.pop(state.L, lua.gettop(state.L)); - - log.error("[LUA]:", err); - } else { - lua.pop(state.L, lua.gettop(state.L)); - } -} - -add_panel :: proc(state: ^core.State, id: core.LuaPanelId) -> (new_panel: core.LuaPanel, ok: bool) { - lua.getglobal(state.L, "__core_panels") - - // create panel list if it doesn't already exist - if !lua.istable(state.L, -1) { - log.info("__core_panels doesn't exist yet, create it..") - - lua.newtable(state.L) - lua.setglobal(state.L, "__core_panels") - } - - lua.getglobal(state.L, "__core_panels") - - l_panel_list := lua.gettop(state.L) - - lua.len(state.L, l_panel_list); - num_panels := lua.tointeger(state.L, -1); - lua.pop(state.L, 1); - - render_ref: i32 - - // lua.pushinteger(state.L, lua.Integer(num_panels + 1)) - { - // create new panel object in lua - - // FIXME: eventually grab the appropriate namespace from the panel id - lua.getglobal(state.L, "nl_spacegirl_plugin_Default_Default_View"); - - lua.getfield(state.L, -1, strings.clone_to_cstring(id.id, allocator = context.temp_allocator)); - if (lua.isnil(state.L, -1)) { - lua.pop(state.L, lua.gettop(state.L)-l_panel_list+1) - - log.error("no lua panel with identifier:", id.id) - return - } - - lua.getfield(state.L, -1, "render") - if lua.isfunction(state.L, -1) { - render_ref = lua.L_ref(state.L, i32(lua.REGISTRYINDEX)); - } else { - lua.pop(state.L, lua.gettop(state.L)-l_panel_list+1) - - log.error("no 'render' function for lua panel:", id.id) - return - } - - lua.getfield(state.L, -1, "new") - if (lua.isnil(state.L, -1)) { - lua.pop(state.L, lua.gettop(state.L)-l_panel_list+1) - - log.error("no 'new' function for lua panel:", id.id) - return - } - - if lua.pcall(state.L, 0, 1, 0) != i32(lua.OK) { - err := lua.tostring(state.L, lua.gettop(state.L)); - lua.pop(state.L, lua.gettop(state.L)-l_panel_list+1); - - // FIXME: do we need this? - // lua.pop(state.L, 1); - - log.error("failed to create new lua panel:", err); - } - log.info("called") - } - lua.rawseti(state.L, l_panel_list, num_panels + 1); - lua.settable(state.L, l_panel_list) - - return core.LuaPanel{ - panel_id = id, - index = i32(num_panels) + 1, - render_ref = render_ref - }, true -} - -// TODO: don't duplicate this procedure -ui_file_buffer :: proc(ctx: ^ui.Context, buffer: ^core.FileBuffer) -> ui.Interaction { - draw_func := proc(state: ^core.State, box: ^ui.Box, user_data: rawptr) { - buffer := transmute(^core.FileBuffer)user_data; - buffer.glyph_buffer_width = box.computed_size.x / state.source_font_width; - buffer.glyph_buffer_height = box.computed_size.y / state.source_font_height + 1; - - core.draw_file_buffer(state, buffer, box.computed_pos.x, box.computed_pos.y); - }; - - relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator) - - buffer_container, _ := ui.push_box(ctx, relative_file_path, {}, .Vertical, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Fill)}); - ui.push_parent(ctx, buffer_container); - defer ui.pop_parent(ctx); - - interaction := ui.custom(ctx, "buffer1", draw_func, transmute(rawptr)buffer); - - { - info_box, _ := ui.push_box(ctx, "buffer info", {}, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Exact, state.source_font_height)}); - ui.push_parent(ctx, info_box); - defer ui.pop_parent(ctx); - - ui.label(ctx, fmt.tprintf("%s", state.mode)) - if selection, exists := buffer.selection.?; exists { - ui.label(ctx, fmt.tprintf("sel: %d:%d", selection.end.line, selection.end.col)); - } - ui.spacer(ctx, "spacer"); - ui.label(ctx, relative_file_path); - } - - return interaction; -} diff --git a/src/lua/lua.odin~ b/src/lua/lua.odin~ deleted file mode 100644 index 0a41189..0000000 --- a/src/lua/lua.odin~ +++ /dev/null @@ -1,693 +0,0 @@ -package plugin - -import "core:strings" - -import core "../core" -import ui "../ui" - -import lua "vendor:lua/5.4" - -state: ^core.State = nil - -new_lua_state :: proc() -> ^lua.State { - L := lua.L_newstate(); - lua.L_openlibs(L); - - bbb: [^]lua.L_Reg; - editor_lib := [?]lua.L_Reg { - lua.L_Reg { - "quit", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - - state.should_close = true; - return i32(lua.OK); - } - }, - lua.L_Reg { - "log", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - text := string(lua.L_checkstring(L, 1)); - log.info("[LUA]:", text); - - return i32(lua.OK); - } - }, - lua.L_Reg { - "register_hook", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - hook := lua.L_checkinteger(L, 1); - - lua.L_checktype(L, 2, i32(lua.TFUNCTION)); - lua.pushvalue(L, 2); - fn_ref := lua.L_ref(L, i32(lua.REGISTRYINDEX)); - - core.add_lua_hook(&state, Hook(hook), fn_ref); - - return i32(lua.OK); - } - }, - lua.L_Reg { - "register_key_group", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TTABLE)); - - table_to_action :: proc(L: ^lua.State, index: i32, input_map: ^core.InputActions) { - lua.len(L, index); - key_group_len := lua.tointeger(L, -1); - lua.pop(L, 1); - - for i in 1..=key_group_len { - lua.rawgeti(L, index, i); - defer lua.pop(L, 1); - - lua.rawgeti(L, -1, 1); - key:= Key(lua.tointeger(L, -1)); - lua.pop(L, 1); - - lua.rawgeti(L, -1, 2); - desc := strings.clone(string(lua.tostring(L, -1))); - lua.pop(L, 1); - - switch lua.rawgeti(L, -1, 3) { - case i32(lua.TTABLE): - if action, exists := input_map.key_actions[key]; exists { - switch value in action.action { - case core.LuaEditorAction: - log.warn("Plugin attempted to register input group on existing key action (added from Lua)"); - case core.PluginEditorAction: - log.warn("Plugin attempted to register input group on existing key action (added from Plugin)"); - case core.EditorAction: - log.warn("Plugin attempted to register input group on existing key action"); - case core.InputActions: - input_map := &(&input_map.key_actions[key]).action.(core.InputActions); - table_to_action(L, lua.gettop(L), input_map); - } - } else { - core.register_key_action(input_map, key, core.new_input_actions(), desc); - table_to_action(L, lua.gettop(L), &((&input_map.key_actions[key]).action.(core.InputActions))); - } - lua.pop(L, 1); - - case i32(lua.TFUNCTION): - fn_ref := lua.L_ref(L, i32(lua.REGISTRYINDEX)); - - if lua.rawgeti(L, -1, 4) == i32(lua.TTABLE) { - maybe_input_map := core.new_input_actions(); - table_to_action(L, lua.gettop(L), &maybe_input_map); - - core.register_key_action_group(input_map, key, core.LuaEditorAction { fn_ref, maybe_input_map }, desc); - } else { - core.register_key_action_group(input_map, key, core.LuaEditorAction { fn_ref, core.InputActions {} }, desc); - } - - case: - lua.pop(L, 1); - } - } - } - - table_to_action(L, 1, state.current_input_map); - - return i32(lua.OK); - } - }, - lua.L_Reg { - "request_window_close", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - core.request_window_close(&state); - - return i32(lua.OK); - } - }, - lua.L_Reg { - "get_current_buffer_index", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.pushinteger(L, lua.Integer(state.current_buffer)); - - return 1; - } - }, - lua.L_Reg { - "set_current_buffer_from_index", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - buffer_index := int(lua.L_checkinteger(L, 1)); - if buffer_index != -2 && (buffer_index < 0 || buffer_index >= len(state.buffers)) { - return i32(lua.ERRRUN); - } else { - state.current_buffer = buffer_index; - } - - return i32(lua.OK); - } - }, - lua.L_Reg { - "buffer_info_from_index", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - buffer_index := int(lua.L_checkinteger(L, 1)); - if buffer_index < 0 || buffer_index >= len(state.buffers) { - lua.pushnil(L); - } else { - push_lua_buffer_info :: proc(L: ^lua.State, buffer: ^FileBuffer) { - lua.newtable(L); - { - lua.pushlightuserdata(L, buffer); - lua.setfield(L, -2, "buffer"); - - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(buffer.cursor.col)); - lua.setfield(L, -2, "col"); - - lua.pushinteger(L, lua.Integer(buffer.cursor.line)); - lua.setfield(L, -2, "line"); - } - lua.setfield(L, -2, "cursor"); - - lua.pushstring(L, strings.clone_to_cstring(buffer.file_path, context.temp_allocator)); - lua.setfield(L, -2, "full_file_path"); - - relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator) - lua.pushstring(L, strings.clone_to_cstring(relative_file_path, context.temp_allocator)); - lua.setfield(L, -2, "file_path"); - } - } - - push_lua_buffer_info(L, core.buffer_from_index(&state, buffer_index)); - } - - return 1; - } - }, - lua.L_Reg { - "query_command_group", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - group := lua.L_checkstring(L, 1); - cmds := core.query_editor_commands_by_group(&state.commands, string(group), scratch_alloc); - - lua.newtable(L); - { - for cmd, i in cmds { - lua.newtable(L); - { - lua.pushstring(L, strings.clone_to_cstring(cmd.name, scratch_alloc)); - lua.setfield(L, -2, "name"); - - lua.pushstring(L, strings.clone_to_cstring(cmd.description, scratch_alloc)); - lua.setfield(L, -2, "description"); - } - lua.rawseti(L, -2, lua.Integer(i+1)); - } - } - - return 1; - } - }, - lua.L_Reg { - "run_command", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - group := lua.L_checkstring(L, 1); - name := lua.L_checkstring(L, 2); - core.run_command(&state, string(group), string(name)); - - return 1; - } - } - }; - bbb = raw_data(editor_lib[:]); - - get_lua_semantic_size :: proc(L: ^lua.State, index: i32) -> ui.SemanticSize { - if lua.istable(L, index) { - lua.rawgeti(L, index, 1); - semantic_kind := ui.SemanticSizeKind(lua.tointeger(L, -1)); - lua.pop(L, 1); - - lua.rawgeti(L, index, 2); - semantic_value := int(lua.tointeger(L, -1)); - lua.pop(L, 1); - - return {semantic_kind, semantic_value}; - } else { - semantic_kind := ui.SemanticSizeKind(lua.L_checkinteger(L, index)); - return {semantic_kind, 0}; - } - } - - push_lua_semantic_size_table :: proc(L: ^lua.State, size: ui.SemanticSize) { - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(i32(size.kind))); - lua.rawseti(L, -2, 1); - - lua.pushinteger(L, lua.Integer(size.value)); - lua.rawseti(L, -2, 2); - } - } - - push_lua_box_interaction :: proc(L: ^lua.State, interaction: ui.Interaction) { - lua.newtable(L); - { - lua.pushboolean(L, b32(interaction.clicked)); - lua.setfield(L, -2, "clicked"); - - lua.pushboolean(L, b32(interaction.hovering)); - lua.setfield(L, -2, "hovering"); - - lua.pushboolean(L, b32(interaction.dragging)); - lua.setfield(L, -2, "dragging"); - - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(interaction.box_pos.x)); - lua.setfield(L, -2, "x"); - - lua.pushinteger(L, lua.Integer(interaction.box_pos.y)); - lua.setfield(L, -2, "y"); - } - lua.setfield(L, -2, "box_pos"); - - lua.newtable(L); - { - lua.pushinteger(L, lua.Integer(interaction.box_size.x)); - lua.setfield(L, -2, "x"); - - lua.pushinteger(L, lua.Integer(interaction.box_size.y)); - lua.setfield(L, -2, "y"); - } - lua.setfield(L, -2, "box_size"); - } - } - - ui_lib := [?]lua.L_Reg { - lua.L_Reg { - "get_mouse_pos", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - lua.pushinteger(L, lua.Integer(ui_ctx.mouse_x)); - lua.pushinteger(L, lua.Integer(ui_ctx.mouse_y)); - - return 2; - } - }, - lua.L_Reg { - "Exact", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - value := lua.L_checknumber(L, 1); - push_lua_semantic_size_table(L, { ui.SemanticSizeKind.Exact, int(value) }); - - return 1; - } - }, - lua.L_Reg { - "PercentOfParent", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - value := lua.L_checknumber(L, 1); - push_lua_semantic_size_table(L, { ui.SemanticSizeKind.PercentOfParent, int(value) }); - - return 1; - } - }, - lua.L_Reg { - "push_parent", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - lua.L_checktype(L, 2, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 2); - box := transmute(^ui.Box)lua.touserdata(L, -1); - if box == nil { return i32(lua.ERRRUN); } - - ui.push_parent(ui_ctx, box); - return i32(lua.OK); - } - }, - lua.L_Reg { - "pop_parent", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - ui.pop_parent(ui_ctx); - return i32(lua.OK); - } - }, - lua.L_Reg { - "push_floating", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - x := int(lua.L_checkinteger(L, 3)); - y := int(lua.L_checkinteger(L, 4)); - - box, interaction := ui.push_floating(ui_ctx, strings.clone(string(label), context.temp_allocator), {x,y}); - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction); - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "push_box", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - flags, err := lua_ui_flags(L, 3); - axis := ui.Axis(lua.L_checkinteger(L, 4)); - - semantic_width := get_lua_semantic_size(L, 5); - semantic_height := get_lua_semantic_size(L, 6); - - box, interaction := ui.push_box(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, axis, { semantic_width, semantic_height }); - - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction) - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "_box_interaction", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx == nil { return i32(lua.ERRRUN); } - - lua.L_checktype(L, 2, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 2); - box := transmute(^ui.Box)lua.touserdata(L, -1); - if box == nil { return i32(lua.ERRRUN); } - - interaction := ui.test_box(ui_ctx, box); - push_lua_box_interaction(L, interaction) - return 1; - } - }, - lua.L_Reg { - "push_box", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - flags, err := lua_ui_flags(L, 3); - axis := ui.Axis(lua.L_checkinteger(L, 4)); - - semantic_width := get_lua_semantic_size(L, 5); - semantic_height := get_lua_semantic_size(L, 6); - - box, interaction := ui.push_box(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, axis, { semantic_width, semantic_height }); - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction) - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "push_rect", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - background := bool(lua.toboolean(L, 3)); - border := bool(lua.toboolean(L, 4)); - axis := ui.Axis(lua.L_checkinteger(L, 5)); - - semantic_width := get_lua_semantic_size(L, 6); - semantic_height := get_lua_semantic_size(L, 7); - - box, interaction := ui.push_rect(ui_ctx, strings.clone(string(label), context.temp_allocator), background, border, axis, { semantic_width, semantic_height }); - lua.pushlightuserdata(L, box); - push_lua_box_interaction(L, interaction) - return 2; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "spacer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - - interaction := ui.spacer(ui_ctx, strings.clone(string(label), context.temp_allocator), semantic_size = {{.Fill, 0}, {.Fill, 0}}); - - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "label", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - - interaction := ui.label(ui_ctx, strings.clone(string(label), context.temp_allocator)); - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "button", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - - interaction := ui.button(ui_ctx, strings.clone(string(label), context.temp_allocator)); - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "advanced_button", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - label := lua.L_checkstring(L, 2); - flags, err := lua_ui_flags(L, 3); - - semantic_width := get_lua_semantic_size(L, 4); - semantic_height := get_lua_semantic_size(L, 5); - - interaction := ui.advanced_button(ui_ctx, strings.clone(string(label), context.temp_allocator), flags, { semantic_width, semantic_height }); - push_lua_box_interaction(L, interaction) - - return 1; - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "buffer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - buffer_index := int(lua.L_checkinteger(L, 2)); - - if buffer_index != -2 && (buffer_index < 0 || buffer_index >= len(state.buffers)) { - return i32(lua.ERRRUN); - } - - ui_file_buffer(ui_ctx, core.buffer_from_index(&state, buffer_index)); - - return i32(lua.OK); - } - - return i32(lua.ERRRUN); - } - }, - lua.L_Reg { - "log_buffer", - proc "c" (L: ^lua.State) -> i32 { - context = state.ctx; - - lua.L_checktype(L, 1, i32(lua.TLIGHTUSERDATA)); - lua.pushvalue(L, 1); - ui_ctx := transmute(^ui.Context)lua.touserdata(L, -1); - - if ui_ctx != nil { - ui_file_buffer(ui_ctx, &state.log_buffer); - - return i32(lua.OK); - } - - return i32(lua.ERRRUN); - } - } - }; - - // TODO: generate this from the plugin.Key enum - lua.newtable(state.L); - { - lua.newtable(state.L); - lua.pushinteger(state.L, lua.Integer(Key.B)); - lua.setfield(state.L, -2, "B"); - - lua.pushinteger(state.L, lua.Integer(Key.T)); - lua.setfield(state.L, -2, "T"); - - lua.pushinteger(state.L, lua.Integer(Key.Y)); - lua.setfield(state.L, -2, "Y"); - - lua.pushinteger(state.L, lua.Integer(Key.P)); - lua.setfield(state.L, -2, "P"); - - lua.pushinteger(state.L, lua.Integer(Key.M)); - lua.setfield(state.L, -2, "M"); - - lua.pushinteger(state.L, lua.Integer(Key.K)); - lua.setfield(state.L, -2, "K"); - - lua.pushinteger(state.L, lua.Integer(Key.J)); - lua.setfield(state.L, -2, "J"); - - lua.pushinteger(state.L, lua.Integer(Key.Q)); - lua.setfield(state.L, -2, "Q"); - - lua.pushinteger(state.L, lua.Integer(Key.BACKQUOTE)); - lua.setfield(state.L, -2, "Backtick"); - - lua.pushinteger(state.L, lua.Integer(Key.ESCAPE)); - lua.setfield(state.L, -2, "Escape"); - - lua.pushinteger(state.L, lua.Integer(Key.ENTER)); - lua.setfield(state.L, -2, "Enter"); - - lua.pushinteger(state.L, lua.Integer(Key.SPACE)); - lua.setfield(state.L, -2, "Space"); - } - lua.setfield(state.L, -2, "Key"); - - { - lua.newtable(state.L); - lua.pushinteger(state.L, lua.Integer(Hook.BufferInput)); - lua.setfield(state.L, -2, "OnBufferInput"); - lua.pushinteger(state.L, lua.Integer(Hook.Draw)); - lua.setfield(state.L, -2, "OnDraw"); - } - lua.setfield(state.L, -2, "Hook"); - - lua.L_setfuncs(state.L, bbb, 0); - lua.setglobal(state.L, "Editor"); - - lua.newtable(state.L); - { - lua.pushinteger(state.L, lua.Integer(ui.Axis.Horizontal)); - lua.setfield(state.L, -2, "Horizontal"); - lua.pushinteger(state.L, lua.Integer(ui.Axis.Vertical)); - lua.setfield(state.L, -2, "Vertical"); - push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.Fill, 0 }); - lua.setfield(state.L, -2, "Fill"); - push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.ChildrenSum, 0 }); - lua.setfield(state.L, -2, "ChildrenSum"); - push_lua_semantic_size_table(state.L, { ui.SemanticSizeKind.FitText, 0 }); - lua.setfield(state.L, -2, "FitText"); - - lua.L_setfuncs(state.L, raw_data(&ui_lib), 0); - lua.setglobal(state.L, "UI"); - } - - return L -} diff --git a/src/main.odin b/src/main.odin index b8bc8a4..4f941c2 100644 --- a/src/main.odin +++ b/src/main.odin @@ -13,13 +13,11 @@ import "core:slice" import "vendor:sdl2" import "vendor:sdl2/ttf" -import "lua" import "core" import "theme" import "ui" -import "plugin" -HardcodedFontPath :: "bin/BerkeleyMono-Regular.ttf"; +HardcodedFontPath :: "bin/JetBrainsMono-Regular.ttf"; State :: core.State; FileBuffer :: core.FileBuffer; @@ -29,58 +27,15 @@ scratch: mem.Scratch; scratch_alloc: runtime.Allocator; state := core.State {}; -StateWithUi :: struct { - state: ^State, - ui_context: ^ui.Context, -} - -// TODO: why do I have this here again? -// TODO: use buffer list in state do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) { - if state.current_input_map != nil { - // if raylib.IsKeyPressed(.ESCAPE) { - // core.request_window_close(state); - // } else if raylib.IsKeyDown(.LEFT_CONTROL) { - // for key, action in &state.current_input_map.ctrl_key_actions { - // if raylib.IsKeyPressed(key) { - // switch value in action.action { - // case core.PluginEditorAction: - // value(state.plugin_vtable); - // case core.EditorAction: - // value(state); - // case core.InputMap: - // state.current_input_map = &(&state.current_input_map.ctrl_key_actions[key]).action.(core.InputMap) - // } - // } - // } - // } else { - // for key, action in state.current_input_map.key_actions { - // if raylib.IsKeyPressed(key) { - // switch value in action.action { - // case core.PluginEditorAction: - // value(state.plugin_vtable); - // case core.EditorAction: - // value(state); - // case core.InputMap: - // state.current_input_map = &(&state.current_input_map.key_actions[key]).action.(core.InputMap) - // } - // } - // } - // } - } } -// TODO: use buffer list in state do_insert_mode :: proc(state: ^State, buffer: ^FileBuffer) { key := 0; for key > 0 { if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 { append(&buffer.input_buffer, u8(key)); - - for hook_proc in state.hooks[plugin.Hook.BufferInput] { - hook_proc(state.plugin_vtable, buffer); - } } key = 0; @@ -286,27 +241,6 @@ register_default_text_input_actions :: proc(input_map: ^core.InputActions) { }, "insert mode on newline"); } -load_plugin :: proc(info: os.File_Info, in_err: os.Errno, state: rawptr) -> (err: os.Errno, skip_dir: bool) { - state := cast(^State)state; - - relative_file_path, rel_error := filepath.rel(state.directory, info.fullpath); - extension := filepath.ext(info.fullpath); - - if extension == ".dylib" || extension == ".dll" || extension == ".so" { - if loaded_plugin, succ := plugin.try_load_plugin(info.fullpath); succ { - append(&state.plugins, loaded_plugin); - - if rel_error == .None { - log.info("Loaded", relative_file_path); - } else { - log.info("Loaded", info.fullpath); - } - } - } - - return in_err, skip_dir; -} - ui_font_width :: proc() -> i32 { return i32(state.source_font_width); } @@ -314,189 +248,108 @@ ui_font_height :: proc() -> i32 { return i32(state.source_font_height); } -draw :: proc(state_with_ui: ^StateWithUi) { - if buffer := core.current_buffer(state_with_ui.state); buffer != nil { - buffer.glyph_buffer_height = math.min(256, int((state_with_ui.state.screen_height - state_with_ui.state.source_font_height*2) / state_with_ui.state.source_font_height)) + 1; - buffer.glyph_buffer_width = math.min(256, int((state_with_ui.state.screen_width - state_with_ui.state.source_font_width) / state_with_ui.state.source_font_width)); +draw :: proc(state: ^State) { + if buffer := core.current_buffer(state); buffer != nil { + buffer.glyph_buffer_height = math.min(256, int((state.screen_height - state.source_font_height*2) / state.source_font_height)) + 1; + buffer.glyph_buffer_width = math.min(256, int((state.screen_width - state.source_font_width) / state.source_font_width)); } render_color := theme.get_palette_color(.Background); - sdl2.SetRenderDrawColor(state_with_ui.state.sdl_renderer, render_color.r, render_color.g, render_color.b, render_color.a); - sdl2.RenderClear(state_with_ui.state.sdl_renderer); + sdl2.SetRenderDrawColor(state.sdl_renderer, render_color.r, render_color.g, render_color.b, render_color.a); + sdl2.RenderClear(state.sdl_renderer); new_ui := transmute(^ui.State)state.ui + new_ui.max_size.x = state.screen_width + new_ui.max_size.y = state.screen_height - ui.open_element(new_ui, nil, { - kind = {ui.Exact(state.screen_width), ui.Exact(state.screen_height)}, - }) - { - // ui.open_element(new_ui, "Hello, I am a text thingy", { - // kind = {ui.Grow{}, ui.Grow{}} - // }) - // ui.close_element(new_ui) - - // ui_file_buffer_2(new_ui, core.current_buffer(state_with_ui.state)) - - // ui.open_element(new_ui, "I am on the right hopefully", { - // kind = {nil, ui.Grow{}} - // }) - // ui.close_element(new_ui) - // - // ui.open_element(new_ui, "Number 4", { - // kind = {nil, ui.Grow{}} - // }) - // ui.close_element(new_ui) - - for panel in state.active_panels { - if panel != nil { - switch v in panel.? { - case core.LuaPanel: { - ui.open_element(new_ui, nil, { - dir = .TopToBottom, - kind = {ui.Grow{}, ui.Grow{}} - }) - { - ui.open_element(new_ui, nil, { - kind = {ui.Grow{}, ui.Grow{}} - }) - { - // ui.open_element(new_ui, "TODO: lua api for new ui", {}) - // ui.close_element(new_ui) - lua.run_panel_render_new(&state, new_ui, v.index, v.render_ref) - } - ui.close_element(new_ui) - - ui.open_element(new_ui, v.panel_id.name, {}) - ui.close_element(new_ui) - } - ui.close_element(new_ui) - } - case core.LibPanel: - log.warn("LibPanel not supported") - } - } - } - + // TODO: use the new panels stuff + if file_buffer := core.current_buffer(state); file_buffer != nil { + ui_file_buffer(new_ui, file_buffer) } - ui.close_element(new_ui) ui.compute_layout_2(new_ui) + ui.draw(new_ui, state) - ui.compute_layout(state_with_ui.ui_context, { state_with_ui.state.screen_width, state_with_ui.state.screen_height }, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root); - ui.draw(state_with_ui.ui_context, state_with_ui.state, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root); - - ui.new_draw(new_ui, &state) - - if state_with_ui.state.mode != .Insert && state_with_ui.state.current_input_map != &state_with_ui.state.input_map.mode[state_with_ui.state.mode] { + if state.mode != .Insert && state.current_input_map != &state.input_map.mode[state.mode] { longest_description := 0; - for key, action in state_with_ui.state.current_input_map.key_actions { + for key, action in state.current_input_map.key_actions { if len(action.description) > longest_description { longest_description = len(action.description); } } - for key, action in state_with_ui.state.current_input_map.ctrl_key_actions { + for key, action in state.current_input_map.ctrl_key_actions { if len(action.description) > longest_description { longest_description = len(action.description); } } longest_description += 8; - helper_height := state_with_ui.state.source_font_height * (len(state_with_ui.state.current_input_map.key_actions) + len(state_with_ui.state.current_input_map.ctrl_key_actions)); - offset_from_bottom := state_with_ui.state.source_font_height * 2; + helper_height := state.source_font_height * (len(state.current_input_map.key_actions) + len(state.current_input_map.ctrl_key_actions)); + offset_from_bottom := state.source_font_height * 2; core.draw_rect( - state_with_ui.state, - state_with_ui.state.screen_width - longest_description * state_with_ui.state.source_font_width, - state_with_ui.state.screen_height - helper_height - offset_from_bottom, - longest_description*state_with_ui.state.source_font_width, + state, + state.screen_width - longest_description * state.source_font_width, + state.screen_height - helper_height - offset_from_bottom, + longest_description*state.source_font_width, helper_height, .Background2 ); index := 0; - for key, action in state_with_ui.state.current_input_map.key_actions { + for key, action in state.current_input_map.key_actions { core.draw_text( - state_with_ui.state, + state, fmt.tprintf("%s - %s", key, action.description), - state_with_ui.state.screen_width - longest_description * state_with_ui.state.source_font_width, - state_with_ui.state.screen_height - helper_height + index * state_with_ui.state.source_font_height - offset_from_bottom + state.screen_width - longest_description * state.source_font_width, + state.screen_height - helper_height + index * state.source_font_height - offset_from_bottom ); index += 1; } - for key, action in state_with_ui.state.current_input_map.ctrl_key_actions { + for key, action in state.current_input_map.ctrl_key_actions { core.draw_text( - state_with_ui.state, + state, fmt.tprintf("-%s - %s", key, action.description), - state_with_ui.state.screen_width - longest_description * state_with_ui.state.source_font_width, - state_with_ui.state.screen_height - helper_height + index * state_with_ui.state.source_font_height - offset_from_bottom + state.screen_width - longest_description * state.source_font_width, + state.screen_height - helper_height + index * state.source_font_height - offset_from_bottom ); index += 1; } } - sdl2.RenderPresent(state_with_ui.state.sdl_renderer); + sdl2.RenderPresent(state.sdl_renderer); } expose_event_watcher :: proc "c" (state: rawptr, event: ^sdl2.Event) -> i32 { if event.type == .WINDOWEVENT { - state := transmute(^StateWithUi)state; - context = state.state.ctx; + state := transmute(^State)state; + context = state.ctx; if event.window.event == .EXPOSED { //draw(state); } else if event.window.event == .SIZE_CHANGED { w,h: i32; - sdl2.GetRendererOutputSize(state.state.sdl_renderer, &w, &h); + sdl2.GetRendererOutputSize(state.sdl_renderer, &w, &h); - state.state.screen_width = int(w); - state.state.screen_height = int(h); - state.state.width_dpi_ratio = f32(w) / f32(event.window.data1); - state.state.height_dpi_ratio = f32(h) / f32(event.window.data2); + state.screen_width = int(w); + state.screen_height = int(h); + state.width_dpi_ratio = f32(w) / f32(event.window.data1); + state.height_dpi_ratio = f32(h) / f32(event.window.data2); - draw(state); + // KDE resizes very slowly on linux if you trigger a re-render + when ODIN_OS != .Linux { + draw(state); + } } } return 0; } -ui_file_buffer :: proc(ctx: ^ui.Context, buffer: ^FileBuffer) -> ui.Interaction { - draw_func := proc(state: ^State, box: ^ui.Box, user_data: rawptr) { - buffer := transmute(^FileBuffer)user_data; - buffer.glyph_buffer_width = box.computed_size.x / state.source_font_width; - buffer.glyph_buffer_height = box.computed_size.y / state.source_font_height + 1; - - core.draw_file_buffer(state, buffer, box.computed_pos.x, box.computed_pos.y); - }; - - relative_file_path, _ := filepath.rel(state.directory, buffer.file_path, context.temp_allocator) - - buffer_container, _ := ui.push_box(ctx, relative_file_path, {}, .Vertical, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Fill)}); - ui.push_parent(ctx, buffer_container); - defer ui.pop_parent(ctx); - - interaction := ui.custom(ctx, "buffer1", draw_func, transmute(rawptr)buffer); - - { - info_box, _ := ui.push_box(ctx, "buffer info", {}, semantic_size = {ui.make_semantic_size(.Fill), ui.make_semantic_size(.Exact, state.source_font_height)}); - ui.push_parent(ctx, info_box); - defer ui.pop_parent(ctx); - - ui.label(ctx, fmt.tprintf("%s", state.mode)) - if selection, exists := buffer.selection.?; exists { - ui.label(ctx, fmt.tprintf("sel: %d:%d", selection.end.line, selection.end.col)); - } - ui.spacer(ctx, "spacer"); - ui.label(ctx, relative_file_path); - } - - return interaction; -} - -ui_file_buffer_2 :: proc(s: ^ui.State, buffer: ^FileBuffer) { +ui_file_buffer :: proc(s: ^ui.State, buffer: ^FileBuffer) { draw_func := proc(state: ^State, e: ui.UI_Element, user_data: rawptr) { buffer := transmute(^FileBuffer)user_data; if buffer != nil { @@ -527,613 +380,10 @@ ui_file_buffer_2 :: proc(s: ^ui.State, buffer: ^FileBuffer) { ui.close_element(s) } ui.close_element(s) - - /* - ui.open_element(s, nil, { - kind = {ui.Grow{}, ui.Exact(state.source_font_height)} - }) - { - ui.open_element(s, fmt.tprintf("%s", state.mode), {}) - ui.close_element(s) - - if selection, exists := buffer.selection.?; exists { - ui.open_element(s, fmt.tprintf("sel: %d:%d", selection.end.line, selection.end.col), {}); - ui.close_element(s) - } - ui.open_element(s, nil, { - kind = {ui.Grow{}, ui.Grow{}} - }) - ui.close_element(s) - - ui.open_element(s, relative_file_path, {}); - ui.close_element(s) - } - ui.close_element(s) - */ - } ui.close_element(s) } -init_plugin_vtable :: proc(ui_context: ^ui.Context) -> plugin.Plugin { - return plugin.Plugin { - state = cast(rawptr)&state, - register_hook = proc "c" (hook: plugin.Hook, on_hook: plugin.OnHookProc) { - context = state.ctx; - - core.add_hook(&state, hook, on_hook); - }, - register_highlighter = proc "c" (extension: cstring, on_color_buffer: plugin.OnColorBufferProc) { - context = state.ctx; - - extension := strings.clone(string(extension)); - - if _, exists := state.highlighters[extension]; exists { - log.error("Highlighter already registered for", extension, "files"); - } else { - state.highlighters[extension] = on_color_buffer; - } - }, - register_input_group = proc "c" (input_map: rawptr, key: plugin.Key, register_group: plugin.InputGroupProc) { - context = state.ctx; - - to_be_edited_map: ^core.InputActions = nil; - - if input_map != nil { - to_be_edited_map = transmute(^core.InputActions)input_map; - } else { - to_be_edited_map = state.current_input_map; - } - - // TODO: change this to use the given mode - if action, exists := to_be_edited_map.key_actions[key]; exists { - switch value in action.action { - case core.LuaEditorAction: - log.warn("Plugin attempted to register input group on existing key action (added from Lua)"); - case core.PluginEditorAction: - log.warn("Plugin attempted to register input group on existing key action (added from Plugin)"); - case core.EditorAction: - log.warn("Plugin attempted to register input group on existing key action"); - case core.InputActions: - input_map := &(&to_be_edited_map.key_actions[key]).action.(core.InputActions); - register_group(state.plugin_vtable, transmute(rawptr)input_map); - } - } else { - core.register_key_action(to_be_edited_map, key, core.new_input_actions(), "PLUGIN INPUT GROUP"); - register_group(state.plugin_vtable, &(&to_be_edited_map.key_actions[key]).action.(core.InputActions)); - } - }, - register_input = proc "c" (input_map: rawptr, key: plugin.Key, input_action: plugin.InputActionProc, description: cstring) { - context = state.ctx; - - to_be_edited_map: ^core.InputActions = nil; - description := strings.clone(string(description)); - - if input_map != nil { - to_be_edited_map = transmute(^core.InputActions)input_map; - } else { - to_be_edited_map = state.current_input_map; - } - - if action, exists := to_be_edited_map.key_actions[key]; exists { - switch value in action.action { - case core.LuaEditorAction: - log.warn("Plugin attempted to register key action on existing key action (added from Lua)"); - case core.PluginEditorAction: - log.warn("Plugin attempted to register key action on existing key action (added from Plugin)"); - case core.EditorAction: - log.warn("Plugin attempted to register input key action on existing key action"); - case core.InputActions: - log.warn("Plugin attempted to register input key action on existing input group"); - } - } else { - core.register_key_action(to_be_edited_map, key, input_action, description); - } - }, - create_window = proc "c" (user_data: rawptr, register_group: plugin.InputGroupProc, draw_proc: plugin.WindowDrawProc, free_window_proc: plugin.WindowFreeProc, get_buffer_proc: plugin.WindowGetBufferProc) -> rawptr { - context = state.ctx; - window := new(core.Window); - window^ = core.Window { - input_map = core.new_input_actions(), - draw = draw_proc, - get_buffer = get_buffer_proc, - free_user_data = free_window_proc, - - user_data = user_data, - }; - - register_group(state.plugin_vtable, transmute(rawptr)&window.input_map); - - state.window = window; - state.current_input_map = &window.input_map; - - return window; - }, - get_window = proc "c" () -> rawptr { - if state.window != nil { - return state.window.user_data; - } - - return nil; - }, - request_window_close = proc "c" () { - context = state.ctx; - - core.request_window_close(&state); - }, - get_screen_width = proc "c" () -> int { - return state.screen_width; - }, - get_screen_height = proc "c" () -> int { - return state.screen_height; - }, - get_font_width = proc "c" () -> int { - return state.source_font_width; - }, - get_font_height = proc "c" () -> int { - return state.source_font_height; - }, - get_current_directory = proc "c" () -> cstring { - context = state.ctx; - - return strings.clone_to_cstring(state.directory, context.temp_allocator); - }, - enter_insert_mode = proc "c" () { - state.mode = .Insert; - sdl2.StartTextInput(); - }, - draw_rect = proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor) { - context = state.ctx; - - core.draw_rect(&state, int(x), int(y), int(width), int(height), color); - }, - draw_text = proc "c" (text: cstring, x: f32, y: f32, color: theme.PaletteColor) { - context = state.ctx; - - core.draw_text(&state, string(text), int(x), int(y), color); - }, - draw_buffer_from_index = proc "c" (buffer_index: int, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool) { - context = state.ctx; - core.buffer_from_index(&state, buffer_index).glyph_buffer_width = glyph_buffer_width; - core.buffer_from_index(&state, buffer_index).glyph_buffer_height = glyph_buffer_height; - - core.draw_file_buffer( - &state, - core.buffer_from_index(&state, buffer_index), - x, - y, - show_line_numbers); - }, - draw_buffer = proc "c" (buffer: rawptr, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool) { - context = state.ctx; - - buffer := transmute(^core.FileBuffer)buffer; - buffer.glyph_buffer_width = glyph_buffer_width; - buffer.glyph_buffer_height = glyph_buffer_height; - - core.draw_file_buffer( - &state, - buffer, - x, - y, - show_line_numbers); - }, - iter = plugin.Iterator { - get_current_buffer_iterator = proc "c" () -> plugin.BufferIter { - context = state.ctx; - - it := core.new_file_buffer_iter(core.current_buffer(&state)); - - // TODO: make this into a function - return plugin.BufferIter { - cursor = plugin.Cursor { - col = it.cursor.col, - line = it.cursor.line, - index = plugin.BufferIndex { - slice_index = it.cursor.index.slice_index, - content_index = it.cursor.index.content_index, - } - }, - buffer = cast(rawptr)it.buffer, - hit_end = it.hit_end, - } - }, - get_buffer_iterator = proc "c" (buffer: rawptr) -> plugin.BufferIter { - buffer := cast(^core.FileBuffer)buffer; - context = state.ctx; - - it := core.new_file_buffer_iter(buffer); - - // TODO: make this into a function - return plugin.BufferIter { - cursor = plugin.Cursor { - col = it.cursor.col, - line = it.cursor.line, - index = plugin.BufferIndex { - slice_index = it.cursor.index.slice_index, - content_index = it.cursor.index.content_index, - } - }, - buffer = cast(rawptr)it.buffer, - hit_end = it.hit_end, - } - }, - get_char_at_iter = proc "c" (it: ^plugin.BufferIter) -> u8 { - context = state.ctx; - - internal_it := core.FileBufferIter { - cursor = core.Cursor { - col = it.cursor.col, - line = it.cursor.line, - index = core.FileBufferIndex { - slice_index = it.cursor.index.slice_index, - content_index = it.cursor.index.content_index, - } - }, - buffer = cast(^core.FileBuffer)it.buffer, - hit_end = it.hit_end, - } - - return core.get_character_at_iter(internal_it); - }, - get_buffer_list_iter = proc "c" (prev_buffer: ^int) -> int { - context = state.ctx; - - return core.next_buffer(&state, prev_buffer); - }, - iterate_buffer = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult { - context = state.ctx; - - // TODO: make this into a function - internal_it := core.FileBufferIter { - cursor = core.Cursor { - col = it.cursor.col, - line = it.cursor.line, - index = core.FileBufferIndex { - slice_index = it.cursor.index.slice_index, - content_index = it.cursor.index.content_index, - } - }, - buffer = cast(^core.FileBuffer)it.buffer, - hit_end = it.hit_end, - } - - char, _, cond := core.iterate_file_buffer(&internal_it); - - it^ = plugin.BufferIter { - cursor = plugin.Cursor { - col = internal_it.cursor.col, - line = internal_it.cursor.line, - index = plugin.BufferIndex { - slice_index = internal_it.cursor.index.slice_index, - content_index = internal_it.cursor.index.content_index, - } - }, - buffer = cast(rawptr)internal_it.buffer, - hit_end = internal_it.hit_end, - }; - - return plugin.IterateResult { - char = char, - should_continue = cond, - }; - }, - iterate_buffer_reverse = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult { - context = state.ctx; - - // TODO: make this into a function - internal_it := core.FileBufferIter { - cursor = core.Cursor { - col = it.cursor.col, - line = it.cursor.line, - index = core.FileBufferIndex { - slice_index = it.cursor.index.slice_index, - content_index = it.cursor.index.content_index, - } - }, - buffer = cast(^core.FileBuffer)it.buffer, - hit_end = it.hit_end, - } - - char, _, cond := core.iterate_file_buffer_reverse(&internal_it); - - it^ = plugin.BufferIter { - cursor = plugin.Cursor { - col = internal_it.cursor.col, - line = internal_it.cursor.line, - index = plugin.BufferIndex { - slice_index = internal_it.cursor.index.slice_index, - content_index = internal_it.cursor.index.content_index, - } - }, - buffer = cast(rawptr)internal_it.buffer, - hit_end = internal_it.hit_end, - }; - - return plugin.IterateResult { - char = char, - should_continue = cond, - }; - }, - iterate_buffer_until = proc "c" (it: ^plugin.BufferIter, until_proc: rawptr) { - context = state.ctx; - - // TODO: make this into a function - internal_it := core.FileBufferIter { - cursor = core.Cursor { - col = it.cursor.col, - line = it.cursor.line, - index = core.FileBufferIndex { - slice_index = it.cursor.index.slice_index, - content_index = it.cursor.index.content_index, - } - }, - buffer = cast(^core.FileBuffer)it.buffer, - hit_end = it.hit_end, - } - - core.iterate_file_buffer_until(&internal_it, transmute(core.UntilProc)until_proc); - - it^ = plugin.BufferIter { - cursor = plugin.Cursor { - col = internal_it.cursor.col, - line = internal_it.cursor.line, - index = plugin.BufferIndex { - slice_index = internal_it.cursor.index.slice_index, - content_index = internal_it.cursor.index.content_index, - } - }, - buffer = cast(rawptr)internal_it.buffer, - hit_end = internal_it.hit_end, - }; - }, - iterate_buffer_peek = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult { - context = state.ctx; - - // TODO: make this into a function - internal_it := core.FileBufferIter { - cursor = core.Cursor { - col = it.cursor.col, - line = it.cursor.line, - index = core.FileBufferIndex { - slice_index = it.cursor.index.slice_index, - content_index = it.cursor.index.content_index, - } - }, - buffer = cast(^core.FileBuffer)it.buffer, - hit_end = it.hit_end, - } - - char, _, cond := core.iterate_peek(&internal_it, core.iterate_file_buffer); - - it^ = plugin.BufferIter { - cursor = plugin.Cursor { - col = internal_it.cursor.col, - line = internal_it.cursor.line, - index = plugin.BufferIndex { - slice_index = internal_it.cursor.index.slice_index, - content_index = internal_it.cursor.index.content_index, - } - }, - buffer = cast(rawptr)internal_it.buffer, - hit_end = internal_it.hit_end, - }; - - return plugin.IterateResult { - char = char, - should_continue = cond, - }; - }, - until_line_break = transmute(rawptr)core.until_line_break, - until_single_quote = transmute(rawptr)core.until_single_quote, - until_double_quote = transmute(rawptr)core.until_double_quote, - until_end_of_word = transmute(rawptr)core.until_end_of_word, - }, - buffer = plugin.Buffer { - get_num_buffers = proc "c" () -> int { - context = state.ctx; - - return len(state.buffers); - }, - get_buffer_info = proc "c" (buffer: rawptr) -> plugin.BufferInfo { - context = state.ctx; - buffer := cast(^core.FileBuffer)buffer; - - return core.into_buffer_info(&state, buffer); - }, - get_buffer_info_from_index = proc "c" (buffer_index: int) -> plugin.BufferInfo { - context = state.ctx; - buffer := core.buffer_from_index(&state, buffer_index); - - return core.into_buffer_info(&state, buffer); - }, - color_char_at = proc "c" (buffer: rawptr, start_cursor: plugin.Cursor, end_cursor: plugin.Cursor, palette_index: i32) { - buffer := cast(^core.FileBuffer)buffer; - context = state.ctx; - - start_cursor := core.Cursor { - col = start_cursor.col, - line = start_cursor.line, - index = core.FileBufferIndex { - slice_index = start_cursor.index.slice_index, - content_index = start_cursor.index.content_index, - } - }; - end_cursor := core.Cursor { - col = end_cursor.col, - line = end_cursor.line, - index = core.FileBufferIndex { - slice_index = end_cursor.index.slice_index, - content_index = end_cursor.index.content_index, - } - }; - - core.color_character(buffer, start_cursor, end_cursor, cast(theme.PaletteColor)palette_index); - }, - set_current_buffer = proc "c" (buffer_index: int) { - state.current_buffer = buffer_index; - }, - open_buffer = proc "c" (path: cstring, line: int, col: int) { - context = state.ctx; - - fmt.eprintln("opening file from dll", path) - - path := string(path); - should_create_buffer := true; - for buffer, index in state.buffers { - if strings.compare(buffer.file_path, path) == 0 { - state.current_buffer = index; - should_create_buffer = false; - break; - } - } - - buffer: ^core.FileBuffer = nil; - err := core.no_error(); - - if should_create_buffer { - new_buffer, err := core.new_file_buffer(context.allocator, strings.clone(path)); - if err.type != .None { - log.error("Failed to open/create file buffer:", err); - } else { - runtime.append(&state.buffers, new_buffer); - state.current_buffer = len(state.buffers)-1; - buffer = core.current_buffer(&state); - } - } else { - buffer = core.current_buffer(&state); - } - - if buffer != nil { - buffer.cursor.line = line; - buffer.cursor.col = col; - buffer.glyph_buffer_height = math.min(256, int((state.screen_height - state.source_font_height*2) / state.source_font_height)) + 1; - buffer.glyph_buffer_width = math.min(256, int((state.screen_width - state.source_font_width) / state.source_font_width)); - core.update_file_buffer_index_from_cursor(buffer); - } - }, - open_virtual_buffer = proc "c" () -> rawptr { - context = state.ctx; - - buffer := new(FileBuffer); - buffer^ = core.new_virtual_file_buffer(context.allocator); - - return buffer; - }, - free_virtual_buffer = proc "c" (buffer: rawptr) { - context = state.ctx; - - if buffer != nil { - buffer := cast(^core.FileBuffer)buffer; - - core.free_file_buffer(buffer); - free(buffer); - } - }, - }, - ui = plugin.Ui { - ui_context = ui_context, - - push_parent = proc "c" (ui_context: rawptr, box: plugin.UiBox) { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - box := transmute(^ui.Box)box; - - ui.push_parent(ui_context, box); - }, - - pop_parent = proc "c" (ui_context: rawptr) { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - - ui.pop_parent(ui_context); - }, - - spacer = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - label := strings.clone(string(label), context.temp_allocator); - - interaction := ui.spacer(ui_context, label); - - return plugin.UiInteraction { - hovering = interaction.hovering, - clicked = interaction.clicked, - }; - }, - // TODO: allow this to have more flags sent to it - floating = proc "c" (ui_context: rawptr, label: cstring, pos: [2]int) -> plugin.UiBox { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - label := strings.clone(string(label), context.temp_allocator); - - floating, _ := ui.push_floating(ui_context, label, pos); - return floating; - }, - rect = proc "c" (ui_context: rawptr, label: cstring, background: bool, border: bool, axis: plugin.UiAxis, size: [2]plugin.UiSemanticSize) -> plugin.UiBox { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - label := strings.clone(string(label), context.temp_allocator); - - size := [2]ui.SemanticSize { - ui.SemanticSize { - kind = ui.SemanticSizeKind(size.x.kind), - value = size.x.value, - }, - ui.SemanticSize { - kind = ui.SemanticSizeKind(size.y.kind), - value = size.y.value, - }, - }; - - rect, _ := ui.push_rect(ui_context, label, background, border, ui.Axis(axis), size); - return rect; - }, - - label = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - label := strings.clone(string(label), context.temp_allocator); - - interaction := ui.label(ui_context, label); - - return plugin.UiInteraction { - hovering = interaction.hovering, - clicked = interaction.clicked, - }; - }, - button = proc "c" (ui_context: rawptr, label: cstring) -> plugin.UiInteraction { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - label := strings.clone(string(label), context.temp_allocator); - - interaction := ui.button(ui_context, label); - - return plugin.UiInteraction { - hovering = interaction.hovering, - clicked = interaction.clicked, - }; - }, - buffer = proc "c" (ui_context: rawptr, buffer: rawptr, show_line_numbers: bool) { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - buffer := transmute(^FileBuffer)buffer; - - ui_file_buffer(ui_context, buffer); - }, - - buffer_from_index = proc "c" (ui_context: rawptr, buffer_index: int, show_line_numbers: bool) { - context = state.ctx; - ui_context := transmute(^ui.Context)ui_context; - - buffer := core.buffer_from_index(&state, buffer_index); - - ui_file_buffer(ui_context, buffer); - }, - }, - }; -} - main :: proc() { _command_arena: mem.Arena mem.arena_init(&_command_arena, make([]u8, 1024*1024)); @@ -1150,35 +400,10 @@ main :: proc() { panel_catalog = make([dynamic]core.PanelId), - window = nil, directory = os.get_current_directory(), - plugins = make([dynamic]plugin.Interface), - new_plugins = make([dynamic]plugin.NewInterface), - highlighters = make(map[string]plugin.OnColorBufferProc), - hooks = make(map[plugin.Hook][dynamic]plugin.OnHookProc), - lua_hooks = make(map[plugin.Hook][dynamic]core.LuaHookRef), - log_buffer = core.new_virtual_file_buffer(context.allocator), }; - // TODO: please move somewhere else - { - ti := runtime.type_info_base(type_info_of(plugin.Hook)); - if v, ok := ti.variant.(runtime.Type_Info_Enum); ok { - for i in &v.values { - state.hooks[cast(plugin.Hook)i] = make([dynamic]plugin.OnHookProc); - } - } - } - { - ti := runtime.type_info_base(type_info_of(plugin.Hook)); - if v, ok := ti.variant.(runtime.Type_Info_Enum); ok { - for i in &v.values { - state.lua_hooks[cast(plugin.Hook)i] = make([dynamic]core.LuaHookRef); - } - } - } - // context.logger = core.new_logger(&state.log_buffer); context.logger = log.create_console_logger(); state.ctx = context; @@ -1198,45 +423,45 @@ main :: proc() { register_default_text_input_actions(&state.input_map.mode[.Normal]); - core.register_editor_command( - &state.commands, - "nl.spacegirl.editor.core", - "Open New Panel", - "Opens a new panel", - proc(state: ^State) { - Args :: struct { - panel_id: string - } + // core.register_editor_command( + // &state.commands, + // "nl.spacegirl.editor.core", + // "Open New Panel", + // "Opens a new panel", + // proc(state: ^State) { + // Args :: struct { + // panel_id: string + // } - if args, ok := core.attempt_read_command_args(Args, state.command_args[:]); ok { - log.info("maybe going to open panel with id", args.panel_id) + // if args, ok := core.attempt_read_command_args(Args, state.command_args[:]); ok { + // log.info("maybe going to open panel with id", args.panel_id) - for p in state.panel_catalog { - switch v in p { - case core.LuaPanelId: - { - if v.id == args.panel_id { - if index, ok := lua.add_panel(state, v); ok { - for i in 0..= 32 && char <= 125 && len(buffer.input_buffer) < 1024-1 { append(&buffer.input_buffer, u8(char)); - - for hook_proc in state.hooks[plugin.Hook.BufferInput] { - hook_proc(state.plugin_vtable, buffer); - } - for hook_ref in state.lua_hooks[plugin.Hook.BufferInput] { - /* - lua.rawgeti(state.L, lua.REGISTRYINDEX, lua.Integer(hook_ref)); - if lua.pcall(state.L, 0, 0, 0) != i32(lua.OK) { - err := lua.tostring(state.L, lua.gettop(state.L)); - lua.pop(state.L, lua.gettop(state.L)); - - log.error(err); - } else { - lua.pop(state.L, lua.gettop(state.L)); - } - */ - } } } } @@ -1613,51 +702,22 @@ main :: proc() { } } - draw(&StateWithUi { &state, &ui_context }); - - ui.prune(&ui_context); + draw(&state); switch state.mode { case .Normal: - if state.window != nil && state.window.get_buffer != nil { - buffer := transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data)); - do_normal_mode(&state, buffer); - } else { - buffer := core.current_buffer(&state); - do_normal_mode(&state, buffer); - } + buffer := core.current_buffer(&state); + do_normal_mode(&state, buffer); case .Insert: - if state.window != nil && state.window.get_buffer != nil { - buffer := transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data)); - do_insert_mode(&state, buffer); - } else { - buffer := core.current_buffer(&state); - do_insert_mode(&state, buffer); - } + buffer := core.current_buffer(&state); + do_insert_mode(&state, buffer); case .Visual: - if state.window != nil && state.window.get_buffer != nil { - // TODO - } else { - buffer := core.current_buffer(&state); - do_visual_mode(&state, buffer); - } - } - - if state.should_close_window { - state.should_close_window = false; - core.close_window_and_free(&state); + buffer := core.current_buffer(&state); + do_visual_mode(&state, buffer); } runtime.free_all(context.temp_allocator); } - for plugin in state.plugins { - if plugin.on_exit != nil { - plugin.on_exit(); - } - } - - lua.close(state.L); - sdl2.Quit(); } diff --git a/src/plugin/plugin.odin b/src/plugin/plugin.odin deleted file mode 100644 index a2bc3e4..0000000 --- a/src/plugin/plugin.odin +++ /dev/null @@ -1,451 +0,0 @@ -package plugin; - -import "base:intrinsics" -import "core:dynlib" -import "core:fmt" -import "core:log" - -import "../theme" - -OnInitializeProc :: proc "c" (plugin: Plugin); -OnExitProc :: proc "c" (/* probably needs some state eventually */); -OnDrawProc :: proc "c" (plugin: Plugin); -Interface :: struct { - on_initialize: OnInitializeProc, - on_exit: OnExitProc, - on_draw: OnDrawProc, -} - -NewInterface :: struct { - name: string, - version: string, - namespace: string, -} - -BufferIndex :: struct { - slice_index: int, - content_index: int, -} - -Cursor :: struct { - col: int, - line: int, - index: BufferIndex, -} - -BufferIter :: struct { - cursor: Cursor, - buffer: rawptr, - hit_end: bool, -} - -IterateResult :: struct { - char: u8, - should_continue: bool, -} - -BufferInput :: struct { - bytes: [^]u8, - length: int, -} - -BufferInfo :: struct { - buffer: rawptr, - file_path: cstring, - input: BufferInput, - - cursor: Cursor, - - glyph_buffer_width: int, - glyph_buffer_height: int, - top_line: int, -} - -Buffer :: struct { - get_num_buffers: proc "c" () -> int, - get_buffer_info: proc "c" (buffer: rawptr) -> BufferInfo, - get_buffer_info_from_index: proc "c" (buffer_index: int) -> BufferInfo, - color_char_at: proc "c" (buffer: rawptr, start_cursor: Cursor, end_cursor: Cursor, palette_index: i32), - set_current_buffer: proc "c" (buffer_index: int), - - open_buffer: proc "c" (path: cstring, line: int, col: int), - open_virtual_buffer: proc "c" () -> rawptr, - free_virtual_buffer: proc "c" (buffer: rawptr), -} - -Iterator :: struct { - get_current_buffer_iterator: proc "c" () -> BufferIter, - get_buffer_iterator: proc "c" (buffer: rawptr) -> BufferIter, - get_char_at_iter: proc "c" (it: ^BufferIter) -> u8, - get_buffer_list_iter: proc "c" (prev_buffer: ^int) -> int, - - iterate_buffer: proc "c" (it: ^BufferIter) -> IterateResult, - iterate_buffer_reverse: proc "c" (it: ^BufferIter) -> IterateResult, - iterate_buffer_until: proc "c" (it: ^BufferIter, until_proc: rawptr), - iterate_buffer_until_reverse: proc "c" (it: ^BufferIter, until_proc: rawptr), - iterate_buffer_peek: proc "c" (it: ^BufferIter) -> IterateResult, - - until_line_break: rawptr, - until_single_quote: rawptr, - until_double_quote: rawptr, - until_end_of_word: rawptr, -} - -UiInteraction :: struct { - hovering: bool, - clicked: bool -} - -UiAxis :: enum { - Horizontal = 0, - Vertical, -} - -UiSemanticSize :: struct { - kind: int, - value: int, -} - -UiBox :: rawptr; - -UiPushParentProc :: proc "c" (ui_context: rawptr, box: UiBox); -UiPopParentProc :: proc "c" (ui_context: rawptr); -UiFloatingProc :: proc "c" (ui_context: rawptr, label: cstring, pos: [2]int) -> UiBox; -UiCreateBoxProc :: proc "c" (ui_context: rawptr, label: cstring) -> UiBox; -UiRectProc :: proc "c" (ui_context: rawptr, label: cstring, background: bool, border: bool, axis: UiAxis, size: [2]UiSemanticSize) -> UiBox; -UiSimpleProc :: proc "c" (ui_context: rawptr, label: cstring) -> UiInteraction; -UiBufferProc :: proc "c" (ui_context: rawptr, buffer: rawptr, show_line_numbers: bool); -UiBufferIndexProc :: proc "c" (ui_context: rawptr, buffer: int, show_line_numbers: bool); -Ui :: struct { - ui_context: rawptr, - - push_parent: UiPushParentProc, - pop_parent: UiPopParentProc, - - spacer: UiSimpleProc, - floating: UiFloatingProc, - rect: UiRectProc, - - button: UiSimpleProc, - label: UiSimpleProc, - - buffer: UiBufferProc, - buffer_from_index: UiBufferIndexProc, -} - -OnColorBufferProc :: proc "c" (plugin: Plugin, buffer: rawptr); -InputGroupProc :: proc "c" (plugin: Plugin, input_map: rawptr); -InputActionProc :: proc "c" (plugin: Plugin); -OnHookProc :: proc "c" (plugin: Plugin, buffer: rawptr); - -WindowInputProc :: proc "c" (plugin: Plugin, window: rawptr); -WindowDrawProc :: proc "c" (plugin: Plugin, window: rawptr); -WindowGetBufferProc :: proc(plugin: Plugin, window: rawptr) -> rawptr; -WindowFreeProc :: proc "c" (plugin: Plugin, window: rawptr); -Plugin :: struct { - state: rawptr, - iter: Iterator, - buffer: Buffer, - ui: Ui, - - register_hook: proc "c" (hook: Hook, on_hook: OnHookProc), - register_highlighter: proc "c" (extension: cstring, on_color_buffer: OnColorBufferProc), - - register_input_group: proc "c" (input_map: rawptr, key: Key, register_group: InputGroupProc), - register_input: proc "c" (input_map: rawptr, key: Key, input_action: InputActionProc, description: cstring), - - create_window: proc "c" (user_data: rawptr, register_group: InputGroupProc, draw_proc: WindowDrawProc, free_window_proc: WindowFreeProc, get_buffer_proc: WindowGetBufferProc) -> rawptr, - get_window: proc "c" () -> rawptr, - - request_window_close: proc "c" (), - get_screen_width: proc "c" () -> int, - get_screen_height: proc "c" () -> int, - get_font_width: proc "c" () -> int, - get_font_height: proc "c" () -> int, - get_current_directory: proc "c" () -> cstring, - enter_insert_mode: proc "c" (), - - draw_rect: proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor), - draw_text: proc "c" (text: cstring, x: f32, y: f32, color: theme.PaletteColor), - draw_buffer_from_index: proc "c" (buffer_index: int, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool), - draw_buffer: proc "c" (buffer: rawptr, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool), -} - -Hook :: enum { - BufferInput = 0, - Draw = 1, -} - -Key :: enum { - UNKNOWN = 0, - ENTER = 13, - ESCAPE = 27, - BACKSPACE = 8, - TAB = 9, - SPACE = 32, - EXCLAIM = 33, - QUOTEDBL = 34, - HASH = 35, - PERCENT = 37, - DOLLAR = 36, - AMPERSAND = 38, - QUOTE = 39, - LEFTPAREN = 40, - RIGHTPAREN = 41, - ASTERISK = 42, - PLUS = 43, - COMMA = 44, - MINUS = 45, - PERIOD = 46, - SLASH = 47, - NUM0 = 48, - NUM1 = 49, - NUM2 = 50, - NUM3 = 51, - NUM4 = 52, - NUM5 = 53, - NUM6 = 54, - NUM7 = 55, - NUM8 = 56, - NUM9 = 57, - COLON = 58, - SEMICOLON = 59, - LESS = 60, - EQUAL = 61, - GREATER = 62, - QUESTION = 63, - AT = 64, - LEFTBRACKET = 91, - BACKSLASH = 92, - RIGHTBRACKET = 93, - CARET = 94, - UNDERSCORE = 95, - BACKQUOTE = 96, - A = 97, - B = 98, - C = 99, - D = 100, - E = 101, - F = 102, - G = 103, - H = 104, - I = 105, - J = 106, - K = 107, - L = 108, - M = 109, - N = 110, - O = 111, - P = 112, - Q = 113, - R = 114, - S = 115, - T = 116, - U = 117, - V = 118, - W = 119, - X = 120, - Y = 121, - Z = 122, - CAPSLOCK = 1073741881, - F1 = 1073741882, - F2 = 1073741883, - F3 = 1073741884, - F4 = 1073741885, - F5 = 1073741886, - F6 = 1073741887, - F7 = 1073741888, - F8 = 1073741889, - F9 = 1073741890, - F10 = 1073741891, - F11 = 1073741892, - F12 = 1073741893, - PRINTSCREEN = 1073741894, - SCROLLLOCK = 1073741895, - PAUSE = 1073741896, - INSERT = 1073741897, - HOME = 1073741898, - PAGEUP = 1073741899, - DELETE = 127, - END = 1073741901, - PAGEDOWN = 1073741902, - RIGHT = 1073741903, - LEFT = 1073741904, - DOWN = 1073741905, - UP = 1073741906, - NUMLOCKCLEAR = 1073741907, - KP_DIVIDE = 1073741908, - KP_MULTIPLY = 1073741909, - KP_MINUS = 1073741910, - KP_PLUS = 1073741911, - KP_ENTER = 1073741912, - KP_1 = 1073741913, - KP_2 = 1073741914, - KP_3 = 1073741915, - KP_4 = 1073741916, - KP_5 = 1073741917, - KP_6 = 1073741918, - KP_7 = 1073741919, - KP_8 = 1073741920, - KP_9 = 1073741921, - KP_0 = 1073741922, - KP_PERIOD = 1073741923, - APPLICATION = 1073741925, - POWER = 1073741926, - KP_EQUALS = 1073741927, - F13 = 1073741928, - F14 = 1073741929, - F15 = 1073741930, - F16 = 1073741931, - F17 = 1073741932, - F18 = 1073741933, - F19 = 1073741934, - F20 = 1073741935, - F21 = 1073741936, - F22 = 1073741937, - F23 = 1073741938, - F24 = 1073741939, - EXECUTE = 1073741940, - HELP = 1073741941, - MENU = 1073741942, - SELECT = 1073741943, - STOP = 1073741944, - AGAIN = 1073741945, - UNDO = 1073741946, - CUT = 1073741947, - COPY = 1073741948, - PASTE = 1073741949, - FIND = 1073741950, - MUTE = 1073741951, - VOLUMEUP = 1073741952, - VOLUMEDOWN = 1073741953, - KP_COMMA = 1073741957, - KP_EQUALSAS400 = 1073741958, - ALTERASE = 1073741977, - SYSREQ = 1073741978, - CANCEL = 1073741979, - CLEAR = 1073741980, - PRIOR = 1073741981, - RETURN2 = 1073741982, - SEPARATOR = 1073741983, - OUT = 1073741984, - OPER = 1073741985, - CLEARAGAIN = 1073741986, - CRSEL = 1073741987, - EXSEL = 1073741988, - KP_00 = 1073742000, - KP_000 = 1073742001, - THOUSANDSSEPARATOR = 1073742002, - DECIMALSEPARATOR = 1073742003, - CURRENCYUNIT = 1073742004, - CURRENCYSUBUNIT = 1073742005, - KP_LEFTPAREN = 1073742006, - KP_RIGHTPAREN = 1073742007, - KP_LEFTBRACE = 1073742008, - KP_RIGHTBRACE = 1073742009, - KP_TAB = 1073742010, - KP_BACKSPACE = 1073742011, - KP_A = 1073742012, - KP_B = 1073742013, - KP_C = 1073742014, - KP_D = 1073742015, - KP_E = 1073742016, - KP_F = 1073742017, - KP_XOR = 1073742018, - KP_POWER = 1073742019, - KP_PERCENT = 1073742020, - KP_LESS = 1073742021, - KP_GREATER = 1073742022, - KP_AMPERSAND = 1073742023, - KP_DBLAMPERSAND = 1073742024, - KP_VERTICALBAR = 1073742025, - KP_DBLVERTICALBAR = 1073742026, - KP_COLON = 1073742027, - KP_HASH = 1073742028, - KP_SPACE = 1073742029, - KP_AT = 1073742030, - KP_EXCLAM = 1073742031, - KP_MEMSTORE = 1073742032, - KP_MEMRECALL = 1073742033, - KP_MEMCLEAR = 1073742034, - KP_MEMADD = 1073742035, - KP_MEMSUBTRACT = 1073742036, - KP_MEMMULTIPLY = 1073742037, - KP_MEMDIVIDE = 1073742038, - KP_PLUSMINUS = 1073742039, - KP_CLEAR = 1073742040, - KP_CLEARENTRY = 1073742041, - KP_BINARY = 1073742042, - KP_OCTAL = 1073742043, - KP_DECIMAL = 1073742044, - KP_HEXADECIMAL = 1073742045, - LCTRL = 1073742048, - LSHIFT = 1073742049, - LALT = 1073742050, - LGUI = 1073742051, - RCTRL = 1073742052, - RSHIFT = 1073742053, - RALT = 1073742054, - RGUI = 1073742055, - MODE = 1073742081, - AUDIONEXT = 1073742082, - AUDIOPREV = 1073742083, - AUDIOSTOP = 1073742084, - AUDIOPLAY = 1073742085, - AUDIOMUTE = 1073742086, - MEDIASELECT = 1073742087, - WWW = 1073742088, - MAIL = 1073742089, - CALCULATOR = 1073742090, - COMPUTER = 1073742091, - AC_SEARCH = 1073742092, - AC_HOME = 1073742093, - AC_BACK = 1073742094, - AC_FORWARD = 1073742095, - AC_STOP = 1073742096, - AC_REFRESH = 1073742097, - AC_BOOKMARKS = 1073742098, - BRIGHTNESSDOWN = 1073742099, - BRIGHTNESSUP = 1073742100, - DISPLAYSWITCH = 1073742101, - KBDILLUMTOGGLE = 1073742102, - KBDILLUMDOWN = 1073742103, - KBDILLUMUP = 1073742104, - EJECT = 1073742105, - SLEEP = 1073742106, - APP1 = 1073742107, - APP2 = 1073742108, - AUDIOREWIND = 1073742109, - AUDIOFASTFORWARD = 1073742110, -} - - -load_proc_address :: proc(lib_path: string, library: dynlib.Library, symbol: string, $ProcType: typeid) -> ProcType - where intrinsics.type_is_proc(ProcType) -{ - if address, found := dynlib.symbol_address(library, symbol); found { - return transmute(ProcType)address; - } else { - log.warn("Could not find symbol", symbol, "in library", lib_path); - } - - return nil; -} - -try_load_plugin :: proc(lib_path: string) -> (plugin: Interface, success: bool) { - library, ok := dynlib.load_library(lib_path) - if !ok { - return {}, false; - } - - interface := Interface { - on_initialize = load_proc_address(lib_path, library, "OnInitialize", OnInitializeProc), - on_exit = load_proc_address(lib_path, library, "OnExit", OnExitProc), - on_draw = load_proc_address(lib_path, library, "OnDraw", OnDrawProc), - }; - - if interface.on_initialize == nil do return interface, false; - if interface.on_exit == nil do return interface, false; - - return interface, true -} diff --git a/src/ui/imm.odin b/src/ui/imm.odin deleted file mode 100644 index 34a6a15..0000000 --- a/src/ui/imm.odin +++ /dev/null @@ -1,729 +0,0 @@ -package ui - -import "core:fmt" -import "core:strings" -import "core:math" -import "core:log" -import "vendor:sdl2" - -import "../core" -import "../theme" - -Context :: struct { - root: ^Box, - current_parent: ^Box, - persistent: map[string]^Box, - current_interaction_index: int, - - clips: [dynamic]Rect, - renderer: ^sdl2.Renderer, - - last_mouse_x: int, - last_mouse_y: int, - - mouse_x: int, - mouse_y: int, - - mouse_left_down: bool, - last_mouse_left_down: bool, - - mouse_right_down: bool, - last_mouse_right_down: bool, -} - -Rect :: struct { - pos: [2]int, - size: [2]int, -} - -Key :: struct { - label: string, - value: int, -} - -Interaction :: struct { - hovering: bool, - clicked: bool, - dragging: bool, - - box_pos: [2]int, - box_size: [2]int, -} - -Flag :: enum { - Clickable, - Hoverable, - Scrollable, - DrawText, - DrawBorder, - DrawBackground, - RoundedBorder, - Floating, - CustomDrawFunc, -} - -SemanticSizeKind :: enum { - FitText = 0, - Exact, - ChildrenSum, - Fill, - PercentOfParent, -} - -SemanticSize :: struct { - kind: SemanticSizeKind, - value: int, -} - -Axis :: enum { - Horizontal = 0, - Vertical = 1, -} - -CustomDrawFunc :: proc(state: ^core.State, box: ^Box, user_data: rawptr); -Box :: struct { - first: ^Box, - last: ^Box, - next: ^Box, - prev: ^Box, - parent: ^Box, - - key: Key, - last_interacted_index: int, - - flags: bit_set[Flag], - - label: string, - - axis: Axis, - semantic_size: [2]SemanticSize, - computed_size: [2]int, - computed_child_size: [2]int, - computed_pos: [2]int, - - scroll_offset: int, - - hot: int, - active: int, - - custom_draw_func: CustomDrawFunc, - user_data: rawptr, -} - -init :: proc(renderer: ^sdl2.Renderer) -> Context { - root := new(Box); - root.key = gen_key(nil, "root", 69); - root.label = strings.clone("root") - - return Context { - root = root, - current_parent = root, - persistent = make(map[string]^Box), - clips = make([dynamic]Rect), - renderer = renderer, - }; -} - -gen_key :: proc(ctx: ^Context, label: string, value: int) -> Key { - key_label: string; - - if ctx != nil && (ctx.current_parent == nil || len(ctx.current_parent.key.label) < 1) { - key_label = strings.clone(label); - } else if ctx != nil { - key_label = fmt.aprintf("%s:%s", ctx.current_parent.key.label, label); - } else { - key_label = strings.clone(label); - } - - return Key { - label = key_label, - value = value, - }; -} - -@(private) -make_box :: proc(ctx: ^Context, key: Key, label: string, flags: bit_set[Flag], axis: Axis, semantic_size: [2]SemanticSize) -> ^Box { - box: ^Box = nil; - - if cached_box, exists := ctx.persistent[key.label]; exists { - // NOTE(pcleavelin): its important to note that the _cached_ key _is not_ free'd - // as that would invalid the maps reference to the key causing memory leaks because - // the map would think that an entry doesn't exist (in some cases) - delete(key.label) - - if cached_box.last_interacted_index < ctx.current_interaction_index-1 { - box = cached_box; - - box.last_interacted_index = ctx.current_interaction_index; - box.hot = 0; - box.active = 0; - } else { - box = cached_box; - } - } else { - box = new(Box); - ctx.persistent[key.label] = box; - - box.key = key; - box.last_interacted_index = ctx.current_interaction_index; - } - - box.label = strings.clone(label, context.temp_allocator); - - box.first = nil; - box.last = nil; - box.next = nil; - box.prev = ctx.current_parent.last; - box.parent = ctx.current_parent; - box.flags = flags; - box.axis = axis; - box.semantic_size = semantic_size; - - if ctx.current_parent.last != nil { - ctx.current_parent.last.next = box; - } - if ctx.current_parent.first == nil { - ctx.current_parent.first = box; - } - - ctx.current_parent.last = box; - - return box; -} - -make_semantic_size :: proc(kind: SemanticSizeKind, value: int = 0) -> SemanticSize { - return SemanticSize { - kind = kind, - value = value - }; -} - -FitText :[2]SemanticSize: { - SemanticSize { - kind = .FitText, - }, - SemanticSize { - kind = .FitText, - } -}; - -ChildrenSum :[2]SemanticSize: { - SemanticSize { - kind = .ChildrenSum, - }, - SemanticSize { - kind = .ChildrenSum, - } -}; - -Fill :[2]SemanticSize: { - SemanticSize { - kind = .Fill, - }, - SemanticSize { - kind = .Fill, - } -}; - -push_box :: proc(ctx: ^Context, label: string, flags: bit_set[Flag], axis: Axis = .Horizontal, semantic_size: [2]SemanticSize = FitText) -> (^Box, Interaction) { - key := gen_key(ctx, label, 0); - box := make_box(ctx, key, label, flags, axis, semantic_size); - interaction := test_box(ctx, box); - - return box, interaction; -} - -push_parent :: proc(ctx: ^Context, box: ^Box) { - ctx.current_parent = box; - - push_clip(ctx, box.computed_pos, box.computed_size, !(.Floating in box.flags)); -} - -pop_parent :: proc(ctx: ^Context) { - if ctx.current_parent.parent != nil { - ctx.current_parent = ctx.current_parent.parent; - } - - pop_clip(ctx); -} - -test_box :: proc(ctx: ^Context, box: ^Box) -> Interaction { - hovering: bool; - - mouse_is_clicked := !ctx.last_mouse_left_down && ctx.mouse_left_down; - mouse_is_released := ctx.last_mouse_left_down && !ctx.mouse_left_down; - mouse_is_dragging := !mouse_is_clicked && ctx.mouse_left_down && (ctx.last_mouse_x != ctx.mouse_x || ctx.last_mouse_y != ctx.mouse_y); - - if ctx.mouse_x >= box.computed_pos.x && ctx.mouse_x <= box.computed_pos.x + box.computed_size.x && - ctx.mouse_y >= box.computed_pos.y && ctx.mouse_y <= box.computed_pos.y + box.computed_size.y - { - if len(ctx.clips) > 0 { - clip := ctx.clips[len(ctx.clips)-1]; - - if ctx.mouse_x >= clip.pos.x && ctx.mouse_x <= clip.pos.x + clip.size.x && - ctx.mouse_y >= clip.pos.y && ctx.mouse_y <= clip.pos.y + clip.size.y - { - hovering = true; - } else { - hovering = false; - } - } else { - hovering = true; - } - } - - if hovering || box.active > 0 { - box.hot += 1; - } else { - box.hot = 0; - } - - if hovering && mouse_is_clicked && !mouse_is_dragging { - box.active = 1; - } else if hovering && mouse_is_dragging && box.active > 0 { - box.active += 1; - } else if !ctx.mouse_left_down && !mouse_is_released { - box.active = 0; - } - - // TODO: change this to use the scroll wheel input - if .Scrollable in box.flags && box.active > 1 && ctx.mouse_left_down { - box.scroll_offset -= ctx.mouse_y - ctx.last_mouse_y; - - box.scroll_offset = math.min(0, box.scroll_offset); - } - - if box.hot > 0 || box.active > 0 { - box.last_interacted_index = ctx.current_interaction_index; - } - return Interaction { - hovering = .Hoverable in box.flags && (hovering || box.active > 0), - clicked = .Clickable in box.flags && (hovering && mouse_is_released && box.active > 0), - dragging = box.active > 1, - - box_pos = box.computed_pos, - box_size = box.computed_size, - }; -} - -delete_box_children :: proc(ctx: ^Context, box: ^Box, keep_persistent: bool = true) { - iter := BoxIter { box.first, 0 }; - - for box in iterate_box(&iter) { - delete_box(ctx, box, keep_persistent); - } -} - -delete_box :: proc(ctx: ^Context, box: ^Box, keep_persistent: bool = true) { - delete_box_children(ctx, box, keep_persistent); - - if box.last_interacted_index < ctx.current_interaction_index-1 { - delete_key(&ctx.persistent, box.key.label) - } - - if !(box.key.label in ctx.persistent) || !keep_persistent { - delete(box.key.label); - free(box); - } -} - -prune :: proc(ctx: ^Context) { - iter := BoxIter { ctx.root.first, 0 }; - - for box in iterate_box(&iter) { - delete_box_children(ctx, box); - - if !(box.key.label in ctx.persistent) && box != ctx.root { - free(box); - } - } - - ctx.root.first = nil; - ctx.root.last = nil; - ctx.root.next = nil; - ctx.root.prev = nil; - ctx.root.parent = nil; - ctx.current_parent = ctx.root; - - ctx.current_interaction_index += 1; -} - -// TODO: consider not using `ctx` here -ancestor_size :: proc(ctx: ^Context, box: ^Box, axis: Axis) -> int { - if box == nil || box.parent == nil || .Floating in box.flags { - return ctx.root.computed_size[axis]; - } - - switch box.parent.semantic_size[axis].kind { - case .FitText: fallthrough - case .Exact: fallthrough - case .Fill: fallthrough - case .PercentOfParent: - return box.parent.computed_size[axis]; - - case .ChildrenSum: - return ancestor_size(ctx, box.parent, axis); - } - - return 1337; -} - -prev_non_floating_sibling :: proc(ctx: ^Context, box: ^Box) -> ^Box { - if box == nil { - return nil; - } else if box.prev == nil { - return nil; - } else if !(.Floating in box.prev.flags) { - return box.prev; - } else { - return prev_non_floating_sibling(ctx, box.prev); - } -} - -compute_layout :: proc(ctx: ^Context, canvas_size: [2]int, font_width: int, font_height: int, box: ^Box) { - if box == nil { return; } - - axis := Axis.Horizontal; - if box.parent != nil && !(.Floating in box.flags) { - axis = box.parent.axis; - box.computed_pos = box.parent.computed_pos; - box.computed_pos[axis] += box.parent.scroll_offset; - } - - if .Floating in box.flags { - // box.computed_pos = {0,0}; - } else if box.prev != nil { - prev := prev_non_floating_sibling(ctx, box); - - if prev != nil { - box.computed_pos[axis] = prev.computed_pos[axis] + prev.computed_size[axis]; - } - } - - post_compute_size := [2]bool { false, false }; - compute_children := true; - if box == ctx.root { - box.computed_size = canvas_size; - } else { - switch box.semantic_size.x.kind { - case .FitText: { - box.computed_size.x = len(box.label) * font_width; - } - case .Exact: { - box.computed_size.x = box.semantic_size.x.value; - } - case .ChildrenSum: { - post_compute_size[int(Axis.Horizontal)] = true; - } - case .Fill: { - if .Floating in box.flags { - box.computed_size.x = ancestor_size(ctx, box, .Horizontal); - } - } - case .PercentOfParent: { - box.computed_size.x = int(f32(ancestor_size(ctx, box, .Horizontal))*(f32(box.semantic_size.x.value)/100.0)); - } - } - switch box.semantic_size.y.kind { - case .FitText: { - box.computed_size.y = font_height; - } - case .Exact: { - box.computed_size.y = box.semantic_size.y.value; - } - case .ChildrenSum: { - post_compute_size[Axis.Vertical] = true; - } - case .Fill: { - if .Floating in box.flags { - box.computed_size.y = ancestor_size(ctx, box, .Vertical); - } - } - case .PercentOfParent: { - box.computed_size.y = int(f32(ancestor_size(ctx, box, .Vertical))*(f32(box.semantic_size.y.value)/100.0)); - } - } - } - - if true || compute_children { - iter := BoxIter { box.first, 0 }; - child_size: [2]int = {0,0}; - - // NOTE: the number of fills for the opposite axis of this box needs to be 1 - // because it will never get incremented in the loop below and cause a divide by zero - // and the number of fills for the axis of the box needs to start at zero or else it will - // be n+1 causing incorrect sizes - number_of_fills: [2]int = {1,1}; - number_of_fills[box.axis] = 0; - - our_size := box.computed_size; - for axis in 0..<2 { - if box.semantic_size[axis].kind == .ChildrenSum { - if box.computed_size[axis] == 0 { - our_size[axis] = ancestor_size(ctx, box, Axis(axis)) - } else { - our_size[axis] = box.computed_child_size[axis] - } - } - } - - for child in iterate_box(&iter) { - if .Floating in child.flags { continue; } - - compute_layout(ctx, canvas_size, font_width, font_height, child); - if child.semantic_size[box.axis].kind == .Fill { - number_of_fills[box.axis] += 1; - } else { - child_size[box.axis] += child.computed_size[box.axis]; - } - } - - iter = BoxIter { box.first, 0 }; - for child in iterate_box(&iter) { - if !(.Floating in child.flags) { - for axis in 0..<2 { - if child.semantic_size[axis].kind == .Fill { - child.computed_size[axis] = (our_size[axis] - child_size[axis]) / number_of_fills[axis]; - } - } - } - - compute_layout(ctx, canvas_size, font_width, font_height, child); - } - } - - { - box.computed_child_size[Axis.Horizontal] = 0; - - iter := BoxIter { box.first, 0 }; - for child in iterate_box(&iter) { - if .Floating in child.flags { continue; } - - switch box.axis { - case .Horizontal: { - box.computed_child_size[Axis.Horizontal] += child.computed_size[Axis.Horizontal]; - } - case .Vertical: { - if child.computed_size[Axis.Horizontal] > box.computed_child_size[Axis.Horizontal] { - box.computed_child_size[Axis.Horizontal] = child.computed_size[Axis.Horizontal]; - } - } - } - } - } - if post_compute_size[Axis.Horizontal] { - box.computed_size[Axis.Horizontal] = box.computed_child_size[Axis.Horizontal]; - } - - { - box.computed_child_size[Axis.Vertical] = 0; - - iter := BoxIter { box.first, 0 }; - for child in iterate_box(&iter) { - if .Floating in child.flags { continue; } - - switch box.axis { - case .Horizontal: { - if child.computed_size[Axis.Vertical] > box.computed_child_size[Axis.Vertical] { - box.computed_child_size[Axis.Vertical] = child.computed_size[Axis.Vertical]; - } - } - case .Vertical: { - box.computed_child_size[Axis.Vertical] += child.computed_size[Axis.Vertical]; - } - } - } - } - if post_compute_size[Axis.Vertical] { - box.computed_size[Axis.Vertical] = box.computed_child_size[Axis.Vertical]; - } -} - -push_clip :: proc(ctx: ^Context, pos: [2]int, size: [2]int, inside_parent: bool = true) { - rect := Rect { pos, size }; - - if len(ctx.clips) > 0 && inside_parent { - parent_rect := ctx.clips[len(ctx.clips)-1]; - - if rect.pos.x >= parent_rect.pos.x && - rect.pos.y >= parent_rect.pos.y && - rect.pos.x < parent_rect.pos.x + parent_rect.size.x && - rect.pos.y < parent_rect.pos.y + parent_rect.size.y - { - //rect.pos.x = math.max(rect.pos.x, parent_rect.pos.x); - //rect.pos.y = math.max(rect.pos.y, parent_rect.pos.y); - - rect.size.x = math.min(rect.pos.x + rect.size.x, parent_rect.pos.x + parent_rect.size.x); - rect.size.y = math.min(rect.pos.y + rect.size.y, parent_rect.pos.y + parent_rect.size.y); - - rect.size.x -= rect.pos.x; - rect.size.y -= rect.pos.y; - } else { - rect = parent_rect; - } - } - - // sdl2.RenderSetClipRect(ctx.renderer, &sdl2.Rect { - // i32(rect.pos.x), - // i32(rect.pos.y), - // i32(rect.size.x), - // i32(rect.size.y) - // }); - - append(&ctx.clips, rect); -} - -pop_clip :: proc(ctx: ^Context) { - if len(ctx.clips) > 0 { - // rect := pop(&ctx.clips); - - // sdl2.RenderSetClipRect(ctx.renderer, &sdl2.Rect { - // i32(rect.pos.x), - // i32(rect.pos.y), - // i32(rect.size.x), - // i32(rect.size.y) - // }); - } else { - // sdl2.RenderSetClipRect(ctx.renderer, nil); - } -} - -draw :: proc(ctx: ^Context, state: ^core.State, font_width: int, font_height: int, box: ^Box) { - if box == nil { return; } - - push_clip(ctx, box.computed_pos, box.computed_size, !(.Floating in box.flags)); - { - defer pop_clip(ctx); - - if .Hoverable in box.flags && box.hot > 0 { - core.draw_rect_blend( - state, - box.computed_pos.x, - box.computed_pos.y, - box.computed_size.x, - box.computed_size.y, - .Background1, - .Background2, - f32(math.min(box.hot, 20))/20.0 - ); - } else if .DrawBackground in box.flags { - core.draw_rect( - state, - box.computed_pos.x, - box.computed_pos.y, - box.computed_size.x, - box.computed_size.y, - .Background1 - ); - } - - if .DrawText in box.flags { - core.draw_text(state, box.label, box.computed_pos.x, box.computed_pos.y); - } - - if .CustomDrawFunc in box.flags && box.custom_draw_func != nil { - box.custom_draw_func(state, box, box.user_data); - } - - iter := BoxIter { box.first, 0 }; - for child in iterate_box(&iter) { - draw(ctx, state, font_width, font_height, child); - } - } - - push_clip(ctx, box.computed_pos, box.computed_size); - defer pop_clip(ctx); - - if .DrawBorder in box.flags { - core.draw_rect_outline( - state, - box.computed_pos.x, - box.computed_pos.y, - box.computed_size.x, - box.computed_size.y, - .Background4 - ); - } -} - -BoxIter :: struct { - box: ^Box, - index: int, -} - -iterate_box :: proc(iter: ^BoxIter, print: bool = false) -> (box: ^Box, idx: int, cond: bool) { - if iter.box == nil { - return nil, iter.index, false; - } - - box = iter.box; - idx = iter.index; - - iter.box = iter.box.next; - iter.index += 1; - - return box, iter.index, true; -} - -debug_print :: proc(ctx: ^Context, box: ^Box, depth: int = 0) { - iter := BoxIter { box.first, 0 }; - - for box, idx in iterate_box(&iter, true) { - for _ in 0..<(depth*6) { - log.debug("-"); - } - if depth > 0 { - log.debug(">"); - } - log.debug(idx, "Box _", box.label, "#", box.key.label, "ptr", transmute(rawptr)box); //, "_ first", transmute(rawptr)box.first, "parent", transmute(rawptr)box.parent, box.computed_size); - debug_print(ctx, box, depth+1); - } - - if depth == 0 { - log.debug("persistent"); - for p in ctx.persistent { - log.debug(p); - } - } -} - -spacer :: proc(ctx: ^Context, label: string, flags: bit_set[Flag] = {}, semantic_size: [2]SemanticSize = {{.Fill, 0}, {.Fill,0}}) -> Interaction { - box, interaction := push_box(ctx, label, flags, semantic_size = semantic_size); - - return interaction; -} - -push_floating :: proc(ctx: ^Context, label: string, pos: [2]int, flags: bit_set[Flag] = {.Floating}, axis: Axis = .Horizontal, semantic_size: [2]SemanticSize = Fill) -> (^Box, Interaction) { - box, interaction := push_box(ctx, label, flags, semantic_size = semantic_size); - box.computed_pos = pos; - - return box, interaction; -} - -push_rect :: proc(ctx: ^Context, label: string, background: bool = true, border: bool = true, axis: Axis = .Vertical, semantic_size: [2]SemanticSize = Fill) -> (^Box, Interaction) { - return push_box(ctx, label, {.DrawBackground if background else nil, .DrawBorder if border else nil}, axis, semantic_size = semantic_size); -} - -label :: proc(ctx: ^Context, label: string) -> Interaction { - box, interaction := push_box(ctx, label, {.DrawText}); - - return interaction; -} - -button :: proc(ctx: ^Context, label: string) -> Interaction { - return advanced_button(ctx, label); -} - -advanced_button :: proc(ctx: ^Context, label: string, flags: bit_set[Flag] = {.Clickable, .Hoverable, .DrawText, .DrawBorder, .DrawBackground}, semantic_size: [2]SemanticSize = FitText) -> Interaction { - box, interaction := push_box(ctx, label, flags, semantic_size = semantic_size); - - return interaction; -} - -custom :: proc(ctx: ^Context, label: string, draw_func: CustomDrawFunc, user_data: rawptr) -> Interaction { - box, interaction := push_box(ctx, label, {.CustomDrawFunc}, semantic_size = { make_semantic_size(.Fill), make_semantic_size(.Fill) }); - box.custom_draw_func = draw_func; - box.user_data = user_data; - - return interaction; -} diff --git a/src/ui/ui.odin b/src/ui/ui.odin index d88ee93..e560d01 100644 --- a/src/ui/ui.odin +++ b/src/ui/ui.odin @@ -13,6 +13,8 @@ State :: struct { num_prev: int, curr_elements: []UI_Element, prev_elements: []UI_Element, + + max_size: [2]int, } UI_Element :: struct { @@ -134,7 +136,11 @@ close_element :: proc(state: ^State, loc := #caller_location) -> UI_Layout { child_index = child.next } } - case Grow: { /* Done in the Grow pass */ } + case Grow: { + if _, ok := e.parent.?; !ok { + e.layout.size = state.max_size + } + } } switch v in e.layout.kind.y { @@ -186,10 +192,26 @@ close_element :: proc(state: ^State, loc := #caller_location) -> UI_Layout { } } +@(private) +non_fit_parent_size :: proc(state: ^State, index: int, axis: int) -> [2]int { + if _, ok := state.curr_elements[index].layout.kind[axis].(Fit); ok { + if parent_index, ok := state.curr_elements[index].parent.?; ok { + return non_fit_parent_size(state, parent_index, axis) + } else { + return state.max_size + } + } else { + return state.curr_elements[index].layout.size + } +} + @(private) grow_children :: proc(state: ^State, index: int) { e := &state.curr_elements[index] + x_e := non_fit_parent_size(state, index, 0); + y_e := non_fit_parent_size(state, index, 1); + children_size: [2]int num_growing: [2]int @@ -220,7 +242,7 @@ grow_children :: proc(state: ^State, index: int) { } if num_growing.x > 0 || num_growing.y > 0 { - remaining_size := e.layout.size - children_size + remaining_size := [2]int{ x_e.x, y_e.y } - children_size to_grow: [2]int to_grow.x = 0 if num_growing.x < 1 else remaining_size.x/num_growing.x to_grow.y = 0 if num_growing.y < 1 else remaining_size.y/num_growing.y @@ -318,7 +340,7 @@ compute_layout_2 :: proc(state: ^State) { } } -new_draw :: proc(state: ^State, core_state: ^core.State) { +draw :: proc(state: ^State, core_state: ^core.State) { for i in 0..