From 5fe5f622a916e2b9ff1003ca9caac2e3454564e5 Mon Sep 17 00:00:00 2001 From: Peconia Date: Tue, 30 Sep 2025 17:24:11 +0100 Subject: [PATCH 1/4] initial test --- package.json | 1 + public/translations/en.json | 20 +- public/wasm/tree-sitter-python.wasm | Bin 0 -> 457883 bytes public/wasm/tree-sitter.wasm | Bin 0 -> 205488 bytes src/assets/stylesheets/EditorPanel.scss | 126 ++++ .../Editor/EditorPanel/EditorPanel.jsx | 103 ++- src/hooks/useTreeSitterParser.js | 713 ++++++++++++++++++ webpack.config.js | 9 + yarn.lock | 13 + 9 files changed, 980 insertions(+), 5 deletions(-) create mode 100644 public/wasm/tree-sitter-python.wasm create mode 100755 public/wasm/tree-sitter.wasm create mode 100644 src/hooks/useTreeSitterParser.js diff --git a/package.json b/package.json index b95589d36..b5198b93e 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "ts-pnp": "1.2.0", "url": "^0.11.4", "watch": "^1.0.2", + "web-tree-sitter": "0.25.9", "web-vitals": "^1.0.1" }, "scripts": { diff --git a/public/translations/en.json b/public/translations/en.json index 364d2367d..33ff54d7a 100644 --- a/public/translations/en.json +++ b/public/translations/en.json @@ -15,7 +15,25 @@ "characterLimitExplanation_one": "Files in the editor are limited to {{count}} character", "characterLimitExplanation_other": "Files in the editor are limited to {{count}} characters", "viewOnly": "View only", - "close": "close" + "close": "close", + "pythonSyntaxErrors": { + "default": "Syntax error: Check your code for missing symbols or incorrect formatting", + "unknownFunction": "Unknown function: '{{name}}' is not defined or imported", + "missingClosingParenthesis": "Missing closing parenthesis: Add a ) to match your opening (", + "missingOpeningParenthesis": "Missing opening parenthesis: Add a ( to match your closing )", + "missingClosingBracket": "Missing closing bracket: Add a ] to match your opening [", + "missingOpeningBracket": "Missing opening bracket: Add a [ to match your closing ]", + "missingClosingBrace": "Missing closing brace: Add a } to match your opening {", + "missingOpeningBrace": "Missing opening brace: Add a { to match your closing }", + "missingDecorator": "Missing decorator: Add a @ symbol before this function definition", + "missingColon": "Missing colon: Add a : at the end of this statement", + "indentationIssue": "Indentation issue: Make sure your code is properly indented with 4 spaces for each level", + "missingClosingDoubleQuote": "Missing quote: Add a matching \" to complete your string", + "missingClosingSingleQuote": "Missing quote: Add a matching ' to complete your string", + "functionCallSyntaxError": "Function call syntax error: Check your function arguments and parentheses", + "syntaxErrorAfterKeyword": "Syntax error after keyword: Check the syntax following your {{keyword}} keyword", + "loadingParser": "Loading Python parser..." + } }, "filePanel": { "errors": { diff --git a/public/wasm/tree-sitter-python.wasm b/public/wasm/tree-sitter-python.wasm new file mode 100644 index 0000000000000000000000000000000000000000..dc9de36f0f342aae941728ed4642f7e2c6f6e6a4 GIT binary patch literal 457883 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%0D0X3meX#6^wJ3 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%0AhXR?cb5r2gA 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 VyU+WG@D{zi@CM=Us#;uQ#y>fWb<+R< literal 0 HcmV?d00001 diff --git a/public/wasm/tree-sitter.wasm b/public/wasm/tree-sitter.wasm new file mode 100755 index 0000000000000000000000000000000000000000..10916b8ecda0a4d90ff5a0978b12f2db82c55db3 GIT binary patch literal 205488 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Ës0kb$JWf5KtbBLV8fVz-#-rGp}g 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�LPz(S`eJtpx%$L~^aC4p7?sjSpjmm~Gf>+bJs5kHb7Og|rl{qMe$1{- z41y=cP%ZT!fYi`NG9+qhO(BV$&AHOk3itg`7?MG?>j^?|n0 zP<#@VGvApjM#oK01iV6nU}pSISph8Mk9ATiq^ty&nDP2i>2k0iG@`(@=RP%%<$N^4 zWe#y{J$TH1C?eszVsJ< z2_pQHa#{pp!rfC=s}Oco_tjyjwRN1yh?M*3OHV#^&NHp}-3N29-Xq&l4R zNo2zcdOdoG{CB1MmR*wNtY8IB4=4CLxy;j01ajr0IS{gQ_~<==gR9-?f&N9I+zx@S zyT9w5m_2sMAhF#^*9@o0bKAXB92v$&+z~jzHBU!8#pR3hfK8AM_ba^~x>U&4hUxZB zG1GOXb=C8}6Yb_b>ud6ahCQ6DGQEZr7_P7M;|a{eAvK-Yw^Ou*A91x?MH-55Rd-UY z1VWBsq8-ezYHWqAjFrF zA8swhN{-L9ynNjyHxJBd`Sk3(`Q$JVxe&Ugg984%F;rm%b zj(4GnKznY=g`&wN4y+gsBbNmpSvM^;AT86kX`UI{5&j)?g)M7DLs)})0jpR%07U^t zMhdPzVp4oa@;r znT=}yY}Ea1$fV|jUAoUXY*=f)7|k6mbSRHezfz@Fd}<@69QPnyHiRq3)f$fJDu z)sFeRiuU@K8P4}#pxNt5M%~qSdZy)dTipC&v3FoPIGX=AIl8($nM{r@dtEKnM~|)@ zEsq`@MCih>`|G?soE#k;dZ>L z=Atnuz&YArclTvF#1c9w!wx&qKo;g_hudTA4P?|zT8NUTM8Em1_v|(|ya1F!nOVQ6 zo_@o*gFgW0y5Zd5nduG9D7cp#^w@oz7ep(tj2irb@E+%M)vlUOBu87jdNVdKywIJ= zl@EV_slVY|ZRNvR1lm&ek9!D}$v!_##pdu@!kS_2l*!Eni>?u`%7wbR9!>2N$w(?#L#%_KzvT6YbXayz14Y8u8+8a8_(-t-u8ch^|!`RR-8#Y#IKIfEmN|8#PMy# zw>a;7u+|)f`gkhhtx6UaM)zz1xXDbXsbjpR)6+)@rUMbC2sHys5}*-Ob#(^FROZ|A{d~tkCgk zxAt&L{>>6kW>C?iocoT+r%sFx!`cxgR1p}`07GCY9Enjc_{tPlqc0@i$$q}wUm+t` z_YNE`)|?yI_|kYZrZijuxw6ioJ!LCJD6*6eiaJ+5{Pq&BsjBpqkG|vDkIod={Oa5C zqgiX+aJPE`H5IbX9s1U~_^xPE9IX#66{D-Re`|HkM!s|Zqbr*T5nqg+DXa36;Rh>1 zPoLb+%qABGSP!Z*m4K)?w|1$m&aHj&$6v@Ypa>QJv*iysw^NLvpzJDyQ&YVV&Vu~8 z4%W4g6GprC@LvA920d_2l6s<9O5$Vm@nnWxs^VrAqhTPi@wRpDaI7yWQ<2Y-!f) zBpUohG+q9Bi_kzgK@FrCWqS9xpr2i95c_6Q&<&uwanAsPQ%zsrOWiN2 zq;t3&b5y7j(C(St?)j=i;K9^cAvq9S8(#O7x{cLi%>(2j)B5>ejDG!lfBSEDzqHs# za0vMF{k`;dtSBR1?m#*nl^<`xhi-)Pu68$mlvBcZn_9_x+^8#aM9FmPTroWc5^;!L zp!UJ1nq{b{+4|Hc+8fYy^5O+^=qiS57A*bbD#+ENt`$%2r#d8}t3Sx=@Mjx;P9>4#0 zai_}HT^HOD4<6(S5eb4c@a^dbyI=lc5$61sSyC<$RNIcb)&G!|P!-S;ZFvBQoam!<&Nq7s*pxWxjeaazXG)+Cj7NbZ4X5U$@xdiBl@u1q6~kS6nH&Fky$NK z6a#hkHFgPly)Zo@n_KlXDe{1I*dSXF{OUd2`muWw_hh-gd$RkataHgc@_yDHAENIrLfB{t-|!L^`1J~I)nQYjS`&K$uv1~u|2qT z4;ZzkC2cit@44VTi%TnR3lBn!Ex zDM3`SdTcKS(QYfchEPlTZWdqtn3QSV>P2P}Y~#NJoXl0HME5fDC5^>p071x*SVBw* z9}q)cQ(hQSx-Z%a3s&i+nuQlsRNa-hyr-lM1BkX$djcqI9wNGC^U+YJ?Y7pfeq_tmD46syH%irDOt*Ru__d5Q)w7M!$PhJ$>9vu2 zn1XZy`l+Kq0mSW)05C1PGmhy6Gxv>>T42C$9#8=z`v|C{3Cu7Kgf1?FhoBn+I)dd* z>xE+=hSekt-C=>RfxITA`Ob2;%IwEG-cX|o zRLNGRSN!Ys9}c9?2RW}^z+PvtbvI(pJNP~j&K&j3&UBOM*_>Ve61+2^g43;a-<=k4 za@1z?7oUf%iZsDYH-Awcdnl<-n7G78x66+*_MV_FgLu-Oup@EIT?86xc}!JM)d8*! z?(QQj4fY@`+13YTA@^QD+X=-^pZO3FmtTQy!AHDAyYv;muvIWce=X@!MR8^aos7}T z$N;@xL+4_AueBA$4>c>HgLm&Xq=K*-6f(w`fl?Gd3@m~Z)rk`^vxi`PElR-h9vV%a z4Kx5g1*vSLUDBD3ZO%H-dEI+8v}&AJCV8-4#BgG<)aNAMXbb3Vk4FfqJ=BwI?n3qH zfpZ_<%;uhr9L=(GkG6u!j}g$UkA7jPTE_kX9C z*YMhF2cK%|b3YL)B$qC&{JVTqohv^v4L@t$8wrTxvPjy5$k-?t;_n1qB&C~bs$-sX3?Yt*_odf!G?zMyB>T37ZpDtlT>C$dGh-PqKyP|^v z2~`mAcn>cmkG~cOh;AIv5iWttQk0L||59u^)EOHX6LUf_7)9=6QS(_g~` zF**H|{?yf{Y9wBzK&xT@?j@GXX!_PqJ>67K{{}9A(ch}m;atxnZqxFtL;eNKOWctJ zg|U;&6&apF0u2=VVS-8sA|5~z&z3~zQhL%*(!q(UyAiDBEGuu^$7h4?vo1lLisEN1 z-VdiS%mYmSEvj6CM6jFRTm7aa&kh_L>9(zv>EGt|d#hJUW?gJt>RzE&OR;OSI8pH` zrra`9U&Y&r@(qg!3?kI*UT(xx86$`D<1bx6u}muiMxvcjyBkoOaT|=rfDf}o zGEF=^g%(cm0G$_oxxq=>@ElgN8VUVCt2KyRO<;*zpBn3%5NT-}B z*7cuc5&t&`(2D)hycrBo0DXW5B~l00z}52O-OFAT9VaqEqTQt(p1)i}gWaW<>W{|G zoW;)oE04yHcdy20eI>J6yE@l%iIBY4Ui*N2ckz4p0^c8gy!+Bszn@h=tdogLv5A<0JV$7HK+=@5oZJHQVTvR?}W#Dp_?o13e((__ND_s>~Mrqmi8_- zB^MVSdh^{9J+Im^j_z-s~SW>#Fk8B>`0H^lxhV$SD4#RuJzby-?Ih-ryc^4-*^&*nBS48_{0U2poGi< zVHx?`-Gsggo}+|Fcf;;xW^(26f4?pEjHu#K`Pg3fJ?7Q#iU5vuk}F}xVqFICfognw zj63DysB`?@bk`OsZ+kHI5CeHlj9t=c;?QZon0mG3dLz>-9AM4NbKTweIDxTD4kN=A z0zz!baOsbNm$B;6U%CkMqK*nIUy(ql3@l$ZXv785Al#;OqGOsPgR!$%!<*;x2#dKJ zY~;rYbS6P9HL?U)IcEsWjZwRlT2^(@N|Z4$r9j4EfzBRD6)O#S!nt+%goCQ$eN4D1 z252ot>4aA?;ef5Fr+^_%IOC1?X(1*Y44X~(AWb;Rd_T2`yHvqO`D|K%$Ouf=bSCZx zVH!2qQ93d+L7|WEWF91%mJuB8gzW$yw<&YaoFOs-*p<_B~8UQj6P( z=y2+#yY8zgoChImd1$Oj3`C1zAdaMNvULQ)uJ`O%wMWDnHKk9$8@k!}KAq(zv2ruq zqHo*GR5t)we~rf_pG~U(24jdCkdIcRnB>s(_(2NMlWjahOZ#hj37!fzzZVc#@#g?# zLQ8Iau3nruQB$r~%w{PK8~)_I^J%TcP{}P$vY0&_wMHsgL=H8az=Bhe&V(=b|J2I+TpuKB3&lLq%&i2L&2!h<%Ka z8!uuB1Y5@X7|?2!iG=Qv$|f}kElJ%mlc+W_A38ywaf~SVqhX*hYq(l$n00L^f4rQ2 z0-wpuBVF}Mk9ixFY28t-r{`> zF}`4^G1-j3D2u2e3?KoVr;*VMV3e8+V65%69>4}uICgB*8Gu)2i!A4mylNuZvr%SL*i>SRcigDI&Zx+R%?cqave~mZJ;OuK;cK`b-O; zfrBKbR-Vedx%R{7X^F_e8rm9`5lSfl_)zvdtlbBkf9=ua=6Kk>D>;sr0QS}5U zjHh%H8}P%#s|eFcJpw?=eDMAyTwQoJm{}}7wt3Ml^x@U$6H9H)YM@`V|B}cpt||a4 zv>8Y$v>DVSwHZLHt{4%-gPF$2_A6m|kT9;M`_H_IO;F3$Hd!YlHn@I^O7bcH?SEeYcL=3mA)p&F?)-axX=C~lt<3C zK*{5B(pDNC#^bY3uiioCq5L5$OktFiyb2PO?9(+Lh&@rlnaqtU+2}V4~=fFk)>U>~w5V+JLCS)B%m0tLS;bdoeof zUtcFEG6u-X9oHhG6O^t%R90bs^n$WJ;}<4b#xE@Pj9*a# zzp^Q9HV5>6DMMW=P*yI<>);pnv695Ec;F_~ct8(}F5FWpA6PT!L>bt+T7p^4xCt=p znG_}2)0}I-P9-ev>=S1yl)Z}raR#t;-!MsmeMMlOYNm}cHPQ_jia3)H&}$URq23@o z1u;#|u+O@C>?^=NBmmZSnXGMRIjn6o?tpIcnJU)c0m!rN3%`SEQwG*SM9tD;ZEOO} zVvjjp4nRaV=tR(?{VPB^d|-ieO9|=1+=idjJ54zgW<*yrq2in^f^*;($Tt&a$;sg8 zR6;-}*+4&7hi+q8oCOHbJ_N`h;aq&u14J3Cdy7w)QKMcLagIuQrl!RL?uv6P+6Fl# zoLjq$qhUK`j3Z@4#<(agU|i^9RDM;AgG}rN_2#+eSSBA89LeiG&%+i>F&Q9E1ScQx^5ojgp!0 zmaq-{xEe|!X%gEo$Qw7wl~_do_G3|+6DRcbu?Uyio5LbnxGENLKXc40u&AeqS@%H! zW9}A7h{?N5_;SD~w-&^&(-qp)qWD$tCu{OD@f#XW)^|dI>opwq%H9-4?;A^B036EE z<+>kHpfCzamF$>ke>fr`iYN7?;SYjgSG{h^E{B;UrbjPT-R2Zw*GppT>5#-#lEgqR z)W2X)E#*-ELKL&;t6j+lk0k?eE{d~VN%a$JZ(z?|=pc1Fg9)(Yxn_xQ;Wd~$9Vtej zC5)6HXEm4%ry)id$+ldTE7qvshZh7x(EZsBZ_qG9S%M$F%F3 zm9o;6GH8?E|G&3ufsdlP`ge9An-Cre?}v~jJSBj^Rt2nvsGukY1dK0q-0V)0F`M0W zA0(;D24DE9)nayrM z`t$3+ow;+*x#ymH?z!ijxpQZO-Am*M;af1EgRsp4k7ebsC>5}*v_3!^yUA}1=G{)T z`hv#>Jf9El0A;m=2q~ONe?*vn-wzM({A3SN)6Q8&#k)9mKJ;Nawwhj=#x@*^!f{VO z-=N(xwz86t^^#Yp>L%W~o%RJWbFp@Nka2M1oUb!Jn z!{Zmd!UTHITM|1b`$o{)^`!8b>e*6Gg*>OUG0^ zzmvTUg6EL(-GHm6J~cJnF6%sDt_`kb7M3l<2WS| zvJB|gGJp=pEIUY!nE`4%02nGCdepd9>Tw+u#@jvrxXQsVF$3t6Eg6^=)xnehP)q}8 z4f&TqB^EBwR|JYQ!7HB?nKRhR~#k zAMgc^ECkZDpsQ&J9DzZBBPNI

zP6Pvpv3(c$iagEx-|9O>tzVW((l@IwR?29qlf zLaBnZqEX9%BdRmL4jlPjuk;lLj^Ly)aHQY?wj-zlM~FpDWq!u};DIApEPN)XeaRRc zzsuJafga4=$8tQsDE&M{;K)Lt7ga1KZ;gRv1xl<#k`z z2#_t6TOR1)g7lvs9pIbzeGDQ-R}vU`8f`?1L3keE(ktr1SdrzgVnxh!B1n9o9^y(S zNDtv!w4{3!wUKo3CItQnYNlgFtxDj!f2>$qpFT*eSg2q+RwM=U#fnfc9V;Hbf?<}H z(jvF32agpoI_?ukiCD3u;v8PASVD0)RxF`7rA7*6{#cP_4Tl;93pJRI7-JPH;%8TB zTFQk&dUDLx@+TXZ69$|hc}U*cL^^`si-5MUW*KxL?Sm?_5^_r%E8^)?y?`*VQX}_8 z1m#K%te0G=!7yMR&9(JKgkGvY;x@bDa?Fh6iCSPaGNARBIA(I(J8 zR0R8Y1;P5lkrb;lt=e#?2)&b`A_6}zROJ0^cohb31oDXa;7o+>H%WoaI;p=BhFq|c zla7|^QA3NSvT|L3etrZ~J&&-6Rt)gdK3~IcmTjmFK7{dr0^*lXdg=F3CL(Hti8v{I z-9wY$P{@m@6$bg4jQj-}Ol|y`2z&+zo+{(~+YNFb?^FZE)q=8s{f<9ndIGU?PNF&2C;-9{|)@>;| z+=y-h#LIKC9eiP9U_Xp%6K01t3Dgdz4Km!fj__dY{6<7YBgmpNNOVI;IP?Qg%PY%} z#YdY}Vj40X*l$sbTObcYLFG`wqZ(oSpc1@_XbdmIsbX2W@^n^*s((W-u|)vGfRzmq z#L!cB_=kcZTI@xn{?Z--7<{tqm;>}fNyJc4?U9tN(2!Cqe+qyw$U&U+&J*NGRCs|! zV}_Ycp#>EO5XV5y$kMN^5eESV(4GO_!hydg1LKY-Q+|dKxFv(Y(-W+cbcj{r(8n4N zE4&o+1xsnPFA-EBKP;fp4np(~brFQ9e$WeZutFLmyUMj_^n>_f1=xg$H|n&H2WU_{ zvUPx-!4d#1=!HkkPm5$k9J{2@lB!SNS5Jkq(S@uEHQ*yj?RuRXQ2hAyNhxZQTwn>> zMwCagr04-(HmM~=X$jQeO0u;)t{7wTay+;g8RCH|u{3#64gob*$!Mh6o07EUJ}NRm z!6ou%nW#?~-BQ2F#Ou?fKio~#>7w59hOvHp!LkjYbd7>y=wKtd$e%Zh%5efZ!c&2? zJKgvOCPCTqbmf_(-^4REL1KuIQL`C~oLbQhs0$!iqo=+qHNu3U|<)T(N)6=3xhZt12Zt5L9;HnT}HmA2N~$y`Pw zgP)j{%C-|4DA*qduQ`!vw_CF=3OX_i(0swEj`S*bqeM9gk&wbE81 zi*7~ZxtJ9*(~(4*6`qj`&%;SXqEWOaQjv7VG9#_%x@pDitXP*7k(OjSON7-xP-Ee2 zG8yiSBzkmjGon3`F{AB7G$S&=iMEGZ)5%V=!|Lfuren+x`bl~8x5I*8}3Ik8iAx)c>oszxs_!M?qBAK>Kt234DarLeUi&}}a#AQP!NzofD zt*mAS3?(26mY1)IQY>N+!C(PdUyT;4m6#d`+^^&2j7&J2wxD|%(UP_z9kFCr0$oU% zNpwFOiFUZ)n3bhLlY^5+Ws)OOWQaC7{)q%I%SKBnpiaiAX}M zOAxNoWi`7^QcGx{tM68_RoIDEk+8ZLW{GGr5wj8*OT>~1izu|FBW-q^0ojP1@JqFD z3NS~oDvAkM1R};prJztLWxL(hj)A~*jaq0?8YZn9RLUl$P@SM&SzU?f^DHKfNJm5> zX}ZEv6EBg3h^=&|4Kt;%Omp?hm1|8?m`hi$Ub$K{H8zPem#NYo`rwQO6eZ81ZLvo~;oyf)GxHO%;Vg)X>GcvVDidpNjvv5k&DKV?R zaAwVj&MF}uE}pfnewH92Vzy!p>k?--Ej@SD(v#ONJtZt=WEO|TkRif53@$yIO|PCYbJpx5=hV%u zo;Uxfs;YYYWEUL^j{Zp1M~|thsvhV#b|AoiTz(wYTv)wm@$nx!;l#tB(jYlL$hmq+ zWA#ZVpR#n>si!SJz50w5XEv=|byn5tHEYjqKIh!?&c9&Yg;f`s7gJv&Em4mS@euQF zIagh6^(!g9t*&lutEy_Z2k65k)zuxvl}eOU$2;B9gqthOBvY5Bbx9_hX zAI^TKhOrFQJHYxHgxKr66LjnBar)kHwmS!$J)7TkocEu7bMtwRc~#g`_65f zCIl|p>NJSAwxD-a9JaYdTwKmqLZ^yT`y;ZWuKF(3f z4Y(8<2tV(PaEz@4XsD6(M<5?mwLzRfIPwRe2vpw7l949~+T-jQ;o>{a+p=^EiexS? zoHsewpg7pgwO>z?ew923-b?L4MudR#SLi}{$LTYFU9)|JQ}gQ@$%Q@#dLi9{va9zW zb>gb0oVRzKtmMw~L*oy0WobYLs!pC8m1oY%Q%(0!?w`^H$D!7pyvVDAu($=34|MDT z;6U)i8cz@MyS%)!Zx=V^E6FoY(#~+}l9$4BgWFtTdH*}rKYy_+JuYl;YH)=o<-7de zTlngpsF_|E2;YI}PRHfUL9ergZjh&o)Ki%|if7&aKk^;xV~B8SBy^txvNYrJ@*eur zK=(Aa`N$6zVxi-FOl;V&0rKWDRFXjV9AX;otp)rKA<-`qJN$|J=B1VEH@F6Cvj+s!z1(_~rn8lF)`cP>E%YpRZQ< z3J%T*%JAKYFw~ta^PeTYmac@TG8&3rKd#OzgR*}j3MB4s@Z;7B6P~7v*Z@a9Z!mu( zU8DoVzC7~pG5qnL0KSEA^Ow22Yn*|u{_!I!>L}=|F6679%K`wK(2~vQdL@A<)MUIK z6=EtlX+*wo6j0Kj>k9#!7lg&yr94t05nns~0lIx>lgjma6e@l7T)e`J%YR|9zT^3& zx)u_94}q;~`L2cbJB0afI4Ar2;NkY^Y+vkk=Pxj;d~Dg>*N4ny=6+TrS$K$Z1K_}> z*bk`>7oO(i=2~e#XEM)7HN6dmTo>c@FX4KKx%gV*RHG_r&@JVK z+M(%@9PT`@Z^+euzVj=0?%Q{f^PV%}EhG(>S0GVKoC+af+*Tmbv->b4(utu+*C2^7 z;%lS;QVN62R87WIr{Verl!p-1a161p6R9RmftT{cs;DvSWR>V<^w33R#RZ{?}s><)gBmTLAOcTe;t?dWX*U7y z2gu)t>${N8#FQ%=BMdmTy$WrRy8j(`IE;FnUPQTXEXCGJVO-4Nt}tG_OK{t0D=_#5 zsscX$>4pqz91bSP1+<` z^@EOP{FkN z`+tkJ`W)kXeUA7Y@G2j5dC`66dKEhyW8&W(W5Nzcy!^5wUU}Iueuor9`5Ui}s?w!1O%7ZmuuK9Y+qv6NG-cskn1xps3eDof_cdT)XJJ z#TOjE?qiV?q9*KiUA6R_%O+nw`HIQ;bFV$`y7R9;|AzB#y5Qz@x2${k!mnNQ^^3l7(KF^xFMc8N zv&i+B2di)_=rQd}jjhWEQxY!cUr>&4dG_VvBjZ@l5k8?U+P+E1kJx%8g( z_jcXey}4)e$M3uBzRN#<#pl2Ih40<}#Qje`@cl1t`@&Bjc=f^89{l|!e}3pM5B)Xq z_7~py(z{>!_m|)M^8N>AJ~HRgy2qNnd3AZq_o7e4p0u87{eI_`#Mb1~sb@B9%Wu14 z`zKz!SMfvR`04R7_ZU`IHeBC~sHz@WHcH=> zm5m~14sDsA6|CyhezsgDNQL|0kiVIc}%ZUhauKB ztD{8w2-Ac@4As?Lpb$@QZXUc~RpGzst9rO#VZZXG1%akv!PX=}cNPebs`%y36#x(LJ1-SGPQPTCjWFWuwKNLyWrFWo2Sc*_=6b zp(nmoH$~KesX9YD6uS4V9rLqs!v9cM5 z`>*-q_s%>-9q8-I=H|I|`R3+Db#+if?|$QPxeG2^$M=DU2O%2w!y$>IM{PVuBsXq& ze&>dH8#fN$uqdBAM{L}wBXAD+-@ASMo z31sHQVVLxR$De+B=T6M;L;TyhbIsGI{t~llu)mRz!7qP4*nk{9R=*iY&&EP{ydIjM zk5{%?D_t9=Z5gif>gsw%)3s;Lx>>UP0Wsup%B>8RkoLj!^)#2K5*0N!F3vz1jTjQA z=@~?iYX8ZhN%2)0PepRzhjiJF>xNJ@Bs3p%Jsdv#RG0@|$MI9+{Na!+#P!M9qk5>m ziT|vYuVtTHeNsJSyFLmB$(GGJE7|42FUOL#zPsw?+wKxy(RV^T{sS-n-_8B-d9+7Q z`5*T2IuU$BQT?5d`fgwT!^{5p!y|RLl>Sz_0@*pzBzokhM`K#AqSY#1E5mgxl0Il% zX|TS*m7>B@2d(7MD%Kr!h0y+X1xrJG6k;9T-7YMz@NwhugA$QWOH^0UF1L9ZJBy81 z^SdIMPUEu+@td^cso0do*jCkMMzJNa1>4??iGGOOHmPBO$px@~T<*!`Ez4y(>$vmQE@jG8g4HIj>Ghf@*t)hik?5=GcO=r609N@cWN)QqIl zkse}L?H^TpQ!~|7=w>AlDk#ulJrbD!L-IkBLEVvs0J zn&hvb0N9&p67YypwX!`aYw~1&E!jR*FsY^;n^FR7#~4UhH4~_|-kWK<;>AA7@DZg! zG3>?7T2pDXN>qdvVc2+AYAyGtm^PzKn^?Mj0&2n*XA(2A#z;Bov!JBmMU|6N~BWY#wBZ#p=czzKzmAFv4uIBPG(M~Y2~9*2t9>F;)53MOwKGV z(2EqbA#28xS+gx2NwqJdHYF?lAg7Y?kdU-R-$Izm$elLI$9g1&jly=KWBE*v2T(U* zrOi|lE+T7V`!VnN&*IC9%<1E?CmQpEyoMyB9oiX{@}BLOCm``JTrD}6SMX7g~&Q7h7^89HaIy65i!y4qo2GpR__I+sR7 z){@NNOp!+7JhF=#V5Kn^KnvTxdop(Be9}k>lPY?_P~|G20LiX(Zd)WV8+*~SHl}d; z!b#CcDx1R||4Vb2_*@2yh|$K#UPPm#+v9INq>+IpkgJWGZ**9qm~ERTXBygm}oX$RO! zs-)P&*xFHiUxJSTU}F^5Z5{$f6`f=2;zMYsIVmY<6Yj5Xdm{#WtVNMdV#dTS@v8b@ zw4EQpP$b?#Gfh(Vff2bN49Z&Aw-2|K$y%v6&1{Jmv*ouTN>-i45XC`)e1fb4znP&h z!s<$r%PJ^@?~A6WlwFO&WOH|^Cl^&rM$czf(_Y8j7T_KD!<~%BmxAdTku9plstD0% ztz2;})z9^F`ZKPKft~P&%B<#HhbAbs!`_805b z2C-Fy5vPxjlwj2tbk`V1m%{W9kq!c2{9;n+t~J7?D)}RlO$JRUKL`B=RDV-<9iG+- z5xFyP3ox438{}mRsHnh`Jp2tsa=?IdSgOON8x3*^vVJnvbCW@#2H#4n{Ru9J!P6lq z%w<~_-pum$3KkrTNb{EB!WQe3hA##&)dxsK`c@W>c55vb`4a80@Wl{5Z_C_fOx2Er z+!rD!n$OT+w&*YruvfPm6i*cx2>P&zeP9fbRplx%ICZ3qDANR1(WR2ziDe@ z<9)+8vJ{>+MEaqLaUUt&J>SC*b?;GcXq6~^wu|XJX7u-f+C=dqiq!oWqkgl9y#iEe zC$L^X4L^Qx+=AKkEu*eqw+rY3SAEdQl)qin@j`6b{&&2&0x(51GF~efm;Wj95)`u) zpnSJTfRc{wD#Z8LZTj_$h>q&ziNBm?o8h^;mI8PaK2gKnOF8c1+ zYx(;{-P0s_Xla)46T5Bx3C3V^?%0?cRG_A+mH*rmr#y|zbme#j<^p|U$N)*{ez zZ1@@EC+;Eqh<%u^F4cP8Bj~S1pdWh#Wn29_{u8!?vPIu=2@m&AJ(z#ZgkdkRUV8Op z!#dKWrhn#PaP4A@?X0;>!)G?U&c5g=2b?N>{!esY;+f?a0eK)F0Ydt@H=aHS4U(sx zzwpLW@-`smFL?^}XOyTF%+;3-3jC!^Xr;@~R1xwHZ_u=5pal|QekVI%rX%fv2E5Y0 zj@OxA71r?puX+G?QI1=A&2X2*+-X6r|JqYtpEg1KTMoB-0?9`TqV+}rEwB8y91t)Y{mk>5g*E8Of}8lA*Sd_*q`RO-e(&ia zyTtzRKQKudXz~N23Zix!<78n`g!f18j|xrYdWMX<{$va@;R#|k;!}Dr@@E4tB;-B4 zpY0 z*WZm0R>V@dZ1f)rJDTcwo3L9Mx%;0+nausmD7Q0RkvJBQ{#RvF?U8+)`02L7A3x$1R|0W!JW+t*%c7${|_^(@NhO2t~0N+ZjpS#)Y{QLh9QCB)OQ~ zW@qmROu(wK`-%$sh4orQj(7uPeJU`n7{*QB891V-5=X)ELG3Q4&+UJjvn>eeJ3bST z5|3m*%Z1F!=K>*&5v`cst-z_Cdjb_;ubcV1H$dZsQ3^dD7%HGpl0#l3vC^b@sGh{@ zycYB_0KJr}Xml5>H2GZ{g9c4Mb>-Um^nz)=UYnp8R{M4(<-yYqiY3RVZ0bRgXyif% zrz6L$e0(t-%qrN6iV`iXUESEUX4%r!=1FHS!)3^%;>xDE_O#VY&oRx)DW!l}QjKO> pxT`2P#9$VNm{pZY=F(B?OuTi(GRfJiSB#lY@s$6)?0jg?{{h#KRxSVl literal 0 HcmV?d00001 diff --git a/src/assets/stylesheets/EditorPanel.scss b/src/assets/stylesheets/EditorPanel.scss index 7b72ecb83..b787f779a 100644 --- a/src/assets/stylesheets/EditorPanel.scss +++ b/src/assets/stylesheets/EditorPanel.scss @@ -52,3 +52,129 @@ .rpf-alert { margin: 0; } + + +// tree sitter +.problem-marker { + position: relative; + cursor: help; + + &::before { + content: attr(data-error-type); + position: absolute; + display: none; + z-index: 9999; + } + + &::after { + content: attr(data-message); + position: fixed; /* Changed from absolute to fixed */ + display: none; + left: auto; /* Reset left positioning */ + background: #fff; + padding: 6px 10px; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + z-index: 9999; + white-space: pre-wrap; + min-width: 200px; + max-width: 400px; + font-family: sans-serif; + font-size: 13px; + color: #333; + pointer-events: none; + overflow: visible; + max-height: none; /* Remove max-height constraint */ + } + + &:hover::after, + &:focus::after { + display: block; + } + + /* Position tooltip based on line number and available space */ + &[data-line="0"]::after, + &[data-line="1"]::after, + &[data-line-position="bottom"]::after { + /* For top lines or when explicitly positioned at bottom */ + margin-top: 5px; + } + + &:not([data-line="0"]):not([data-line="1"]):not([data-line-position="bottom"])::after { + /* For other lines, default to above */ + margin-bottom: 5px; + } + + /* Error type-specific styling */ + &[data-error-type^="missing"]::after { + border-left: 4px solid #e91e63; + } + + &[data-error-type^="indentation"]::after { + border-left: 4px solid #2196f3; + } + + &[data-error-type^="syntax"]::after, + &[data-error-type^="unknown"]::after { + border-left: 4px solid #ff4b4b; + } +} + +.problem-error { + border-bottom: 2px dotted #ff4b4b; +} + +.problem-warning { + border-bottom: 2px dotted #ffb74d; +} + +/* Support for touch devices */ +@media (pointer: coarse) { + .problem-marker { + &::after { + display: none; + } + + &:active::after { + display: block; + } + } +} + +// Tooltip styling +.cm-tooltip { + max-width: 300px; + white-space: pre-wrap; + z-index: 1000; +} + +.cm-tooltip .problem-tooltip { + background: #fff; + border: 1px solid #ccc; + border-radius: 4px; + padding: 8px; + font-size: 13px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +// Loading indicator +.parser-loading { + padding: 10px; + background: #fff3cd; + color: #856404; + margin: 10px 0; + border-radius: 4px; + border: 1px solid #ffeaa7; +} + +// Python parser loader +.python-parser-loader { + padding: 10px; + background: #fff3cd; + color: #856404; + margin: 10px 0; + border-radius: 4px; + text-align: center; + font-weight: 500; +} diff --git a/src/components/Editor/EditorPanel/EditorPanel.jsx b/src/components/Editor/EditorPanel/EditorPanel.jsx index 59d69020c..b9fe483b2 100644 --- a/src/components/Editor/EditorPanel/EditorPanel.jsx +++ b/src/components/Editor/EditorPanel/EditorPanel.jsx @@ -10,7 +10,8 @@ import { useCookies } from "react-cookie"; import { useTranslation } from "react-i18next"; import { basicSetup } from "codemirror"; import { EditorView, keymap } from "@codemirror/view"; -import { EditorState } from "@codemirror/state"; +import { EditorState, StateField } from "@codemirror/state"; +import { Decoration } from "@codemirror/view"; import { defaultKeymap, indentWithTab } from "@codemirror/commands"; import { indentationMarkers } from "@replit/codemirror-indentation-markers"; import { indentUnit } from "@codemirror/language"; @@ -25,6 +26,7 @@ import { Alert } from "@raspberrypifoundation/design-system-react"; import { editorLightTheme } from "../../../assets/themes/editorLightTheme"; import { editorDarkTheme } from "../../../assets/themes/editorDarkTheme"; import { SettingsContext } from "../../../utils/settings"; +import useTreeSitterParser from "../../../hooks/useTreeSitterParser"; const MAX_CHARACTERS = 8500000; @@ -40,6 +42,34 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { const settings = useContext(SettingsContext); const [characterLimitExceeded, setCharacterLimitExceeded] = useState(false); + // Use the new Tree Sitter hook + const { + treeSitterParser, + parserInitialized, + analyzePythonCode, + createProblemDecorations, + problemDecorationsEffect + } = useTreeSitterParser(extension, fileName); + + // Create a state field for problem decorations + const problemDecorationsField = StateField.define({ + create() { + return Decoration.none; + }, + update(decorations, transaction) { + decorations = decorations.map(transaction.changes); + + for (let effect of transaction.effects) { + if (effect.is(problemDecorationsEffect)) { + decorations = effect.value; + } + } + + return decorations; + }, + provide: (f) => EditorView.decorations.from(f), + }); + const updateStoredProject = (content) => { dispatch( updateProjectComponent({ @@ -54,12 +84,38 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { const label = EditorView.contentAttributes.of({ "aria-label": t("editorPanel.ariaLabel"), }); + const onUpdate = EditorView.updateListener.of((viewUpdate) => { if (viewUpdate.docChanged) { - updateStoredProject(viewUpdate.state.doc.toString()); + const content = viewUpdate.state.doc.toString(); + updateStoredProject(content); + + // Analyze Python code if applicable + if (extension === "py" && treeSitterParser) { + analyzePythonCode(content).then((problems) => { + // Create decorations for problem areas + if (editorViewRef.current && problems.length > 0) { + const decorationSet = createProblemDecorations( + viewUpdate.state, + problems, + ); + + // Update the editor view with decorations + editorViewRef.current.dispatch({ + effects: [problemDecorationsEffect.of(decorationSet)], + }); + } else if (editorViewRef.current) { + // Clear decorations if no problems + editorViewRef.current.dispatch({ + effects: [problemDecorationsEffect.of(Decoration.none)], + }); + } + }); + } } }); + const getMode = () => { switch (extension) { case "html": @@ -74,6 +130,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { return html(); } }; + const isDarkMode = cookies.theme === "dark" || (!cookies.theme && @@ -84,6 +141,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { (item) => item.extension === extension && item.name === fileName, ); + useEffect(() => { if (!file) { return; @@ -120,6 +178,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { indentUnit.of(customIndentUnit), EditorView.editable.of(!readOnly), limitCharacters, + problemDecorationsField, ], }); @@ -141,10 +200,22 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { img.setAttribute("role", "presentation"); } + // Initial analysis for Python files when the editor is first created + if (extension === "py" && treeSitterParser && parserInitialized) { + analyzePythonCode(code).then((problems) => { + if (problems.length > 0 && view) { + const decorationSet = createProblemDecorations(view.state, problems); + view.dispatch({ + effects: [problemDecorationsEffect.of(decorationSet)], + }); + } + }); + } + return () => { view.destroy(); }; - }, [cookies]); + }, [cookies, treeSitterParser, parserInitialized]); useEffect(() => { if ( @@ -160,12 +231,36 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { }, }); dispatch(setCascadeUpdate(false)); + + // Re-analyze Python code after cascade update + if (extension === "py" && treeSitterParser) { + analyzePythonCode(file.content).then((problems) => { + if (problems.length > 0) { + const decorationSet = createProblemDecorations( + editorViewRef.current.state, + problems, + ); + editorViewRef.current.dispatch({ + effects: [problemDecorationsEffect.of(decorationSet)], + }); + } else { + editorViewRef.current.dispatch({ + effects: [problemDecorationsEffect.of(Decoration.none)], + }); + } + }); + } } - }, [file, cascadeUpdate, editorViewRef]); + }, [file, cascadeUpdate, editorViewRef, treeSitterParser, extension]); return (

+ {extension === "py" && !parserInitialized && ( +
+ {t("editorPanel.pythonSyntaxErrors.loadingParser")} +
+ )} {characterLimitExceeded && ( { + const [treeSitterParser, setTreeSitterParser] = useState(null); + const [parserInitialized, setParserInitialized] = useState(false); + const { t } = useTranslation(); + + // Create effect for problem decorations + const problemDecorationsEffect = StateEffect.define(); + + // Initialize Tree-sitter parser for Python (WASM version) + useEffect(() => { + if (extension !== "py") { + setParserInitialized(false); + return; + } + + const initTreeSitter = async () => { + try { + await Parser.init({ + locateFile() { + return `${process.env.PUBLIC_URL}/wasm/tree-sitter.wasm`; + }, + }); + + const parser = new Parser(); + const response = await fetch( + `${process.env.PUBLIC_URL}/wasm/tree-sitter-python.wasm`, + ); + const wasmBytes = await response.arrayBuffer(); + const pythonLanguage = await Language.load(new Uint8Array(wasmBytes)); + + await parser.setLanguage(pythonLanguage); + setTreeSitterParser(parser); + setParserInitialized(true); + + console.log( + "Tree-sitter Python parser initialized successfully for", + fileName, + ); + } catch (error) { + console.error("Failed to initialize Tree-sitter:", error); + setParserInitialized(false); + } + }; + + initTreeSitter(); + }, [extension, fileName]); + + /** + * Analyze Python code for syntax errors using Tree-sitter + * @param {string} code - Python code to analyze + * @returns {Array} Array of detected problems + */ + const analyzePythonCode = async (code) => { + if (!treeSitterParser || extension !== "py") return []; + + try { + const tree = treeSitterParser.parse(code); + const problems = []; + const handledLines = new Set(); // Track lines we've already handled + let hasMissingColonError = false; // Track if we've found a missing colon error + + // Look for syntax errors by checking for ERROR nodes + const errorQuery = treeSitterParser.language.query(`(ERROR) @error`); + const errorMatches = errorQuery.captures(tree.rootNode); + + for (const match of errorMatches) { + const errorNode = match.node; + const startPos = errorNode.startPosition; + const endPos = errorNode.endPosition; + const errorText = code.slice(errorNode.startIndex, errorNode.endIndex); + + // Get surrounding code for better context + const lineContent = code.split("\n")[startPos.row] || ""; + + // Get context - parent and siblings + const parent = errorNode.parent; + const prevSibling = errorNode.previousSibling; + const nextSibling = errorNode.nextSibling; + + // Default error message + let message = t("editorPanel.pythonSyntaxErrors.default"); + + // Case 1: Check for unrecognized identifiers followed by open parenthesis (undefined function) + if (parent && parent.type === "ERROR" && errorText.match(/\w+\s*\(/)) { + // First, check if this is a function definition without a colon + if (lineContent.trim().startsWith('def ') && !lineContent.includes(':')) { + // This is a function definition missing a colon + message = t("editorPanel.pythonSyntaxErrors.missingColon"); + hasMissingColonError = true; + + // Add this directly to problems with the proper highlighting + problems.push({ + message, + severity: "error", + // Use the entire line for highlighting + from: errorNode.startIndex - startPos.column, // Beginning of the line + to: errorNode.startIndex - startPos.column + lineContent.length, // End of the line + context: { + row: startPos.row, + column: 0, + text: lineContent, + isMissingColon: true + }, + }); + + // Mark this line as handled + handledLines.add(startPos.row); + + // Skip normal error processing + continue; + } + + const funcName = errorText.match(/(\w+)\s*\(/)?.[1]; + if (funcName) { + message = t("editorPanel.pythonSyntaxErrors.unknownFunction", { + name: funcName, + }); + } + } + // New case: Check for missing commas in lists, tuples, or dictionaries + else if ( + // Check if the parent is a list, tuple, dictionary, set or parenthesized expression (which could be a tuple) + parent && + ["list", "tuple", "dictionary", "set", "parenthesized_expression"].includes(parent.type) + ) { + // Check for common missing comma patterns + const prevItem = prevSibling; + const nextItem = nextSibling; + + // If we have a literal or identifier directly followed by another without a comma + if ( + prevItem && + nextItem && + // Check if previous sibling is a value + ["string", "integer", "float", "identifier", "true", "false", "none"].includes(prevItem.type) && + // Check if next sibling is a value or closing bracket + ["string", "integer", "float", "identifier", "true", "false", "none", "]", ")", "}"].includes(nextItem.type) + ) { + // We likely have a missing comma + let containerType = parent.type; + + // Map parenthesized_expression to tuple when it appears to be a tuple + if (containerType === "parenthesized_expression") { + // If there are multiple values inside parentheses, it's likely a tuple + containerType = "tuple"; + } + + const containerName = { + "list": t("editorPanel.pythonSyntaxErrors.containerTypes.list", { defaultValue: "list" }), + "tuple": t("editorPanel.pythonSyntaxErrors.containerTypes.tuple", { defaultValue: "tuple" }), + "dictionary": t("editorPanel.pythonSyntaxErrors.containerTypes.dictionary", { defaultValue: "dictionary" }), + "set": t("editorPanel.pythonSyntaxErrors.containerTypes.set", { defaultValue: "set" }) + }[containerType] || t("editorPanel.pythonSyntaxErrors.containerTypes.collection", { defaultValue: "collection" }); + + message = t("editorPanel.pythonSyntaxErrors.missingComma", { + containerName: containerName, + defaultValue: `Missing comma in ${containerName}` + }); + + // Calculate the exact range to highlight - only include the two items with missing comma between them + const newStartIndex = prevItem.startIndex; + let newEndIndex = nextItem.endIndex; + + // Add this custom error directly + problems.push({ + message, + severity: "error", + from: newStartIndex, + to: newEndIndex, + context: { + row: startPos.row, + column: startPos.column, + text: code.slice(newStartIndex, newEndIndex), + }, + }); + + // Mark this line as handled + handledLines.add(startPos.row); + + // Skip normal error processing + continue; + } + // Special handling for dictionaries with missing comma between key-value pairs + else if (parent.type === "dictionary") { + // Check for missing comma between key-value pairs pattern + // For dict {"key": "value" "key2": "value2"}, the key2 would be the sibling after "value" + if (prevItem && + ["string", "integer", "float", "identifier", "true", "false", "none"].includes(prevItem.type) && + nextItem && + (nextItem.type === "string" || nextItem.type === "identifier")) { + // Missing comma between string value and next key + const containerName = t("editorPanel.pythonSyntaxErrors.containerTypes.dictionary", + { defaultValue: "dictionary" }); + + message = t("editorPanel.pythonSyntaxErrors.missingCommaBetweenPairs", { + containerName: containerName, + defaultValue: `Missing comma between key-value pairs in ${containerName}` + }); + + // Extend the highlighting to include the value and the next key, but not beyond + const newStartIndex = prevItem.startIndex; + const newEndIndex = nextItem.endIndex; + + // Add this custom error directly + problems.push({ + message, + severity: "error", + from: newStartIndex, + to: newEndIndex, + context: { + row: startPos.row, + column: startPos.column, + text: code.slice(newStartIndex, newEndIndex), + }, + }); + + // Mark this line as handled + handledLines.add(startPos.row); + + // Skip normal error processing + continue; + } + // Also check for case when we have a pair followed by another key without comma + else if (prevItem && + prevItem.type === "pair" && + nextItem && + (nextItem.type === "string" || nextItem.type === "identifier")) { + + const containerName = t("editorPanel.pythonSyntaxErrors.containerTypes.dictionary", + { defaultValue: "dictionary" }); + + message = t("editorPanel.pythonSyntaxErrors.missingCommaBetweenPairs", { + containerName: containerName, + defaultValue: `Missing comma between key-value pairs in ${containerName}` + }); + + // Calculate the exact highlighting range + // Try to get just the value part of the previous pair, and the key of the next pair + let newStartIndex = prevItem.startIndex; + const newEndIndex = nextItem.endIndex; + + // Add this custom error directly + problems.push({ + message, + severity: "error", + from: newStartIndex, + to: newEndIndex, + context: { + row: startPos.row, + column: startPos.column, + text: code.slice(newStartIndex, newEndIndex), + }, + }); + + // Mark this line as handled + handledLines.add(startPos.row); + + // Skip normal error processing + continue; + } + } + } + // Case 2: Check for unopened/unclosed parentheses/brackets/braces + else if (errorText.includes("(") && !errorText.includes(")")) { + const hasOpenParen = lineContent.indexOf("(") !== -1; + const hasCloseParen = lineContent.indexOf(")") !== -1; + + if (hasOpenParen && !hasCloseParen) { + message = t( + "editorPanel.pythonSyntaxErrors.missingClosingParenthesis", + ); + } + } else if (errorText.includes(")") && !errorText.includes("(")) { + message = t( + "editorPanel.pythonSyntaxErrors.missingOpeningParenthesis", + ); + } else if (errorText.includes("[") && !errorText.includes("]")) { + message = t("editorPanel.pythonSyntaxErrors.missingClosingBracket"); + } else if (errorText.includes("]") && !errorText.includes("[")) { + message = t("editorPanel.pythonSyntaxErrors.missingOpeningBracket"); + } else if (errorText.includes("{") && !errorText.includes("}")) { + message = t("editorPanel.pythonSyntaxErrors.missingClosingBrace"); + } else if (errorText.includes("}") && !errorText.includes("{")) { + message = t("editorPanel.pythonSyntaxErrors.missingOpeningBrace"); + } + // Case 3: Check for missing decorator symbol + else if ( + prevSibling && + prevSibling.type === "identifier" && + (errorText.trim() === "" || errorText.trim() === ":") + ) { + // Check if this could be a missing decorator symbol + const prevLine = code.split("\n")[startPos.row - 1] || ""; + if (!prevLine.trim().startsWith("@")) { + message = t("editorPanel.pythonSyntaxErrors.missingDecorator"); + } + } + // Case 4: Check for missing colon + else if ( + // Check if this is a control structure missing a colon + lineContent.match(/^\s*(if|for|while|else|elif|def|class)(\s+|$)/) && + !lineContent.includes(":") + ) { + // This is a control structure line without a colon + message = t("editorPanel.pythonSyntaxErrors.missingColon"); + hasMissingColonError = true; + // Make sure we add the correct row number + handledLines.add(startPos.row); + + // Store this error directly to ensure it gets highlighted + problems.push({ + message, + severity: "error", + from: errorNode.startIndex, + to: errorNode.endIndex, + context: { + row: startPos.row, + column: startPos.column, + text: errorText, + isMissingColon: true, // Flag to identify this as a colon error + lineNumber: startPos.row // Store the line number for highlighting + }, + }); + + // Skip the normal error processing for this case + continue; + } + // Original Case 4: Check parent node type for missing colon + else if ( + parent && + [ + "block", + "function_definition", + "class_definition", + "if_statement", + "for_statement", + "while_statement", + ].includes(parent.type) + ) { + const nodeText = code.slice(parent.startIndex, parent.endIndex); + if (!nodeText.includes(":")) { + message = t("editorPanel.pythonSyntaxErrors.missingColon"); + hasMissingColonError = true; + // Get the actual row of the parent statement + const parentLines = code.substring(0, parent.startIndex).split("\n"); + handledLines.add(parentLines.length - 1); + } + } + // Add specific check for function definition with missing colon + else if (errorText.match(/def\s+\w+\s*\([^:]*\)\s*$/)) { + message = t("editorPanel.pythonSyntaxErrors.missingColon"); + hasMissingColonError = true; + handledLines.add(startPos.row); + + // Store this error directly to ensure it gets highlighted + problems.push({ + message, + severity: "error", + from: errorNode.startIndex, + to: errorNode.endIndex, + context: { + row: startPos.row, + column: startPos.column, + text: errorText, + isMissingColon: true, // Flag to identify this as a colon error + lineNumber: startPos.row // Store the line number for highlighting + }, + }); + + // Skip the normal error processing for this case + continue; + } + // Case 5: Check for indentation issues (only if none of the above) + else if ( + (startPos.column === 0 || + (prevSibling && prevSibling.endPosition.row !== startPos.row)) && + !errorText.match(/\w+\s*\(/) && // Not a function call + !errorText.match(/[()[\]{}]/) // Not related to brackets/braces/parentheses + ) { + message = t("editorPanel.pythonSyntaxErrors.indentationIssue"); + } + // Case 6: Check for missing quotes + else if ( + errorText.includes('"') && + errorText.split('"').length % 2 === 0 + ) { + message = t( + "editorPanel.pythonSyntaxErrors.missingClosingDoubleQuote", + ); + } else if ( + errorText.includes("'") && + errorText.split("'").length % 2 === 0 + ) { + message = t( + "editorPanel.pythonSyntaxErrors.missingClosingSingleQuote", + ); + } + // Case 7: Check for potential invalid syntax in function calls + else if ( + parent && + parent.type === "call" && + !message.includes("Missing") + ) { + message = t("editorPanel.pythonSyntaxErrors.functionCallSyntaxError"); + } + // Case 8: Check for potential keyword issues + else if (prevSibling && prevSibling.type === "keyword") { + const keyword = code.slice( + prevSibling.startIndex, + prevSibling.endIndex, + ); + message = t( + "editorPanel.pythonSyntaxErrors.syntaxErrorAfterKeyword", + { keyword }, + ); + } + + // Skip generic syntax errors for lines inside functions with missing colons + // This prevents the extra error highlighting on indented lines within a function + if (hasMissingColonError && + message === t("editorPanel.pythonSyntaxErrors.default") && + // Check if we're at the end of a block that might be caused by a missing colon + errorText.trim().startsWith("\n") && + // Verify if there are any control structures (if, def, etc.) in previous lines + code.split('\n').some(line => + line.match(/^\s*(if|for|while|else|elif|def|class)(\s+|$)/) && !line.includes(':') + )) { + continue; + } + + // Skip duplicate errors on same line (e.g., when missing colon causes multiple errors) + if (handledLines.has(startPos.row)) { + continue; + } + + // Log context for debugging + console.log("Error context:", { + errorText, + lineContent, + parentType: parent?.type, + prevSiblingType: prevSibling?.type, + nextSiblingType: nextSibling?.type, + position: `${startPos.row}:${startPos.column}-${endPos.row}:${endPos.column}`, + message: message, + }); + + problems.push({ + message, + severity: "error", + from: errorNode.startIndex, + to: errorNode.endIndex, + context: { + row: startPos.row, + column: startPos.column, + text: errorText, + }, + }); + + // Mark this line as handled + handledLines.add(startPos.row); + } + + // Log problems to console for debugging + if (problems.length > 0) { + console.log("Python code analysis found problems:", problems); + } else { + console.log("No Python syntax errors detected"); + } + + return problems; + } catch (error) { + console.error("Error analyzing Python code:", error); + return []; + } + }; + + /** + * Create CodeMirror decorations for problem areas + * @param {EditorState} state - Current editor state + * @param {Array} problems - Array of detected problems + * @returns {DecorationSet} Set of decorations to apply to the editor + */ + const createProblemDecorations = (state, problems) => { + const decorations = problems.map((problem) => { + let className = "problem-marker"; + if (problem.severity === "error") { + className += " problem-error"; + } else if (problem.severity === "warning") { + className += " problem-warning"; + } + + // Handle empty error ranges (zero width) + let from = problem.from; + let to = problem.to; + + // Check if this is a multi-line error + const isMultiLineError = + state.doc.lineAt(from).number !== state.doc.lineAt(to).number; + let targetLine; + + if (isMultiLineError) { + // For multi-line errors, try to identify the line with the actual error + + // For missing colons, look for line with def/if/for/while/etc + if (problem.message.includes("Missing colon")) { + // Search through each line in the error range to find a control structure + for ( + let line = state.doc.lineAt(from); + line.number <= state.doc.lineAt(to).number; + line = state.doc.line(line.number + 1) + ) { + const lineContent = line.text; + // Check if this line looks like it should have a colon + if ( + lineContent.match( + /^\s*(if|for|while|else|elif|def|class)(\s+|\()/, + ) + ) { + targetLine = line; + break; + } + } + } + // For missing quotes, look for a line with an odd number of quote characters + else if ( + problem.message.includes("Missing") && + (problem.message.includes("quote") || + problem.message.includes("quotation")) + ) { + // Check each line for unbalanced quotes + for ( + let line = state.doc.lineAt(from); + line.number <= state.doc.lineAt(to).number; + line = state.doc.line(line.number + 1) + ) { + const lineContent = line.text; + // If this line has an odd number of quotes, it's likely the problematic line + const singleQuoteCount = (lineContent.match(/'/g) || []).length; + const doubleQuoteCount = (lineContent.match(/"/g) || []).length; + + if (singleQuoteCount % 2 !== 0 || doubleQuoteCount % 2 !== 0) { + targetLine = line; + break; + } + } + } + // For function definition errors + else if ( + problem.message.includes("function") || + (problem.context?.text && problem.context.text.includes("def ")) + ) { + // Look for the line with 'def' keyword + for ( + let line = state.doc.lineAt(from); + line.number <= state.doc.lineAt(to).number; + line = state.doc.line(line.number + 1) + ) { + if (line.text.match(/^\s*def\s+\w+/)) { + targetLine = line; + break; + } + } + } + + // If we've identified a specific target line, use it + if (targetLine) { + from = targetLine.from; + to = targetLine.from + targetLine.length; + } else { + // Default: use the starting line + const errorLine = state.doc.lineAt(from); + from = errorLine.from; + to = errorLine.from + errorLine.length; + } + } else { + // For single-line errors, use the current line + const errorLine = state.doc.lineAt(from); + targetLine = errorLine; + } + + // If the error is an empty string or just whitespace, highlight a reasonable area + if (from === to || state.doc.sliceString(from, to).trim() === "") { + // For decorator errors, highlight the identifier before + if (problem.message.includes("decorator")) { + from = targetLine.from; + to = targetLine.from + targetLine.length; + } else { + // For other cases, highlight at least one character + to = from + 1; + } + } + + // For indentation errors, highlight the whole line + if (problem.message.includes("Indentation")) { + from = targetLine.from; + to = targetLine.from + targetLine.length; + } + + // For missing colon errors, only highlight the line with the missing colon + if (problem.message.includes("Missing colon")) { + from = targetLine.from; + to = targetLine.from + targetLine.length; + } + + // For missing quote errors, only highlight the current line + if ( + problem.message.includes("Missing") && + (problem.message.includes("quote") || + problem.message.includes("quotation")) + ) { + from = targetLine.from; + to = targetLine.from + targetLine.length; + } + + // Get the line text and adjust the highlighting to exclude comments + const lineText = state.doc.lineAt(from).text; + + // Find comment start position (either # or """) in the line + const hashCommentPos = lineText.indexOf("#"); + const tripleQuotePos = lineText.indexOf('"""'); + + // Get the earlier of the two comment types (if either exists) + let commentPos = -1; + if (hashCommentPos !== -1 && tripleQuotePos !== -1) { + commentPos = Math.min(hashCommentPos, tripleQuotePos); + } else if (hashCommentPos !== -1) { + commentPos = hashCommentPos; + } else if (tripleQuotePos !== -1) { + commentPos = tripleQuotePos; + } + + // If we found a comment in this line, adjust 'to' position to exclude it + if (commentPos !== -1) { + // First, find the first non-whitespace character before the comment + let trimPos = commentPos; + while (trimPos > 0 && /\s/.test(lineText[trimPos - 1])) { + trimPos--; + } + + // Adjust 'to' to exclude comment and preceding whitespace + const lineFrom = state.doc.lineAt(from).from; + const adjustedTo = lineFrom + trimPos; + + // Only adjust if we're not going to make the highlight empty + if (adjustedTo > from) { + to = adjustedTo; + } + } + + // Get line number for tooltip positioning + const lineNumber = state.doc.lineAt(from).number - 1; // Convert to 0-based + + // Create a shorter error type for the data attribute + // Only take the first word, which is usually "Missing", "Indentation", "Syntax", etc. + const errorType = problem.message.split(":")[0].trim().toLowerCase(); + + // Extract just the helpful message part + const friendlyMessage = problem.message; + + // Determine if we need to force tooltip position based on line position + const totalLines = state.doc.lines; + let linePosition = ""; + + // For errors on the last few lines, force tooltips to appear above + if (lineNumber >= totalLines - 3 && lineNumber >= 2) { + linePosition = "top"; + } + // For errors on the top few lines, force tooltips to appear below + else if (lineNumber < 2) { + linePosition = "bottom"; + } + + // Debug logging + console.log( + `Creating error marker: type=${errorType}, message="${friendlyMessage}", position=${linePosition}`, + ); + + return Decoration.mark({ + class: className, + attributes: { + "data-message": friendlyMessage, + "data-error-type": errorType, + "data-line": lineNumber.toString(), + ...(linePosition && { "data-line-position": linePosition }), + }, + }).range(from, to); + }); + + return Decoration.set(decorations, true); + }; + + return { + treeSitterParser, + parserInitialized, + analyzePythonCode, + createProblemDecorations, + problemDecorationsEffect, + }; +}; + +export default useTreeSitterParser; diff --git a/webpack.config.js b/webpack.config.js index 536abd2a4..b7c7b6224 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -86,6 +86,8 @@ module.exports = { fallback: { path: require.resolve("path-browserify"), url: require.resolve("url/"), + fs: false, + module: false, }, }, output: { @@ -124,6 +126,13 @@ module.exports = { ) { res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); } + + // Set MIME type for WASM files + if (req.url.endsWith(".wasm")) { + res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); + res.setHeader("Content-Type", "application/wasm"); + } + next(); }); return middlewares; diff --git a/yarn.lock b/yarn.lock index 9e82a9be5..319a2efaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2898,6 +2898,7 @@ __metadata: url: ^0.11.4 url-loader: 4.1.1 watch: ^1.0.2 + web-tree-sitter: 0.25.9 web-vitals: ^1.0.1 webgl-mock-threejs: ^0.0.1 webpack: 5.95.0 @@ -18786,6 +18787,18 @@ __metadata: languageName: node linkType: hard +"web-tree-sitter@npm:0.25.9": + version: 0.25.9 + resolution: "web-tree-sitter@npm:0.25.9" + peerDependencies: + "@types/emscripten": ^1.40.0 + peerDependenciesMeta: + "@types/emscripten": + optional: true + checksum: e07fc82b876ed89286a68c4f2ba8f5308eb10c677f304c2437df0125180690688a5967279e06e1108b3e413d3fc37cf6a3b772bcdb91ef733e354b97f967b319 + languageName: node + linkType: hard + "web-vitals@npm:^1.0.1": version: 1.1.2 resolution: "web-vitals@npm:1.1.2" From 987c9f6805c416a0526b64c01335d36a1d86058e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=88ivi=20Suomela?= Date: Wed, 1 Oct 2025 12:33:24 +0100 Subject: [PATCH 2/4] simplified --- src/assets/stylesheets/EditorPanel.scss | 39 +- .../Editor/EditorPanel/EditorPanel.jsx | 4 +- src/hooks/useTreeSitterParser.js | 828 ++++++------------ 3 files changed, 290 insertions(+), 581 deletions(-) diff --git a/src/assets/stylesheets/EditorPanel.scss b/src/assets/stylesheets/EditorPanel.scss index b787f779a..1dbf8fa50 100644 --- a/src/assets/stylesheets/EditorPanel.scss +++ b/src/assets/stylesheets/EditorPanel.scss @@ -68,9 +68,10 @@ &::after { content: attr(data-message); - position: fixed; /* Changed from absolute to fixed */ + position: absolute; /* Changed from fixed to absolute for proper positioning */ display: none; - left: auto; /* Reset left positioning */ + top: 100%; /* Position below the marker by default */ + left: 0; /* Align with the left of the marker */ background: #fff; padding: 6px 10px; border: 1px solid #ccc; @@ -85,7 +86,9 @@ color: #333; pointer-events: none; overflow: visible; - max-height: none; /* Remove max-height constraint */ + max-height: none; + border-left: 4px solid #e91e63; + transform: translateY(5px); /* Small offset for better visibility */ } &:hover::after, @@ -93,31 +96,11 @@ display: block; } - /* Position tooltip based on line number and available space */ - &[data-line="0"]::after, - &[data-line="1"]::after, - &[data-line-position="bottom"]::after { - /* For top lines or when explicitly positioned at bottom */ - margin-top: 5px; - } - - &:not([data-line="0"]):not([data-line="1"]):not([data-line-position="bottom"])::after { - /* For other lines, default to above */ - margin-bottom: 5px; - } - - /* Error type-specific styling */ - &[data-error-type^="missing"]::after { - border-left: 4px solid #e91e63; - } - - &[data-error-type^="indentation"]::after { - border-left: 4px solid #2196f3; - } - - &[data-error-type^="syntax"]::after, - &[data-error-type^="unknown"]::after { - border-left: 4px solid #ff4b4b; + /* Position tooltip based on available space */ + &[data-line-position="top"]::after { + top: auto; /* Reset top positioning */ + bottom: 100%; /* Position above instead of below */ + transform: translateY(-5px); /* Offset upwards */ } } diff --git a/src/components/Editor/EditorPanel/EditorPanel.jsx b/src/components/Editor/EditorPanel/EditorPanel.jsx index b9fe483b2..3ab795f3e 100644 --- a/src/components/Editor/EditorPanel/EditorPanel.jsx +++ b/src/components/Editor/EditorPanel/EditorPanel.jsx @@ -48,7 +48,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { parserInitialized, analyzePythonCode, createProblemDecorations, - problemDecorationsEffect + problemDecorationsEffect, } = useTreeSitterParser(extension, fileName); // Create a state field for problem decorations @@ -115,7 +115,6 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { } }); - const getMode = () => { switch (extension) { case "html": @@ -141,7 +140,6 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => { (item) => item.extension === extension && item.name === fileName, ); - useEffect(() => { if (!file) { return; diff --git a/src/hooks/useTreeSitterParser.js b/src/hooks/useTreeSitterParser.js index a02e981fa..0fae0dc03 100644 --- a/src/hooks/useTreeSitterParser.js +++ b/src/hooks/useTreeSitterParser.js @@ -68,408 +68,296 @@ const useTreeSitterParser = (extension, fileName) => { try { const tree = treeSitterParser.parse(code); const problems = []; - const handledLines = new Set(); // Track lines we've already handled - let hasMissingColonError = false; // Track if we've found a missing colon error - - // Look for syntax errors by checking for ERROR nodes - const errorQuery = treeSitterParser.language.query(`(ERROR) @error`); - const errorMatches = errorQuery.captures(tree.rootNode); - - for (const match of errorMatches) { - const errorNode = match.node; - const startPos = errorNode.startPosition; - const endPos = errorNode.endPosition; - const errorText = code.slice(errorNode.startIndex, errorNode.endIndex); - - // Get surrounding code for better context - const lineContent = code.split("\n")[startPos.row] || ""; - - // Get context - parent and siblings - const parent = errorNode.parent; - const prevSibling = errorNode.previousSibling; - const nextSibling = errorNode.nextSibling; - - // Default error message - let message = t("editorPanel.pythonSyntaxErrors.default"); - - // Case 1: Check for unrecognized identifiers followed by open parenthesis (undefined function) - if (parent && parent.type === "ERROR" && errorText.match(/\w+\s*\(/)) { - // First, check if this is a function definition without a colon - if (lineContent.trim().startsWith('def ') && !lineContent.includes(':')) { - // This is a function definition missing a colon - message = t("editorPanel.pythonSyntaxErrors.missingColon"); - hasMissingColonError = true; - - // Add this directly to problems with the proper highlighting - problems.push({ - message, - severity: "error", - // Use the entire line for highlighting - from: errorNode.startIndex - startPos.column, // Beginning of the line - to: errorNode.startIndex - startPos.column + lineContent.length, // End of the line - context: { - row: startPos.row, - column: 0, - text: lineContent, - isMissingColon: true - }, - }); - - // Mark this line as handled - handledLines.add(startPos.row); - - // Skip normal error processing - continue; - } - const funcName = errorText.match(/(\w+)\s*\(/)?.[1]; - if (funcName) { - message = t("editorPanel.pythonSyntaxErrors.unknownFunction", { - name: funcName, - }); - } - } - // New case: Check for missing commas in lists, tuples, or dictionaries - else if ( - // Check if the parent is a list, tuple, dictionary, set or parenthesized expression (which could be a tuple) + // Create a query to find all ERROR and MISSING nodes directly + const queryString = ` + (ERROR) @error + `; + + const query = treeSitterParser.language.query(queryString); + const matches = query.matches(tree.rootNode); + + console.log(`Found ${matches.length} potential syntax errors via query`); + + // Process each matched error node + for (const match of matches) { + const node = match.captures[0].node; + const nodeType = node.type; + const startPos = node.startPosition; + const endPos = node.endPosition; + + // Skip nested errors at the same position + const parent = node.parent; + if ( parent && - ["list", "tuple", "dictionary", "set", "parenthesized_expression"].includes(parent.type) + (parent.type === "ERROR" || parent.type === "MISSING") && + parent.startPosition.row === startPos.row && + parent.startPosition.column === startPos.column && + parent.endPosition.row === endPos.row && + parent.endPosition.column === endPos.column ) { - // Check for common missing comma patterns - const prevItem = prevSibling; - const nextItem = nextSibling; - - // If we have a literal or identifier directly followed by another without a comma - if ( - prevItem && - nextItem && - // Check if previous sibling is a value - ["string", "integer", "float", "identifier", "true", "false", "none"].includes(prevItem.type) && - // Check if next sibling is a value or closing bracket - ["string", "integer", "float", "identifier", "true", "false", "none", "]", ")", "}"].includes(nextItem.type) - ) { - // We likely have a missing comma - let containerType = parent.type; - - // Map parenthesized_expression to tuple when it appears to be a tuple - if (containerType === "parenthesized_expression") { - // If there are multiple values inside parentheses, it's likely a tuple - containerType = "tuple"; - } - - const containerName = { - "list": t("editorPanel.pythonSyntaxErrors.containerTypes.list", { defaultValue: "list" }), - "tuple": t("editorPanel.pythonSyntaxErrors.containerTypes.tuple", { defaultValue: "tuple" }), - "dictionary": t("editorPanel.pythonSyntaxErrors.containerTypes.dictionary", { defaultValue: "dictionary" }), - "set": t("editorPanel.pythonSyntaxErrors.containerTypes.set", { defaultValue: "set" }) - }[containerType] || t("editorPanel.pythonSyntaxErrors.containerTypes.collection", { defaultValue: "collection" }); - - message = t("editorPanel.pythonSyntaxErrors.missingComma", { - containerName: containerName, - defaultValue: `Missing comma in ${containerName}` - }); - - // Calculate the exact range to highlight - only include the two items with missing comma between them - const newStartIndex = prevItem.startIndex; - let newEndIndex = nextItem.endIndex; - - // Add this custom error directly - problems.push({ - message, - severity: "error", - from: newStartIndex, - to: newEndIndex, - context: { - row: startPos.row, - column: startPos.column, - text: code.slice(newStartIndex, newEndIndex), - }, - }); - - // Mark this line as handled - handledLines.add(startPos.row); - - // Skip normal error processing - continue; - } - // Special handling for dictionaries with missing comma between key-value pairs - else if (parent.type === "dictionary") { - // Check for missing comma between key-value pairs pattern - // For dict {"key": "value" "key2": "value2"}, the key2 would be the sibling after "value" - if (prevItem && - ["string", "integer", "float", "identifier", "true", "false", "none"].includes(prevItem.type) && - nextItem && - (nextItem.type === "string" || nextItem.type === "identifier")) { - // Missing comma between string value and next key - const containerName = t("editorPanel.pythonSyntaxErrors.containerTypes.dictionary", - { defaultValue: "dictionary" }); - - message = t("editorPanel.pythonSyntaxErrors.missingCommaBetweenPairs", { - containerName: containerName, - defaultValue: `Missing comma between key-value pairs in ${containerName}` - }); - - // Extend the highlighting to include the value and the next key, but not beyond - const newStartIndex = prevItem.startIndex; - const newEndIndex = nextItem.endIndex; - - // Add this custom error directly - problems.push({ - message, - severity: "error", - from: newStartIndex, - to: newEndIndex, - context: { - row: startPos.row, - column: startPos.column, - text: code.slice(newStartIndex, newEndIndex), - }, - }); - - // Mark this line as handled - handledLines.add(startPos.row); - - // Skip normal error processing - continue; - } - // Also check for case when we have a pair followed by another key without comma - else if (prevItem && - prevItem.type === "pair" && - nextItem && - (nextItem.type === "string" || nextItem.type === "identifier")) { - - const containerName = t("editorPanel.pythonSyntaxErrors.containerTypes.dictionary", - { defaultValue: "dictionary" }); - - message = t("editorPanel.pythonSyntaxErrors.missingCommaBetweenPairs", { - containerName: containerName, - defaultValue: `Missing comma between key-value pairs in ${containerName}` - }); - - // Calculate the exact highlighting range - // Try to get just the value part of the previous pair, and the key of the next pair - let newStartIndex = prevItem.startIndex; - const newEndIndex = nextItem.endIndex; - - // Add this custom error directly - problems.push({ - message, - severity: "error", - from: newStartIndex, - to: newEndIndex, - context: { - row: startPos.row, - column: startPos.column, - text: code.slice(newStartIndex, newEndIndex), - }, - }); - - // Mark this line as handled - handledLines.add(startPos.row); - - // Skip normal error processing - continue; - } - } + console.log(`Skipping nested ${nodeType} at same position`, startPos); + continue; } - // Case 2: Check for unopened/unclosed parentheses/brackets/braces - else if (errorText.includes("(") && !errorText.includes(")")) { - const hasOpenParen = lineContent.indexOf("(") !== -1; - const hasCloseParen = lineContent.indexOf(")") !== -1; - - if (hasOpenParen && !hasCloseParen) { - message = t( - "editorPanel.pythonSyntaxErrors.missingClosingParenthesis", - ); - } - } else if (errorText.includes(")") && !errorText.includes("(")) { - message = t( - "editorPanel.pythonSyntaxErrors.missingOpeningParenthesis", + + // Clamp large error ranges to just the current line plus one + let clampedEndPos = { ...endPos }; + if (endPos.row > startPos.row) { + clampedEndPos = { + row: startPos.row + 1, + column: 0, + }; + console.log( + `Clamping large error range from line ${startPos.row} to ${endPos.row}, now ends at ${clampedEndPos.row}`, ); - } else if (errorText.includes("[") && !errorText.includes("]")) { - message = t("editorPanel.pythonSyntaxErrors.missingClosingBracket"); - } else if (errorText.includes("]") && !errorText.includes("[")) { - message = t("editorPanel.pythonSyntaxErrors.missingOpeningBracket"); - } else if (errorText.includes("{") && !errorText.includes("}")) { - message = t("editorPanel.pythonSyntaxErrors.missingClosingBrace"); - } else if (errorText.includes("}") && !errorText.includes("{")) { - message = t("editorPanel.pythonSyntaxErrors.missingOpeningBrace"); } - // Case 3: Check for missing decorator symbol - else if ( - prevSibling && - prevSibling.type === "identifier" && - (errorText.trim() === "" || errorText.trim() === ":") - ) { - // Check if this could be a missing decorator symbol - const prevLine = code.split("\n")[startPos.row - 1] || ""; - if (!prevLine.trim().startsWith("@")) { - message = t("editorPanel.pythonSyntaxErrors.missingDecorator"); - } - } - // Case 4: Check for missing colon - else if ( - // Check if this is a control structure missing a colon - lineContent.match(/^\s*(if|for|while|else|elif|def|class)(\s+|$)/) && - !lineContent.includes(":") - ) { - // This is a control structure line without a colon - message = t("editorPanel.pythonSyntaxErrors.missingColon"); - hasMissingColonError = true; - // Make sure we add the correct row number - handledLines.add(startPos.row); - - // Store this error directly to ensure it gets highlighted - problems.push({ - message, - severity: "error", - from: errorNode.startIndex, - to: errorNode.endIndex, - context: { - row: startPos.row, - column: startPos.column, - text: errorText, - isMissingColon: true, // Flag to identify this as a colon error - lineNumber: startPos.row // Store the line number for highlighting - }, - }); - // Skip the normal error processing for this case - continue; + // Calculate character offsets for CodeMirror + const lines = code.split("\n"); + let fromOffset = 0; + for (let i = 0; i < startPos.row; i++) { + fromOffset += lines[i].length + 1; // +1 for newline } - // Original Case 4: Check parent node type for missing colon - else if ( - parent && - [ - "block", - "function_definition", - "class_definition", - "if_statement", - "for_statement", - "while_statement", - ].includes(parent.type) + fromOffset += startPos.column; + + let toOffset = fromOffset; // Default if same position + if ( + startPos.row === clampedEndPos.row && + startPos.column === clampedEndPos.column ) { - const nodeText = code.slice(parent.startIndex, parent.endIndex); - if (!nodeText.includes(":")) { - message = t("editorPanel.pythonSyntaxErrors.missingColon"); - hasMissingColonError = true; - // Get the actual row of the parent statement - const parentLines = code.substring(0, parent.startIndex).split("\n"); - handledLines.add(parentLines.length - 1); + // Zero-width error, extend by one character for visibility + toOffset = fromOffset + 1; + } else { + // Calculate end offset based on clamped position + toOffset = 0; + for (let i = 0; i < clampedEndPos.row; i++) { + toOffset += lines[i].length + 1; } + toOffset += clampedEndPos.column; } - // Add specific check for function definition with missing colon - else if (errorText.match(/def\s+\w+\s*\([^:]*\)\s*$/)) { - message = t("editorPanel.pythonSyntaxErrors.missingColon"); - hasMissingColonError = true; - handledLines.add(startPos.row); - - // Store this error directly to ensure it gets highlighted - problems.push({ - message, - severity: "error", - from: errorNode.startIndex, - to: errorNode.endIndex, - context: { - row: startPos.row, - column: startPos.column, - text: errorText, - isMissingColon: true, // Flag to identify this as a colon error - lineNumber: startPos.row // Store the line number for highlighting - }, - }); - // Skip the normal error processing for this case - continue; - } - // Case 5: Check for indentation issues (only if none of the above) - else if ( - (startPos.column === 0 || - (prevSibling && prevSibling.endPosition.row !== startPos.row)) && - !errorText.match(/\w+\s*\(/) && // Not a function call - !errorText.match(/[()[\]{}]/) // Not related to brackets/braces/parentheses - ) { - message = t("editorPanel.pythonSyntaxErrors.indentationIssue"); - } - // Case 6: Check for missing quotes - else if ( - errorText.includes('"') && - errorText.split('"').length % 2 === 0 - ) { - message = t( - "editorPanel.pythonSyntaxErrors.missingClosingDoubleQuote", + // Create a problem with a more helpful error message based on context + const getHelpfulErrorMessage = () => { + // Get the error line text + const errorLine = lines[startPos.row] || ""; + const nextLine = lines[startPos.row + 1] || ""; + + // Use a much narrower context for error analysis - focus only on the current line and a bit of context + // This helps prevent other errors in the file from contaminating the analysis + const lineErrorContext = errorLine; + + // More focused context that still allows us to detect multi-line issues + // Just a few characters before and after the error position + const narrowErrorContext = code.slice( + Math.max(0, fromOffset - 10), + Math.min(code.length, toOffset + 10), ); - } else if ( - errorText.includes("'") && - errorText.split("'").length % 2 === 0 - ) { - message = t( - "editorPanel.pythonSyntaxErrors.missingClosingSingleQuote", - ); - } - // Case 7: Check for potential invalid syntax in function calls - else if ( - parent && - parent.type === "call" && - !message.includes("Missing") - ) { - message = t("editorPanel.pythonSyntaxErrors.functionCallSyntaxError"); - } - // Case 8: Check for potential keyword issues - else if (prevSibling && prevSibling.type === "keyword") { - const keyword = code.slice( - prevSibling.startIndex, - prevSibling.endIndex, - ); - message = t( - "editorPanel.pythonSyntaxErrors.syntaxErrorAfterKeyword", - { keyword }, - ); - } - // Skip generic syntax errors for lines inside functions with missing colons - // This prevents the extra error highlighting on indented lines within a function - if (hasMissingColonError && - message === t("editorPanel.pythonSyntaxErrors.default") && - // Check if we're at the end of a block that might be caused by a missing colon - errorText.trim().startsWith("\n") && - // Verify if there are any control structures (if, def, etc.) in previous lines - code.split('\n').some(line => - line.match(/^\s*(if|for|while|else|elif|def|class)(\s+|$)/) && !line.includes(':') - )) { - continue; - } + console.log("Error detection context:", { + line: startPos.row + 1, + errorLine, + narrowContext: narrowErrorContext, + }); - // Skip duplicate errors on same line (e.g., when missing colon causes multiple errors) - if (handledLines.has(startPos.row)) { - continue; - } + // Check for common Python syntax errors with more precise contexts - // Log context for debugging - console.log("Error context:", { - errorText, - lineContent, - parentType: parent?.type, - prevSiblingType: prevSibling?.type, - nextSiblingType: nextSibling?.type, - position: `${startPos.row}:${startPos.column}-${endPos.row}:${endPos.column}`, - message: message, - }); + // 0. Missing operator between values (takes precedence over other checks) + // This matches patterns like "x = 5 6" or "y = a b" where an operator is missing + if ( + /=\s*\w+\s+\d+/.test(lineErrorContext) || + /=\s*\d+\s+\w+/.test(lineErrorContext) || + /=\s*\d+\s+\d+/.test(lineErrorContext) || + /=\s*\w+\s+\w+/.test(lineErrorContext) || + /\w+\s+\d+\s*$/.test(lineErrorContext.trim()) || + /\d+\s+\w+\s*$/.test(lineErrorContext.trim()) || + /\d+\s+\d+\s*$/.test(lineErrorContext.trim()) + ) { + // Check if the error is at the position of the missing operator + return "Missing operator between values (did you mean +, -, *, /, etc.?)"; + } + + // 1. Missing colons - only check the current line and be more specific about patterns + if ( + /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b[^:]*$/.test( + errorLine.trim(), + ) || + /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b.*[^:][\s]*$/.test( + errorLine.trim(), + ) + ) { + // Determine which statement is missing the colon + const match = errorLine.match( + /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b/, + ); + const statement = match ? match[1] : "statement"; + return `Missing colon : after ${statement} statement`; + } + + // 2. Unclosed strings - only check within the current line + const doubleQuotes = (lineErrorContext.match(/"/g) || []).length; + const singleQuotes = (lineErrorContext.match(/'/g) || []).length; + + if (doubleQuotes % 2 !== 0) { + return 'Unclosed string - missing double quote "'; + } + if (singleQuotes % 2 !== 0) { + return "Unclosed string - missing single quote '"; + } + + // 3. Unbalanced parentheses/brackets - use narrow context + // Focus on immediate context around the error to prevent false positives + const openParens = (narrowErrorContext.match(/\(/g) || []).length; + const closeParens = (narrowErrorContext.match(/\)/g) || []).length; + const openBrackets = (narrowErrorContext.match(/\[/g) || []).length; + const closeBrackets = (narrowErrorContext.match(/]/g) || []).length; + const openBraces = (narrowErrorContext.match(/\{/g) || []).length; + const closeBraces = (narrowErrorContext.match(/}/g) || []).length; + + // Check if the error is at the line level with unbalanced brackets + const lineOpenParens = (lineErrorContext.match(/\(/g) || []).length; + const lineCloseParens = (lineErrorContext.match(/\)/g) || []).length; + const lineOpenBrackets = (lineErrorContext.match(/\[/g) || []).length; + const lineCloseBrackets = (lineErrorContext.match(/\]/g) || []) + .length; + const lineOpenBraces = (lineErrorContext.match(/\{/g) || []).length; + const lineCloseBraces = (lineErrorContext.match(/\}/g) || []).length; + + // Check line level first, then narrow context + if (lineOpenParens > lineCloseParens) { + return "Missing closing parenthesis ) in this line"; + } else if (lineOpenParens < lineCloseParens) { + return "Unexpected closing parenthesis ) in this line, did you mean to open one with ("; + } else if (openParens > closeParens) { + return "Missing closing parenthesis )'"; + } else if (openParens < closeParens) { + return "Unexpected closing parenthesis ), did you mean to open one with ("; + } + + if (lineOpenBrackets > lineCloseBrackets) { + return "Missing closing bracket ']' in this line"; + } else if (lineOpenBrackets < lineCloseBrackets) { + return "Unexpected closing bracket ']' in this line"; + } else if (openBrackets > closeBrackets) { + return "Missing closing bracket ']'"; + } else if (openBrackets < closeBrackets) { + return "Unexpected closing bracket ']'"; + } + + if (lineOpenBraces > lineCloseBraces) { + return "Missing closing brace '}' in this line"; + } else if (lineOpenBraces < lineCloseBraces) { + return "Unexpected closing brace '}' in this line"; + } else if (openBraces > closeBraces) { + return "Missing closing brace '}'"; + } else if (openBraces < closeBraces) { + return "Unexpected closing brace '}'"; + } + + // 4. Indentation errors - only compare with immediate next line + if ( + errorLine.trimStart() !== errorLine && + nextLine.length > 0 && + errorLine.length - errorLine.trimStart().length !== + nextLine.length - nextLine.trimStart().length && + // Only flag if this looks like an actual code block (not just random indentation) + (errorLine.trim().endsWith(":") || + nextLine.trim().startsWith("return") || + nextLine.trim().startsWith("if")) + ) { + return "Inconsistent indentation"; + } + + // 5. Missing commas in collections - limit to current line + if (/\[[^\]]*\w+\s+\w+[^\]]*\]/.test(lineErrorContext)) { + return "Missing comma between list items"; + } + if (/\{[^}]*\w+\s+\w+[^}]*\}/.test(lineErrorContext)) { + return "Missing comma between dictionary items"; + } + + // 6. Invalid Python syntax - using = instead of == for comparison + if (/\s*if\s+\w+\s*=\s*\w+/.test(lineErrorContext)) { + return "Using = for comparison instead of == in condition"; + } + + // 7. Invalid assignment + if (/^\s*\d+\s*=/.test(lineErrorContext)) { + return "Cannot assign to a literal (number)"; + } + + // 8. End with backslash + if (errorLine.trim().endsWith("\\")) { + return "Line ending with backslash \\ - did you mean to continue the line?"; + } + + // 9. Check for print statements with errors (like missing parentheses or quotes) + if (/^\s*print\s+[^(]/.test(lineErrorContext)) { + return "print() function requires parentheses"; + } + + if ( + /print\([^'"]*[^'")\s]\)$/.test(lineErrorContext) || + /print\([^"]*"[^"]*\)$/.test(lineErrorContext) || + /print\([^']*'[^']*\)$/.test(lineErrorContext) + ) { + return "Unclosed string in print statement"; + } + + // Default to a more specific message when possible + if (errorLine.trim().startsWith("def ")) { + return "Syntax error in function definition - check for missing colon (:)"; + } + + if (errorLine.trim().startsWith("class ")) { + return "Syntax error in class definition - check for missing colon (:)"; + } + + if ( + errorLine.trim().startsWith("if ") || + errorLine.trim().startsWith("elif ") || + errorLine.trim().startsWith("else") + ) { + return "Syntax error in conditional statement - check for missing colon (:)"; + } + + if ( + errorLine.trim().startsWith("for ") || + errorLine.trim().startsWith("while ") + ) { + return "Syntax error in loop statement - check for missing colon (:)"; + } + + // Default to a generic error message if we can't identify the specific issue + return "Syntax error in Python code"; + }; + + const errorMessage = getHelpfulErrorMessage(); problems.push({ - message, + message: errorMessage, severity: "error", - from: errorNode.startIndex, - to: errorNode.endIndex, + from: fromOffset, + to: toOffset, context: { row: startPos.row, column: startPos.column, - text: errorText, + text: code.slice(fromOffset, toOffset), + nodeType: nodeType, }, }); - // Mark this line as handled - handledLines.add(startPos.row); + console.log( + `Found ${nodeType} at line ${startPos.row + 1}, column ${ + startPos.column + }`, + { + nodeType: nodeType, + parent: parent?.type, + text: code.slice(fromOffset, toOffset), + fromOffset, + toOffset, + }, + ); } // Log problems to console for debugging @@ -494,205 +382,45 @@ const useTreeSitterParser = (extension, fileName) => { */ const createProblemDecorations = (state, problems) => { const decorations = problems.map((problem) => { + // Create simple class based on severity let className = "problem-marker"; + console.log("Creating decoration for problem:", problem); if (problem.severity === "error") { className += " problem-error"; } else if (problem.severity === "warning") { className += " problem-warning"; } - // Handle empty error ranges (zero width) - let from = problem.from; - let to = problem.to; - - // Check if this is a multi-line error - const isMultiLineError = - state.doc.lineAt(from).number !== state.doc.lineAt(to).number; - let targetLine; - - if (isMultiLineError) { - // For multi-line errors, try to identify the line with the actual error - - // For missing colons, look for line with def/if/for/while/etc - if (problem.message.includes("Missing colon")) { - // Search through each line in the error range to find a control structure - for ( - let line = state.doc.lineAt(from); - line.number <= state.doc.lineAt(to).number; - line = state.doc.line(line.number + 1) - ) { - const lineContent = line.text; - // Check if this line looks like it should have a colon - if ( - lineContent.match( - /^\s*(if|for|while|else|elif|def|class)(\s+|\()/, - ) - ) { - targetLine = line; - break; - } - } - } - // For missing quotes, look for a line with an odd number of quote characters - else if ( - problem.message.includes("Missing") && - (problem.message.includes("quote") || - problem.message.includes("quotation")) - ) { - // Check each line for unbalanced quotes - for ( - let line = state.doc.lineAt(from); - line.number <= state.doc.lineAt(to).number; - line = state.doc.line(line.number + 1) - ) { - const lineContent = line.text; - // If this line has an odd number of quotes, it's likely the problematic line - const singleQuoteCount = (lineContent.match(/'/g) || []).length; - const doubleQuoteCount = (lineContent.match(/"/g) || []).length; - - if (singleQuoteCount % 2 !== 0 || doubleQuoteCount % 2 !== 0) { - targetLine = line; - break; - } - } - } - // For function definition errors - else if ( - problem.message.includes("function") || - (problem.context?.text && problem.context.text.includes("def ")) - ) { - // Look for the line with 'def' keyword - for ( - let line = state.doc.lineAt(from); - line.number <= state.doc.lineAt(to).number; - line = state.doc.line(line.number + 1) - ) { - if (line.text.match(/^\s*def\s+\w+/)) { - targetLine = line; - break; - } - } - } - - // If we've identified a specific target line, use it - if (targetLine) { - from = targetLine.from; - to = targetLine.from + targetLine.length; - } else { - // Default: use the starting line - const errorLine = state.doc.lineAt(from); - from = errorLine.from; - to = errorLine.from + errorLine.length; - } - } else { - // For single-line errors, use the current line - const errorLine = state.doc.lineAt(from); - targetLine = errorLine; - } - - // If the error is an empty string or just whitespace, highlight a reasonable area - if (from === to || state.doc.sliceString(from, to).trim() === "") { - // For decorator errors, highlight the identifier before - if (problem.message.includes("decorator")) { - from = targetLine.from; - to = targetLine.from + targetLine.length; - } else { - // For other cases, highlight at least one character - to = from + 1; - } - } - - // For indentation errors, highlight the whole line - if (problem.message.includes("Indentation")) { - from = targetLine.from; - to = targetLine.from + targetLine.length; - } - - // For missing colon errors, only highlight the line with the missing colon - if (problem.message.includes("Missing colon")) { - from = targetLine.from; - to = targetLine.from + targetLine.length; - } - - // For missing quote errors, only highlight the current line - if ( - problem.message.includes("Missing") && - (problem.message.includes("quote") || - problem.message.includes("quotation")) - ) { - from = targetLine.from; - to = targetLine.from + targetLine.length; - } - - // Get the line text and adjust the highlighting to exclude comments - const lineText = state.doc.lineAt(from).text; - - // Find comment start position (either # or """) in the line - const hashCommentPos = lineText.indexOf("#"); - const tripleQuotePos = lineText.indexOf('"""'); - - // Get the earlier of the two comment types (if either exists) - let commentPos = -1; - if (hashCommentPos !== -1 && tripleQuotePos !== -1) { - commentPos = Math.min(hashCommentPos, tripleQuotePos); - } else if (hashCommentPos !== -1) { - commentPos = hashCommentPos; - } else if (tripleQuotePos !== -1) { - commentPos = tripleQuotePos; - } - - // If we found a comment in this line, adjust 'to' position to exclude it - if (commentPos !== -1) { - // First, find the first non-whitespace character before the comment - let trimPos = commentPos; - while (trimPos > 0 && /\s/.test(lineText[trimPos - 1])) { - trimPos--; - } - - // Adjust 'to' to exclude comment and preceding whitespace - const lineFrom = state.doc.lineAt(from).from; - const adjustedTo = lineFrom + trimPos; - - // Only adjust if we're not going to make the highlight empty - if (adjustedTo > from) { - to = adjustedTo; - } - } + // Use the problem ranges directly + const from = problem.from; + const to = problem.to; // Get line number for tooltip positioning const lineNumber = state.doc.lineAt(from).number - 1; // Convert to 0-based - // Create a shorter error type for the data attribute - // Only take the first word, which is usually "Missing", "Indentation", "Syntax", etc. - const errorType = problem.message.split(":")[0].trim().toLowerCase(); + // Simplify error type to always get the red border + const errorType = "missing-syntax"; // This will always trigger the red left border - // Extract just the helpful message part + // Use the problem message directly const friendlyMessage = problem.message; - // Determine if we need to force tooltip position based on line position + // Basic position logic for tooltips const totalLines = state.doc.lines; let linePosition = ""; - - // For errors on the last few lines, force tooltips to appear above - if (lineNumber >= totalLines - 3 && lineNumber >= 2) { + if (lineNumber >= totalLines - 3) { linePosition = "top"; - } - // For errors on the top few lines, force tooltips to appear below - else if (lineNumber < 2) { + } else if (lineNumber < 2) { linePosition = "bottom"; } - // Debug logging - console.log( - `Creating error marker: type=${errorType}, message="${friendlyMessage}", position=${linePosition}`, - ); - + // Create decoration with consistent format return Decoration.mark({ class: className, attributes: { "data-message": friendlyMessage, "data-error-type": errorType, "data-line": lineNumber.toString(), + "data-severity": problem.severity || "error", ...(linePosition && { "data-line-position": linePosition }), }, }).range(from, to); From 1dba3f43a58b42f12737ae72ebf8917c24ea8304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=88ivi=20Suomela?= Date: Wed, 1 Oct 2025 17:21:29 +0100 Subject: [PATCH 3/4] kinda works --- src/hooks/useTreeSitterParser.js | 534 ++++++++++++++++++------------- 1 file changed, 308 insertions(+), 226 deletions(-) diff --git a/src/hooks/useTreeSitterParser.js b/src/hooks/useTreeSitterParser.js index 0fae0dc03..ff7276c25 100644 --- a/src/hooks/useTreeSitterParser.js +++ b/src/hooks/useTreeSitterParser.js @@ -13,7 +13,7 @@ import { useTranslation } from "react-i18next"; const useTreeSitterParser = (extension, fileName) => { const [treeSitterParser, setTreeSitterParser] = useState(null); const [parserInitialized, setParserInitialized] = useState(false); - const { t } = useTranslation(); + const { t } = useTranslation(); // TODO: use for localization of error messages // Create effect for problem decorations const problemDecorationsEffect = StateEffect.define(); @@ -57,6 +57,104 @@ const useTreeSitterParser = (extension, fileName) => { initTreeSitter(); }, [extension, fileName]); + function calculateOffsets(lines, startPos, clampedEndPos, errorMessage) { + let errorLine = lines[startPos.row] || ""; + const commentIndex = errorLine.indexOf("#"); + if (commentIndex >= 0) { + errorLine = errorLine.substring(0, commentIndex); + } + + let fromOffset = 0; + for (let i = 0; i < startPos.row; i++) { + fromOffset += lines[i].length + 1; // +1 for newline + } + fromOffset += startPos.column; + + let toOffset = fromOffset; // Default if same position + if ( + startPos.row === clampedEndPos.row && + startPos.column === clampedEndPos.column + ) { + // Zero-width error, extend by one character for visibility + toOffset = fromOffset + 1; + } else { + // Calculate end offset based on clamped position + toOffset = 0; + for (let i = 0; i < clampedEndPos.row; i++) { + toOffset += lines[i].length + 1; + } + toOffset += clampedEndPos.column; + } + + // Default to using the original range + let adjustedFromOffset = fromOffset; + let adjustedToOffset = toOffset; + + // Special handling for "Missing comma between items" errors + if (errorMessage.startsWith("Missing comma between")) { + try { + // Find the pattern of two items with whitespace between them (where comma should be) + let missingCommaRegex; + if (errorMessage.includes("list")) { + missingCommaRegex = /(\w+)\s+(\w+)/g; + } else if (errorMessage.includes("dictionary")) { + missingCommaRegex = /(\w+:\s*\w+)\s+(\w+)/g; + } else { + missingCommaRegex = /(\w+)\s+(\w+)/g; + } + + // Find all matches - we want to find the specific occurrence that's causing the error + const matches = [...errorLine.matchAll(missingCommaRegex)]; + + if (matches.length > 0) { + // Find match closest to error position + let bestMatch = matches[0]; + let bestDistance = Infinity; + + for (const match of matches) { + const matchStart = errorLine.indexOf(match[0]); + const distance = Math.abs(matchStart - startPos.column); + + if (distance < bestDistance) { + bestDistance = distance; + bestMatch = match; + } + } + + // Calculate the position of this match in the original line + const matchStart = errorLine.indexOf(bestMatch[0]); + const matchEnd = matchStart + bestMatch[0].length; + + // Adjust the offsets to highlight just the items and whitespace between them + const lineStartOffset = fromOffset - startPos.column; // Start of the line + adjustedFromOffset = lineStartOffset + matchStart; + adjustedToOffset = lineStartOffset + matchEnd; + + console.log("Adjusted missing comma range:", { + original: { from: fromOffset, to: toOffset }, + adjusted: { from: adjustedFromOffset, to: adjustedToOffset }, + match: bestMatch[0], + }); + } + } catch (e) { + console.error("Error processing missing comma pattern:", e); + // Fall back to default highlighting if regex fails + } + } + + // If the error occurs in a line with comments, make sure we only highlight the code portion + if (commentIndex >= 0) { + const lineStartOffset = fromOffset - startPos.column; // Start of the line + const commentStartOffset = lineStartOffset + commentIndex; + + // Don't extend highlighting into the comment + if (adjustedToOffset > commentStartOffset) { + adjustedToOffset = commentStartOffset; + } + } + return { fromOffset, toOffset, adjustedFromOffset, adjustedToOffset }; + } + /** * Analyze Python code for syntax errors using Tree-sitter * @param {string} code - Python code to analyze @@ -69,7 +167,7 @@ const useTreeSitterParser = (extension, fileName) => { const tree = treeSitterParser.parse(code); const problems = []; - // Create a query to find all ERROR and MISSING nodes directly + // Create a query to find all ERROR nodes directly const queryString = ` (ERROR) @error `; @@ -90,7 +188,7 @@ const useTreeSitterParser = (extension, fileName) => { const parent = node.parent; if ( parent && - (parent.type === "ERROR" || parent.type === "MISSING") && + parent.type === "ERROR" && parent.startPosition.row === startPos.row && parent.startPosition.column === startPos.column && parent.endPosition.row === endPos.row && @@ -112,236 +210,23 @@ const useTreeSitterParser = (extension, fileName) => { ); } - // Calculate character offsets for CodeMirror const lines = code.split("\n"); - let fromOffset = 0; - for (let i = 0; i < startPos.row; i++) { - fromOffset += lines[i].length + 1; // +1 for newline - } - fromOffset += startPos.column; - - let toOffset = fromOffset; // Default if same position - if ( - startPos.row === clampedEndPos.row && - startPos.column === clampedEndPos.column - ) { - // Zero-width error, extend by one character for visibility - toOffset = fromOffset + 1; - } else { - // Calculate end offset based on clamped position - toOffset = 0; - for (let i = 0; i < clampedEndPos.row; i++) { - toOffset += lines[i].length + 1; - } - toOffset += clampedEndPos.column; - } - - // Create a problem with a more helpful error message based on context - const getHelpfulErrorMessage = () => { - // Get the error line text - const errorLine = lines[startPos.row] || ""; - const nextLine = lines[startPos.row + 1] || ""; - - // Use a much narrower context for error analysis - focus only on the current line and a bit of context - // This helps prevent other errors in the file from contaminating the analysis - const lineErrorContext = errorLine; - - // More focused context that still allows us to detect multi-line issues - // Just a few characters before and after the error position - const narrowErrorContext = code.slice( - Math.max(0, fromOffset - 10), - Math.min(code.length, toOffset + 10), - ); - - console.log("Error detection context:", { - line: startPos.row + 1, - errorLine, - narrowContext: narrowErrorContext, - }); - - // Check for common Python syntax errors with more precise contexts - - // 0. Missing operator between values (takes precedence over other checks) - // This matches patterns like "x = 5 6" or "y = a b" where an operator is missing - if ( - /=\s*\w+\s+\d+/.test(lineErrorContext) || - /=\s*\d+\s+\w+/.test(lineErrorContext) || - /=\s*\d+\s+\d+/.test(lineErrorContext) || - /=\s*\w+\s+\w+/.test(lineErrorContext) || - /\w+\s+\d+\s*$/.test(lineErrorContext.trim()) || - /\d+\s+\w+\s*$/.test(lineErrorContext.trim()) || - /\d+\s+\d+\s*$/.test(lineErrorContext.trim()) - ) { - // Check if the error is at the position of the missing operator - return "Missing operator between values (did you mean +, -, *, /, etc.?)"; - } - - // 1. Missing colons - only check the current line and be more specific about patterns - if ( - /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b[^:]*$/.test( - errorLine.trim(), - ) || - /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b.*[^:][\s]*$/.test( - errorLine.trim(), - ) - ) { - // Determine which statement is missing the colon - const match = errorLine.match( - /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b/, - ); - const statement = match ? match[1] : "statement"; - return `Missing colon : after ${statement} statement`; - } - - // 2. Unclosed strings - only check within the current line - const doubleQuotes = (lineErrorContext.match(/"/g) || []).length; - const singleQuotes = (lineErrorContext.match(/'/g) || []).length; - - if (doubleQuotes % 2 !== 0) { - return 'Unclosed string - missing double quote "'; - } - if (singleQuotes % 2 !== 0) { - return "Unclosed string - missing single quote '"; - } - - // 3. Unbalanced parentheses/brackets - use narrow context - // Focus on immediate context around the error to prevent false positives - const openParens = (narrowErrorContext.match(/\(/g) || []).length; - const closeParens = (narrowErrorContext.match(/\)/g) || []).length; - const openBrackets = (narrowErrorContext.match(/\[/g) || []).length; - const closeBrackets = (narrowErrorContext.match(/]/g) || []).length; - const openBraces = (narrowErrorContext.match(/\{/g) || []).length; - const closeBraces = (narrowErrorContext.match(/}/g) || []).length; - - // Check if the error is at the line level with unbalanced brackets - const lineOpenParens = (lineErrorContext.match(/\(/g) || []).length; - const lineCloseParens = (lineErrorContext.match(/\)/g) || []).length; - const lineOpenBrackets = (lineErrorContext.match(/\[/g) || []).length; - const lineCloseBrackets = (lineErrorContext.match(/\]/g) || []) - .length; - const lineOpenBraces = (lineErrorContext.match(/\{/g) || []).length; - const lineCloseBraces = (lineErrorContext.match(/\}/g) || []).length; - - // Check line level first, then narrow context - if (lineOpenParens > lineCloseParens) { - return "Missing closing parenthesis ) in this line"; - } else if (lineOpenParens < lineCloseParens) { - return "Unexpected closing parenthesis ) in this line, did you mean to open one with ("; - } else if (openParens > closeParens) { - return "Missing closing parenthesis )'"; - } else if (openParens < closeParens) { - return "Unexpected closing parenthesis ), did you mean to open one with ("; - } - - if (lineOpenBrackets > lineCloseBrackets) { - return "Missing closing bracket ']' in this line"; - } else if (lineOpenBrackets < lineCloseBrackets) { - return "Unexpected closing bracket ']' in this line"; - } else if (openBrackets > closeBrackets) { - return "Missing closing bracket ']'"; - } else if (openBrackets < closeBrackets) { - return "Unexpected closing bracket ']'"; - } - - if (lineOpenBraces > lineCloseBraces) { - return "Missing closing brace '}' in this line"; - } else if (lineOpenBraces < lineCloseBraces) { - return "Unexpected closing brace '}' in this line"; - } else if (openBraces > closeBraces) { - return "Missing closing brace '}'"; - } else if (openBraces < closeBraces) { - return "Unexpected closing brace '}'"; - } - - // 4. Indentation errors - only compare with immediate next line - if ( - errorLine.trimStart() !== errorLine && - nextLine.length > 0 && - errorLine.length - errorLine.trimStart().length !== - nextLine.length - nextLine.trimStart().length && - // Only flag if this looks like an actual code block (not just random indentation) - (errorLine.trim().endsWith(":") || - nextLine.trim().startsWith("return") || - nextLine.trim().startsWith("if")) - ) { - return "Inconsistent indentation"; - } - - // 5. Missing commas in collections - limit to current line - if (/\[[^\]]*\w+\s+\w+[^\]]*\]/.test(lineErrorContext)) { - return "Missing comma between list items"; - } - if (/\{[^}]*\w+\s+\w+[^}]*\}/.test(lineErrorContext)) { - return "Missing comma between dictionary items"; - } - - // 6. Invalid Python syntax - using = instead of == for comparison - if (/\s*if\s+\w+\s*=\s*\w+/.test(lineErrorContext)) { - return "Using = for comparison instead of == in condition"; - } - - // 7. Invalid assignment - if (/^\s*\d+\s*=/.test(lineErrorContext)) { - return "Cannot assign to a literal (number)"; - } - - // 8. End with backslash - if (errorLine.trim().endsWith("\\")) { - return "Line ending with backslash \\ - did you mean to continue the line?"; - } - - // 9. Check for print statements with errors (like missing parentheses or quotes) - if (/^\s*print\s+[^(]/.test(lineErrorContext)) { - return "print() function requires parentheses"; - } - - if ( - /print\([^'"]*[^'")\s]\)$/.test(lineErrorContext) || - /print\([^"]*"[^"]*\)$/.test(lineErrorContext) || - /print\([^']*'[^']*\)$/.test(lineErrorContext) - ) { - return "Unclosed string in print statement"; - } - - // Default to a more specific message when possible - if (errorLine.trim().startsWith("def ")) { - return "Syntax error in function definition - check for missing colon (:)"; - } - - if (errorLine.trim().startsWith("class ")) { - return "Syntax error in class definition - check for missing colon (:)"; - } - - if ( - errorLine.trim().startsWith("if ") || - errorLine.trim().startsWith("elif ") || - errorLine.trim().startsWith("else") - ) { - return "Syntax error in conditional statement - check for missing colon (:)"; - } - if ( - errorLine.trim().startsWith("for ") || - errorLine.trim().startsWith("while ") - ) { - return "Syntax error in loop statement - check for missing colon (:)"; - } - - // Default to a generic error message if we can't identify the specific issue - return "Syntax error in Python code"; - }; + const errorMessage = getHelpfulErrorMessage(lines, startPos); - const errorMessage = getHelpfulErrorMessage(); + // Calculate character offsets for CodeMirror + let { fromOffset, toOffset, adjustedFromOffset, adjustedToOffset } = + calculateOffsets(lines, startPos, clampedEndPos, errorMessage); problems.push({ message: errorMessage, severity: "error", - from: fromOffset, - to: toOffset, + from: adjustedFromOffset, + to: adjustedToOffset, context: { row: startPos.row, column: startPos.column, - text: code.slice(fromOffset, toOffset), + text: code.slice(adjustedFromOffset, adjustedToOffset), nodeType: nodeType, }, }); @@ -353,9 +238,11 @@ const useTreeSitterParser = (extension, fileName) => { { nodeType: nodeType, parent: parent?.type, - text: code.slice(fromOffset, toOffset), + text: code.slice(adjustedFromOffset, adjustedToOffset), fromOffset, toOffset, + adjustedFromOffset, + adjustedToOffset, }, ); } @@ -363,17 +250,212 @@ const useTreeSitterParser = (extension, fileName) => { // Log problems to console for debugging if (problems.length > 0) { console.log("Python code analysis found problems:", problems); + // before returning problems, remove any overlapping ones. Check if on same row, and take the one that is wider + const nonOverlappingProblems = []; + for (const problem of problems) { + // check if same context row + const overlap = nonOverlappingProblems.find( + (p) => p.context.row === problem.context.row, + ); + if (overlap) { + // if the new problem is wider, replace the old one + if (problem.to - problem.from > overlap.to - overlap.from) { + const index = nonOverlappingProblems.indexOf(overlap); + nonOverlappingProblems[index] = problem; + } + } else { + nonOverlappingProblems.push(problem); + } + } + console.log( + "Returning non-overlapping problems:", + nonOverlappingProblems, + ); + return nonOverlappingProblems; } else { console.log("No Python syntax errors detected"); + return problems; } - - return problems; } catch (error) { console.error("Error analyzing Python code:", error); return []; } }; + const getHelpfulErrorMessage = (lines, startPos) => { + // Get the error line text + let errorLine = lines[startPos.row] || ""; + // remove any comments from the line for analysis + errorLine = errorLine.split("#")[0]; + + const nextLine = lines[startPos.row + 1] || ""; + + // Use a much narrower context for error analysis - focus only on the current line and a bit of context + // This helps prevent other errors in the file from contaminating the analysis + const lineErrorContext = errorLine; + + // More focused context that still allows us to detect multi-line issues + // Just a few characters before and after the error position + const narrowErrorContext = [ + startPos.row > 0 ? lines[startPos.row - 1] : "", // Previous line + errorLine, // Error line + nextLine, // Next line + ] + .filter(Boolean) + .join("\n"); + + console.log("Error detection context:", { + line: startPos.row + 1, + errorLine, + narrowContext: narrowErrorContext, + }); + + // Check for common Python syntax errors with more precise contexts + + // 0. Missing operator between values (takes precedence over other checks) + // This matches patterns like "x = 5 6" or "y = a b" where an operator is missing + if ( + /=\s*\w+\s+\d+/.test(lineErrorContext) || + /=\s*\d+\s+\w+/.test(lineErrorContext) || + /=\s*\d+\s+\d+/.test(lineErrorContext) || + /=\s*\w+\s+\w+/.test(lineErrorContext) || + /\w+\s+\d+\s*$/.test(lineErrorContext.trim()) || + /\d+\s+\w+\s*$/.test(lineErrorContext.trim()) || + /\d+\s+\d+\s*$/.test(lineErrorContext.trim()) + ) { + // Check if the error is at the position of the missing operator + return "Missing operator between values (did you mean +, -, *, /, etc.?)"; + } + + // 1. Missing colons - only check the current line and be more specific about patterns + if ( + /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b[^:]*$/.test( + errorLine.trim(), + ) || + /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b.*[^:][\s]*$/.test( + errorLine.trim(), + ) + ) { + // Determine which statement is missing the colon + const match = errorLine.match( + /^\s*(if|elif|else|for|while|def|class|with|try|except|finally)\b/, + ); + const statement = match ? match[1] : "statement"; + return `Missing colon : after ${statement} statement`; + } + + // 2. Unclosed strings - only check within the current line + const doubleQuotes = (lineErrorContext.match(/"/g) || []).length; + const singleQuotes = (lineErrorContext.match(/'/g) || []).length; + + if (doubleQuotes % 2 !== 0) { + return 'Unclosed string - missing double quote "'; + } + if (singleQuotes % 2 !== 0) { + return "Unclosed string - missing single quote '"; + } + + // 3. Unbalanced parentheses/brackets - use narrow context + // Focus on immediate context around the error to prevent false positives + const openParens = (narrowErrorContext.match(/\(/g) || []).length; + const closeParens = (narrowErrorContext.match(/\)/g) || []).length; + const openBrackets = (narrowErrorContext.match(/\[/g) || []).length; + const closeBrackets = (narrowErrorContext.match(/]/g) || []).length; + const openBraces = (narrowErrorContext.match(/\{/g) || []).length; + const closeBraces = (narrowErrorContext.match(/}/g) || []).length; + + // Check if the error is at the line level with unbalanced brackets + const lineOpenParens = (lineErrorContext.match(/\(/g) || []).length; + const lineCloseParens = (lineErrorContext.match(/\)/g) || []).length; + const lineOpenBrackets = (lineErrorContext.match(/\[/g) || []).length; + const lineCloseBrackets = (lineErrorContext.match(/\]/g) || []).length; + const lineOpenBraces = (lineErrorContext.match(/\{/g) || []).length; + const lineCloseBraces = (lineErrorContext.match(/\}/g) || []).length; + + // Check line level first, then narrow context + if (lineOpenParens > lineCloseParens) { + return "Missing closing parenthesis ) in this line"; + } else if (lineOpenParens < lineCloseParens) { + return "Unexpected closing parenthesis ) in this line, did you mean to open one with ("; + } else if (openParens > closeParens) { + return "Missing closing parenthesis )'"; + } else if (openParens < closeParens) { + return "Unexpected closing parenthesis ), did you mean to open one with ("; + } + + if (lineOpenBrackets > lineCloseBrackets) { + return "Missing closing bracket ']' in this line"; + } else if (lineOpenBrackets < lineCloseBrackets) { + return "Unexpected closing bracket ']' in this line"; + } else if (openBrackets > closeBrackets) { + return "Missing closing bracket ']'"; + } else if (openBrackets < closeBrackets) { + return "Unexpected closing bracket ']'"; + } + + if (lineOpenBraces > lineCloseBraces) { + return "Missing closing brace '}' in this line"; + } else if (lineOpenBraces < lineCloseBraces) { + return "Unexpected closing brace '}' in this line"; + } else if (openBraces > closeBraces) { + return "Missing closing brace '}'"; + } else if (openBraces < closeBraces) { + return "Unexpected closing brace '}'"; + } + + // 5. Missing commas in collections - limit to current line + if (/\[[^\]]*\w+\s+\w+[^\]]*]/.test(lineErrorContext)) { + return "Missing comma between list items"; + } + if (/\{[^}]*\w+\s+\w+[^}]*}/.test(lineErrorContext)) { + return "Missing comma between dictionary items"; + } + if (/\([^)]*\w+\s+\w+[^)]*\)/.test(lineErrorContext)) { + return "Missing comma between items"; + } + + // 6. Invalid Python syntax - using = instead of == for comparison + if (/\s*if\s+\w+\s*=\s*\w+/.test(lineErrorContext)) { + return "Using = for comparison instead of == in condition"; + } + + // 7. Invalid assignment + if (/^\s*\d+\s*=/.test(lineErrorContext)) { + return "Cannot assign to a literal (number)"; + } + + // 8. End with backslash + if (errorLine.trim().endsWith("\\")) { + return "Line ending with backslash \\ - did you mean to continue the line?"; + } + + // 9. Check for print statements with errors (like missing parentheses or quotes) + if (/^\s*print\s+[^(]/.test(lineErrorContext)) { + return "print() function requires parentheses"; + } + + // Default to a more specific message when possible + const syntaxErrorPatterns = { + "def ": "Syntax error in function definition", + "class ": "Syntax error in class definition", + "if ": "Syntax error in conditional statement", + "elif ": "Syntax error in conditional statement", + else: "Syntax error in conditional statement", + "for ": "Syntax error in loop statement", + "while ": "Syntax error in loop statement", + }; + + const trimmedLine = errorLine.trim(); + for (const [pattern, message] of Object.entries(syntaxErrorPatterns)) { + if (trimmedLine.startsWith(pattern)) { + return message; + } + } + + // Default to a generic error message if we can't identify the specific issue + return "Syntax error in Python code"; + }; + /** * Create CodeMirror decorations for problem areas * @param {EditorState} state - Current editor state From 6aa8527389e84faaad49b41aef36b81f7f36762e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=88ivi=20Suomela?= Date: Wed, 1 Oct 2025 17:30:29 +0100 Subject: [PATCH 4/4] remove unused --- public/translations/en.json | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/public/translations/en.json b/public/translations/en.json index 33ff54d7a..364d2367d 100644 --- a/public/translations/en.json +++ b/public/translations/en.json @@ -15,25 +15,7 @@ "characterLimitExplanation_one": "Files in the editor are limited to {{count}} character", "characterLimitExplanation_other": "Files in the editor are limited to {{count}} characters", "viewOnly": "View only", - "close": "close", - "pythonSyntaxErrors": { - "default": "Syntax error: Check your code for missing symbols or incorrect formatting", - "unknownFunction": "Unknown function: '{{name}}' is not defined or imported", - "missingClosingParenthesis": "Missing closing parenthesis: Add a ) to match your opening (", - "missingOpeningParenthesis": "Missing opening parenthesis: Add a ( to match your closing )", - "missingClosingBracket": "Missing closing bracket: Add a ] to match your opening [", - "missingOpeningBracket": "Missing opening bracket: Add a [ to match your closing ]", - "missingClosingBrace": "Missing closing brace: Add a } to match your opening {", - "missingOpeningBrace": "Missing opening brace: Add a { to match your closing }", - "missingDecorator": "Missing decorator: Add a @ symbol before this function definition", - "missingColon": "Missing colon: Add a : at the end of this statement", - "indentationIssue": "Indentation issue: Make sure your code is properly indented with 4 spaces for each level", - "missingClosingDoubleQuote": "Missing quote: Add a matching \" to complete your string", - "missingClosingSingleQuote": "Missing quote: Add a matching ' to complete your string", - "functionCallSyntaxError": "Function call syntax error: Check your function arguments and parentheses", - "syntaxErrorAfterKeyword": "Syntax error after keyword: Check the syntax following your {{keyword}} keyword", - "loadingParser": "Loading Python parser..." - } + "close": "close" }, "filePanel": { "errors": {