From fc66b3d43f40691f98ca9b9d57afc40548a56ac8 Mon Sep 17 00:00:00 2001 From: Goswin Rothenthal Date: Sun, 20 Oct 2024 01:54:06 +0200 Subject: [PATCH] better completions --- FeshHosting.fsproj | 11 +- FeshStandalone.fsproj | 44 ++-- Media/READMEnuget.md | 73 ++++-- Media/screen2.png | Bin 0 -> 178018 bytes README.md | 74 ++++-- Src/Config/DefaultCode.fs | 4 + Src/Editor/BracketHighlighter.fs | 6 + Src/Editor/Checker.fs | 96 ++++--- Src/Editor/ColumnRulers.fs | 20 +- Src/Editor/Completions.fs | 261 ++++++++++--------- Src/Editor/CursorBehavior.fs | 3 +- Src/Editor/DocChanged.fs | 256 +++++++++++-------- Src/Editor/ErrorHighlighter.fs | 31 +-- Src/Editor/EvaluationTracker.fs | 389 ++++++++++++++++------------- Src/Editor/InteractionState.fs | 4 +- Src/Editor/Selection.fs | 28 ++- Src/Editor/SelectionHighlighter.fs | 10 +- Src/Editor/TypeInfo.fs | 10 +- Src/Fsi.fs | 144 ++++++----- Src/Model.fs | 22 +- Src/Util.fs | 2 +- Src/Views/Commands.fs | 127 +++++----- Src/Views/Editor.fs | 53 ++-- Src/Views/Fesh.fs | 6 +- Src/Views/Menu.fs | 17 +- Src/Views/StatusBar.fs | 11 +- Src/Views/Tabs.fs | 2 +- 27 files changed, 984 insertions(+), 720 deletions(-) create mode 100644 Media/screen2.png diff --git a/FeshHosting.fsproj b/FeshHosting.fsproj index f97dac1..7282321 100644 --- a/FeshHosting.fsproj +++ b/FeshHosting.fsproj @@ -3,7 +3,6 @@ Library - net472;net7.0-windows NETFRAMEWORK @@ -24,14 +23,12 @@ Fesh Fesh - 0.12.0 - 0.12.0 - 0.12.0 + 0.13.0 + 0.13.0 + 0.13.0 - - add check for updates on startup - - fix version number in Title - - enable cancellation of running code in net48 and net8.0 + - catch fsi startup errors GoswinR diff --git a/FeshStandalone.fsproj b/FeshStandalone.fsproj index ec1b2f4..ffd751b 100644 --- a/FeshStandalone.fsproj +++ b/FeshStandalone.fsproj @@ -3,26 +3,25 @@ WinExe - net472;net8.0-windows - NETFRAMEWORK - win-x64;win-x86 - Release - true true + net472;net8.0-windows + + NETFRAMEWORK preview - + + embedded --> en en true - true + true Fesh Fesh @@ -30,9 +29,9 @@ Fesh Fesh - 0.12.0 - 0.12.0 - 0.12.0 + 0.13.0 + 0.13.0 + 0.13.0 Goswin Rothenthal GoswinR @@ -42,26 +41,26 @@ Media/logo.ico Media/logo.res + Release bin/standalone - + $(OtherFlags) --warnon:1182 - + - - - + + + @@ -141,11 +140,12 @@ - - + + + + + - - diff --git a/Media/READMEnuget.md b/Media/READMEnuget.md index 7b7e666..2148a75 100644 --- a/Media/READMEnuget.md +++ b/Media/READMEnuget.md @@ -4,20 +4,29 @@ ![code size](https://img.shields.io/github/languages/code-size/goswinr/Fesh.svg) [![license](https://img.shields.io/github/license/goswinr/Fesh)](LICENSE) -Fesh is a Scripting Editor for F# on Windows. -![Screenshot](https://raw.githubusercontent.com/goswinr/Fesh/main/Media/screenshot1.png) +Fesh is a ***F***# ***E***ditor *& ***S***criptin*g ***H***ost.\ +On Windows. -It is based on the excellent [AvalonEdit](https://github.com/goswinr/AvalonEditB). -The editor supports F# 8.0 and has modern IDE features like semantic syntax highlighting, type-info and autocomplete. -Unlike a typical F# REPL this editor has the input and the output in two separate windows. -Just [like Don Syme always wanted it](https://github.com/dotnet/fsharp/issues/2161#issuecomment-270465310). You can even color the output with [Fescher](https://www.nuget.org/packages/Fesher)! +It is designed for embedding F# as application scripting tool.\ +But it works standalone just as well. -By using [FSharp.Compiler.Service](https://www.nuget.org/packages/FSharp.Compiler.Service/43.8.400) in the same process the Editor can easily be hosted in other apps to create an embedded application scripting tool. -This was in fact the primary motivation for creating this editor. -It is public since 2024. But I started prototyping it in 2017. I used it for scripting in Rhino3D professionally since 2019. -Initially I used the Tsunami F# editor, [see my talk on Louvre Abu Dhabi](https://www.youtube.com/watch?v=ZY-bvZZZZnE). -But it is no longer available. +![Screenshot](https://raw.githubusercontent.com/goswinr/Fesh/main/Media/screen2.png) + +It is based on the excellent [AvalonEdit](https://github.com/goswinr/AvalonEditB), that is why it only works on Windows (for now).\ +The editor supports F# 8.0 and has modern IDE features like semantic syntax highlighting, type-info and autocomplete.\ +Unlike a typical F# REPL this editor has the input and the output in two separate windows.\ +Just [like Don Syme always wanted it](https://github.com/dotnet/fsharp/issues/2161#issuecomment-270465310).\ +You can even color the output with [Fescher](https://www.nuget.org/packages/Fesher). + +Contrary to most F# editors, it uses [FSharp.Compiler.Service](https://www.nuget.org/packages/FSharp.Compiler.Service/43.8.400) in the same process as the UI.\ +There is no separate language server or FSI process.\ +Therefore, Fesh can easily be hosted in other apps to create an embedded application scripting tool.\ +This was in fact the primary motivation for creating Fesh.\ +It is public since 2024.\ +But I started prototyping it in 2017. I used it for scripting in Rhino3D professionally since 2019.\ +Initially I used the Tsunami F# editor, like seen in [my talk on the Louvre Abu Dhabi Dome](https://www.youtube.com/watch?v=ZY-bvZZZZnE).\ +But it is no longer available. So I created Fesh. For hosting there is the nuget package [Fesh](https://www.nuget.org/packages/Fesh/). See hosting examples [Fesh.Rhino](https://github.com/goswinr/Fesh.Rhino) and [Fesh.Revit](https://github.com/goswinr/Fesh.Revit). @@ -25,18 +34,18 @@ For hosting there is the nuget package [Fesh](https://www.nuget.org/packages/Fes ## How to install ### Standalone -Just download the from [Releases](https://github.com/goswinr/Fesh/releases). -to any location, **unblock** and run the Fesh.exe. -Or build from source with: +Build from the source with ```bash dotnet build FeshStandalone.fsproj ``` +or download the from [Releases](https://github.com/goswinr/Fesh/releases) to any location, **unblock** and run Fesh.exe. + +### For hosting in another app +use the nuget package [Fesh](https://www.nuget.org/packages/Fesh/).\ +Or, to build the Fesh nuget package run: -### for Hosting in another App -use the nuget package [Fesh](https://www.nuget.org/packages/Fesh/). -or, to build the Fesh nuget package run: ```bash dotnet build FeshHosting.fsproj ``` @@ -44,16 +53,18 @@ dotnet build FeshHosting.fsproj ## Features ### Syntax Highlighting -Initial static syntax highlighting is done via AvalonEdit's regex based highlighting. -See [SyntaxHighlightingFSharp.xshd](https://github.com/goswinr/Fesh/blob/main/Src/SyntaxHighlightingFSharp.xshd). -The F# Compiler Service provides additional semantic highlighting. +Initial static syntax highlighting is done via AvalonEdit's regex based highlighting.\ +See [SyntaxHighlightingFSharp.xshd](https://github.com/goswinr/Fesh/blob/main/Src/SyntaxHighlightingFSharp.xshd).\ +The F# Compiler Service provides additional semantic highlighting.\ +If you want different colors go the menu: `About` -> `Open and watch SyntaxHighlighting in VS Code`.\ +You wil see any changes upon every save in VS Code.\ ### Auto complete -Auto complete works on enter and dot, also in middle of a word. +Auto complete works on enter and dot, also when typing in the middle of a word. ### Type info -The type info includes the inferred signature. -All of the xml docs and information about the containing assembly +The type info includes the inferred signature.\ +All of the xml docs and information about the path of the containing assembly. ### Status bar The statusbar at the bottom shows compiler error count, click on it to scroll to the first error. @@ -61,9 +72,24 @@ The statusbar at the bottom shows compiler error count, click on it to scroll to ### Selection highlighting Selected text is highlighted in both code and output window. The count is shown in the status bar. +### Drag and drop +Drag any file or folder into the editor to get the full path as a string at the cursor position.\ +For *.dlls it will be at the top and prefixed with `#r`.\ +For *.fsx it will be at the top and prefixed with `#load`. + +### Font +The default font is [Cascadia Mono](https://github.com/microsoft/cascadia-code).\ +You can change it in the 'Settings.txt' file at `%APPDATA%\Local\Fesh\Settings.txt`.\ +Or via the menu: `About` -> `Open Settings Folder`. + ## Release notes +`0.13.0` +- fix crash on assembly load conflict +- faster completion window +- optional tracking of evaluated code via settings + `0.12.0` - add check for updates on startup - fix version number in Title @@ -82,3 +108,4 @@ Selected text is highlighted in both code and output window. The count is shown - first public release + diff --git a/Media/screen2.png b/Media/screen2.png new file mode 100644 index 0000000000000000000000000000000000000000..16bcf83e0c1c9eb86902e0a55596d48a2edc26e9 GIT binary patch literal 178018 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%0AxyqEZ}f=Az)h%~ul`gD zu%!QzlfHmaApI$F?@=Ld6c(O)=o|Gh=8>s1GB#iZmt?fW)7nYNHO2RE>%gT&9!to_ zO_2-`nj{|jdBsb2^y>P4euu?}o?o$SH}sg>Bk^Iu>H)WDpIMKQb-Bu2RcyZWV3Cau zdtRe}{k|P45(DC7@27MDuXk2J_o7Yv4TI-fCMv`qliU~kbr)wS*8_{0+c>crBK;!0 zORdEHAN%bKW%AEQ(-{p}sq8j*5fiN^X=(Pc@v&khtRqGVkH}f}ip_==Q#n*K#KrrA z>L-qgb}`BN@enIGl%)LS6{$r!DxT-2b--PE;dPE@F6wOf@Gg$&|c$R zeRH z?&n_U6AOQomw?Z1>S5nG{} z?vJ^W(L4bL&FO0ex(bcHPTN9spU9`XoZFlu!cTiHXw0^SQvdBE{*#doaPn9&{TPK0 zG5~>tO2~oZ@MHG_5CBY7wNV{lzYG-zMt%m}&%)8F5fh%0t-{Mw@ zU-Zf<$oKlR<-HpdDmsl3aPLKbj;0MaUARiY&usjBdTKuO3z5QUy>H4pI6dDw&lfeF z$igkIdu{$qJ$N^;XnA&U<`TgU|FWw?k+R@lI+Z3XVY~f6Qh33pZ+Y8DeTAImi*B`f zQQ;hXmBs4YQsLE!FT<=UvIwY1sw6VlA~wH2;>LEYbe6e##!+mKqN>8H8{ zHTx;d&(v^=mkctX9p?r2I`P?&IPz%q!4s&bNcJxoIW_7JWrNYb^~%bMCz;z->Q%UR z$_AjP8Q@q{I?pb@1vilhYt2b}f<}yK#s|e{L!`58LIq$oUYHe&E!|4|H1_p5 zx@bA@WbZ!jJzppDI~sqve^!=RxBK?e`*m5EKtwQhv<5s>3^F~<`!7;`j34@4!dea2 zOU7oxh1lZMz^pq@$bhkbq>Jw1m8$bqdZF8K5Vhp45uWYy8Byz5S|F9z5$DB(*y6{- z^6;960XJLG=)WiI;2)#k!^$Qa28O2$%uiA=0e$-s5Ie|6Qilqp$U&jdl+@J65b~cd z?$1)bO)G!&Lkf8cBNXwo{kQS=l)n)UKa9`)M->4#K)Vte?fC?F(SYlJoaYFo8Ruaf zIZON0Rq^>Bc>=io`g2DVb^%~T>7B7SIx~Li^W>Y@7LFbG0+RCI8Tvfw&gB-ASh%9r zI?w-kDzhUP#jMz@WgjrmEGj<4Gv<40wKlU%oha9-DM5nMWN=PEno)@l7glq zPEg5j>2-A!Un<<90T=l18tui!e~{Mi>=GHZX%R2(yGq3f+$#mU@JZMO(5U~OIbZ~A zTqTbJCC;NUas5zAL7~`gRR5nr65q^wd!^b$QsS_Qaj~AB|0Qet!1$j6h!M(v0H}xl zHOzM#nnJD!#U#e+M~#*pNa*>F?`Zf{c*$EDqOP8jOo~fHMDTqm5gWcvDISOv_|2sB z59uN(JPifx{%0Vbx=2y^qCIBwgQj{i$pC7UoT}0+j!a6#-UmtB@MjR=J3E-Wy9^>x zz#;1f6NSGQ%>T~`=^d$z2~h)R<2Ycsf#QW)qHy44lrJ#i2qTrZ&>MHIj0ENg+CA7D z&lBiNYO-sX44QrvAgh#=#!g!U)yaQ}V%)+*n7KbVbd1V+Flig`To&~t6 zsx=I*IRx!Rz2F~6R4;!P52KijU2gYtGGGUysP|NIDF?rF`j`Fy1ZT)CzE0Wng#JTS z#rVq&iy0x~v;q+(8^M2UCSYr;1_SaUVZ$o{tKXRaS@(k@)N)%dR5MSdj*olV@q4NR za*Z7Sw*Mh_Vra=HU^7F2dI&ADPvgg=2TmY9YmV?-hzVVaB)T|B^%&Qk*!D~_q{Zr- zDI8iU*uCV??}?h?%N)8{C}~}%m&6Dl3yB18+pp!s=Z{f#2-#~`5{gz! zU(Y96usmS>p|??=O3%4{Ty0C=aT7 z6)RestO&Xj4ByG-Dob)$5P$O2?T-|pGO7Ye=&akdQ_&=gGM4Ps`mgK*e;0;TESaSX z;X;q@0sitQ_Cp@@sO&tqF1~|;5w-VET;CmU=o1xES-rVqL#O9dPuhKYg!%Al^6MjW z&-jfnfnC7K5(vT0+`e)2u^N)=LYX8a>W@O>@_2oJ(h6R&X;vJ*+V4h%!T}L#3;I`K z^AJ(|8YN(TfWB_#`+p!|0|48YDu8P3XY`wX1X zP`~%E@Yx>*48q4@>^dY;wxidJxMwkw59f8FHm|9hIZ`|EYo@3)lolOVr_hR;=2@%f zM)crT=robn4hnMnY2Cs^k5X;2ElYnaf;LITZtA_k9Po=JcFY z`}Z%2K=_YUnf&(p-~Ukb7Vl}7l>zH~N+c3~g_SP+hlH!8aM&$hu{d?zowq8uA$_wt z8_#^$S7=jW()xY#hltby0xK~%r}uBfe1E*+zgGer{^Zdh_Dz6n06x%`!l1SX6zsE+ zv->y<+1?s5MtsVS65{%ImB*54ZIp90M+#FIaf`WGA!c(U@Q8JuG_Z`V8DIg7`5PekFF0v#JuT)RdsSV> zkm22b-eMi_I%6C=R9F9FoITbE^mnI)8Vxqu^Zn`d;!m zS9Uy6hX~@gOQlZLm-bzcJkrUeXGY!&{+*3tx>3QgRD#!nY{$;}M^+g51Z(!-;^7o` zTCy3QB(+WD+WX{I@}l`ybf{!HbC4Cydv+T8ukVYZP&(eH;InZJt%rjbFg!PKb7XB) z5<{B)xEL*5-4Y}Bb|iglhmNEiX8p4QRfLELYBvzX@jE)SOx45NhS}zmE!I60bmEep zFoDLF=M`i7)TU}Zoa?JN!XzZKm{~e8!512@5%~sa7>o26*doMlJBBnEaA%;lPXDCd zXprrtz)j-UpfUh;NvFqLt|7F$wK>kbY=dZTSOY028IAfWm7QoJv+zFD5c9}31&pdfQV?NolA+Yv^!rS9-;xsEU zSgK5VaFL$&3kEy*8*tLQ@~{DYIRLjySE$lvi8%L8JDE(2c=za)j_vHm1HzaO3Q2#F`r6a zCl7=x5f`)Q(IQQK8MI(;J$VPy#Rlqaj%C^3l*#Q%ORYk2oR)MQFFhW{alWjhKJqbx zDtuPB(cD-RYB0Gpa#|t1P7y5R{HQKrv>s~EE&^N0OP;aTB}8WvZaO{EL9dbb3xVB) zCC>%7*JUiN0D_&+b`j?`XT3#_*%F|>@p9?kr(;1g%_-GeO11w=6|Pi*-MF{L5f%Q! zC^hkQvvOfk5fPYqv1Izh#H6Arp?bLHXCUOOl@$#6ux9B?{D*jyjp$}r` zmUuD;oIl0OnXq+0@KYr7xLaiUfsO83Iy6%`wqxB0&--?ypfo9W?XzQHq#&w$YECv& z_^hXYQ+wIKLE%by}u1Q(%l-5 z8KIoVsxpk7zbyZ@j#El8bLaf)llS~Kev7o1-NS?#7GG>&0lPO$UkKG;48@3fID~kq zdvHEJ1^$E9#PqMLO=$th-f(KJGrh@?@M~K6THN%e=^YI_>pRAD4yIr<7dvrx8F&4# z_iC?63VVq|=A$<1@kPhqR9X8^N+-jF z-cGr0=vk9rD(DLK=Wryqw|?rzZO_vWl`LW9>!G&ll^_83VEPg-lqW@8U@Jl89`|W) zWf`0(mz62S)p6>It+Vm3)8snFC)_aTsi`J3atw_KWEot>xWhD<{dY^Ii@Jk}?d+0fbZ> zm2k&!!4a^{uwYS@#}0b6;&lPk_S@T_8`eFSV}E&InI0V~gFY8SrH#%OBal~oIjvu9 z{oL_&OR4wNFC|-*eEez4oe0fO#$>)@z?Y5nW}hm?cdT;;`0Vis(sAbi<;K_iOh;K} zB-z`+l?}ls)wSW@(K8AO{xu}(1#~%SQ~&($r8H-YjYh}|@J~dY%DT5B=Xb3>Mmrsj zS5+HwPip2y0l|m_N!^OnlUxZk^;-%uJgN0!r%Ke)2sjr_{fM)8GKx41mQxkC1gNDd z%w3+OIfaAfl?6%NjDw35oQ&{UL&940(8Nb1+sl zCoa7ujp8K%D`;GSB%v3C!8_FU$jV?K^#x+hekk^(eQy)56f4EfVfg0gcgQdsyrSOH z&-xp0-+4-0L(oX+`{%V}v=ccr=r&LOK=nLgrS7)fDX^N89s(9@pnW0HH%2Z zK=^=Go!6K0M7T+0+WM%g73^R+BlCx`1?0;5P|qnz!6}L(za+rkKsIP~sA6lg2o)Qs zWVbhoIy>Lz_t)AN&eAAQD|W1~F=MV_<=<9hVPGa~&|>>o&H8Pjkosu$ykRoE-f3FI z=FaBbQR7%c&o5yR8!}AFhm3=rOHgQR|L4#ce$uYwKLkp-`C}SB9^WRga9NE;QFnMV z(ehhxd?+xWbOY3fJnnik#gI46)XKjXMkdUcCemZazuS&~ppvyl*UYlIUrQVL+Thye zV`?6pYdsm8>2VPr=ZEL@g*({EU5igI-1Y~!F1ECQc5fC|1L>S_eb>AX5fLL`1lHO$ z=XJdtcVG{;z=d0G-z1bk4)EenQJkdlaJ@Q)0yVw$30l)8#gHFa6mFLK>Cw_+=`LOQ zuVg!6NCLq3i=HB+rIw>IM`2ZvW|2cu{@Z3atnioNMQI5iEb*{y>nL`ED~&W#k56j~ zaMO$8=O_sJSwV2HL~|C})-Ck47B0BQEZQDQ%GP31^D~aEr0K;P^$%V74L7@lMVYcm zj|!Yn*siDFgPCL7oQzWVwBE?adu9L=Fb8rT{Mpy2BzGI82Pu;T;Euo=F$dO}NnS+< z99rcMX#$3yCi{e8OVTZ3YKqdM_J})bOW?SH-6iEclFvpu+6FGBAtp`3X zjJ})~w;=8Us{5*e)spiGw5srb_imIBAg+B$gsYoN29M*G=S!rLiz#OI;)KiMmoOJ8 zlma7rf`(RzkIe?l$UF;M%uhjEu=QD|q6N8~#paWll=j=H%z@8E5`%6GP*V>Ig9--Z z<}+PygpFhY=ggGX+5APlL8NLib3$E$cGg-Ky<}T;PLLHnsQyI;{gB~lq4XOjl|MAr zOi(_zzI7*`(59yBsJWouADiAPC@STEq@<+W-VDtQ?>X$o$Ooy!dt0}k^dFYNTeQ_w zUPFmZdp{T%8aI4ziE9YxpVTNh8FR-l6#Zcvx)oUZYUW3STdR$&4`*pfCcv9*{3Kdg zdOJbxBI|eWG1Qm)P%5UJT~pkj5Z&xznO!5$(#}rautD^*&zgD8yTh`vE?n1hSvMXe z{1Jy9M;Z|!8zUSSXZq@TgpC2+IhZH#K7Flb(bXmTx%^|Vt*;E5>J+yxp?sxtGuW@sfs&ZCGYj*!(TcvAGtrIkPY9?E z2ZliY$S&rOJ_*T2>uyx`Q=;HDwC0%16T1uCVr7jc1s%t|c=tKoKDJ$^<}6P(`#DiC zdxKmL*en|t+_L)?(BMvB=!>o((4AwtoEZ;&pvgZE`1LVx0dgj$i+mQrRiL&-5!-oV z^wuu_a|hKo6qKfb>67YuPw(bk&HgG*miw0#pst(-UHl-bRgClWhBmlixctJl@b{sp zb2|-LaCrCXSZ))3($Z(au>2@wJrxLS0I-{m7WG-)H>+*#YKv9x;Mw?8``!~JoZy%q zy2N`k5|TJ&Vd0`pfi0XN-haOSkpZG7~u>H8LVs`fqKHr(~ zA9;`^ra6_8mK=If8zbeCg42=#qrYm%JEW;MJJJW{k&PL-n!beNrk9mR4#$q{G)QyY z6Xn7dm@0S(to^w&72nOVBCk;)?4&xW06qK)+Cy|<2_;F|5y%9^iU~`7mcHvrm;vkV zMgK9lT7A@2*Ss@A9Zd{*KCZKHq8DgZH3JOA1a}4FKM!c}+f+~}$%n&CC^yFIKb*X- zMEt}W3H&aY+Oi?y84#>*&9KFyC1_vpkI|)9(;3#tS82`EOFO9yXCPbBe(0u-<^)Rg z0k%Vc=(D#?_qlGQUcAaW2M$zocYkZRO04?~zO~0Ki@@9*Bb^_-w-?RZc^LmAn4CSJ zu%m*M9hWkyzRMI5lJxPV4apmY*g9C@74!s}|Kw!2Fx0!S^=Hhmeggg>pu&Zgh* zQgzq|ngnCL8(*tTxPLD(avh&OI)az>$c4=nXoYAWS=?L9NM)P>S~35G;ovVG{snS~ zz;x6;?~#}!7E6M^e&M<-I5kmF&T;f>hXYPqqdo$@9eLm_VI1Z!0s z%7(g$)>-af4Lx$cD^M}1Kel)mEsr&&JE@h+6um&{Y1z;iPhlt@F2rH~q0B5DV7P0N z5PR;E*yd`d(XAQQNoE4eFjs-~c_GAJbqRYQ@xYbrx-ONgri`2e6>!3K2R>N;;W^w> zJQrxd3BTL-{tFit%Ow1pLHz!6%_WG9GC=4pu-pMvn+|hYp9p7=T7R_%)uybwI)&l; za4|P$=@_3Hw^D+E8s@7X(J|kY>+3aRdAac!r6rx=G2Q(fRj9VZNq_7X1$*y6^4CVd z48ZYKuWP!}u*r#3jzw_U*rShxTb~l&BNY;{;yvhWJBPKI; zVHqv!BG%9#;ud!mO*UB-{i^4O!g?C>sni@}<}am1iEp@i6rkN3>*}2(~WHBe*r14N-g0OA9?gv^KmVV8^Q<=H$z4QD(l#zC!SO@&b?M3hcg>WF? zJH6|*WIZ4N6x5J0Q7u-e99VBiW%R%&K~C%aAheg?jjKd!0?N3VMQPLeX}YddY)0JD zS28Anl}eM}|Y|>^vLmsAA z!7q0si{5grpGP?)ZJ%6s8Vgr%U+g(Qs|bk-l3WxBg~(-i$MuB~(Z$2(npJnxRG0=; zj2Z?7fg!+t&Kp?`b%}5ow@7|9ta>`_O`1zrD}$!wFwtWDVH}Sc%jb%70_UW$7~rEvx9y0K((vX8!?$lY9XfvgyXW zDZeQ(<<_AE>Zl9lL+f9_;$)Q_jxmkcT>B=rlzP{nx_aaP1PF!x3m{~WT}R)Mbaxcf z)Rg_(8*<7zNOYU1Nm`Y(8197&^;iB-pBkL3`1Hw6ka_{2i>2P|{tU+$6wOqg%Bty) zrL2~dvJMf-EuEZ%uMOQ^JZ`ny-+6>PqC`_>;fTv|D>$y_iLwzkDqutMycItjI#kI@ z(o-`q@K&b$x`$|}KM_=s9^3&tE`$|%HR~A}sFwxh<~F~cG5a+`w@?%}#3R_&n}5y) zD-01htlm7!JxyMs2`>l=oBgnEd1z`V@npl@tSAVgG zO&hDr!SUch!v|SOF^!2cLYZGIJBD9@V%B5LI8bl!1PvWn<{9=s{XPZxX(1eLM_v$C z#O`9&qwUK17N~t2(c|CjVMxrkGpzS3+2%&;QqBKh6RmcQ;Q6^go`37=QFh2YV%*XN z>rc&bn8}_{P@kI&jm}_IV7JQ6t}8dKiwK1^r;mDSF)mhWQBrf?-h3kL!Ss z>}#Z4$QSEe6~N5A9$R<4?m*|gd;IJ*VSax8Z#jHh;_=TU1?f2oyL>F0NU)i@eWTrv zaNcka3G2PT&GrORkC?{DGpc*#FFpEJpJ1mzo1($`IiTeU(q+>woPEZ}O=gGPYl-vS z%s-^@np#=?08rk2g z-T#lb*bKKeL}5!B&R5*&VwN1G&miBME5}(1&i^j6UlK_dA#*=7WDN=`1q^pI0I`rs zE8W)BZ6&8Co_!Nad(y>iv*xBrADrI#6Sw@t+O0GkSyp`6JjojoBxOkO9KJSG8$Hem|O%{!^mu2m%!r{hS!yjmJ z-|}e-xcj4iL-{5QWGg*Y1*Iu5%lUWm)BxR6fdEHGQ}$*;%C{tj2UPL!J?^o<)V87j z&_Esd(o7JYGG6B9`n#{@vz)?OKpND}So2p{UR|Akb3oyLvKG>uE zAE7PaA3X1H7=f&Z7i~1z<4&B&km2|QE_6*P@W)plsinZ034bO00k01OqxGidW=bp0 z$7m1L>oNhOjC958|(#bd35%nn|d9KIP zZHYf(as;QK00kO~JsdcYvZd5lcbQsoJ>RjDgt7@5)3owj(FCZ@+$LuHP>v^s)_;;7LUt4luO+-zY-r|99xHFkB;r zi%M*z553A#XMJX7rjt9Hm!MENYX}biR5BR~!|4Ci_qrzielAymAn~NEC%1gus=F)C z^7u3a7B*e0z5k-{al!2UoiK zVUTgG_+aZGT~-XY1rKxHL-14fNw0eobI%In6pF8`U({_(B5jh$Mh97RXuwWA0|m%z|4XS3;(@@@71PltIGk~ zRXNcgia`;YH)Okq0i3UeQoq4eSiT}YI4}_6OoBReZ3q-b9*#(`&1&x$2Pu0*$G_U;n;C|3x0u+=P!2gX3Y?VKPi* z3wD5!i}Wt@;kjI9QhIUs2Su@w5zDS50d*73B>)8f9FNb2e210@h-#eTn)%^_HJK=D zMDK214G9f74YVy0rVkgj{vpcPVsn7mikPYj4#Bn-7a+7bF~cKs6Sl4GS0E28&FH(s6(3)P1#tm8*XG%E-x7?F2hb&v<(8@}q@yaj#Hm3Av>mHe#DzZ%cUs|4v zG-3rRh2cn-SeZF0?ZiAxU`01?1CAX=3;2`MA}gu+WL8i1T$04t#UM^DVKLJ3FvZxD z_ZAl%3+nsa&S5bSGTAM)N}Tp5mBBy9`UIrlxf_#yuiXv3J{XZm_dADC>Q&RBe{Pav z2(2am%jYMhj&^nX{bv^ID>X+*(re~&wlBr-0T#d|G0MKbE+o%D_UlN2m|xO8hbUv| zn>izavb5zje>!Ke{O*@xl}p&Mc?qMYSIO-Bar4ts94`LE;INLX;XzN^W>1ZJ+F&;@ zGVMgv<%_EYd$h8!njvgw zij{#u@RU~Zy9%ZvD20#}6z-RlC~;G9UBRKbr#JR{k!d~x`L@BFlDp^7wBbs=YI`i~>af>Nmk8B2-KDO-Qv{7} ziVCCS>MfG>Cc=u<3->fU;fyD8CpjCPkl=UiTjUlQMe4K6rly-67$ROn9oWOO*D_YR z#Cne*5Og$XNm0b05YMSHV3k09gnRI9KuLbsd{5}Hm-iz$NbcQRA!p#%OT!ci@@JDA zd!TRf)-Z-ZSUjRz|#{`@ zj+DE5V+EW=O*3tyMHd19n`GIX-=hrD*+^_76Ty&~_ z9%X*$)*JI)oSXYBdzYCt$en(M5nJ?#x`EyP;{581*2a8kMFpAe!1&0Q(P&6dbC*`A z`58Qy+QtNTYYesSVG<5P@NU-L=q7fu3aQi{({)OF$`b+t08GWN9u4>6Y%v3cVt!Ln zPo;Ce!PgpsA&m^So2a-N_5jClDeaz&=I~0+>xS!H=gt}vtO(K4ARyEJ*L5*8EXkM zm6n#mLOwl@-SM3`2HTm1L=}BC@L6hM`2mw{)`W(x{Q8CrHgHeNEA;)7q{%{IdwgCi zgR3hmIF%>XQ?O+*Tr6B;`SP-|)Z0Z%D(bIyz-_gS0&Owmchxb)yDZk1AK%mbaf;W@ zt~!huMFFO5`REz-e;+V zRXa|In(EQpLzlppgq?i81t@N27+qfmVC8Cmyqa%Hwg?lLpv1J2x~e-8LNi|)?Q2MH zbQkf$scLYOQ5^6i$p$*Iv^k;-0Y&j*_AEOW#IOm&c!BU`wY=pG3gojUr zCxnC}hC~gTM9_m1Dm%=<+>*GV7r%>JPSp3y`FF~irb~Nc1ji?--x*|(jnnH~ z6}j_6?SJjCsmFO}O#cX;W{WN!0|y9&k8=^m!{r6r>S*2)XkixwQ_vx6BglkD&pqnw zG6}qBE1T+8IfiWRuW#>9x8MUJXA6EoQj6U$b+J{yS~@Eg@`pK^CrK6$a+Ys!92QTW z6L>+YVcS&C%GqMr4yi8B-I=^oF|Ke|l?u*#e}W8kloO_+DdV0HIN6c=+< z#Qn=81vn~fd0^_wawtY%P^~>jlb0qS_Z%PJtg4NO+fO$LzjL|%85$Rz4fn))ZsZ)W zRr`&kIF2vm+^N}m%I`zkx@^CGWQe zH&8@R4^^4T)(dU4)|H#-Wg_K-PVa_hYg~byV$~b?yR%)jkhu* zg3=al+2Z z!z1K=w%hD+Zu_o4k}}InlgrDI`;F8s6~)Z{e4Xc7fg)v~1yzsWKo^9hLca8jvG8hU z*yntZ4_g>(I4QpM&iwps`?&4==)T8P=x-c8)MogMkq2Ss384)mZY$HczQ&R2A@^ou zF7o?pr_ry`j#>Cxn3H86J(oMge9l=-B_$0v7_!W6QCqXib!@A5nCXCF%qE9ECH~oQ zl!1NO-1|TZB5=FMO-B1~D*Ff7@Z77-jup$wHfHjOz{4=3;8|f2=U}Ic$lq*I(5*p) zl%cefEx1CQd4P4CGaO^Ozs5{Wneb}2g8LPXvi%jwj?x_Xl`O^B%9I%3^X+z_20A zvtFt_*X-Dqbc)7W_Jt7el7{J}l#(Wh;9R*ofR*?X6v0~#>VGPW47RLA=jaBZ_mVRa^&>e78JMDhrH85YOD}6#QH$bXg$?lKYjQKOhIJA*X z{K^H_XqDk&{v;`XKuZJ(Hz5dJM-Fh{VO!kiBqtf2`h!ppupr2|toMTvcYZnbxLAQI zU}+LIy|&$w(h9$>`^ZkyVf?kyUqX;j?6EXl$W<33rcTqVD<%$Y$sGxttaeW zP>@30*hgcvcb1osEN|Sgi{Xrhqkz-VtvkV$K8CIR1!c&eQ7InrTfVEAE%T$9i#@j` zf20xYcyolDh%H)s6B&4NPH{TjFPOMynHV33lb?{1WMr6S^q5Pk$D@o}r`icg!eK}b z%9622Vk3M0J{UK+!mu@{{xni%>%-nC9A`QN6Y@<0KNZzdXKEGIgZ7I{ zsYqh!RTJsBOa(hT(#r;|B5~pR?Ku`hkI+TN#`D?cU(>&B1X!LCc3mTOJ5CLL}I3~EF}UkBlc4ls$J31(Rti@FN9DNZ<^6pwlFbSqoK() zz6I=#M?uXR+7IQE*!{K1C-Yj$?Z5b5JTj1U6PIvvS(oYRPhc_rd@%dmSi8mCQyQtx zgt+p-rrZxr=3=DZVQ z9vL!AT}J#ffz8H<9+tV=;VjPHBSi7|&c|rB*|#sx70fX7(Y<1_$t%!An!-sqe6zs8 z65XFD+`IoMzV--AL6zVAI$q%3W(O%Ol^?*QGFyMx9?sjdx1PSa?DjkvY%n#i zZVe153x2ybY~(y5MWUGg4*xFVM30VA*!AYX?LGemgFc$k!&-g&Dxqt}9kQo;q9?zd zcn=E=#Ef?uzpj0ESZb4~G51D$p{@T_s(#Pj^8F9(B9_IgtkqaqgnAR=2yc?m`0XtTyRPGL4~cRzr}1QO7LrX*Jt}_@yamc8 z=Y6<5^vBVp{GhKeGsrsNtWfld@Sxv7O&8opm(*c3=eB=`8~|52?~WH^UA(ZXt*5xs z)Bi;xXPoV}9Nyh{^ju;6_Rh6CYC-WyxR`ULMC7jr_TuhY-PXPBCxL%NnGim>hOMOL zD=p@GleS)!sE`oG3I|zBOS*lGf(F|;C8BI$%GRmL<5-HY3;in9D-^+r_>l)m{Wd|B?UwqJVq4x;mT zD?Po-6f2#;tEICy-46X`9bL{z9>eR|O27^)sL`H`&o+mpdm-1T8l{=8pcov@tk40% znocOMz!Vv3aXs)Qm?pZ_A~KZkY+gm^b+J%Cx!V;dbc#ppVz(Oy9utEFTCa|K?{Ow* z-P45cj_zOTFMW#^KELeE@_OP@_5J&I(CxM~w4wwZreHl)(Q?=f_%F7ZsE_HWo4Gf1 zr;*Ejx=kq6>Q?;LWzcdyGU4cm4g8m3&f}!^dF%R+YHi9N1%24}$I6#6UD9=^g9}o0 z7yf(T*E*yq3*Y{B$8G#j9(CL4b=b6Zf6<U$$?{+~R*J-jA|D<3Z}ueP>R$7$p37vT z4V8|*&me9yrBZ2H9f&sFeBU>UV4U?oYpdL}YMSj5ZOc^F-lUml{@wvv$${KUANzw$ z^_^9(N-I0FzC;rVi66{qd*TVTd?rdCPRwQE90-T;P1=4FFnpqsAQd81G%bOSSF%ls z6%B}4zUSm8dD=|`o_zCp-}GaTF3Z#x&C zI~dwvGtb2FHf7Mao&3A?3nAXoySG9Z3R5o6Tmljk_^b-XYI zD1xU6yhU|9RMc`^0Npv9GKd=U+McJQ0IxL(@Ym4W32p9A6d9SQSf~u{;Zo(ts_)yW z)}Q0l6*wPyeouRkMg&$_3((b|o3g|%5vwhxpm>UhCc0SBc75-Cer3ZLkZPt`ci7>p zqH}PG$z<@5xw9%H4CZ<7k@bet{d`zkv(KAl%uN&`DYli>IAkb*~j!NZQ z@+0%9{=U9O(eg|5$`p5s{1Nf79ymm(u_1WW)G{z1A-8bVS6Bt{bh-g-%s`J_W0P~Q zyL+?TZ8kEJf7=wTv^M$V=H$g@5*+BLmXx-ldPr6>9&H#`M*xXCaPp*@#cH{3a{zL7 zE+`QKn;h!G0QpTpVX0zbGwm?qf`KKkIj6TqzMxhW!RW}yLfyx9g7aSPzRPYj-c-x^ z0gmoHmi(~zV7%Z?>t>(ZCzoQ_;mg{5tbU-mV#_zzEE$KdT#F_qx~Sc|-GR0boC1_T z?O`h}TYSFH=#drfJ9L*1A2islWLJGv7W%=Kg8KC?nLc<&+4V3;@Elb#ktB3dR$KI} zKYLSnXJ#wYupZ6O=$d~P@+M;`%^f;rQ!&uG(|{;_eM2&3s?Y|wm1#PdydBU6mu*s9 z&Y_8xuV`O{zWB{X8bRDha@6eX>_j{aIPrCKU-{(i?!8jypW9K#YScDwS^;by8G27M zb5tUhv%5P>wa0SbdxgPPGt3Y*HM~=9(JuDT2Iran&=(B9e*Wq_PfpE!oTKx9KF_y`9@Wn+w?c2y$PTC% zwHK-JD(t!fFjw$L9PSVX+%CHsbwOT-^)?F+H>!N30zIz6G{wZkq?>-d1$?Z^xNrLk z-y}3qYTxeH+-LCGriw#xF1EI`jc!-NIl6c5@$y%*xjxR=MZ zwA>LoUs^*db6*l)=s_31(_*j$hC_QCMgh%x!{W&0kzRg)z`QgaSx`^b}~d} zp=(5&$<7o8476P)o*f`6iKr95cmNStM=I!m+O(Dxu|e$aC-P$(#HAGH&#V)*$4!}? zmWBI(vuA=HZdx6&OG@FzvrO}V<%XXpvH|YTQCW?&Vhk8YLuC&PvV9wVjhYFNI<=?w zx{e~3eeScq(v21@k{XW|yjX85m`*^nfk+9oU1>jUtleX8HN7YJiYB)8#XZxhvEn8!5q4N(EZEBA9Tv1bJRI4wZP}W(tNe4XX=<=@_pSG;|{kF z2crC){O+4V${X~OqB8^s;lHE97N3AJPRbU~nZNC~+p?5SXUQ@jI5TYu7fh$9o#pHb_)u?v4sTKiJ z7A@>i7C!h9HB3uS-+A+5*98R%cG;WkZsAj84vv1{b=2Je)TBYcn-oyX*2OcLgruD0 zH^4pJJE@CQZ242)92WygEm*6gp!IsU)Ve*Fh&KB**@xwDy_z@g_O?VBv~Rc9d~~-| zp>8_N!MPIo>`V<0%)wBMLW~Ki%gUF~sqxX{#e(vx*#wSum=JZp#8x&O`zievnsFob zutt36?A$S<{NA)U2@MT0)k(3z37DEBWFSGjfD}qA(%IM% z&l(5&q>LTU77j8-M`=C7@qB#4*4yQ9n8P#fv^3+L)3pd2LwX~7?NWfZQkL$`7~Mdy zk5zdM*FF5^KH25t^C>B})!+9Nf%YXeuaQbb^5KwL2BTz%*)yh=vR(zqT`0XwPKra* z;o`<%EuC?6^Bu(POK)dnMxN&vCGWRW>eE?p8vmsx&^0KGzPU)f)LU2qolcvAF;l$* z$L<|t)aI|E2A4S6RC%A0d!yvs?pegp{n+DMQ77D~_x1zorcl%L^ZG5;V)LUZk1s>z za5ARmmKN!o=ZMhlJ+daZLXPFYmj@CNguY=gi!3vm@;|Pw&euS$Ct+pG8N)fpWfXNz zJ{4=Z+6+)kV=;0UJ3efgsS*KVu$Au<=6H_QQ>=`jT@DmNuVz!whuEj@z0~ep&%kma zjS>FwSRF4<1XBs$l841FYkP#e_h!}->*e2s%@lvS^fK^OnKc`mcDzOx&+U}Ztd4Ty zxWxM4>@U<5>QS)02gF<=j)s@pbN+?0B(n}T7IeX#OpYZSxZF)hgtMi)aZ19wSKP>A;7QvL4q?jVVJxc~S@5z?;xk>OYu z1XZJ|k0<9bm%VzZDxx!`%NV>Ad5S@XdbW8+?v=XDp?oRKdB1&C%(i;x3Id z6L|r2#CNS&$MBuqe2xqsIxl>btCue0QDH> z+xv=VL6)iC0NM>tgI=uiACUNleft!x9eG1nj#}e~z&-Uoi>it`72K?+yEUKCXLazz z-+~^CD0)%Zl1rvBby`1cpLh{X-_3)HEsV;|QByRcNu@B1d}#b4e4=7vg597wqW|er zTOC@P+gkVI&m~{E6Zos178q+gcsPh4T5!#cDVkZDLx&$TBYjTN$h0eT6$@9S-=M03 zvyM3xv!cZyPsWhj$?uXYDn`1OcBhuyvG2Z&fhKt9@)Hm*NPTq2W|o)hP@6HL_g9BN zBl3;XZ6$|}a%&mi>Q^9uBD{*|t3yNCjoZ&;IxU?E2oiC2rf}C&gZ5UwAJ{RXG$Z!(MUl?} z?%PGLa}`xqI4G|yn6<~6ny<~T`4^g9$c&zXh_96?V&>=3?sUd4Cn5^5M7&RiK(-uEm-c*?n>b_$%c$}r!*zK(CfOPPk$6?q*SiIFiBLF)<*xN2Y zHwlFgQ3_`A8h1Uu=JnkF(R+WJ#jst7*?-kwxQ z2v{Ot{GDXzG4p0hH2wX-kn+W1^KbGMhWLX0o}|D%9>Szj6853S_1O8P#rf6JnRsa- zNyD$Q=w92~?V$Ijj&vxZ9PhW8o`+W$Ag_>2-uP}JReTVM8)3nUJ;}IGpf&|DCHe+} zA$&;WYJ3c}S+xn|pSDk(4bJ%Sp75WEn@U{Nc%v#hx`>XYWs#!8>l%YUwv)=s+|`a7 za(AG}FO%hZ?M@^&aj~iN{%%Ps>sNh12YJMXyzJk-b=tzb85MU?GM&=(1V*^pnNHb_P) z9`QjCRNg92fbF_(pB&kr521`na-N653|d^pCfI(WYXnVJ7z~(;i+{h<(A4bF+K{BA zpgG#mS&jSci4&{G_H!o^06cK~Zgkwc1NL`iI#?T!%kB|$?6Bqb5w2_S2^OU{56+!DA105=#0yfs6Y`0GT`Z>Y=fPRVCPdn;&ekom{3{UvZU`v-QybZza3G(=*@;Lk77BiJ z!AewvN_!q5pTrE|G9jJG8W`ynzTJ0m{3sc2d(JJyIgVJiz(ko65MPskyFMDjq$nTz z>~>T}1?pFJ9JT35-@L!V)W|juD9aUo#|}B}(NyI^CHLP+Eq)puEQGIFCsxIW3C(&Y z@qKfiY=>K0?MyrJ1iEHtAlBpXICLcUAhkH(IW?xmnyR9n)_j&UJWF!oW^dDJ_f8UG zlxY$8X;M6_F}1z?Hpg2u@R$Y(8&EDN=(g8zpJ1exqFlnjNesgWcX1)@GOr$H1R@1R zui}Z-jHL(K=W9SdL>euI@%OAB`zxPdDy+}wtxSCya+$JDtn5$rDn5=$gHy7C!GS8- zCzPd&(eM-YU=Zqb?fvm{u*MIuPkPMA*I0pXXx9aGu*kX!BS7VNt85EJ(Q6I)!eH)% zGT{tmS-xjb>6Xbb67UOph3}Aja8Xq*bu>wQ6)9VAUO}HFAJ6h)`=Yj*+?{2BYxc&J zFQ!gvl98mvJ!-3^N0gSt=Mf)a61Id7krRa5R!ypJY`9}dt>* zT24fREMv%XXx+_cajdv^Sy=+v5DOt@mCyHQNGR6$_sf*i60?kIy{;UQ1zSMtN*Ok2 z!oVpnm8mTF%YN_V508&zX0NDc2N|-%*MC0hl{ge*>5DU|D;$K~_5wh2SCpyCp0%Ih z1_vEGjukqeyi19Xe}Ss-st^@|7AEHsav(?Zu#l84!6abiW?i3=33r@U&xtk&+^`BzG#FUNThYq{v}{s;75$TL7Yk7)<6j zlLIQ*!HUh&yEQ808*V4KOS44ag43|@J)9;QQ#*HKeval8K(HRleaE`qjhM1L`ZODu zSn=rUdJnw{*FaJ#Gb?y~w4&~_WTtr!^W|9HQu8C`C%aeSKY7w7$bPHbteWu+HoNRy zX`P&c%!Vb`k6jpa1cabdqw5IwY%1r+dOD_I5vmI}+EJq^6uu={PMNthDx~Ot9C}e+3I)c+K1a@58;RN)8ql6K--9J}1@>PYJ=)Vo-)Bz&}lYBo_zy7+~if=3hfIq9;D= zR@SmD3SZ=zZjE>H*tA)4U(xYKT-rdjb9oWBZ(o;c2S)_K*VGG2z1`M`vzcCMLNV%c z-s`Qu)!SX^Eex{GeViavw0vE;cZrE2eZ>Toenhmi8jHjs;}cuJ_wjXSJ$qFo?+r5{ z(RZ<;vW9Y%P}{!*0%`JIS`MLHTTsKvw5+@0T6|NjAZA|tp5DO-wQ>a9dVI3v?ueb{ zg1$24MsY*}4?F1z=`rkJ`(b^g0|Ubc)m>=c8=59+uVJ9@mFU!`rZLm9!rQ7Gpl+{w1 zAgD#D-Ln?{vQGv-q?_DI+OL~W+c`P{*o0{SsK~Y=@nd&LgQ9^iLiK46C z=6P0_Z51)t)vm0yJGlFc6F=!4i>0I_B{`{U*S+3TuSy&y25jKc5Na z7fFZT9$hwg%r-A9N$J%{uiSPDU}P(TiIc@oUVy#IcyC7X)_^?x%8S<66?i6U;8+7Q zOnNV+6T6nkX9Nu5=Epz;bBGna9*K56oMnMoU6WXHLVGbP1U7Vb|0Y%UKF4H=e>|6V zb*a#4fr`IlNd1ap9dFT-ZQfX{2wwN{-Np7WuRDLYU#akuxPE03|0#isUujm|xj#=c zDb?1+R<>F$7P}%tSplEHyQ@Jz3+-nSUB1*DuancU1DswXg{EsQ;%g*Al zun2;DjygG5blF%o1YTc~fJJ)Yg7NhJl~;bG6dLH6`t|7`1a39WhUA|Y%D+V&bTrEY zi5yuba1^7&>oP%tcx`J}w|}InNZSbucB(u80PivL`-hQG3@T0XubNyUW@eg*8!GzA zgI;dbY7R{ZfXQ^)7XlNbj>jl9za@EuJnOr6FMXOjhKW|_V}O{)VbILVAgyVrDgiJs}U!K*0Pl)fNTZXxiU1qOFbKW%aJA@lPGwjB5$KVI>F)|U);WijE zPFakBssx5@HruiqwtV0OhmR1650k=}@fiRq8Am`dDm}k6qq$=H@;!n@fsS6A+yVV89N6Sz@$Aau5l+&#SJ-qO7DuV1%v&)jk7|36leHRv36FLa@-2j^-Q&ybD1z{*685YOYrmQZmOwd57e^t^Ly%H;0K5l;p?ot_j2n876SNK9z7|7l1AT+o4&m636Kuabn2FmbfBKr{j41=xYeVS*@uROeOOT1ETa z6zZEBaMWsa$UY4MAU3G%mqA^0)@f6z25_HwNIdVF&Qsot5+X<*Ep`aH9$Aea}O6&(s*ry@Wg@1B5bg>Sr@=K_p*eTh>Z!8>W-vM~<*gNki4IV5?N}$$2eNPh;iTvmY(u@P5ctvM(@MFaLN#VY7N{Ol5iCmufNU+yH5M#hQ2H@OqUX zhndk3=KdqN{G0;`>xFaAl3LnsaX7W12KMADV0wU5mAYIbkF?0$H-3*-rh?EQ^{oT0tZ$JBa-}gAazc>zsd#?MMb**cy zbDe9QY`{P7U4P(FDO#Q*nF`!@$TvR-{hr6eq%^Sn@ZquopusY!o9wD zeW47701N)-6e@Tbl@3g;7}9EzU2h2G^LG`IXb)nXeC@r1z^-0dp8$IYeS_ma3IVf! zgA4SB0fNKUOYgmoC)pM6`99Kmi$=LCk2%-jY%$X|9QHECx`r>RA9>=8T+M$7Vl|AU*cE^lH;=UyHcUn^m>%Hu2Hcm01QjXk8fbNMz`m)k#zU97OC^9h8t zL{DaaF;dUn5TG{j+m?S!G6dJuR@7gmVkd7g-s4T|8;1@8&lO_*l@+vz?cPadRf0F~ z_Zsp$tt5&{>gjUT9sk>7rLX3U7C!YFP?WTZ|6%znWC!2?_&jF*o(K2S+|Qd*3T<~% z98s}<2EtWx&1cww8HTWOkTIv(ST{>hyaI#H<6E2Ju0>illJv(Y!vRd`H>dnf?fqX> zH$ak{U6>?DyRfNjwdL&&PC8Ca1*#!$Wvwrd5jXrnp1HJ_@Vo#AHmJIyhDR5ny1n{~ zL@o=IAFhy!-cVE6aNg9|)h~ChZ7Yi{ra5&ISEuQy$YOF=;MB5_bqN~Bwaolj);T+q z_|@`x#-u;4*O=r8_dNhS@AztmwiWS{UKiB-QpcD=#&>8l@~ywVioR!ndr2dND|hP$ zQx(+jf3ev%puMe+o7H)qF2dpF3E!kBWs&V7^$SnpSoJB$oxUJG12>-d+IB$=g%AJl zDsrGBmu`b@siRp6sJFLqZ=#&hMe%#H)LgOz|i%Ln+B!fB-+*JIT?(9>P%0)GGJ zZxAX=Y3$nX&L=6JnWo3S;wQL*~!Ipd*Vtkb>9*pR$}H!B$IdSxm8hb%`#W!wv1~2ZfNT+I<3{{d<2`fo}yjg6~PBJp+Cq zFBnimhTv{^$&tvoZuji^3Nd!6N0ehhbD+MH@HmDJELKz` zr>GrPIN(d$bJLbG)=nQ#NeOj5L|$Y^ z-|_A2rgug=&^Rj}#hbKY;|7QOeAuc4$U4`kq$e%x%jORxC^kH!a?f1{*~MHg-=Iql zXatE^6oe`9J{w;1xv;R^it6Q%Py0$CCq2{PGIy`^esK@~GiU(c$ZmkhimN%$|;92d%{%fm@Rm9Ue>Gk(7_art^ZYhN{Ak@zY zK7`Pk&RH(!$(O3+5-uVUC@(NSH{DBq?oj{goNVd&Yo;BEAd>J)uSMRYfE(Z#0DLOe zoj2^jsJ6g%12#YZ6AJ1p8Db_392_Fo%N_%^gndm#B!tr%im)tXf)7Q#yPF6q`ZoL1 zW30HDL0w*27eMBR$e;+bw>*OGH9P<;?(Xlcr{yNhC`|PL(6h1|EwPg>T>#mvnhrnH zm9#cDxAt&J^?@P~le6Y9m~&ciGKmf{@#{eb%WBm?@dRrQtyhUa9ei!dEQQI$|Lz(K zia$$lMEeFF)Yr?5|J_NlZjl_4b4ME#gqAU$m&=;GxN3zBSY| z(;e0aK0(noLxg1lRoh!BbRM&f%rkMVdYKTK4+J-_6@ejYY=1BR*+4y@FlHC91S%U+ z+PD0ouAj&#GhAxEY$_{3q9oDMkeKY?KoJD4*=;$PMXZX~c33UBrc0vK{1kSP?Q{`P zUK(+VPFTvJ+&569X!3RVS2!6-wZKgPD6%shE~CGUrMFy-;Yv>ck#Ba|?;rAdWG&Bh z-T-hlyaX(qg*5N|@3lx8hk;q4wEYZpEGp~7UsXs*pQkFm-jLAK-_Wrcp)4WPJI^dC z_TCV#jz_b2g7hR_%WQ_`Yd-9KA%v++yBl}BotaNaDDJ$)44DsDNQxiXT`FCT$} zi;W#B-8U5TNIWe`L+}L?h~Zkp6e4K$`t{`IUF$Y(u`F*~jL0xK*?wN<-%bgg_>fYe z3Pb2}{w#J*01=d%9(M`oG*XJU44AW zj7s7#N^3CDi!wg>&KP&)rXB3JFr9`^IYh1NKe8;(R;yJC^r+V3C#&qA>_I+SlG(JRpGA%)~y&QCCk2jGWkv zxu2c-Hq(}cM{JnTX}e=fO0t5bJ_AZCGe?dCPy^}sVt!=Njw!wFC63kWqMV3_7Y)CA zl^oa^K^H9Tfk8P!1Ua7N@v9d!i&>F+x{#^UPDrR>`z)_{OK+|6YwhgQxftQG`KEjb z+#A*+o_6q?Gk)-T>yTc8#%m)tT$udyIQfz5-oofwJ}hC7sD#04$&8U!PNx+CFDOpC zwDV>+TTK@mw;a$??c4Dr3U>-_LLD>qC}5?fs@QUX zrex#t2%cUxhoaQ9xd0lGYAk$~>b77NGdXI0TxTPZQ@iJt33J~N&cA+59n67vi91Rs z-t`9hGzjvvKcsvNLUr3c!VD05rn0m%1NPnz{_vBiWT>-Dz)jL|oCPRXbaQMr9Lq1Q zZ9n=^G2MFU=enwL#aQ$c*DI&>$4z4P{5uk=aN)Fk&ntt__Ic9t2HTDYM&kEj?QcJU zaBsc%U$!=+3S41M8=x{(^{AJzn`+!qac&g${cnZmZZq#WX1+M#n>UEd|ip9 zEfbnrz4v*@@o~bdSWKDsJvau1amT!0Qf*|L$J)-jY|OU{2t00_;z>#1+m;pAOP8d; zCnZh$Y860!qUjg|dZ24{(LjnFOWoDgMLpA#^^kP&Y)fly-1dEZ$HOiLP0&uS{^p#a zYsv0b{`glY)mMh-hjGf#c(tMC@{&Shj(ELwdviY3_~J}SDVb8jzIu|jc;G&K6fT&k zom)|heISJAT2c~Pc6IQ;D8WFdb{1nT^3^OXG7ZFP>A7YmJPxkQ>Rdu|`uzlwq`M^d z)f&r0ORo8Nk(IhEyT*z0je5+ab#20+#lC#f)x$}bdP79mg{wD@XQ;VrX={Pe~ z6#YVh_ewGIrZf}4^|d%g;__T3nUj{vY+Zs0E-r{IC}tOC_Q8awg=&sjIiHwbZ0j6I zWKphhMuQ?5`1D2}b;GL&b0}73YY(tqq8hMf-c_4155ElsD|y{Mi*c0F%$Qc+rd->|iPPClk0<7xO@6mq77 zWk?&9^;+d`4fxu?7)9 zF5d@y6Ef>28pBt6GSv|g1YWEgo$+(*EKu&E?{EbD{x*Lv=I0u$SfE<@^*ni}O&Hk9 z^+@${LkFRC17h9k1Tf-vLEarTH1w)LyWv$n8TAfA2;<7xlQ!yxvzeX2nJg}S=FzgE zBcV>ir%}L=B63|7Jw17|mDY*D17FR>@dG{F$7fHcfjIe3pmlph>)ZT5!^tb5O%WTj zC>?*5IL&-K4X`$(RyIz1k}iQdYct#h8gJQ<;GAcp35hXXUATM;`A?sP&932^aq!8N zkMrHl*>!CTi9gULSNQO?gkfltrO8JPbMutqF;m#wpg--$YIvx6(t{6$B}{}< z!TfP$Ww1H2LQeb+DN$q>ys$TPazRq7M>Plky*pR`ojhY+Q*wV2fQ)UVoYQ7$*b_3V z%Zvg=ABGv@_q`DiU_eD5CNFb&@Xvl3T(1l8;iK(!|MX}^P3=eTC-^u*@IY)=TgpGJ zbnO@ZEWI7KqTED+if8P1_wtD31DQL_D92V^uDzk46w|%@Y{)_sGYVb{c%xh@=HD92 zSv)|8EO)_$*N2Lh(xHK|k$Jhr^-e43sanKBe+)JYDrE@YDaA_t^{#cqG7YMZ#U#-U}jY1i{5^Z=sg1)-Lrv8ke9@tZo!B0%?xy+m3KleXl;HpVHfZIY9zh|*FVCkwR(6{vP@)s%KM zHJ{_`wrf}@_?YL){oU>;h1vGI@!`+alzH#jdjv;U&AR=%Qng}t!}Tq_Ph7>wxB3vN zCCze&+MoV znVD3*aHkabxG`-&43Wh<+VKd0sz(w02p3z}QAg8))}ylqKreW|>ZnM2$5a(D2$D)? zK8?splZ^-!cuIyxN*YVY%vOo%RQu|6d@E9RymIEH1jVE^%iBi2*J$vO38;fMkX1wA7LZ0P`oN)ZFDflQ(Zfolf0$OqexF$xg|f;xI@i;(nd0N2lBJ zDH`)w$yD~0ko!eKrNk+8t8_?E^GQpW9z*vJ3G2HfyieItLXA#B!jfrxAID8ZFe&uo z;<5tV!g8AO8wy$*8=Jed`op2LGmaw$?7SXA+1YcD9T~GxXodbY)m0f|`!!ZXCXnQ7PcLU3AO&$Z%uvNv0Vew(}*%@`D~=*PA@FRlgQb z;~A(&N#sqyP48yFi^82sUG;xr0_5q-$!Ht%;Td($?IkQ+-BO6TvX!o9>$t}01i{!k z4`M{`nZ@~n#4IsrXt0w7pz|Vg zgqR4V^e@>H)flT-6X$TR6cnxvq#GeJ z(S(|HWc`9Qwf$;LMv>hwLbKK{pFrMtjlQd;12YK4R9h8Cjfy?3^QsrzoFW%N%#NRt zg&}0e-oBc(qO3I*NEsrXTcSPivJlA9ACXQgB$LHx7*%$;#NP9*LTXl5jcZ>5a2vhP z))J8p0hcxhq}QpDK>Maw5E5rK$ooUtDphwj3!SpznyiwL>1zGu*4Ex$$QN`PjPp61 zYII{DI*>QBvwA0axdgLIeRDo(n^bvI7vgDVuWMz5c%GwBvfyg>aWP7v#HDuo1AeFN zY<2KA@p>sj4z?*+W^bOWfkQ*Rkox<4%V{CV((tT@HjHhuyv`TIpAST$@S zE&vMiR}y}EHyn^eQYv?RgJ%U1LGE&NO91>0JC{dzcM7VwqG6<$3`K2ETuaEq>R_qK zu6%)Bighw#6d=V)H9o&q*QrWAJkWmFiufWOh3N0{(D$D`ElXOxr_AJeceMmW-NT?# z!2i8*8pT_wUL-jVcIx6BT2@HuVg_c-XY=tk|IoQkaq8#KgL>8tOp7=DD{y!=0DmZv z#!H8K4MmJ|oo*(jH*2UDTl`)c=Hg|sA`v0s1V)dJh!xY0_Yn(TKm8gNXsA^nxKttp|!A^n`;U6f; z2xNEkWW44Civ$AQL<2zc6gSW?Ex>U3zkXQ^N(;ybd;p#b2vGjLuG=3Z9Dmlv3ryAA znrdj6%GkjNo}}{@RJQiDgg)x<$G%Nv%H9Xg|MRg}+Q^zD2FyxMW2HB_aEAO^{^@%W zCmy<*BJ#PSG^)s=!BF=5f7J#{E-GP3TPw{d#PZpaQ}3;b*z-gfD?*M3#)Q{{HSf(DpbWbLp$O7MDyb zpRW*@NC;05HF$34#_Ld(`I4u+C?gzv0ipBoXqa0iLU+cHN;`*#T?#4`fTo^pH`(dM z(wvjjRZu5-tT3uJ9~Y7MEWU!N2MY15l_2n^8);tr+l~H>@X(`33mwt@mqoR(*_^YdP%V~_^xlyPt z;fbijPVr^=?y+xm0@<4{@a_GQFkT;6L#gOdmuu|gzRm|}rW_pGIv^r&*k`)Rt;6Bj zj%<_At3rid{7oa~>$rVxC`Hi)WgzQ#J-$vGN&0z zOguiiWF&ZIsVDj&zN+UvQErh%##CvQed5m%E#=<0&-eSS(1w>?fDvxh!=@4c?S5|I zy%2XOtvDTuDW}gFqF2*gBCoHkYLsua`vQ=(o)@REo1a*@d3j~C*u2`(ids ze^{rXC@dlB{0Q4^w6uM)hw&N=fyR*!#erU)Kq4P(@fvTz1cR( zVGC(Zm$^W)h{MAN$0zliY14%GV4saLvo;6fVkM2M-!J3g$&io3S;=}aUj`B?soWJ* z4?iuUUZVP@c0R73*9{Qy$Dk68=ZGL=ylcP^utkrXINl!rCdY$Z*6X}{=eCFdB8WaY z!xjns4g09V_p?c<93L2V5jHZleWjY_TJjrDW_x>U=zGq#+qy~3(4M6>p~P>?F0G0 zeaQm_UbBx$l#p)~QYFFOwc?tcHux_V7RXDah_+!i>}8kYt;TY?tFE1&BeCZq9M6jp zc(CcouHT$8EU>%2%GT&cKi!6CL*tk@@}?B;PU(}s@1IJ~5iUmdxkF)9dp{WVB)tsh z$q;>dGikNccJ;@?ydO_fQnm34E4j)7L~XdEAX}VbvvtNU&*}8J{zScpYWEuTVC<%auH$9j@-j1 z&{KH(m~92945o+IbQD0@Obu<~KJHdcJ|y*g|F6JnKm#iT+*dA6fp{3{Qj#+cIn#8k z7nPSBi7|;yBEJ@2Z1+P);K-Ypte+|%k1apwe{|X~vv3(2FX2&MoaN&LV$|cg@?uh# z^NwNtMz6s~j9cZFumsuMkSxlfqo7&Dvi@lt@`JChkwq|9BoS$5Xh%c6zvFN3$<*GAIeY`qx(cc*&WqXmv z20h(SI4u=WDdi@sto_m=YMZHw9v=xH&7#Y1EWm{iBhF2Rw9n)1Fj|Xl7M8IV-8po3hwZCU-!BQ&hmrIZ?f8<D^MwFPH5wxCdM&+3r z#kHu&j-FLf|I*2Z3VE7dlFhao>5;X+)u$r+1*cQG4DI%VHqq)b`s zb6rQ5wFEJbBI3w(tCvG4%OWcND~e!$20$x@K}~A9A^|FC+LLWAIoyOMZ*x#heI8>^ z%Cge7O_R%PPpD8ku8lVKlG@d&e(b1_=uMz6&ZUYc)mSSMqlTzr%Bi$8tbSNk*n9tCL9GqftIu{e^MlsGzolD$KG_dQ5zL!; zg$J=F&#Nl5UFfNKq&i=XJFRS`P(x(LEc9zB6aUdvWlW7p*RBdv&8zrbpPbADF3fEP z95r$s6O{L}?kF}WjJ`Ar)^S2!;o=p-eD2Sdqo^5(g z)J(09F1qihGoDbCthk`#S$5c#+x{kk9M)FdEA=cEhW6y+q;I3#z_vZ>9y=jVAoo-^ z4fpOnfy8|YI<3o_$N5ssqXXqNM`-yvP*u@bSSdu#m!x2{Xl}@Uuua18&Rj~~Xr=

JaeJ`W>v?}LXp!#$H_T?i7CfEx4D0xm)5mx{-q`Yjg5U^AQ&voqO zNDjZQs~l4K41I}iT39#Mxv45W#w`#rrBz3!-B548PpqZ=X`d>hHU-iO5d-0wZpg5A z5&_MTQx6+Ev!48etQfFS0nzkVzykj>SOg=@YUN};w3I~~!owR18U}zj-XR4jq9}$n zwZU70t5xC-;Na=blzWGEw`gMOBf6%>W_VXYtN>RSU8`vc^KM8JMe;imRmAMJwUAQ7 zX?Wq!#p?igwBY`P`8&5+AYR4bXjUvl8RA?y;fU+C))-#Uo|Drr47Cg)RnG|pNK16H zn_v?(0F`quhC&VrP+vS4u}FHf8ZJ_~=P*wO#<`DARc)E38%IVY8^#-!rt_qowx<~> zF}!L#DZJOr?D8fOAWSDG?c$56yo0%rWragxAN<{worpYw&df3)!B&1-e{!wX zDf6paCV53j?BznF)Ni@}R7W*1f$+&3YR23j2fh0%q-M$^Al0+fzlMoXmtmO2*D9_696Ej#}V;-eL#BKfdptp zGza=m;mVN@hCSSPQ*)F5sX8Rw-iic(U;r8BFu92Zx#pN5g2>D)YE$R!xCselJ$FjJ zRiWf~_*5H-*V>J%%PUs*IkI9m-}#UhmEv`B8Fbp>zK@tcM+`0|Z~v_41A)r+kTAM%^Ki#tRcYk9$sjF6FYac~DIQS3TB`FHcnSBe-r5*cRbLcZ4b-(C z(4x#c8+^6zW-xC645gMdo<@2cAxFW!U2E6T`0m|6QfvUD{#Fzwcpe8fQ=2Kj!Ebmp z=Om`LFyueqoSLIwH=;Ha%ikzqd?9OPT1C$Cs1%U`g*)*edN+x7lGEIluR`vLsc(@^ z-hO-upZ?~)zTUP$)!os}k=&iD;Xz6r+4IR3zOxNxTGGoPu%7%og}6s%S4jV@mqO(8 z-BDi~y-#DM?(?`dQ;}{Kw|O2Zb#!a@9MH1-(~y~?R)R zi8On$l~F-y@E#xX`J&!Gh!vF(9f~efLhqB#4^X5=?fdW4C`o8-ZS5y!?N3-zNbHcF zXOIUpC`o)?DDktrB;4VRbg7lVy}i8^3Q?xp22ZD$YAT$jCs|%WP|+~BANcs~ua0^= zw!8l2LEVi;8(FwahcntvU9gJbYtIs?mDZbzRp2ES!#fNH70mX$uKeZfgV3+JgxO0! z#W8YP{&2W)I2AQ)Ri8D*#=&MB(Q7^w!u5A7tnR>Nv{>;;*N_oNJbzTYaaf2hz{N}u z(E73NK|=GkoBktDY+NFb!g6LEf#-NbB&J)5ZIdD^Qu{lL4F?YfI;wr3W^7y?B8Eln z9v2-H|AHor0AV*eI+P^EZL37NgkGc9DbrPZ*vmjyVD4BP9zDu*nE%Z#?(XQLjMZQk zo8|7{EPqDgo-qr{AB9E5Y=YYh3krfyW&6)qV8IS(w|51+X;`d?fw}q#3b+^+4=*6> z(E38~0_M0FMUxpG^CoVyof$s?%Qa0>oE6ux;=Mq<0~_mrCXfCGBQ|D*vh3*X^*qGr zVq^KEokUv>4Fj(m^<4*ZIy_ff|6h91dhS1i$AJ6kJ1mcksVUO)$kzn-(n6?sApKFw zofMvVMi104{TmRjsKDz^K`cF@&5}a{+@s8Y-6l98;Kw0NP(Zu)EsHr6p{?BN<;yoY zIqGV!h)gvB25)>cKwG4HpNhn5IGXV(116q{hUC`PS{(3>s%#!G?;H37-(1dkwGC-% zz^`um^$#!_Z~MY>(gnZv5sYy~(c#A@Zx9)c3~0viKfm%79*oaZ?>GXwB9*k1E@lER3`YG3BO48D!lwMOBX3B#G~C0 zoC)_^UspWB)y~%@v~?80T;S%Q;QI}4FRC;yxnxD=1x0k|kVo+v-pcut>%yz&vw70v z`P=CbYwp&T)5jDCi;sl+mm2(?sLXLq*g=uGg=6=&TMt%E4PSo!SN_oLt%w9PZo|QJ zDDlrA1uQz$5XT3ggYV_u$TgNX{R3%9X{5->-q(&=XD#!j<>%&nyEY!?te*0=@Ivtw zoGnX==m_ajLl}ImZ1yI@tFRjQKm+pG!P@q{UwZ9SK;IF#83kV34PMMDkHh>NFF!XqP|MUyGq1$_asQeWJ_LLs=80C`gRmVzSapxq; zBl9RYPz}Y1A*Wp32-iMf=38A%9rL*{>H?0j-=IaLI=|#%1@eRU9U+WZondfo5^$>r zek%E$6Xml2L++}>9?KYb?H8RmnNNQa(pk-Nb1gzSmy7C^5 zg|e(G)s1I$hZBWs) zwhTlQN79F49e?Gokrn|{Kuw%G-~9ECf8*SuzzwzJ=RU{&&nfZa(+76yFA!1C<_ls{ z^>0Gmr+4zm*r`!egjNC-1#aV1CY%fq*8!g67WZFRz7ZSX6IUhDp7_G8>?#m*+5&Z} zcO!wXLSk{`PeGum3IJ#R^LZqGfHnq69J~quDRp(N&@jM2RfGT{1~lsYIR;Bc|7I(0 z+5vA11ngQaXe3GyTU9YyMgt7N|NaYL5cog%y(t#Jb){AWNS$mB! zE6rM2hFB9qtCC|Z1Sbmy1qGl?9nzHX*QT41{I-4b&wTRk7dvKiXIKEepG^m9#?Tk# z@r|j(2($UW-XHkwsL=q`r}qu}6*aCzs#cN+6z$q2vf`DoTCzJ~BDe%fU*9b0NDm z&%W#+GCn3YPsa*lXlKLfVRKx!T-kQ6-0BH__VwUpw>+Iqkq>ki{|BQ^Y4T-)-0&dQ zJ-+JYk1vuSrH5#J7L*k2MoJ zcW!ArJfxfx*GOw$wZ?@I((!zqz7T$W5?7)C)h2chK3U;D@#BQ^U#6`{q6wEYr`W!J z56cK+bUD!`h(qj;wy7!NkPWFvA;O=}x{%H%9`ZUpfn#r;3wGYm6jBz85N>?=DX%Hxp8idN7VfAj_Jtu{my<~_Ce z#}5b~fa3<*rvZxv{5SAF#!Q_I!M>k>3uZLtsN$s5U}})~kCw!j$((z4|C@ddtdXR@ zT>8U{unMBTHSagki&+RU{>>K$SR|icz%~AuLRih;N@je*f34c>A*igiN?upZ!t#(6 zkQxqt@P|2lt67zWUG(bVy;`^c`qA-pNnXQD7@d5LGxzKB8LU5A1V(p&^?w1lBWlVf zBCY|lkKqKGXp?H_+9S3_O7!bn#v%<&RIrJWc}QAFQ^e1s1Vu@ABorC;xH0+RPnG@; zhVPLFt2AmIKkN{jsX?JwYqeo~r5uevEB7$7V|Q&O4Dl-7ZTWi)Ve+g~x2HuC@;J_6 zcfAd1vJFqsQ8oX5usSsqQcY*eubD$kk9v+qKYxB~y@)=~5Js1n6q^ahM-Kr5->`A@ zpYKo!_0^$~Kul=zIS$@jMaj9q zm|EqSp%__^oj2&({GjT|)@RE?0);3|I`C&xw zdz@PVW8*J|fDHkLW|iL;E9NhwT3R(5rdl}QTu?bZAs?q99lNC@VVgLlWmiD|35^7; zVd_v5=`SJP^<7-T9>x6A-SFy@*N=*V2#ElRSH_kL9`0`(q#qOy*gV6JNzfb8LPA&+ zNmJyd`11PRzUMMK?7;u(p>d`Y+EiZtiz>060l}tQ?n&eBQX<|23XS8+#KIDs-e%09ee9n=>rK@RO4x27S zA;*rIl6?E7-h=}9AQ!H!HBYj@S(trj)g6Gytb0|YJSptmMgW)zQ}GQ_{|c5pie^I&d#il_}tTsx7-sf3niZR6Te3U`IWR-T7R-KJDfg}`NxP^9%$$? zJo#Vr?N7bkmiadHGR3L$_dHcu24bgDhe517eiJXxlNiU0g5CMw&x*$5NcxEFjgE|z zzlLBJ9o9EYc`)Dk@e-u;nA9M*bzDnNQABQqj|hSnOdHWfsZN+n&X*C@u@eNuu!I40 zsC#IC>Sr9Yw`UJ)5)?UPuDS;*tKwH@71kosr%IYa_#* z@xizOK-=*qTYVv!+{(jVf;Bv)L9);=2`FL-F*AwFz2C9UPjGvX;r@4uWY9W-!l1MO zYA5&eqd!y8_v!i3Nj-h+@fnl26HWriLPX*D@J((qhaOepw`RA}1#v-1?eQkEnlcHV zJSGkuVSlD`L<12z?E4HNOP)j6quI4doO4fUVc?5=ga7(B!^;vPNWeN&-~83n!O7v7 zuci(lTyuJUA_`K{RK11usV!Fnw8y$cREP#He;Fi%`Of(+^b1}uv-*^~|z z!itn1nyPPlp(%(E|1!yr{)xh-JxgV z(9;5$tNy?yguIiMa8z+r`XHJcGN1%?n|)uzPeS>~?IjnO;o3MY?bvCng!Nr0)$Vr3 zS~i+=27J3l-S|~cG~gLV-=Q8~eDW2A@`C>oJNo0SJ_Az?!4hfmu`H@_2RFZ^h@7fU z9CPd?#=r_}+oPc2coVA1t-=|XihWDkcN1&5KNn}lf%yYRrv!W4*9VScxNdzl0m}on zEv6$T)#$>5F2U5HN0`b2y#VE1u=+b{?^w}AMWH#1M;iZ4Xe%ck#D+cK070_kA&$?b zc{)imo{B#av(Lu(PA>i}OAF&clkeU8^+nec4QuqSllxM9)05DmZ`+N0ujP?j>el(q z-qBi5v#-yD&m!8IA)z(KR}M+~^ClYa?EL2(bnE-RM=UK_eN9SldB=J8F`8EhA2d(@ z>4#T=&+_WmuK2_A8=n2~a7a;K;ASvf*vh(tpU=`;CUfB%^y#q;E(Z z$yZpcpfI_3ZK&JQlJ(=$!RFIltAqL0?@2Z{(dIsf0#|GOyVU0=*^w^M7BvSv8ut7~ zX|@27cwj+}yL-xy)ruOX)dzo=7klU&UC?l0rjgA}ewK_K|L!Q1Qz8^Rn;Bt6jVS1S z`>I*2o&t9HIHIt)y1F`sUaCV(jA}Kao$Cebd!X5kQ`w8@Mc&UHMKhYH>)MQ!(RZIk ze!spVA|fFqBvr?t3-z#DtIVy7)ZTWYFgEIJd~G|NL*)JgtUhk7JA6WT56% zSI{sls_oppp{ro^xs;1$#A$cCBIaUkT5v%I8IG9$)ugF4v$cqqfb|7Ekd26!LMJ<& zM+8>;<*Q>4wLs$0H|q37C&W0<@=+<~*gPk!*M36d02%7{Q)Oy9%$NZiTbwZQ={G1>(cUe6zKJ znW>jia$;&*^B1gxb=$H9Iz9lBOt4lf3LNE0Y1?m&MLmx{YV*CUATf~xt}tu)wofm1 zs^d&@qc$8fGFA>qvI$eI^hd{}69jaN!ib;O-KZ4RH&j2Pi|QVAoUMsVlm9*X+$p`P zCpz{`{G-!}rP4Ucmqo~ftSon+d%I83oeAsO%(UvfwjmQAue?7dXP%0mA$v39K{a{}#uji`ImaCiPAYB{W&DzUUN7-Z-E?+X-g7B1bQ^<08g6F=!u zNsLwFIa_Bx6TtS4{`TA*pT^J8B?t_Z7If&zwPlSi}2@*Z}4&Axx};&Eo%TTk9PqOqS* zZ+UrbdMsYELVNUG)YHyPhlNUv(O98*x~g!~cNgt4*cmm}aX&??7KKht4XdPvQ|*tQ zrx&c)F^%{%L@o%|g#aP?PRg?XlNkk)KNp^3M%!()8~`etR4o3Q!>IC8vhbkJOqUKd zIp|#4sdKN2!KNRW1#hGbUr5qctLAVSo&UX6lZ5X2`dfg(v|*Giw`BROq{1R-p>B?u zx>@IDhW>ohEK$fk*ttH{wT#c-6@R(5d3|m}o?~3QPL}no=B(~_X^l+|ZPr`FB*zCl zKCm2Rpn8TULMyTcaxPHPW$}HZtFN#ToSk71VAs~OMvnsHF4xkc?8ASwD|}vl3dw$# zYM{1cO4Sje-xldF<)7llH#gyT*CFtKd_z?;HAJ^3+YYFuJp@&$4_cv^HXl%`51r*Q z$~o>^ex7>79i9nn+};2%i#G=jTm_x@1U zu)#&3_-XK0HO9uwbj0=V_4CR*l&>Y`rO=(0$^%bFy!cU~flENtrpFpL0wnF*e zo7c|mx~Mt=g6m*vau~Z6baQ7cTGG*$8TGt|uq!qf$Ga(BL?Z2m2qHy5S|z?tMk|roecHd~Rp)Uzrcs9M zukz5OCg+t-7IC13E{q7W6zd-D0}LfXxHUM#R&L}bE?rCx%b6+cYbEqqQW}lq=L&Yv&%FAqV`VA_px7cjuS}Wz*8{8jRpK89+9-Oq}i3-$BaV&0? zI_%fP*3y-63_1u1cNoM%2)`?GEK{dlMkIZme+7Ujv&fn zN+l#TV`+NX5pX_@ZPT9WDAZ^aUXNtzt-Ax=+@|!XBuijgAMYRyEL2 zSv{zZc_)<_`CZaKK!Uc{$tr9;u>iOir|*1xU~9xPtLOa64Ws=TgxSC6?furE7?Vq3 zTVNi9mUf>~mZ5H|5mG`zs^fZkqq#OE%lJ{_8;!nwqQX#SAR!XPsl4Sb{>u+NxB&{J zMd6G^eKFF9g>V&Z;#1wJ0@dDZR1<@C=z?OgY`iH!@|N3ki!bq|Ogz)t)~XOk3lCfD z-FfLx=P#32zegE_fukQv3CGHb#MIc?8zECQa?**i6Jw=+-ns69C)@iuWL1an^h`%< z!pQ;n#I3>WlnNPa8EIp|-jMp#Zx%M=$ZGVX>l=`FmUT(G$^d`kGO=6e71GoaVY?^$ zpmWZ_Xb$Q^v&FmNmQB_; zWz_9?`?6gp9F69qyuUv^#%9&djzxY9>yq==(d9CL_5>VmzfCS8Ok=psTtDjnZ7EdD z`(VYmDJXSGZpg=+6!5la5|HCCb91?+&2+NNGT7{2M+o|y9Go4;TE7Kf2nfGCe%1SW zmvyfc`<(;Vp<~v*^Jv=CA3}^RrL`y@-ho+YrSHeq=7&s z3qVpAi0ka%!P>;rvKTn|(tdKkmQ=Ysc#iGmvHJov44Kt(M`7gQQmy5n`EThgO~SKN z98V2@e+kSZ+a!RmPMdb*O`T|=VyJrSJZR!qNL7%*5X)U?OrdC^!Qpl3>^%$6>9iKm z*(oGrf1D==>G=Udy`4+Z`UAAxs+xxJ+~N@c@ejc}h20{yYhfi}`>=o~xBn|zlH@g6 zDNI#8`>O*c2dYo@KI!n%Gss6;MR^NBW1I|yl2icq*&b0S*{$KS_0Z+Dc?wH^acsB2 z8_jR@4P-Z>{NYJOS0u^iA16_CLXCo#&QF;sBnzrq)+c*PvqFY;I%@(CXBK|g+0FC` zp7Z>8y4z{BgKie(@>{Soe3f#aF)@FxP4GPqPbD2w*;1Bo$+zb`DP=EMP1`_`^Y*sG zAM2DQZoTI|`%VCl#r8r^M>a>?2@wxr+#b1yX1H9N2c3R2Fkr;?}<(svuLQkW)XfhSEl)Wk=Syp{4h7vH@=gpiz45mh!&#Ew#Wj)0P{M z-Iu?a$^cCI0)r#Y5SZTAtr2<)YXEz3cwf!w|HhAyI{ys_Z4msQfKYG!n>HfI=BE~l z#$y%UWW26|xYCC`i9+-^f1Z!`WikZ-oEAxfny!cVT{LqF23w>4d6mCRN_GgX;|XZr z*{`+GI6!pM4=vq&?)Mk{E zzs}dl@vGSF=8za_4AMUd06G`tVijAXMXsa))RRy}o7`F?P*;0^D7&A-Q3iW3b+Z7z zoi88x$mEL@#k-;W@e%W4FX70U2rVh`P?ytb`{iF$7~c)G*H@5d~wghq*435 zhPbtoM@HJ=XJ5h057_{y(I`SK7Z(1_j4<_8Svd@V5CN%oU^>DdTe%(LFBSwYdCcI? zCsAmP%Zm6P{`M9=Y3F%?RSl;CDI6|$n^x7w3}e7vW2zvJs{;kgnjrzy_1_j$7OMA_ zlD?%Ub4cZ#jOrP9Z72kguVQ^EZG z9|3yed&PhibP_Sy;q5R-#EBiH|e zm;x+)SHEHmeQFP2XAj<5FP2E#yqD9y<@_~Iq9W6LXv3eD?zkMqM*z7Zw>-n!bL^zb zS}DHG2BTFyfSTuDcJ${g+zv6$CxSRj7CD|ercTx?S=eG1NhO97DrsgwlPQev|9`E0 zbzGEd+wC?f0gIAS5Rpc@K~h?zV~~^>Ivvsiq#aTkq@;%K5>Obrq+5m@N;*W&J-D~q z9q)U-^ZkxL|5BLenJ4ahU2CmtT?Fr`ix`wp!FGT@Icp22Ph%Lryd6HOh80H%{ewGM zciJUl9Rf5vcrySogt_~F@9Tdit7@O2mY8qz4o|=`kRuH@vTRO>7*gjF4>R95cspqXy<_RTQiQ zxn(-?+N#RZ1+r2-DtmPYRB^7mC0zY<$HG4D!QM6TyYAfQ&0My)U$UkO?_1=v7RDpp z&Hd*l(A*D2c0xw)23HW<9eV~#4Q6_0!*<^oKaC76BW>D82iXe0!->s3vJ7A9!dY&0 zeH-}^=W6Sx9ojn({{{R@q)5n4<{+hVVUCw*o3ptCk&`Dc7HFP04!|!RQhp@=;_OXJK`Teh}D1X9S zN8DFe!0@NLn*khYF(vj?4H}6os-SP7TLI+^h*Z&&IGeVo!6q+uCo5g9?T}h4ZrOaU zBOZw_LfRc)FIs$z))^Lsf!P(|9ijU9lx)30)oQ$)Dd9GT{N|+IIZ}y+Fcir{@_xPJ zcts0h`psui?~G?sQhJvLAT|j$SQ^ParuQ83ilbIh3cAqWEwhAq`C}R19an z2I%$0vI*&D`3ZKh_&;bv!kZg;46oQ{NogF4Z@V50hd0geP1rDKQiX<)-msqB>a_jTHwxd6b*0fx2clMvyirU>g zVv$Nvc&=Di%2Sd&@xDkzSkZGXZU>bF0`ebZC-BJPtM_MWjP0Be4i^7S#XyuxQd`MK zG^fr&+cB=uN%4)#E#A6`tSHtBeb3mXCv0$Do9wmX9^!;T*ie6LGE*?lw%(E33hFzb zi}K!8*eo}%kZqKP%MCoVXr-#>SIMvl8f0^kV;H#JKR=VU8NqJLkk8Fkb1NB))9BBPj{4g%a+t1Bz*rmw|mvo zy{|=}wPf@uIWu3vJ!M||Y_IvXj2p)2hUij|ifd}}vcC-`(5g0Z%*osr8}hBwxBd3v zr*}svj0_SK z+lQy(^ul|`c?`df+!;!wp{3Dt$5lomC_kq-K~aS%DJn|?REYO^d8L($lDDn8oX^d> zNXlTLoG2OSVp*7J!US``e%Mq=2lV8?f(ivsHnk{ur9<#I_U5X7EOd~wlB>Hw(8Yrt zsKA%##(WF6Y=i2po%vs%O1nECIYsMkbcR`!j#Plq9x<%(qEXENjU2R3f(JhVZK>~3 ze7Nk>xp90=RKl}&A_^B#QJ=M@?LVI)jH1RG3msCbeNgTX`rD6``Weqz!&~ws#~JK&a+%hEB2)^6@Yd5=&`mxB+BWkfLi}=>8p-qt_0ARPUf1x5Q)y0 z8%#19*SM>Ls?=%4cXHj)dTz@^f14*YS62B(C5+=HwqTxV3`1I_p1Ju`kBb&k3!|DP zC0FN_h8SlrV6BM`SRzoIK<-YL(SlR+}VzOlC? zK7^CRXiT=^l3H6k10vL*we`{a_T6&L2dNUWNd|&Y*XUf10i@B4_eCD`RTzRrz?saB zNKdssK2UR~+d*l=elFg&s_*t5;!Z^{oTLCk!=q@>cGl>BMVNq{ z1n3=kzDDWHkd#NfkAhozCyfRM)S!yN)=@=PG0f%53REt=!`B_k=c9=76d+u@UT%5w z&nXEqi>C^jZpK&!8Jt)RhnGL#U2~C_WgGH#3HW}Xok6Hi1F^}RfGVZTdfJUmZd!9@ zzIVfmUZSh#p(dFsEz8?BxxOT!Bh5_Xm$@{%!RWNEgD&Jz1{i=Zc(_JOn_EqJ;$L~E zv&pf{D-V1JA;hJnBb(t6Ujnyd*f(N``jXw^-I&qT_#{U@iitrQZ6mHF zF}zn&A1ehW!qmrScu9WL901J#4H}?+VCv9eD!u?_8b+CH5LO$!Hiz@R2f2BHHS2OU z&Gmwu@F9jpLh`$2x~QEb4Ua6!6J5uYV&z(G(eFd03{GO#yPr6Lw_#cbHDSu|HQVe5 zP`Z4ASt>6*Mmcj0MJ+I<6?KRQ_k6(z`fW#KrrGsn-Rdqels36Yy^R>EbRou@S830h zwIdyYW})#ncDdTt)OJ*!7so_zS1G5J=!_5@ z*X*{i4MktrFTk68eWgne;99NDV2FZwiDp=t#WwcK*FpkHk3aj17f;ya=Y$=Z!xtEq zDuT5Gh0LNgW=3ebRdiMKES9I45ShU-UkmP=Jc=4LRX$Qm5D<0>thp~qHTGh4$U9JI zw;=F)zjah>SgtXG7Np0d%=pJ}@7PUH1hZI;OMC^m9ONHjly?e%IUDLhkDldhTlQvJ zUkxM2z~Cp(XlF}xjx3@4wm~D_>rDOWWI(_3=)uQ+2*m$*(yO2qwr~2G06zSGM12?l zU%FgLI#EfJ67r#ay^8XFZKc@5qvT!dvhj9Ow|nnFUmvs(&XjJ9>J(UZ84v+=gbZ)x z-I)!S@nIK}1lhyjgM^IbjQ3I~Rz=ZEnsSo*J!;2?%r+GtPL^z|q&-`hb)$R--?3Sv1=Wi99d9~v*&dK_yHrV`G(E;^tm zzvs;_^O7T*Se5t(r&2+a=*2t)9skFtO`l@Xle29xEBzwnm08W>D!1}=H+>m=p`}C2 z+z|IdLo9A@)P1s#goA_(n-yoq7XKj12AofcGRj^S*1QOUQQ|j9s0Q7ZyTQ3(y7{+; z4L4~*%Wy9R$_XX9a4bF8h7Z5*DOkJ>vk5K~;#6nHrY3(D`oX$GdMq#mMR1FhHa~rX z)MA>%zNkZ;-euSIed{@<-`w)^XdQl)q~n6)lOaa~GD)ki#?1$&58(?Z*`EGFMNH*8BmGa5BX!p1l_G`A zfB@cfuwt_fh~_yePx8MSV7`X#MJjx@p1%0bH<%f>9ld96z;xFSe}M@ZNLGNP<&7WX zq+V0Q7!FWJxH}Z$tl?&N9Rsr!I^##r`LY3qaqM3xFy+L^ zp#JY))<)l4m*HgNJyarD%{p$1G+BC;RptJ;ID^bU`|*HVUo=l#$nldbcMl<$x%BKC zV4-^|3F>DO)+OS%NJll(J1wfU7h7Hjh$g0{DCbj3qV7AFZ-5EF)Y8rJc)0z>mB#dQ z5!HlE^g==te3@dzEj-smO3Q}Ak|<=G2RL!?HGdS{a}>JfT9QuUI{pnOs!%Kv*cI6k zpMa;s(jQf!Xk2AG)AM{*9&%575^8g6cje~nt1euoa=c3Ia?kR#JpZw}{#QN-L&#w4 zL7!q?3njhFOYL!}mMni~z8He@9T>Kyv7?dXQlS5aXZCsARSFSnYwI(*?9pFlPjDtg zJ%&bAKxpHSdQfi~!vy@|2SPg9VqILv6s(s~FZ=-4GUGu6bM9?!_p%D2t9{u0OPKmE z{7->YBA;8B$lxSVN7VrhD4?-lU>5QCOq%r)heRcZ!1To+pNs!stT4wm&i%dX7kO|7 zbN3HMX>8N>zX#S_G2)|P!;~DrUIadnWAtBs`Ygs;_wJv3RDskae{z0{jqpI{6G{8* z^qdC!jB^UKm=ohTJu`5ff9;R|S@L58YWTjc#+w8RrhJc%xI4&%vL zyMJ&A)H_*gQFG;4bXN_zLMayw`zSXkQ4eRa|2MmB|~PrGv}kJ{t^3xVX~ z^&k8=tV!%VGW?PlZtaRMxF3eYK<84!A>trYD@c3vBh%49e$-l>F+HBC8_588VsWy6 zGmT+3?8`76b%D>gfodaEH2JDIVX|{D^)lk>6s;K6&bx(SUk*7;-KuEHCGqCw1B8Gs znExbMaP=RKnGaHvSy#8HZ^yz=6f){4{xQ7V?r*IHse0QxyDk|XDfO-{=UhW^ny$j0 z3qQDR0AjV5R%4;mh~sZKEWZQXXP9sDmL`8fBPQn|>bg2v?hdiFY?)p*Qj<5F&f51~ zhm*DjyxEd7Vh)+&pfT|p_$?E$|MoCQlp<3(bt0>g=-jSIIY&rHmtO9hHr(KGCwGZ+ zQ`q?ku5dPbjBm;wkd|@Nu*(%l$B*RndY0IEKCQh(FtaYesU3SJK#q-11~E)4@&ShF zWT$~Z(M*n}YQxT@OSsqjI*e2W%Rc~~0F%RrvDDd`|LsJ+G$XgJ!vxfUNwryYB9WQ6 zqiW+ijEZH`Lz{;t4iO6>IYPLENeY8HG(ni9(@EA_c}9PhnvMS0?x$4|BJIA>KiW>; zBX#JLm0)s(ev?Ya2)5^jA93pKn61A+0eU~za?DIpflqNZpR;b~^&Rt~wH(C^LHhUf zQ-&G>1~?-uQ;~0>hcYvWLsfK0=CFD_^_+pG4B`c1eSVq`I5i4Wb`NDOW#1ewoN%Sb zE)ML>vkZpqv{-oAM;p@B64|sk&oN#T^zzPEvGh8;(|P_bMP}u$N*NRIhD5(2ZN1fO zvG%lHi8h=z#JJuTQ9~PS(r{Cn05yJaDI++Y6R(=(qRQ2ygVini%SyTiwGx z&TLFlSBCi@95ZN?Hzgz@d{#F8v@yir6dLHeFqEUrIk7^w*{nF=Nj&%m{*Og zs-E2P?pkdS$LmN>A>3kSxlfL_owR1WM~E;A^CA?aui&RSVo3c#n_kDzrqan$`qq&F zK>;#xsC)2CwobX|wk@c_siE}3FWyiLDxm6YAcC7{y{zE-aBmawfE-{v_ zU%aZ~KOf1CH6&g@YmIQ}dr2I*XjX$umWZ5H4rK-dV9ul8&^it9Y+3s87Kg0L|+l z7Fuc43A@KmtZ%DpjdP$m6EEi)i1nI+7BeISSha4Pu!_Nlve+S<)Ad@u^l4pXNVI6) z{v*Nf<@4fZNp8hn-}0fVx#J0YR?-WV*9Z_&laQ#Z`EJCgCv*NQA!b7bZH8+&B!E{= z&&knFV50_;=ph$GRsaOh`ehj_h<%J1@lXq~*3(u`REyP=${4MxSTFk3RXHa{JvwS6 zRi(z*osXC;qjlP-e)+$UU&Y`HA)z~2o^dRDCm%00{;s(qB7pFYem|zZ<#$c43ZuHt zWBZoRHkZ+&75Z#D*16PqrC4ZOEe;JU9?@*R$XFgSvv0caUD>;zds*8 zDRC5H+9pDF^*vqp)O0g4{Jrhd5t_W)3&zYgj3xGS&%w?ZPYvxrCjBA0awmw9B9KrX z4NW!j+?^e;4O!@4{9J>(lV@^2F6FgT2}_@rHHv}shdcilEB}vsIq(frvteciHti(~ z0|PIe1grcH3-6IHt>T^#sd+2!J{YgC?9Z3J^M?PGNU2M}?x#EnQGgr!DwdolN@T~q zUVN^PnHt7Pr)AlEJ^ZC5XsbP$ru#%4)a53cHt6Ea&P2x2@lN6N=>HkkzB?cAGA`F^xbxFqU_ zQ`!WF`7SqCc&0%z(wH+91ZxAu{%57Cu?0i~2vp`G1_0f+SJg4b=^O6cGw{HC^b5T= zr)1}Yp;6E%(DFnY>t_xT_-_)v-1+~GI04@KHO>1}ufjZ@27yBM+qXS)Ynk3UZ()Sj z%jXTj?(?rEcW{Wm0n2}dirn>Lq{b{53k>+%MTl8y9{#zM{{D3fFXbFu0G9JpP<)>3 zB1W@XNJpY`qSy7wvmFXX&Q#@uMRcLAvU4$+ zO1$NgrQLQh+37Kxe>+vch+%mdZ7)_X#YEivAqnciWQr@RZi~tT9LflMOnM{8fEv!; zr`894Q5_i`2RnViDSh8`>S?;;w<&c!pV^kBYY!W#>|cT9Rbml$e|Km&tSC~_;1B{) z-uXFMDcsLu9Use&^scrqc=8{$f2!_Nd^sif>wP&rQ(&o{T)JpY_L)4z<*vGgHrZTB zDNQt!ax?=TS1`i1!bMfeD=I#s=|xZthpM3NZ$KVI+h}{m>Cjw}-Wq;RQ063Qp5uM* zgD1vkel4+2J)_q`Y&dfSk#w_Hfsn}4hH^JFGwUUS$Fvx>rbZ8h(_Mu(-wE6^)9+ga zXR|cl#96$2T$`ac_Rh>HTA4I1lnHZVeh0q)gp2XzF1y)!mCUsCqP(g+j5PV(iJmu2 z9&6CXQ6^X4v7hyoEcO0mC_8dJ3@N->KV+yuXLGPSMoDi?ql4l$Th}GmVHX$kSDd5SGL`GF{4CW6(pzbLPjs56t&v^PgnQnSbbbn1Xoo+Jz?U*gmGhmq`rajc>*m|1Jtd0e$)>t>Mahk zA2C?$G6sv0!V#rAk(C@UeP`O3HM^2h*GEabxHRh>TvQfsNQ&61eLDFQTn?h9o9HA0 zYebB{+<~ud=r|JQ0+|yC!oxRBDnfcbJwtBQh@X^@_~cDGHGWiVg5}DPm3ATV_Y~&W+xuXd}P1 zBuk~y0*zt4-~gfiuX#C*xEILx$gc5P@!fgOdi^RqCdIfGLvQ;>)xv3-cuJE-y$9dMtST#DiWoPp1 zenTF`L5U#S%~!Hp|AU=XaB(V^QE>Z!%0(TL#BBb}1hfEvHWaWjWo@?}+n))ms!evbQ)r-+aQv*t2?^_fH zYKt?|V_28g8OEXCX6MrS(GgJmhL{PD&g6D;LN6WA;!*XWp1m}j`HSSFggo=Qe*W{# zEZ5J{7QW_ZWIhi_RS^#9&U+f^I9AAVjuXEqNcvYF_$vGX>!7XXyBOXc`{?RrdBvb5TZq=d>R{Sc zT^TltTn_&Z! z(s7f!Lg9r~z`hFGTCmkmJvz_LsPqq&6yGH9zD3}@ptyIn&?@^u=pnd`^y5Zhoc^Sw z|KF_J7JoL$v|i=7OhwVCHU88N?lpRTV)W{xHp=J~lfIWx9(y58c5Xu#EY@OlA19-8 zHAW|^R2)xyi5*U+8DDUfcV#p6IKOuxo1U+axtWo3?^g(YUI#Uxk;Tz(s=cPTDGeH3 z=P3zB7f=u8P`#G>Ft0Czk>uCf4s@~8nKZ0Mip`+N%};ps+7*`WdS0GvA*Yld zm<9R6w}t_Kd5F?i`igaGv5=9rC!d1g3%BBsE9zUZLhbwwEs>6OOLF1k)v?n&()!ab z{;I*-m!ES0*FzGOHls8OXYiH`>dDcVZkZ5*F2Mxi>vR_`gPR{eu{aAxl*y8A?`(o* zT>yxILH(3gqRCg(On$?tHcHGx7?g{IvW4t-MK`01(`-m`zCL(a6Kq2}b>%H`cAF9o z@!VZGrXtMQ7&Oj12YH0U%0HNa3n}%r1-7*dj%E2-S*?k4rK$nRNldp2WV+6JHh*-6 zUgKtkn<*xib{}z<0L1&I>a&W=qYl}N|+{cA&MORK*j)M z5&jA5F`HH;vb)<%m9?$CeIz1q#=mB;&8ne^HJ6S|1lYBOuS#g!m=znf{M7i0fgQ}5 zr>j!hb1#EuX2n+1-}6$;n_`yDLuIDA&mkvyl|3VwleyF<=+Wy;no}G9rA)O0KIFox zccTV*AlBPlev6|ooy_M@qbRxd6M@f8sVP5zH0fk_R`V#$?rg@YU4im$)hc5xf{Uk1 zw>TqkKCh075Ih+r+!L`9z1127PEP6Af~k*`vS!8WCcGYv^`cj-5kh627TA<2`Ri(C zy!Bc97EQ~vL=XL}EiIRM(yYF_w5xEWB^D1EvI^MmPvcN(w}urN4v1;$B$+MVe;inq z7IsNrMtW5BlUF(WZIuadw1R3=tm4)Wo)CP2U}pnmlm^S#4YIPy0kho*6)N&L9j%BW z)0ymt93s?KeSHcp?aqYyRowofBcCNNj9>mc`gSaI-?K@n>^J8++Fdo~Wc?OcGl}3q zvh+eLjM zc8yED{_L)?mH&|>?~KuL#^xE(2diUNmC^UMjX=lw)Q}OarrckR(f_SPpT0WyuN8U} zB)PI5V~ggN|GO%v=?Z{t;mkLIzIjWZ%m7eI-kj2W;7Kp$%d}}VSBV=FK?{K-e=>L0{B$h9KW?e?O%7+;(XM z<%ALyr{Gchg&Y?u7JF!K^P&d5Eo6IOC7+irf;T~AmHlqxV8aT)jwBasLuaaX#OLmu z^eiVhXp&r=0*`>M%8GCLc&e?LH>kfl@1>2QK|KF%&0Uv>PiR_)^P7eF4C_68l{vM| zS5b&p!XB<8-xb^#C+v#Qt+ddE4oHEA<89ZXzqMn}FNm{7uTRxKPVN2RWNp2gBHS#O60fc;hQ28R&h|^b#W<8(tME8SMU_xuU(V z;DfL|Zy8s__1Ih!?xS`>dV;Sg1q znQLl&ttr#q;#jhL31NOAb^QIonY~NA0p2zFChu${$TXK%%73*-PcK&ilEtB^Qg~}d zi3n4^)#Vq&hU58&_l}QuJmK5i+#HSwyt1St$ORJCg1G}qZ`mVS*e{83O)h(CBO2y> z@vp&ZcN>Ry@S<1vG+1;l1^BvKu8Kiixa|Cerg}2RS+yt=CDNwYf2LdfzMPAmX#rQx zW4}NrFC-`^C@}El@-^LQalN5YxM8x?WO4;VUg3F*m3en|k<;3n^?_Dr>!-De>hSPz z3rJ37B|i%bzT6^BVj=71ibiqXh>TgkzRXL5-^yqDwep#i@uq4_)*2OYM92pd_!l0c zcAN)@?yTbIZ**oKb2P;y0EXeOiz&{tQ4>8qiqEv*4;qcu&U#kNaJuPVH0=lw+8iHy z-f(nEJycE`a?_K*U@-T+H6OR=5i2DnCEw9#@f3r0y&z|}Of%9L@>u3V9HY@}wzZ4{ zORCvuR|*o^F<$THX&!sj#Ow>$;=dld(ztl!%j_%^3LU8t9HvpG5SFEeuH#aXuK(@` zr_gY``cOrs(EH^31Qu@Z? zLfZO$ok*w4@p=CzZtoWz(FNW@ij+>J3~`-;)p(udiaZq9E8h+UR}!Kw7IJ5ok-og0 zEU~>8>tG$2*b5OcvMXnGq1SG=Fh)llubC2g7hK=$ENnV%TaZAxCRp_{v) zYF*EiV0D9Ys2~02S@$;&KF|>A_m6Rd2e}IR6Fl-)PoT{HO%n24xi# zf$+@N7PsGH<&r*qB0>N0tb~ptm#dg>{+?F1q1kb@mi03vkGNO7qeMaN4)667j zT7Sw|*|n^f#Z~SL~!+g?)D$d z!qn0VNcNC8ectr2F;C6;o4>PrmnAQ)uC68|a%lxg%b$xogRE&C?3jO5JZ=KPd(1p; z`7FdmUOld??qw&gc~5y{+$i~_kiM|8Fb=;In`tYZWeV;&YdSh>(?|k&Rjhh@8Qp5B zg}ZE~fgQo)?{-~0W#gJx*wq~7210x_st%L6ZcwIM$}oK{h#Os1q+Av0Qo6p1AJQl1 zcVD7w%EVDEIxLmfKDyV){zI#s{oB}zQhucRkbsqd%RuwRp#gi%`@9VOW#zr%6)0jE z^_V6jXEg^+NQymIRTOvJ(k%)iZUVehwJv?Ufx14gHznr_0p!bjr&V0U*{+Wr1T*h> z%E|+(Z`m==WSz?Kjs1gz&C9wJF9T`@o2CCutNAf%$&sK*jS?&&fBW|*LB3f61lM72 z3lsMy-MdrIJMO;BZ`8(~H2L|1oG&WHyo~g)qg&M|TY6~n*@P%w@spxy*T+ z#W&RX@EYQRevWjKW+%x&GC84aSH}*IvAtCk2JA0^Fgo{L19mf(+I#oDxrbg{ZPvoB z!!^70H2mHl!8aY)^NrVG0R-9wocl*tzQ22$JqvhWGF>nyk>{W1bRX_o=U0C>W0?;G z3pHziAga#k96RC{yktLz2Sl$la@#05p=;7aAS8Xh^McQ_{$ znrswdlry7CQe`)SJURFFgO$0&@_6P#$n7bX$(wy z!Hj?WahasqCBA=rkK<`^W5F4l&?@&?V_T0V;@#Fzm)vLgK!m)P3<FMQzEKS9zz9R5BdYWr?0`kTlyv`D8(zPv~3oc4a7(@>uZ z&dNzk==G=9-F#24E$C!#aT;oxbt|dy6wt#q^lfh-A4~3)ugwgUyi!U; za?*T(;&qL+%u_Z;rT7~GH7cDCKuA>sc{5Q`Ikwc~L-51^>&wr$Dbk>uX98=$bz1wz zuFYF;wslVdRqDOpUF~&}ZDDXB3{L`8S&G~;On#s#M=wW&A6ScGM z(>fMsu2Fk2yo^xyNzS5MIC;QP6hLyjRyTBGe6~~ z3M(4pYQy-Mv3rF-7%SSC6I?m^~Ri|gn(Ka znFz`GdV~r+Ta7!C;1ppqJ+VGex_UY10Qf@-#R)zYq+8i0?io%y?AuhDbT+Bj@p(h+ z{l1+=^4A#2e`#f}+G6p)sFCV{T4Q#vmHL7Uk;7fO#I^2fl04|xEsd>Pn~Y(Qg+(-hmH+D(lx`E} z@Beb!-d@4@2hoddhr^p+CrWppcc!y9QF5Z`EQD*yJhEBvHJ)&Zy7`)zto zueJ?g$ul;x#Rd0P?<9|)m86yUDHiQzuN&W_?y0xjvw~w8&LjO=M`!L8+$dD}W=V-a zRK%bAqCtx$e49K5Q(xe)F^S!SetTr|qF6_bwowH^D;_Wwm-8kR$`JG9kv~PC>m!sM zYm!t3WbWLMI3gNyA-^0s>4D|a#Y|LR)N<=01%>~#`2Hp5DI`NLB`#OYUgbdz1;>nY z_rZpsy&%j*)5q+K{g$ETf*7hYUJce!HCL^?ob`!TCpgzYVXU~3Tk#y-t#QlTSE|py zwT;t6Gtb7~*m{N-*iS9!oal&^Z-y5;kO_b3((|?XbhavQPrK4z$U6t$Tr%STdB}<- z6C$4palclEyoQj~m>x3mn{D%y2}06}E8_KQ8#29+;8grN$LPZ2@OfwbZB?WKB@u_)KL1p~Mx_{}(d0J`I*Vz^bn!}AhUToIv z*uUn!ZpW2|h0$}pj^iN9vZek0{iY9BEJ2>!>9H>uALS)PLT3!lmc+1hv$2XAjJywDaNn$LO<8Q>y;V={0R*b7f%QwMT z>9PassIC`DObQoTTfFwYCiX3Q42Da9HMfTsABz_9VGI}>`>=F?{LtmY8Z`B4T#Ofs zv4Y&gx5gG>VPkuCJR)Yj{mLZW3>W_6MrCZ}$?ndDz3G@<(8jlH)UPv=2xbw%FWqG! zHmcs=HIHXD)(?8Q`rOfRJoelhX={D=ExJvEB;E71+A(tdxFyMUv7bs8ZfRVj3XzxX z7(9dD!bZv0eFnZzcE^^bHXkVi<~Hj)7cPN~qHGJl|KfSFxb&#tN>OZk{S^_%0%5zN zAYI4b-Zj?-S5}e$i+_{2aMe9-E15#~r)*)>MAvibYCjLIu_@ zgWkv*z_9T(md$-^g?MUsi!hn7*a42_{*V|6WJm>M4nOy^diX6+y!;ZwCSt}XLH7Za zZ+;V`wXU_2o@-OS>r$g-1a{7D@QTmcqQhrF{Py1(>bL_5%Z`qAHn4m1ThH zA+3)3y1JtGb@0(`3IE#-Gahkl>%t%Ih>+iQARDe!CYje+aJkRP%XWqMHFC?omAQ^h zrow-3-UCPUmJm#o*SjZ(4&LHZGCF%BztSVhE=vscRWOsRF*o5He8KDcLO60(d^F3R z64;J!EW)uRsp=hLZ*`24UV-`8p!bLU-T$)6bdbFkI_dm!K5s#<^KiUsuqw2aUIT^i z`m}B)K?vf{9=$t!r1Fv?(8ORt z1VT#P;PceK=GHp7DEL2>p9c%9*H&6@xnHBV=T_?BiCW^X^uoUJhzoh z4e#KUUA=^8`IO#N(pdNY+dHDBbsa%m8e4-CcEu3EAVevm6RX=!l8Syw&nK-R!7s?s zhe2pqj=h9}B$_!#M9%F!ZZ285dFw{?V8{z3Ey-drG!LEKI`IL_@BRb&&F9h?z%|JZ zKMf(Y=fb8SOdx#yDMFR(Yo9u6X^U$fU4o1DmT=$qkPwd+a^a%b1u^&3TbrYz9#$dH zvYB*U^Qqw!Nh<##0r7iS{ZzAS_i>;tGNaq}-K}$kEgi|wlY++j@4KjP#PbOiipm?$; zt%6dSXeL;gD|n4^c+B;%v1%OGgiOWZQgnU0gD}&}oo>^(aEWS@_%V?QDwhVO`5;Lu z@1m86ib0?2(ZbhreF>{S`~m&g&A35>ZC7bw(te2})AhqEumYs9smM8q>46^v8#mu` zv(tNGiFNZ;+z09H*B{q{x0_h`mYVIdxI}3PiD9`b0q%H$hU+>uiU?&^!{uPt`i@cb zS3eiw6(a-Pq*~Vl3u?{R<-h@#{fZ>;NkqSCRz|u0`jL-k81hM@@RlLxIk4K?QG{ z=JF}Zi1W5Tiq})V1mmT-X&k-O-KkZx_QqrF# Editor & Scripting Host**.\ +On Windows. -![Screenshot](https://raw.githubusercontent.com/goswinr/Fesh/main/Media/screenshot1.png) +It is designed for embedding F# as application scripting tool.\ +But it works standalone just as well. -It is based on the excellent [AvalonEdit](https://github.com/goswinr/AvalonEditB). -The editor supports F# 8.0 and has modern IDE features like semantic syntax highlighting, type-info and autocomplete. -Unlike a typical F# REPL this editor has the input and the output in two separate windows. -Just [like Don Syme always wanted it](https://github.com/dotnet/fsharp/issues/2161#issuecomment-270465310). You can even color the output with [Fescher](https://www.nuget.org/packages/Fesher)! +![Screenshot](https://raw.githubusercontent.com/goswinr/Fesh/main/Media/screen2.png) -Contrary to most F# editors, it uses [FSharp.Compiler.Service](https://www.nuget.org/packages/FSharp.Compiler.Service/43.8.400) in the same process as the UI. -There is no separate language server or FSI process. -Therefore, Fesh can easily be hosted in other apps to create an embedded application scripting tool. -This was in fact the primary motivation for creating Fesh. -It is public since 2024. But I started prototyping it in 2017. I used it for scripting in Rhino3D professionally since 2019. -Initially I used the Tsunami F# editor, [see my talk on Louvre Abu Dhabi](https://www.youtube.com/watch?v=ZY-bvZZZZnE). -But it is no longer available. +It is based on the excellent [AvalonEdit](https://github.com/goswinr/AvalonEditB), that is why it only works on Windows (for now).\ +The editor supports F# 8.0 and has modern IDE features like semantic syntax highlighting, type-info and autocomplete.\ +Unlike a typical F# REPL this editor has the input and the output in two separate windows.\ +Just [like Don Syme always wanted it](https://github.com/dotnet/fsharp/issues/2161#issuecomment-270465310).\ +You can even color the output with [Fescher](https://www.nuget.org/packages/Fesher). + +Contrary to most F# editors, it uses [FSharp.Compiler.Service](https://www.nuget.org/packages/FSharp.Compiler.Service/43.8.400) in the same process as the UI.\ +There is no separate language server or FSI process.\ +Therefore, Fesh can easily be hosted in other apps to create an embedded application scripting tool.\ +This was in fact the primary motivation for creating Fesh.\ +It is public since 2024.\ +But I started prototyping it in 2017. I used it for scripting in Rhino3D professionally since 2019.\ +Initially I used the Tsunami F# editor, like seen in [my talk on the Louvre Abu Dhabi Dome](https://www.youtube.com/watch?v=ZY-bvZZZZnE).\ +But it is no longer available. So I created Fesh. For hosting there is the nuget package [Fesh](https://www.nuget.org/packages/Fesh/). See hosting examples [Fesh.Rhino](https://github.com/goswinr/Fesh.Rhino) and [Fesh.Revit](https://github.com/goswinr/Fesh.Revit). @@ -29,18 +34,18 @@ For hosting there is the nuget package [Fesh](https://www.nuget.org/packages/Fes ## How to install ### Standalone -Just download the from [Releases](https://github.com/goswinr/Fesh/releases). -to any location, **unblock** and run the Fesh.exe. -Or build from source with: +Build from the source with ```bash dotnet build FeshStandalone.fsproj ``` +or download the from [Releases](https://github.com/goswinr/Fesh/releases) to any location, **unblock** and run Fesh.exe. + +### For hosting in another app +use the nuget package [Fesh](https://www.nuget.org/packages/Fesh/).\ +Or, to build the Fesh nuget package run: -### for Hosting in another App -use the nuget package [Fesh](https://www.nuget.org/packages/Fesh/). -or, to build the Fesh nuget package run: ```bash dotnet build FeshHosting.fsproj ``` @@ -48,16 +53,18 @@ dotnet build FeshHosting.fsproj ## Features ### Syntax Highlighting -Initial static syntax highlighting is done via AvalonEdit's regex based highlighting. -See [SyntaxHighlightingFSharp.xshd](https://github.com/goswinr/Fesh/blob/main/Src/SyntaxHighlightingFSharp.xshd). -The F# Compiler Service provides additional semantic highlighting. +Initial static syntax highlighting is done via AvalonEdit's regex based highlighting.\ +See [SyntaxHighlightingFSharp.xshd](https://github.com/goswinr/Fesh/blob/main/Src/SyntaxHighlightingFSharp.xshd).\ +The F# Compiler Service provides additional semantic highlighting.\ +If you want different colors go the menu: `About` -> `Open and watch SyntaxHighlighting in VS Code`.\ +You wil see any changes upon every save in VS Code.\ ### Auto complete -Auto complete works on enter and dot, also in middle of a word. +Auto complete works on enter and dot, also when typing in the middle of a word. ### Type info -The type info includes the inferred signature. -All of the xml docs and information about the containing assembly +The type info includes the inferred signature.\ +All of the xml docs and information about the path of the containing assembly. ### Status bar The statusbar at the bottom shows compiler error count, click on it to scroll to the first error. @@ -65,9 +72,24 @@ The statusbar at the bottom shows compiler error count, click on it to scroll to ### Selection highlighting Selected text is highlighted in both code and output window. The count is shown in the status bar. +### Drag and drop +Drag any file or folder into the editor to get the full path as a string at the cursor position.\ +For *.dlls it will be at the top and prefixed with `#r`.\ +For *.fsx it will be at the top and prefixed with `#load`. + +### Font +The default font is [Cascadia Mono](https://github.com/microsoft/cascadia-code).\ +You can change it in the 'Settings.txt' file at `%APPDATA%\Local\Fesh\Settings.txt`.\ +Or via the menu: `About` -> `Open Settings Folder`. + ## Release notes +`0.13.0` +- fix crash on assembly load conflict +- faster completion window +- optional tracking of evaluated code via settings + `0.12.0` - add check for updates on startup - fix version number in Title diff --git a/Src/Config/DefaultCode.fs b/Src/Config/DefaultCode.fs index fff5fab..d4e21f6 100644 --- a/Src/Config/DefaultCode.fs +++ b/Src/Config/DefaultCode.fs @@ -22,8 +22,12 @@ type DefaultCode (runContext:RunContext) = match runContext.DefaultCode with | Some dc -> dc | None -> + "#r \"nuget: Fesher, 0.2.0\" // for printf with color" "" "open System" + "open Fesher" + "" + "Printfn.red \"Hello, Fesh!\"" "" "" ] diff --git a/Src/Editor/BracketHighlighter.fs b/Src/Editor/BracketHighlighter.fs index b0183fb..1a40ac3 100644 --- a/Src/Editor/BracketHighlighter.fs +++ b/Src/Editor/BracketHighlighter.fs @@ -106,6 +106,12 @@ module ParseBrackets = Int32.MaxValue // string flows over to the next line else skipString code[i+2] (i+2) // jump over escaped quote \" + | '\\', '\\' -> + if i+1 = lastIdx then + Int32.MaxValue // string flows over to the next line + else + skipString code[i+2] (i+2) // jump over escaped backslash \\ + | _ -> skipString next (i+1) diff --git a/Src/Editor/Checker.fs b/Src/Editor/Checker.fs index af5039b..a7e2c8f 100644 --- a/Src/Editor/Checker.fs +++ b/Src/Editor/Checker.fs @@ -20,7 +20,7 @@ type ParseCheckRes = { type PositionInCodeEx = { lineToCaret :string - row : int + lineIdx : int column : int offset : int setback : int @@ -34,12 +34,12 @@ type PositionInCodeEx = let setback = Str.lastNonFSharpNameCharPosition ln // to maybe replace some previous characters too let dotBefore = let i = pos.column - setback - 1 - if i >= 0 && i < ln.Length then ln.[i] = '.' else false + i >= 0 && i < ln.Length && ln.[i] = '.' let ifDotSetback = if dotBefore then setback else 0 let colSetBack = pos.column - ifDotSetback { lineToCaret = ln - row = pos.row + lineIdx = pos.lineIdx column = pos.column offset = pos.offset setback = setback // to maybe replace some previous characters too @@ -180,8 +180,6 @@ module FsCheckerUtil = /// Only a single checker exist that is referenced on all editors type Checker private () = - /// when the checker is running just before completion window we actually don't want to update the status bar while the completion window is open - static let mutable raiseStateChangedEvent = true static let mutable fsChecker: FSharpChecker Option = None // "you should generally use one global, shared FSharpChecker for everything in an IDE application." from http://fsharp.github.io/FSharp.Compiler.Service/caches.html @@ -192,11 +190,6 @@ type Checker private () = static let checkingStateEv = new Event () - static let updateCheckingState (ied:IEditor) state = - ied.FileCheckState <- state - if raiseStateChangedEvent then - Fittings.SyncWpf.doSync (fun () -> checkingStateEv.Trigger state ) - static let mutable projectOptions = None: FSharpProjectOptions option @@ -220,6 +213,8 @@ type Checker private () = return checkRes } + + //----------------------------------------------------------------- //---------------static members------------------------------------ //----------------------------------------------------------------- @@ -233,25 +228,47 @@ type Checker private () = static member OptArgsDict = optArgsDict /// Returns None if check failed or was superseded by a newer Document change ID - static member CheckCode(ed:IEditor, state:InteractionState, code, changeId, raiseEvent) : option= - raiseStateChangedEvent <- raiseEvent - updateCheckingState ed Checking - match parseAndCheck( state, code, ed.FilePath, changeId) with - |None -> - //IFeshLog.log.PrintfnDebugMsg $"*parseAndCheck: aborted early, waiting for newer checke state event." - None - |Some parseCheckRes -> - let errs = ErrorUtil.getBySeverity parseCheckRes.checkRes - let res = - { - parseRes = parseCheckRes.parseRes - checkRes = parseCheckRes.checkRes - errors = errs - changeId = changeId - editor = ed.AvaEdit - } - updateCheckingState ed (Done res) - Some res + static member CheckCode(ed:IEditor, state:InteractionState, code, changeId, forCompl) : option = + try + let aCheckingState = + match ed.FileCheckState with + | Done res | WaitForCompl res | WaitForErr res -> if forCompl then WaitForCompl res else WaitForErr res + | NotChecked -> NotChecked + + Fittings.SyncWpf.doSync (fun () -> checkingStateEv.Trigger aCheckingState ) + ed.FileCheckState <- aCheckingState + + match parseAndCheck( state, code, ed.FilePath, changeId) with + |None -> + //IFeshLog.log.PrintfnDebugMsg $"*parseAndCheck: aborted early, waiting for newer checke state event." + None + |Some parseCheckRes -> + let errs = ErrorUtil.getBySeverity parseCheckRes.checkRes + let res = + { + parseRes = parseCheckRes.parseRes + checkRes = parseCheckRes.checkRes + errors = errs + changeId = changeId + editor = ed.AvaEdit + } + + Fittings.SyncWpf.doSync (fun () -> checkingStateEv.Trigger (Done res) ) + ed.FileCheckState <- Done res + Some res + with + | :? TypeLoadException as e -> + IFeshLog.log.PrintfnAppErrorMsg "TypeLoadException in CheckCode: \r\n%A" e + IFeshLog.log.PrintfnFsiErrorMsg "This may indicate an assembly version conflict" + if state.Config.RunContext.IsHosted then + let host = state.Config.RunContext.HostName |> Option.defaultValue "" + IFeshLog.log.PrintfnFsiErrorMsg $"Please restart the host app {host}. And try again." + + None + | e -> + IFeshLog.log.PrintfnAppErrorMsg "Fesh.Editor.Error in CheckCode: \r\n%A" e + None + /// Currently unused optional argument to GetDeclarationListSymbols /// Completion list would get huge !!! @@ -286,25 +303,23 @@ type Checker private () = [] /// Checks for items available for completion - static member GetCompletions (pos:PositionInCode, res:FullCheckResults ) = - //IFeshLog.log.PrintfnDebugMsg "*2.0 GetCompletions for:\r\n%A" pos - let pCtx = PositionInCodeEx.get(pos) - + static member GetDeclarations (posX:PositionInCodeEx, res:FullCheckResults ) = + // IFeshLog.log.PrintfnDebugMsg "*2.0 GetCompletions for:\r\n%A" posX // Symbols are only for finding out if an argument is optional let symUse = res.checkRes.GetDeclarationListSymbols( Some res.parseRes // ParsedFileResultsOpt - , pos.row // line - , pos.lineToCaret // lineText - , pCtx.partLoName // PartialLongName + , posX.lineIdx // line + , posX.lineToCaret // lineText + , posX.partLoName // PartialLongName //, (fun () -> Checker.GetAllEntities(res, true)) // getAllEntities: (unit -> AssemblySymbol list) // TODO use that too like FsAutocomplete does ??? ) // for auto completion let decls = res.checkRes.GetDeclarationListInfo( Some res.parseRes // ParsedFileResultsOpt - , pos.row // line - , pos.lineToCaret // lineText - , pCtx.partLoName // PartialLongName + , posX.lineIdx // line + , posX.lineToCaret // lineText + , posX.partLoName // PartialLongName //, (fun () -> Checker.GetAllEntities(res, true)) // getAllEntities: (unit -> AssemblySymbol list) // TODO use that too like FsAutocomplete does ??? //, completionContextAtPos // TODO use it ? Completion context for a particular position computed in advance. ) @@ -313,6 +328,7 @@ type Checker private () = IFeshLog.log.PrintfnAppErrorMsg "*ERROR in GetDeclarationListInfo: %A" decls //TODO use log None else + // IFeshLog.log.PrintfnDebugMsg $"*2.1 GetCompletions for {posX} found {decls.Items.Length}" // Find which parameters are optional and set the value on the passed in dictionary. // For adding question marks to optional arguments. // This is still done in Async mode @@ -323,7 +339,7 @@ type Checker private () = if opts.Count>0 then optArgsDict.[symb.Symbol.FullName] <- opts - Some (decls,pCtx) + Some (decls) /// Create a new Checker static member Reset() = diff --git a/Src/Editor/ColumnRulers.fs b/Src/Editor/ColumnRulers.fs index c55f202..d9c894b 100644 --- a/Src/Editor/ColumnRulers.fs +++ b/Src/Editor/ColumnRulers.fs @@ -22,14 +22,20 @@ type ColumnRulers (editor:TextEditor) as this = [0 .. 10] |> List.map ( fun i -> i * editor.Options.IndentationSize) //[ 0; 4; 8; 12 ; 16 ; 20 ; 24 ; 28 ; 32 ; 36] - let mutable color = Brushes.White |> darker 20 //24 + let mutable color = Brushes.White |> darker 24 + + // make it more transparent + let addTransparency (amount:int) (br:SolidColorBrush) = + SolidColorBrush(Color.FromArgb(clampToByte (int br.Color.A - amount), br.Color.R, br.Color.G, br.Color.B)) + let pens = [ for _ in columnsInit do - let p = new Pen(color, 1.2 ) + let p = new Pen(color, 1.1 ) + // color <- brighter 2 color // fade out next ruler + color <- addTransparency 20 color // fade out next ruler p.Freeze() - color <- brighter 2 color // fade out next ruler p ] @@ -50,12 +56,14 @@ type ColumnRulers (editor:TextEditor) as this = | _-> ()//log.PrintfnAppErrorMsg "other left margin: %A" uiElm // TODO other left margin: System.Windows.Shapes.Line - member this.Layer = KnownLayer.Background + member this.Layer = + // KnownLayer.Background + KnownLayer.Selection member this.Draw(textView:TextView, drawingContext:DrawingContext) = - + let width = textView.WideSpaceWidth for column,pen in Seq.zip columns pens do - let offset = textView.WideSpaceWidth * float column + let offset = width * float column let markerXPos = PixelSnapHelpers.PixelAlign(offset, pixelSize.Width) - textView.ScrollOffset.X let start = new Point(markerXPos, 0.0); let ende = new Point(markerXPos, Math.Max(textView.DocumentHeight, textView.ActualHeight)) diff --git a/Src/Editor/Completions.fs b/Src/Editor/Completions.fs index 058ff48..010a477 100644 --- a/Src/Editor/Completions.fs +++ b/Src/Editor/Completions.fs @@ -21,9 +21,13 @@ open Fesh.XmlParser open System open System -type TryShow = DidShow | NoShow +type TriedShow = DidShow | NoShow -type ShowRestrictions = JustAll |JustDU | JustKeyWords +type RestrictedShowList = + | JustAllFrom of DeclarationListInfo + | JustDuFrom of DeclarationListInfo + | JustDeclModifiers + | JustDirectives module UtilCompletion = @@ -38,34 +42,12 @@ module UtilCompletion = tb.Padding <- Thickness(0. , 0. , 8. , 0. ) //left top right bottom / so that it does not appear to be trimmed tb -type CompletionItemForKeyWord(state: InteractionState, text:string, toolTip:string) = - let priority = 1.0 + state.Config.AutoCompleteStatistic.Get(text) // create once and cache ? - let textBlock = UtilCompletion.mkTexBlock(text,FontStyles.Normal) // create once and cache ? - member this.Content = textBlock :> obj - member this.Description = toolTip :> obj - member this.Image = null - member this.Priority = priority - member this.Text = text - member this.Complete (textArea:TextArea, completionSegment:ISegment, e:EventArgs ) = - ignore e // because of warn on unused variables and , needed in F#: implementing the interface members as properties too: https://github.com/icsharpcode/AvalonEdit/issues/28 - state.JustCompleted <- true - if Selection.getSelType textArea = Selection.RectSel then - RectangleSelection.complete (textArea, completionSegment, text) - else - textArea.Document.Replace(completionSegment, text) - if text[ text.Length - 1 ] = '"' then // to insert cursor between quotes - textArea.Caret.Offset <- textArea.Caret.Offset - 1 - - interface ICompletionData with // needed in F#: implementing the interface members as properties too: https://github.com/icsharpcode/AvalonEdit/issues/28 - member this.Complete(t,s,e) = this.Complete(t,s,e) - member this.Content = this.Content - member this.Description = this.Description - member this.Image = this.Image - member this.Priority = this.Priority - member this.Text = this.Text +type CompletionInfo = + | Decl of decl:DeclarationListItem * getToolTip:(DeclarationListItem -> obj) // getToolTip is a function that returns the tooltip text + | KeyWord of text:string*toolTip:string -type CompletionItem(state: InteractionState, getToolTip, it:DeclarationListItem, isDotCompletion:bool) = +type CompletionItem(state: InteractionState, info:CompletionInfo , isDotCompletion:bool) = //let style = // if it.IsOwnMember then FontStyles.Normal @@ -76,47 +58,63 @@ type CompletionItem(state: InteractionState, getToolTip, it:DeclarationListItem, let priority = //if it.IsOwnMember then 1. else 1. if isDotCompletion then 1.0 // not on Dot completion - else 1.0 + state.Config.AutoCompleteStatistic.Get(it.NameInList) //if p>1.0 then log.PrintfnDebugMsg "%s %g" it.Name p + else + match info with + | Decl (it,_) -> 1.0 + state.Config.AutoCompleteStatistic.Get(it.NameInList) //if p>1.0 then log.PrintfnDebugMsg "%s %g" it.Name p + | KeyWord (text,_) -> 1.0 + state.Config.AutoCompleteStatistic.Get(text) // create once and cache ? + + let textVal, textDisplay= + match info with + | Decl (it,_) -> it.NameInList ,UtilCompletion.mkTexBlock(it.NameInList , FontStyles.Normal) // create once and cache ? + | KeyWord (text,_) -> text ,UtilCompletion.mkTexBlock(text , FontStyles.Normal) // create once and cache ? - let textBlock = UtilCompletion.mkTexBlock(it.NameInList ,FontStyles.Normal) // create once and cache ? + member this.Content = textDisplay :> obj // the displayed item in the completion window + member this.Description = + match info with + | Decl (it, getToolTip) -> getToolTip(it) // this gets called on demand only, not when initially filling the list. + | KeyWord (_ , toolTip) -> toolTip :> obj - member this.Content = textBlock :> obj // the displayed item in the completion window - member this.Description = getToolTip(it) // this gets called on demand only, not when initially filling the list. member this.Image = null //TODO or part of text box ? member this.Priority = priority - member this.Text = it.NameInList // not used for display, but for priority sorting ? + member this.Text = textVal // not used for display, but for priority sorting ? member this.Complete (textArea:TextArea, completionSegment:ISegment, e:EventArgs) = ignore e // because of warn on unused variables and , needed in F#: implementing the interface members as properties too: https://github.com/icsharpcode/AvalonEdit/issues/28 //log.PrintfnDebugMsg "%s is %A and %A" it.Name it.Glyph it.Kind state.JustCompleted <- true let mutable complText = - //TODO move this logic out here - if it.Glyph = FSharpGlyph.Class && it.NameInList.EndsWith "Attribute" then - "[<" + it.NameInList.Replace("Attribute",">]") + match info with + | KeyWord (txt,_) -> txt + | Decl (it,_) -> + //TODO move this logic out here + if it.Glyph = FSharpGlyph.Class && it.NameInList.EndsWith "Attribute" then + "[<" + it.NameInList.Replace("Attribute",">]") - // elif UtilCompletion.needsTicks it.Name then "``" + it.Name + "``" // fixed in FSharp.Compiler.Service 42.7.100 by using it.NameInCode + // elif UtilCompletion.needsTicks it.Name then "``" + it.Name + "``" // fixed in FSharp.Compiler.Service 42.7.100 by using it.NameInCode - elif it.NameInList = "struct" then - "[]" - else - it.NameInCode // may include backticks + elif it.NameInList = "struct" then + "[]" + else + it.NameInCode // may include backticks // add '()' at end of word if this is a function taking unit: let taggedTextSig = - match it.Description with - | ToolTipText.ToolTipText (els) -> - match els with - |[] -> None - |[el] -> - match el with - | ToolTipElement.None -> None - | ToolTipElement.CompositionError _ -> None - | ToolTipElement.Group(tooTipElemDataList) -> - match tooTipElemDataList with - |[] -> None - |[eld] -> Some eld.MainDescription - | _ -> None // there are multiple signatures - | _ -> None // there are multiple signatures + match info with + | KeyWord _ -> None + | Decl (it,_) -> + match it.Description with + | ToolTipText.ToolTipText (els) -> + match els with + |[] -> None + |[el] -> + match el with + | ToolTipElement.None -> None + | ToolTipElement.CompositionError _ -> None + | ToolTipElement.Group(tooTipElemDataList) -> + match tooTipElemDataList with + |[] -> None + |[eld] -> Some eld.MainDescription + | _ -> None // there are multiple signatures + | _ -> None // there are multiple signatures match taggedTextSig with |None -> () @@ -128,19 +126,32 @@ type CompletionItem(state: InteractionState, getToolTip, it:DeclarationListItem, && ts.[ts.Length-7].Text = ":" then complText <- complText + "()" - //config.Log.PrintfDebugMsg "completionSegment: '%s' : %A" (textArea.Document.GetText(completionSegment)) completionSegment + // IFeshLog.log.PrintfnDebugMsg "completionSegment: '%s' : %A" (textArea.Document.GetText(completionSegment)) completionSegment + // Extend the completionSegment to the right to include the whole word: + let doc = textArea.Document + let extendedSegment = + let mutable k = 0 + let mutable e = completionSegment.EndOffset + let inline isValidIdentifierChar c = Char.IsLetterOrDigit(c) || c = '_' || c = '\'' || c = '`' + while k < 20 && e < doc.TextLength && isValidIdentifierChar(doc.GetCharAt(e)) do // don't extend by more than 20 chars + e <- e + 1 + k <- k + 1 + ISegment.FormTill(completionSegment.Offset, e) + + // replace in the document: if Selection.getSelType textArea = Selection.RectSel then - RectangleSelection.complete (textArea, completionSegment, complText) + RectangleSelection.complete (textArea, extendedSegment, complText) else - textArea.Document.Replace(completionSegment, complText) + doc.Replace(extendedSegment, complText) if not isDotCompletion then - state.Config.AutoCompleteStatistic.Incr(it.NameInList) + state.Config.AutoCompleteStatistic.Incr(textVal) state.Config.AutoCompleteStatistic.Save() + // Event sequence on pressing enter in completion window: - // (1)Close window - // (2)Insert text into editor (triggers completion if one char only) - // (3)Raise InsertionRequested event + // (1) Close window + // (2) Insert text into editor (triggers completion window agin if it was one char only) + // (3) Raise InsertionRequested event // https://github.com/icsharpcode/AvalonEdit/blob/8fca62270d8ed3694810308061ff55c8820c8dfc/AvalonEditB/CodeCompletion/CompletionWindow.cs#L100 interface ICompletionData with @@ -174,10 +185,47 @@ type Completions(state: InteractionState) = // to close other tooltips that might be open from type info let showingEv = Event() + let hashDirectives = + let lines = ResizeArray() + lines.Add( CompletionItem(state,KeyWord("#if INTERACTIVE", "Compiler directive to exclude code in compiled format, close with #endif or #else" ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("#if COMPILED", "Compiler directive to exclude code in interactive format, close with #endif or #else" ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("#else", "else of compiler directives " ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("#endif", "End of compiler directive " ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("#r \"nuget: \"", "Nuget package reference " ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("#r \"\"", "Dll file reference " ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("#I \"\"", "Dll folder reference " ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("#load \"\"", "fsx file reference " ), false) :> ICompletionData) + lines + + let underscore = + let lines = ResizeArray() + lines.Add( CompletionItem(state,KeyWord("__SOURCE_DIRECTORY__","Evaluates to the current full path of the source directory" ), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("__SOURCE_FILE__" ,"Evaluates to the current source file name, without its path"), false) :> ICompletionData) + lines.Add( CompletionItem(state,KeyWord("__LINE__", "Evaluates to the current line number"), false) :> ICompletionData) + lines + + /// Initially returns "loading.." text and triggers async computation to get and update with actual text + let getToolTip (it:DeclarationListItem) : obj = + async{ + let ttText = it.Description + let structured = + if Checker.OptArgsDict.ContainsKey it.FullName then TypeInfo.makeFeshToolTipDataList (ttText, it.FullName, Checker.OptArgsDict.[it.FullName]) + else TypeInfo.makeFeshToolTipDataList (ttText, it.FullName, empty) + do! Async.SwitchToContext Fittings.SyncWpf.context + if win.IsSome then // might get closed during context switch + if selectedCompletionText() = it.NameInList then + win.Value.ToolTipContent <- TypeInfo.getPanel (structured, {declListItem=Some it; semanticClass=None; declLocation=None; dllLocation=None }) + //TODO add structure to a Dict so it does not need to be recomputed if browsing up and down items in the completion list? + // else + // IFeshLog.log.PrintfnDebugMsg $"getToolTip: '{selectedCompletionText()}' <> '{it.NameInList}'" + // else + // IFeshLog.log.PrintfnDebugMsg $"win.IsSome2: '{win.IsSome}'" + } |> Async.Start + TypeInfo.loadingText :> obj + [] member _.OnShowing = showingEv.Publish // to close other tooltips that might be open from type info - member _.IsOpen = win.IsSome /// to set state.DocChangedConsequence <- React, @@ -189,85 +237,62 @@ type Completions(state: InteractionState) = win.Value.Close() win <- None - - /// Initially returns "loading.." text and triggers async computation to get and update with actual text - member this.GetToolTip(it:DeclarationListItem)= - async{ - let ttText = it.Description - - let structured = - if Checker.OptArgsDict.ContainsKey it.FullName then TypeInfo.makeFeshToolTipDataList (ttText, it.FullName, Checker.OptArgsDict.[it.FullName]) - else TypeInfo.makeFeshToolTipDataList (ttText, it.FullName, empty) - if this.IsOpen then - do! Async.SwitchToContext Fittings.SyncWpf.context - if this.IsOpen then // might get closed during context switch - if selectedCompletionText() = it.NameInList then - win.Value.ToolTipContent <- TypeInfo.getPanel (structured, {declListItem=Some it; semanticClass=None; declLocation=None; dllLocation=None }) - //TODO add structure to a Dict so it does not need to be recomputed if browsing up and down items in the completion list? - } |> Async.Start - TypeInfo.loadingText // :> obj - - member _.ComplWin with get() : Option = win - and set(w : Option) = win <- w + and set(w:Option) = win <- w member val justKeyWords = let lines = ResizeArray() for kw,desc in FSharpKeywords.KeywordsWithDescription do // add keywords to list if kw.StartsWith "pri" || kw.StartsWith "mut" || kw.StartsWith "inl" || kw.StartsWith "int" then - lines.Add( CompletionItemForKeyWord(state,kw,desc) :> ICompletionData) |>ignore + lines.Add( CompletionItem(state, KeyWord(kw,desc), false) :> ICompletionData ) lines + member val directives = + hashDirectives + member val justAll = let lines = ResizeArray() - lines.Add( CompletionItemForKeyWord(state,"#if INTERACTIVE", "Compiler directive to exclude code in compiled format, close with #endif or #else" ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"#if COMPILED", "Compiler directive to exclude code in interactive format, close with #endif or #else" ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"#else", "else of compiler directives " ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"#endif", "End of compiler directive " ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"#r \"nuget: \"", "Nuget package reference " ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"#r \"\"", "Dll file reference " ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"#I \"\"", "Dll folder reference " ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"#load \"\"", "fsx file reference " ) :> ICompletionData) |>ignore - - lines.Add( CompletionItemForKeyWord(state,"__SOURCE_DIRECTORY__","Evaluates to the current full path of the source directory" ) :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"__SOURCE_FILE__" ,"Evaluates to the current source file name, without its path") :> ICompletionData) |>ignore - lines.Add( CompletionItemForKeyWord(state,"__LINE__", "Evaluates to the current line number") :> ICompletionData) |>ignore for kw,desc in FSharpKeywords.KeywordsWithDescription do // add keywords to list - lines.Add( CompletionItemForKeyWord(state,kw,desc) :> ICompletionData) |>ignore + lines.Add( CompletionItem(state, KeyWord(kw,desc), false) :> ICompletionData ) + lines.AddRange hashDirectives + lines.AddRange underscore lines - member this.CompletionLines ( decls: DeclarationListInfo, pos:PositionInCodeEx, showRestrictions:ShowRestrictions )= - match showRestrictions with - |JustKeyWords -> + member this.MakeCompletionLines ( pos:PositionInCodeEx, rShowList:RestrictedShowList ) : ResizeArray = + match rShowList with + |JustDeclModifiers -> this.justKeyWords - |JustDU -> + |JustDirectives -> + this.directives + |JustDuFrom decls -> let lines = ResizeArray(decls.Items.Length) for it in decls.Items do match it.Glyph with |FSharpGlyph.Union |FSharpGlyph.Module |FSharpGlyph.EnumMember -> - lines.Add (new CompletionItem(state, this.GetToolTip, it, pos.dotBefore)) // for DU completion add just some. + lines.Add (new CompletionItem(state, Decl(it, getToolTip ), pos.dotBefore)) // for DU completion add just some. |_ -> () lines - |JustAll -> + |JustAllFrom decls -> let kws = this.justAll let lines = ResizeArray(decls.Items.Length + kws.Count) if not pos.dotBefore then lines.AddRange kws for it in decls.Items do - lines.Add (new CompletionItem(state, this.GetToolTip, it, pos.dotBefore)) // for normal completion add all others too. + lines.Add (new CompletionItem(state, Decl(it, getToolTip ), pos.dotBefore)) // for normal completion add all others too. lines /// must be called from UI thread - member this.TryShow( decls: DeclarationListInfo, pos:PositionInCodeEx, showRestrictions:ShowRestrictions, checkAndMark:unit->unit) : TryShow = + member this.TryShow( posX:PositionInCodeEx, rShowList:RestrictedShowList, checkAndMark:unit->unit) : TriedShow = willInsert <- false - - //IFeshLog.log.PrintfnDebugMsg $"*3.0 TryShow Completion Window , {decls.Items.Length} items, onlyDU:{showRestrictions}:\r\n{pos}" + // IFeshLog.log.PrintfnDebugMsg $"*5.0 TryShow Completion Window , {rShowList} items, onlyDU:{rShowList}:\r\n{pos}" if AutoFixErrors.isMessageBoxOpen then // because msg box would appear behind completion window and type info NoShow else - let completionLines = this.CompletionLines(decls, pos, showRestrictions) + let completionLines = this.MakeCompletionLines( posX, rShowList) + // IFeshLog.log.PrintfnDebugMsg $"*5.1: CompletionLines with {completionLines.Count} items" + // for l in completionLines do IFeshLog.log.PrintfnDebugMsg "*5.2 CompletionLine: %s" l.Text if completionLines.Count = 0 then NoShow else @@ -279,7 +304,7 @@ type Completions(state: InteractionState) = complData.Add (cln) let caret = avEd.TextArea.Caret - let stOff = pos.offset - pos.setback // just using w.StartOffset - setback would sometimes be one too big.( race condition of typing speed) + let stOff = posX.offset - posX.setback // just using w.StartOffset - setback would sometimes be one too big.( race condition of typing speed) // prefilter needs to be calculated here, a few characters might have been added after getCompletions started async. let prefilterLength = caret.Offset-stOff if prefilterLength < 0 then // caret moved back before start of completion window @@ -287,7 +312,7 @@ type Completions(state: InteractionState) = NoShow else let prefilter = avEd.Document.GetText(stOff, prefilterLength ) - //IFeshLog.log.PrintfnDebugMsg "*5.2: prefilter '%s'" prefilter + //IFeshLog.log.PrintfnDebugMsg "*5.2.1: prefilter '%s'" prefilter if prefilter.Length > 0 then complList.SelectItem(prefilter) //to pre-filter the list by al typed characters @@ -296,11 +321,10 @@ type Completions(state: InteractionState) = if complList.ListBox.Items.Count > 0 // list is empty if prefilter does not match any item && not AutoFixErrors.isMessageBoxOpen // because msg box would appear behind completion window and type info && IEditor.isCurrent avEd // switched to other editor - && caret.Line = pos.row // moved cursor to other line - && caret.Offset >= pos.offset then // moved cursor back before completion ( e.g. via deleting) + && caret.Line = posX.lineIdx // moved cursor to other line + && caret.Offset >= posX.offset then // moved cursor back before completion ( e.g. via deleting) // Only now actually show the window: - w.MaxHeight <- 500 // default 300 w.Width <- 250 // default 175 //complList.Height <- 400. // has UI bug @@ -312,12 +336,12 @@ type Completions(state: InteractionState) = w.MinHeight <- StyleState.fontSize w.MinWidth <- StyleState.fontSize * 8.0 w.CloseAutomatically <- true - w.CloseWhenCaretAtBeginning <- not pos.dotBefore + w.CloseWhenCaretAtBeginning <- not posX.dotBefore w.StartOffset <- stOff // to replace some previous characters too let taCaretChanged = new EventHandler(fun _ _ -> match complList.ListBox.Items.Count with - | 0 -> w.Close() // close when list is empty. willInsert is still false so checkAndMark will be called in closing event handler + | 0 -> w.Close() // close when list is empty. 'willInsert' is still false so checkAndMark will be called in closing event handler | 1 -> match complList.SelectedItem with // insert and close if there is an exact match and no other possible match available | null -> () | it -> @@ -335,9 +359,9 @@ type Completions(state: InteractionState) = ta.Caret.PositionChanged.RemoveHandler taCaretChanged // Event sequence on pressing enter in completion window: - // (1)raise InsertionRequested event - // (2)in one of the event handlers first Closes window - // (3)then on the item line this.Complete (TextArea, ISegment, EventArgs) is called and change the Document + // (1) raise InsertionRequested event + // (2) in one of the event handlers first Closes window + // (3) then on the item line this.Complete (TextArea, ISegment, EventArgs) is called and change the Document // https://github.com/goswinr/AvalonEditB/blob/main/AvalonEditB/CodeCompletion/CompletionWindow.cs#L110 this.CloseAndEnableReacting() //IFeshLog.log.PrintfnDebugMsg "Completion window just closed with selected item: %A " complList.SelectedItem @@ -352,7 +376,6 @@ type Completions(state: InteractionState) = complList.SelectedItem <- unbox complList.ListBox.Items.[0] //complData.[0] // select the only item if there is just one - //IFeshLog.log.PrintfnDebugMsg "*5.4 Show Completion Window with %d items prefilter: '%s' " complList.ListBox.Items.Count prefilter showingEv.Trigger() // to close error and type info tooltip this.ComplWin <- Some w diff --git a/Src/Editor/CursorBehavior.fs b/Src/Editor/CursorBehavior.fs index f5a1f89..4ebb1e2 100644 --- a/Src/Editor/CursorBehavior.fs +++ b/Src/Editor/CursorBehavior.fs @@ -437,7 +437,8 @@ module CursorBehavior = // not needed: if not ed.IsComplWinOpen then match getSelType(ed.TextArea) with | RectSel -> - RectangleSelection.insertText(ed, e.Text) ; e.Handled <- true // all input in rectangular selection is handled here. + RectangleSelection.insertText(ed, e.Text) // all input in rectangular selection is handled here. + e.Handled <- true // so typing can be canceled | NoSel | RegSel -> addWhitespaceAfterChar(ed,e) if not e.Handled then diff --git a/Src/Editor/DocChanged.fs b/Src/Editor/DocChanged.fs index d81e3e8..fa8083f 100644 --- a/Src/Editor/DocChanged.fs +++ b/Src/Editor/DocChanged.fs @@ -1,6 +1,8 @@ namespace Fesh.Editor open System +open System.Windows.Threading +open System.Windows.Input open AvalonEditB open AvalonEditB.Document @@ -9,7 +11,6 @@ open Fesh.Model open Fesh.Util open Fesh.Util.Str open Fesh -open System.Windows.Threading module DocChangeUtil = @@ -48,7 +49,7 @@ module DocChangeUtil = let st = loop off code.Substring(st,off-st) - let getPosInCode2(avaEdit:TextEditor) : PositionInCode = + let getPosInCode(avaEdit:TextEditor) : PositionInCode = let doc = avaEdit.Document let car = avaEdit.TextArea.Caret let caretOffset = car.Offset @@ -56,7 +57,7 @@ module DocChangeUtil = let caretOffsetInThisLine = caretOffset - ln.Offset { lineToCaret = doc.GetText(ln.Offset, caretOffsetInThisLine)// this line will include the character that trigger auto completion(dot or first letter) - row = car.Line + lineIdx = car.Line column = caretOffsetInThisLine // equal to amount of characters in lineToCaret offset = caretOffset } @@ -71,7 +72,7 @@ module Redrawing = errors : ErrorHighlighter semantic : SemanticHighlighter selection : SelectionHighlighter - evalTracker : option + evalTracker : EvaluationTracker // not tracked by tryDraw !! TODO, runs in sync } type EventCombiner(services:DrawingServices, state:InteractionState) = @@ -91,6 +92,7 @@ module Redrawing = if state.IsLatest id && idSemantics=id && idBrackets=id && idErrors=id && idSels=id && idFolds=id then async{ do! Async.SwitchToContext Fittings.SyncWpf.context + //eprintfn $"Redrawing full: id={id}, idSemantics={idSemantics}, idBrackets={idBrackets}, idErrors={idErrors}, idSels={idSels}, idFolds={idFolds}" // first render then fold do avoid this ? //System.ArgumentException: Cannot dispose visual line because it is in construction! //at AvalonEditB.Rendering.TextView.DisposeVisualLine(VisualLine visualLine) @@ -101,9 +103,8 @@ module Redrawing = // do! Async.Sleep 50 // to try to avoid: InvalidOperationException: Line 117 was skipped by a VisualLineElementGenerator, but it is not collapsed. at AvalonEditB.Rendering.TextView.BuildVisualLine(..) // if state.IsLatest id then services.folds.RedrawFoldings() - - //eprintfn $"Redrawing full: id={id}, idSemantics={idSemantics}, idBrackets={idBrackets}, idErrors={idErrors}, idSels={idSels}, idFolds={idFolds}" - } |> Async.Start + } + |> Async.Start // else // IFeshLog.log.PrintfnAppErrorMsg $"Can't redraw, check id that is wrong: id={id}" // IFeshLog.log.PrintfnAppErrorMsg $"idSemantics={idSemantics}, idBrackets={idBrackets}, idErrors={idErrors}, idSels={idSels}, idFolds={idFolds}" @@ -121,8 +122,8 @@ module Redrawing = do services.brackets.FoundBrackets.Add doneBrackets services.semantic.FoundSemantics.Add doneSemantics // includes Bad Indentation and Unused declarations - services.errors.FoundErrors.Add doneErrors services.selection.FoundSels.Add doneSels + services.errors.FoundErrors.Add doneErrors services.folds.FoundFolds.Add doneFolds module DocChangeMark = @@ -132,7 +133,7 @@ module DocChangeMark = /// only for Single char changes let mainWait = 50 - let updateAllTransformersConcurrently(iEd:IEditor, code:string, drawServ:DrawingServices, state:InteractionState, id) = + let updateAllTransformersConcurrently(iEd:IEditor, code:string, drawServ:DrawingServices, state:InteractionState, id, lineIdx) = // first: Foldings, ColorBrackets when full text available async. async{ state.CodeLines.UpdateLines(code, id) @@ -140,11 +141,12 @@ module DocChangeMark = drawServ.selection.UpdateTransformers(id) drawServ.brackets.UpdateAllBrackets(id) drawServ.folds.CheckFolds(id) + drawServ.evalTracker.SetLastChangeAt(lineIdx) } |> Async.Start // second: Errors and Semantic Highlighting and BadIndentation on FCS check result . async{ - match Checker.CheckCode(iEd, state, code, id, true) with // code checking does not need to wait for CodeLines.Update + match Checker.CheckCode(iEd, state, code, id, false) with // code checking does not need to wait for CodeLines.Update |None -> () |Some res -> if state.IsLatest id then @@ -158,24 +160,24 @@ module DocChangeMark = } |> Async.Start //should be called from any thread pool thread - let updateAllTransformersSync(iEd:IEditor, doc:TextDocument, drawServ:DrawingServices, state:InteractionState, id ) = + let updateAllTransformersSync(iEd:IEditor, doc:TextDocument, drawServ:DrawingServices, state:InteractionState, id , lineIdx) = match SelectionHighlighting.makeEditorSnapShot(doc,state,id) with | None -> () - | Some code -> updateAllTransformersConcurrently (iEd, code, drawServ, state, id) + | Some code -> updateAllTransformersConcurrently (iEd, code, drawServ, state, id, lineIdx) // Must be called from UI thread only - let updateAllTransformersAsync (iEd:IEditor, drawServ:DrawingServices, state:InteractionState, id ) = + let updateAllTransformersAsync (iEd:IEditor, drawServ:DrawingServices, state:InteractionState, id, lineIdx) = let doc = iEd.AvaEdit.Document // get Doc in Sync async { if mainWait <> 0 then do! Async.Sleep mainWait match SelectionHighlighting.makeEditorSnapShot(doc,state,id) with | None -> () - | Some code -> updateAllTransformersConcurrently (iEd, code, drawServ, state, id) + | Some code -> updateAllTransformersConcurrently (iEd, code, drawServ, state, id, lineIdx) } |> Async.Start -type ShowAutocomplete = DoNothing | JustMark| ShowOnlyDU | ShowAll | ShowKeyWords +type ShowAutocomplete = DoNothing | JustMark| ShowOnlyDU | ShowAll | DeclModifiers | ShowDirectives [] @@ -225,10 +227,10 @@ module MaybeShow = elif 'a' <= first && first <= 'z' then // a lower case identifier or a keyword if checkForKeyW && ln.Length - fi > 2 then match ln.Substring(fi,3) with - | "pri" -> ShowKeyWords // private - | "mut" -> ShowKeyWords // mutable - | "int" -> ShowKeyWords // internal - | "inl" -> ShowKeyWords // inline + | "pri" -> DeclModifiers // private + | "mut" -> DeclModifiers // mutable + | "int" -> DeclModifiers // internal + | "inl" -> DeclModifiers // inline | _ -> JustMark else JustMark // writing a lowercase name binding @@ -256,6 +258,10 @@ module MaybeShow = else false // writing not a DU but an operator like |> or |] + let isHashDirective inStr (ln:string) = + let hashIdx = lastIdxAtStartOrWithSpace inStr "#" ln // test if its the first char or preceded by a space + if hashIdx = -1 then ShowAll + else ShowDirectives let isLetDeclaration inStr (ln:string) = //test if we are after a 'let' but before a '=' or ':' @@ -267,7 +273,6 @@ module MaybeShow = // a : or = is after the let, so show all completions if max eqIdx colonIdx < letIdx then isDUorKeyword true (letIdx+3) ln else ShowAll - let isFunDeclaration inStr (ln:string) = //test if we are after a 'fun' but before a '->' or ':' let funIdx = max (lastIdx inStr " fun " ln) (lastIdx inStr "(fun " ln) @@ -304,7 +309,7 @@ module MaybeShow = ShowAll else if containsFrom memIdx "." ln then ShowAll - elif isKeyword memIdx ln then ShowKeyWords + elif isKeyword memIdx ln then DeclModifiers else JustMark @@ -314,14 +319,15 @@ module MaybeShow = |ShowOnlyDU -> ShowOnlyDU |JustMark -> JustMark |DoNothing -> DoNothing - |ShowKeyWords -> ShowKeyWords + |DeclModifiers -> DeclModifiers + |ShowDirectives -> ShowDirectives let completionWindow ( pos:PositionInCode) : ShowAutocomplete = let ln = pos.lineToCaret // this line will include the character that trigger auto completion(dot or first letter) let len = ln.Length //IFeshLog.log.PrintfnDebugMsg "*2.1 maybeShowCompletionWindow for lineToCaret: \r\n '%s'" ln - if len=0 then // line is empty, still check because deleting might have removed errors. - JustMark + if len=0 then // line is empty, + ShowAll // ctrl+space was triggered at start of line empty line, // not JustMark, an empty line after deleting is caught in singleCharChange() already else let last = ln.[len-1] if isCaretInComment ln then @@ -339,6 +345,8 @@ module MaybeShow = |> bind isForDeclaration inStr ln |> bind isBarDeclaration inStr ln |> bind isThisMemberDeclaration inStr ln + |> bind isHashDirective inStr ln + //|> (fun x -> printfn $"*2.2 maybeShowCompletionWindow for lineToCaret: \r\n '{ln}'\r\n is {x}"; x) /// checks if it is a letter or a digit preceded by a letter let inline isAlpha c = @@ -354,116 +362,167 @@ module MaybeShow = || (pos.column > 2 && isNum pos.lineToCaret[pos.column-1] && isAlpha pos.lineToCaret[pos.column-2]) + let inline getCtrlDown() = Keyboard.IsKeyDown Key.LeftCtrl || Keyboard.IsKeyDown Key.RightCtrl // can't be async + let inline getSpaceDown() = Keyboard.IsKeyDown Key.Space // can't be async + let inline lastCharTriggersCompletion (lastChar, pos) = match lastChar with | c when isAlpha c -> true // a ASCII letter | c when isNum c && isAlphaBefore pos -> true// an number preceded by a letter | '.' // dot completion | '_' // for __SOURCE_DIRECTORY__ or in names - | '`' // for complex F# names in `` `` - | '#' -> true// for #if directives + //| '`' // for complex F# names in `` `` // not needed + | '#' -> true // for #if directives | _ -> false - let getShowRestriction s = - match s with - |DoNothing // never happens - |JustMark // never happens - |ShowAll -> JustAll - |ShowKeyWords-> JustKeyWords - |ShowOnlyDU -> JustDU module DocChangeCompletion = open DocChangeUtil open Redrawing open FSharp.Compiler.EditorServices + open FSharp.Compiler.Tokenization + + let keywords = [| for kw, _desc in FSharpKeywords.KeywordsWithDescription do kw |] + + //pre check (still async) if the posX.query is contained in any name in the completion list + // let containsQuery (name:string, decls:DeclarationListInfo)= + // decls.Items |> Array.exists (fun d -> d.NameInList.IndexOf( name, StringComparison.OrdinalIgnoreCase) >= 0) + // || + // keywords |> Array.exists (fun kw -> kw.IndexOf( name, StringComparison.OrdinalIgnoreCase) >= 0) + + + + let handelShow(pos:PositionInCode, doc:TextDocument, iEd:IEditor, drawServ:DrawingServices, state:InteractionState, chId:int64) = + + let inline check() = Checker.CheckCode(iEd, state, doc.CreateSnapshot().Text , chId, true) + + let inline getDecls(posX:PositionInCodeEx) : DeclarationListInfo option = + let res = + // re-check every time? + // Timer.InstanceRedraw.tic() + // let r = check() // can take up to 500ms on file with 1k lines + // let t = Timer.InstanceRedraw.tocEx + // IFeshLog.log.PrintfnDebugMsg $"check time for compl: {t}" + // r + + // Or reuse old result? TODO: refresh the completion window when the new result is available? + // if posX.lineToCaret.EndsWith "." then // not even that seems to be necessary + // check() + // else + match iEd.FileCheckState with + | Done r -> + if r.changeId <> chId then async{check()|> ignore} |> Async.Start // the change id is not the same as the current one, so the check is outdated, start a new check in the background + Some r // but return the old result + | WaitForCompl r | WaitForErr r -> Some r + | NotChecked -> check() + + match res with // false for not aborting on a new check id + | None -> None + | Some res -> Checker.GetDeclarations(posX,res) + // if containsQuery(posX.query, decls) then + + + async{ + let show = MaybeShow.completionWindow(pos) + + match show with + |DoNothing -> + // DoNothing means the cursor is typing in a comment: + // BUT ! Don't just do nothing!! + // Even when typing in a comment, the checker should run because it makes a new change ID and that new change ID would + // abort any still running checker and not trigger a new one. unless we do it here. + do! Async.Sleep (300) + DocChangeMark.updateAllTransformersSync(iEd, doc, drawServ, state, chId, pos.lineIdx ) + + |JustMark -> + DocChangeMark.updateAllTransformersSync(iEd, doc, drawServ, state, chId, pos.lineIdx ) + + | ShowAll | ShowOnlyDU | DeclModifiers | ShowDirectives -> + // WaitForCompletions: from now on any typed character will NOT increment the doc change + // AND never trigger a checking or or new completion window + // the characters go to the doc and will be taken from there as a prefilter for the completion list + state.DocChangedConsequence <- WaitForCompletions // incrementing IDs is disabled now + + let posX = PositionInCodeEx.get(pos) + + let showListOpt : option = + match show with + | ShowAll -> getDecls(posX)|> Option.map JustAllFrom //|> Option.orElse ( if posX.dotBefore then None else Some JustDeclModifiers) // if nod declarations where found, and there was no dot before, try show keywords + | ShowOnlyDU -> getDecls(posX)|> Option.map JustDuFrom + | DeclModifiers -> Some JustDeclModifiers + | ShowDirectives -> Some JustDirectives + | DoNothing -> None // never reached + | JustMark -> None // never reached + + match showListOpt with + | Some showList -> + // Switch to Sync and try showing completion window: + let checkAndMark = fun () -> DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, state.Increment(), pos.lineIdx ) // will be called if window closes without an insertion + + do! Async.SwitchToContext Fittings.SyncWpf.context + match drawServ.compls.TryShow( posX, showList, checkAndMark) with + |NoShow -> + state.DocChangedConsequence <- React + DocChangeMark.updateAllTransformersAsync(iEd, drawServ, state, state.Increment(), pos.lineIdx )// incrementing because it was disabled from state.DocChangedConsequence <- WaitForCompletions + |DidShow -> + // completion window is showing now, + // no need to do anything, + // DocChangedConsequence will be updated to 'React' when completion window closes + // and checkAndMark will be called. + () + | None -> + state.DocChangedConsequence <- React + DocChangeMark.updateAllTransformersSync (iEd, doc, drawServ, state, state.Increment(), pos.lineIdx ) + + } |> Async.Start + - let containsQuery (posX:PositionInCodeEx, decls:DeclarationListInfo)= - decls.Items |> Array.tryFind (fun d -> d.NameInList.Contains posX.query) /// for single character edits - let singleCharChange (iEd:IEditor, drawServ:DrawingServices, state:InteractionState, chId:int64) = - let pos = getPosInCode2(iEd.AvaEdit) + let singleCharChange (iEd:IEditor, drawServ:DrawingServices, state:InteractionState, chId:int64, lineIdx ) = + let pos = getPosInCode(iEd.AvaEdit) let tx = pos.lineToCaret if tx.Length = 0 then // empty line after deleting - DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, chId) + DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, chId, lineIdx ) else let lastChar = tx[tx.Length-1] if lastChar <> '.' && state.JustCompleted then // if it is not a dot avoid re-trigger of completion window on single character completions, just do check and mark state.JustCompleted <- false // reset it - DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, chId) + DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, chId, lineIdx ) else let doc = iEd.AvaEdit.Document // get in sync async{ if DocChangeMark.mainWait <> 0 then - do! Async.Sleep DocChangeMark.mainWait // to not trigger completion if tying is fast + do! Async.Sleep DocChangeMark.mainWait // to not trigger completion if typing is fast if state.IsLatest chId then if not <| MaybeShow.lastCharTriggersCompletion (lastChar,pos) then // The typed character should not trigger completion. // DocChangedConsequence is still 'React', no need to reset. - DocChangeMark.updateAllTransformersSync(iEd, doc, drawServ, state, chId) + DocChangeMark.updateAllTransformersSync(iEd, doc, drawServ, state, chId, lineIdx ) else - let show = MaybeShow.completionWindow(pos) - //IFeshLog.log.PrintfnDebugMsg $"MaybeShow.completionWindow for {lastChar} is {show}" - match show with - |DoNothing -> - // DoNothing mean we are typing in a comment: - // BUT ! Don't just do nothing!! - // Even when typing in a comment, the checker should run because it makes a new change ID and that new change ID would - // abort any still running checker and not trigger a new one. unless we do it here. - do! Async.Sleep (300) - DocChangeMark.updateAllTransformersSync(iEd, doc, drawServ, state, chId) - - |JustMark -> - DocChangeMark.updateAllTransformersSync(iEd, doc, drawServ, state, chId) - - |ShowAll |ShowOnlyDU |ShowKeyWords -> - // WaitForCompletions: from now on any typed character will NOT increment the doc change - // AND never trigger a checking or or new completion window - // the characters go to the doc and will be taken from there as a prefilter for the completion list - state.DocChangedConsequence <- WaitForCompletions // incrementing is disabled now - - let declsPosX = - Monads.maybe{ - let fullCode = doc.CreateSnapshot().Text - //let! _ = state.IsLatestOpt id2 // incrementing is disabled so no point in checking ( WaitForCompletions ) - let! res = Checker.CheckCode(iEd, state, fullCode, chId, false) // false for not aborting on a new check id - let! decls, pos = Checker.GetCompletions(pos,res) - let! _ = containsQuery(pos, decls) //pre check (still async) if the posX.query is contained in any name in the completion list - return decls, pos - } - - match declsPosX with - |None -> - state.DocChangedConsequence <- React - DocChangeMark.updateAllTransformersSync (iEd, doc, drawServ, state, state.Increment()) - - |Some (decls, posX) -> - - // Switch to Sync and try showing completion window: - let checkAndMark = fun () -> DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, state.Increment()) // will be called if window closes without an insertion - let showRestrictions = MaybeShow.getShowRestriction show - do! Async.SwitchToContext Fittings.SyncWpf.context - match drawServ.compls.TryShow(decls, posX, showRestrictions, checkAndMark) with - |NoShow -> - state.DocChangedConsequence <- React - DocChangeMark.updateAllTransformersAsync(iEd, drawServ, state, state.Increment())// incrementing because it was disabled from state.DocChangedConsequence <- WaitForCompletions - |DidShow -> - // completion window is showing now, - // no need to do anything, - // DocChangedConsequence will be updated to 'React' when completion window closes - // and checkAndMark will be called. - - () - + handelShow(pos, doc,iEd, drawServ, state, chId) } |> Async.Start + + module DocChangeEvents = open DocChangeUtil + let ctrlSpace(iEd:IEditor, drawServ:Redrawing.DrawingServices, state:InteractionState, eventArgs:KeyEventArgs) = + match state.DocChangedConsequence with + | WaitForCompletions -> () + | React -> + if eventArgs.Key = Key.Space && MaybeShow.getCtrlDown() then + eventArgs.Handled <- true // to not actually insert a space + let pos = getPosInCode(iEd.AvaEdit) + let doc = iEd.AvaEdit.Document // get in sync + DocChangeCompletion.handelShow (pos, doc, iEd, drawServ, state, state.DocChangedId.Value) + + // gets called before the document actually changes - let changing (state:InteractionState) (a:DocumentChangeEventArgs) = + let changing(state:InteractionState, a:DocumentChangeEventArgs) = // (1) increment change counter match state.DocChangedConsequence with | WaitForCompletions -> @@ -478,7 +537,7 @@ module DocChangeEvents = state.FastColorizer.AdjustShifts shift state.ErrSegments.AdjustOneShift shift - let changed (iEd:IEditor) (drawServ:Redrawing.DrawingServices) (state:InteractionState) (eventArgs:DocumentChangeEventArgs) : unit = + let changed (iEd:IEditor, drawServ:Redrawing.DrawingServices, state:InteractionState, eventArgs:DocumentChangeEventArgs) : unit = match state.DocChangedConsequence with | WaitForCompletions -> // no type checking ! just keep on tying, @@ -486,8 +545,9 @@ module DocChangeEvents = () | React -> let id = state.DocChangedId.Value // the increment was done before this event in Doc.Changing (not Doc.Changed) - if isASingleCharChange eventArgs then DocChangeCompletion.singleCharChange (iEd, drawServ, state, id)// maybe shows completion window - else DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, id) + let lineIdx = iEd.AvaEdit.Document.GetLineByOffset( eventArgs.Offset).LineNumber + if isASingleCharChange eventArgs then DocChangeCompletion.singleCharChange (iEd, drawServ, state, id, lineIdx)// maybe shows completion window + else DocChangeMark.updateAllTransformersAsync (iEd, drawServ, state, id, lineIdx) (* diff --git a/Src/Editor/ErrorHighlighter.fs b/Src/Editor/ErrorHighlighter.fs index c19a89d..41c0a03 100644 --- a/Src/Editor/ErrorHighlighter.fs +++ b/Src/Editor/ErrorHighlighter.fs @@ -28,8 +28,7 @@ module ErrorUtil = /// for clicking through the errors in the status bar let mutable private scrollToIdx = -1 - - let maxErrorCountToTrack = 9 + let maxErrorCountToTrack = 5 /// split errors by severity and sort by line number let getBySeverity(checkRes:CodeAnalysis.FSharpCheckFileResults) :ErrorsBySeverity = @@ -45,15 +44,15 @@ module ErrorUtil = // let m2 = maxErrorCountToTrack * 2 for i = 0 to all.Length - 1 do let e = all.[i] - if i=0 // to filter out duplicate errors, a bug in FCS ! - ||( let p = all.[i-1] in p.StartLine <> e.StartLine || p.StartColumn <> e.StartColumn || p.EndLine <> e.EndLine || p.EndColumn <> e.EndColumn) then + // to filter out duplicate errors, a bug in FCS ! + if i=0 || ( let p = all.[i-1] in p.StartLine <> e.StartLine || p.StartColumn <> e.StartColumn || p.EndLine <> e.EndLine || p.EndColumn <> e.EndColumn) then match e.Severity with | FSharpDiagnosticSeverity.Error -> if ers.Count < m then (ers.Add e ; erWs.Add e) | FSharpDiagnosticSeverity.Warning -> if was.Count < m then (was.Add e ; erWs.Add e) | FSharpDiagnosticSeverity.Hidden -> if his.Count < m then his.Add e | FSharpDiagnosticSeverity.Info -> if ins.Count < m && e.ErrorNumber <> 3370 then ins.Add e // exclude infos about ref cell incrementing ?? - //printfn $"Errors: {ers.Count} Warnings: {was.Count} Infos: {insCount} Hidden: {his.Count} " + //printfn $"Errors: {ers.Count} Warnings: {was.Count} Infos: {ins.Count} Hidden: {his.Count} " { errors = ers; warnings = was; infos = ins; hiddens = his; errorsAndWarnings = erWs } let linesStartAtOne i = if i<1 then 1 else i @@ -112,7 +111,8 @@ module ErrorUtil = let rec getNextSegment(ied:IEditor) = match ied.FileCheckState with - | Checking -> None + | NotChecked | WaitForCompl _ | WaitForErr _-> + None | Done res -> let ews = res.errors.errorsAndWarnings if ews.Count=0 then @@ -139,12 +139,14 @@ type ErrorRenderer (state: InteractionState) = // better would be https://github.com/icsharpcode/SharpDevelop/blob/master/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/Textsegmentservice.cs /// Draw the error squiggle on the code - member _.Draw (textView:TextView , drawingContext:DrawingContext) = // for IBackgroundRenderer + + member _.Draw(textView:TextView , drawingContext:DrawingContext) = // for IBackgroundRenderer //AvalonEditB.Rendering.VisualLinesInvalidException: Exception of type 'AvalonEditB.Rendering.VisualLinesInvalidException' was thrown. // at AvalonEditB.Rendering.TextView.get_VisualLines() // at Fesh.Editor.ErrorRenderer.Draw(TextView textView, DrawingContext drawingContext) in D:\Git\Fesh\Src\Editor\ErrorHighlighter.fs:line 138 // at AvalonEditB.Rendering.TextView.RenderBackground(DrawingContext drawingContext, KnownLayer layer) // at AvalonEditB.Editing.CaretLayer.OnRender(DrawingContext drawingContext) + if textView.VisualLinesValid then //to avoid above error. let vls = textView.VisualLines if vls.Count > 0 then // check needed ! @@ -176,7 +178,6 @@ type ErrorRenderer (state: InteractionState) = //elif till > offEn then () // eprintfn "till > offEn " // avoid jumping to next line //elif from < offSt then () // eprintfn "from < offSt" // avoid jumping to previous line else - // background color: // when drawing on Caret layer background must be disabled. // let geoBuilder = new BackgroundGeometryBuilder (AlignToWholePixels = true, CornerRadius = 0.) @@ -185,18 +186,18 @@ type ErrorRenderer (state: InteractionState) = // drawingContext.DrawGeometry(seg.BackgroundBrush, null, boundaryPolygon) //foreground, squiggles: - let iSeg = {new ISegment with - member _.Offset = from - member _.EndOffset = till - member _.Length = till - from } + let iSeg = ISegment.FormTill(from,till) let rects = BackgroundGeometryBuilder.GetRectsForSegment(textView, iSeg) |> ResizeArray if rects.Count = 1 then // skip if line overflows and there is more than one rect let geo = ErrorUtil.getSquiggleLine(rects[0], -1.0) // neg offset to move down drawingContext.DrawGeometry(Brushes.Transparent, seg.UnderlinePen, geo) member _.Layer = - // when drawing on Caret layer the background change must be disabled - KnownLayer.Caret// .Selection// for IBackgroundRenderer + + // when drawing on Caret layer This method is called on every blink of the CaretLayer + // KnownLayer.Caret// .Selection// for IBackgroundRenderer + KnownLayer.Caret + interface IBackgroundRenderer with member this.Draw(tv,dc) = this.Draw(tv,dc) @@ -228,6 +229,8 @@ type ErrorHighlighter ( state:InteractionState, folds:Folding.FoldingManager, is let rec insert lnNo = if lnNo > enLn then true + elif lnNo-stLn > 2 then // don't insert more than 2 lines of errors, because the are costly to draw + true else match state.CodeLines.GetLine(lnNo,id) with | ValueNone -> false diff --git a/Src/Editor/EvaluationTracker.fs b/Src/Editor/EvaluationTracker.fs index 3147366..a51567e 100644 --- a/Src/Editor/EvaluationTracker.fs +++ b/Src/Editor/EvaluationTracker.fs @@ -17,174 +17,116 @@ module private EvaluationTrackerRendererUtil = //let backGround = Brushes.Teal |> brighter 230 |> freeze //let backGround = Brushes.Ivory |> brighter 5 |> freeze - //let backGround = Brushes.LightGray |> brighter 30 |> freeze - let backGround = SolidColorBrush(Color.FromArgb(120uy,239uy,239uy,239uy))|> freeze // a=0 : fully transparent A=255 opaque + let backGround = Brushes.Gray |> brighter 110 |> freeze + // let backGround = SolidColorBrush(Color.FromArgb(120uy,239uy,239uy,239uy))|> freeze // a=0 : fully transparent A=255 opaque - //let pen = new Pen(Brushes.Black |> freeze , 0.5) |> Pen.freeze + let border = + let p = new Pen(Brushes.Gray |> darker 20 |> freeze , 1.0) + p.Freeze() + p + //let border = new Pen( Brushes.Teal |> freeze , 0.7) |> Pen.freeze - let inline newSegmentTill(endOff) = - if endOff <= 0 then - null - else - let s = TextSegment() - s.StartOffset <- 0 - s.EndOffset <-endOff - s - - /// c<>' ' && c <> '\r' && c <> '\n' - let inline isNonWhite(c:Char) = - c <> ' ' - && c <> '\r' - && c <> '\n' - - /// c=' ' || c = '\r' || c = '\n' - let inline isWhite(c:Char) = - c = ' ' - || c = '\r' - || c = '\n' open EvaluationTrackerRendererUtil /// IBackgroundRenderer -type EvaluationTrackerRenderer (ed:TextEditor) = - let doc = ed.Document +type EvaluationTrackerRenderer (ed:TextEditor, state:InteractionState ) = - let mutable evaluatedCodeSeg: ISegment = null + let [] ``1`` = 1 - /// Offset of where evaluation should continue from - /// Holds the index of the first unevaluated offset, this might be out of bound too, if all doc is evaluated. - let mutable topMostUnEvaluated = 0 + let mutable evalFromLine = ``1`` - let formatChar (ch:char) = - if ch = '\n'then "\\n" - elif ch = '\r'then "\\r" - else ch.ToString() - - let computeParagraphAndDraw() = - if topMostUnEvaluated = 0 then - if isNull evaluatedCodeSeg then () // nothing evaluated yet, do nothing - else evaluatedCodeSeg <-null // happens when there is a segment but the first char gets deleted + let recomputeEvalFromLineByIndent(changedLineIdx) = + if changedLineIdx <= ``1`` then + evalFromLine <- ``1`` else - let len = doc.TextLength - if topMostUnEvaluated >= len then - evaluatedCodeSeg <- newSegmentTill(len-1)// recalculate just to be sure it hasn't changed - ed.TextArea.TextView.Redraw() - - elif isNull evaluatedCodeSeg || topMostUnEvaluated-1 <> evaluatedCodeSeg.EndOffset then - let lastInEval = doc.GetCharAt(topMostUnEvaluated-1) - //IFeshLog.log.PrintfnColor 144 222 100 "computeParagraphAndDraw:\r\n'%s'" ed.Text - - // first try to look ahead if there is only white space till a paragraph starts. - // in that case keep the current evaluated marking - let rec keepMark prev i = - if i = len then - true - else - let ch = doc.GetCharAt(i) - //IFeshLog.log.PrintfnColor 200 0 100 "prev: '%s' ch: '%s'" (formatChar prev)(formatChar ch) - if isNonWhite ch then // non whitespace - if prev = '\n' then // find a line that does not start with white space - true //i-1 // there is only white space till 'i' where a paragraph starts - else - false //a non white but not at beginning of line - else - keepMark ch (i+1) - let keep = keepMark lastInEval topMostUnEvaluated - //IFeshLog.log.PrintfnColor 55 99 100 "keep is: '%b'" keep - - //now search back needed since the next non white is at position 0 in line - if keep then - let mutable j = topMostUnEvaluated-1 - while j>0 && isWhite (doc.GetCharAt(j)) do // to remove white space too - j <- j-1 - topMostUnEvaluated <- j+2 - evaluatedCodeSeg <- newSegmentTill(j+1) + let lns = state.CodeLines + let id = state.DocChangedId + let rec findIndent i = + if i <= ``1`` then ``1`` else - let rec searchBack after i = - if i = -1 then 0 + match lns.GetLine(i,id.Value) with // checks for 0 index + | ValueSome ln -> + if ln.indent = 0 then + i else - let this = doc.GetCharAt(i) - if this = '\n' && isNonWhite after then // find a line that does not start with white space - //i // end segment will include line return - let mutable j = i-1 - while j>0 && isWhite (doc.GetCharAt(j)) do // to remove white space too - j <- j-1 - j+1 - else - searchBack this (i-1) - - //IFeshLog.log.PrintfnColor 0 200 100 "topMostUnEvaluated-1: %d" (topMostUnEvaluated-1) - let segmentEnd = - let segEnd = searchBack lastInEval topMostUnEvaluated // GetCharAt cant be -1 because there is a check at the top - - /// now include any attributes and comments in the lines above , and skip whitespace again - let rec moveUp (ln:DocumentLine) = - if ln.LineNumber = 1 then - min segEnd ln.EndOffset // min() because segEnd might be smaller than ln.EndOffset - else - let st2 = doc.GetText(ln.Offset,2) - let st5 = doc.GetText(ln.Offset,5) - if st2 = "[<" || st2 = "//" then // for attributes comments - moveUp(ln.PreviousLine) - elif String.IsNullOrWhiteSpace(doc.GetText(ln)) then - moveUp(ln.PreviousLine) - elif st5 = "open " || st5 = "#if I" then // TODO: detect correctly if current end segment is inside a #if #elif #else block ! - moveUp(ln.PreviousLine) - else - ln.EndOffset - moveUp(doc.GetLineByOffset(segEnd)) - - - if segmentEnd = 0 then - topMostUnEvaluated <- 0 - evaluatedCodeSeg <- null - else - topMostUnEvaluated <- segmentEnd+1 - evaluatedCodeSeg <- newSegmentTill(segmentEnd) - - ed.TextArea.TextView.Redraw() + findIndent (i-1) + | ValueNone -> + ``1`` + let noIntentLine = findIndent(min lns.LastLineIdx changedLineIdx) + printfn "noIntentLine: %d" noIntentLine + + let rec nextNonWhiteLine i = + if i <= ``1`` then ``1`` + else + match lns.GetLineText(i,id.Value) with + | ValueSome txt -> + if String.IsNullOrWhiteSpace(txt) then nextNonWhiteLine (i-1) + else i+1 + | ValueNone -> + IFeshLog.log.PrintfnDebugMsg "nextNonWhiteLine: Line not found %d" i + ``1`` + + evalFromLine <- nextNonWhiteLine (noIntentLine-1) /// Triggered on each document changed - member _.SetLastChangeAt(offset) = - if offset < topMostUnEvaluated then - //IFeshLog.log.PrintfnColor 0 200 0 "Doc change topMostUnEvaluated offset : %d '%s' " offset (formatChar<| doc.GetCharAt(offset)) - topMostUnEvaluated <- offset - computeParagraphAndDraw() - - member _.MarkNoneEvaluated() = - topMostUnEvaluated <- 0 - evaluatedCodeSeg <- null + member _.SetLastChangeAt(changedLineIdx) = + if changedLineIdx < evalFromLine then + recomputeEvalFromLineByIndent(changedLineIdx) + + member _.ClearMarking() = + evalFromLine <- ``1`` ed.TextArea.TextView.Redraw() - /// provide the index of the first unevaluated offset, this might be out of bound too if all doc is evaluated and will be checked - member _.MarkEvaluatedTillOffset(offset) = - let len = doc.TextLength // off might be bigger than doc.TextLength because expandSelectionToFullLines adds 2 chars at end. - topMostUnEvaluated <- min len offset - computeParagraphAndDraw() + /// provide the index of the last evaluated Line + member _.MarkEvaluatedTillLine(changedLineIdx) = + evalFromLine <- changedLineIdx + 1 member _.MarkAllEvaluated() = - let mutable len = doc.TextLength-1 - while len > 0 && isWhite (doc.GetCharAt(len)) do // to exclude empty lines at end - len <- len-1 - topMostUnEvaluated <- len + 2 - evaluatedCodeSeg <- newSegmentTill(len+1) + let cLns = state.CodeLines + let mutable li = cLns.LastLineIdx + let inline isWhite(i) = + match cLns.GetLineText(i, state.DocChangedId.Value) with + | ValueSome txt -> String.IsNullOrWhiteSpace(txt) + | ValueNone -> false + while li > ``1`` && isWhite(li) do // to exclude empty lines at end + li <- li-1 + evalFromLine <- li + 1 ed.TextArea.TextView.Redraw() - /// Offset of where evaluation should continue from - member _.EvaluateFrom = topMostUnEvaluated + /// Line Number where evaluation should continue from + member _.EvaluateFromLine = evalFromLine // for IBackgroundRenderer - member _.Draw (textView:TextView , drawingContext:DrawingContext) = - if notNull evaluatedCodeSeg then + member _.Draw(textView:TextView , drawingContext:DrawingContext) = + if evalFromLine > ``1`` then try - let geoBuilder = new BackgroundGeometryBuilder (AlignToWholePixels = true, CornerRadius = 0.) - geoBuilder.AddSegment(textView, evaluatedCodeSeg) // TODO: what happens if the code became shorter and this segment is now bigger than the document ? - let boundaryPolygon = geoBuilder.CreateGeometry() // creates one boundary round the text - drawingContext.DrawGeometry(backGround, null, boundaryPolygon) // pen could be null too - //drawingContext.DrawGeometry(backGround, border, boundaryPolygon) // pen could be null too + if textView.VisualLinesValid then //to avoid above error. + let vls = textView.VisualLines + if vls.Count > 0 then // check needed ! + let topLine = vls[0].FirstDocumentLine + let topIdx= topLine.LineNumber + // let botLine = vls[vls.Count-1].LastDocumentLine.LineNumber + let tillLineIdx = evalFromLine-1 + if tillLineIdx >= topIdx then + let mutable endIdx = 0 + while endIdx < vls.Count-1 && vls[endIdx].FirstDocumentLine.LineNumber < tillLineIdx do + endIdx <- endIdx + 1 + let endLine = vls[endIdx] + let y = endLine.VisualTop - textView.VerticalOffset + endLine.Height + let rect = RectangleGeometry(new Windows.Rect(1.0, -1.0, textView.ActualWidth - 1.0, y + 2.0)) + drawingContext.DrawGeometry(backGround, border, rect) // pen could be null too + + + // let geoBuilder = new BackgroundGeometryBuilder (AlignToWholePixels = true, CornerRadius = 0.0 ) + // let seg = ISegment.FormTill(0, textView.Document.GetLineByNumber(evalFromLine-1).EndOffset) + // geoBuilder.AddSegment(textView, seg) // TODO: what happens if the code became shorter and this segment is now bigger than the document ? + // let boundaryPolygon = geoBuilder.CreateGeometry() // creates one boundary round the text + // //drawingContext.DrawGeometry(backGround, null, boundaryPolygon) // pen could be null too + // drawingContext.DrawGeometry(backGround, border, boundaryPolygon) // pen could be null too // TODO draw a draggable separator instead: // http://www.fssnip.net/9N/title/Drag-move-for-GUI-controls @@ -192,49 +134,148 @@ type EvaluationTrackerRenderer (ed:TextEditor) = IFeshLog.log.PrintfnAppErrorMsg "ERROR in EvaluationTrackerRenderer.Draw(): %A" ex // for IBackgroundRenderer - member _.Layer = KnownLayer.Caret// KnownLayer.Background + member _.Layer = + // KnownLayer.Caret // to draw over all text a transparent layer + // KnownLayer.Selection + KnownLayer.Background interface IBackgroundRenderer with member this.Draw(tv,dc) = this.Draw(tv,dc) member this.Layer = this.Layer +type EvaluationTracker (ed:TextEditor, state, config:Config.Config) = -type EvaluationTracker (ed:TextEditor, config:Fesh.Config.Config) = + let isActive = config.Settings.GetBool(EvaluationTracker.SettingsStr, EvaluationTracker.onByDefault) - let renderer = EvaluationTrackerRenderer(ed) + let renderer = EvaluationTrackerRenderer(ed,state) //TODO on tab change and "EvalInteractionNonThrowing returned Error:" reset too ! do - ed.TextArea.TextView.BackgroundRenderers.Add(renderer) - let fsi =Fsi.GetOrCreate(config) - fsi.OnReset.Add (fun _ -> renderer.MarkNoneEvaluated()) // reset for all editors - fsi.OnCanceled.Add (fun _ -> if IEditor.isCurrent ed then renderer.MarkNoneEvaluated()) - fsi.OnRuntimeError.Add(fun _ -> if IEditor.isCurrent ed then renderer.MarkNoneEvaluated()) - fsi.OnCompletedOk.Add (fun evc -> - if IEditor.isCurrent ed then //this event will be hooked up for each tab so check id too - //IFeshLog.log.PrintfnColor 150 150 150 "Fsi.OnCompletedOk:%A" evc - //IFeshLog.log.PrintfnFsiErrorMsg "Fsi.OnCompletedOk:renderer.EvaluateFrom:%d" renderer.EvaluateFrom - match evc.amount with - |All |ContinueFromChanges -> renderer.MarkAllEvaluated() - |FsiSegment s -> - let endOffset = s.startOffset + s.length - if s.startOffset <= renderer.EvaluateFrom // check start is in already evaluated region - && endOffset > renderer.EvaluateFrom then - renderer.MarkEvaluatedTillOffset(endOffset) - //IFeshLog.log.PrintfnFsiErrorMsg "Fsi.OnCompletedOk,FsiSegment:renderer.EvaluateFrom:%d" renderer.EvaluateFrom + if isActive then + ed.TextArea.TextView.BackgroundRenderers.Add(renderer) + let fsi =Fsi.GetOrCreate(config) + fsi.OnReset.Add (fun _ -> renderer.ClearMarking()) // reset for all editors + fsi.OnCanceled.Add (fun _ -> if IEditor.isCurrent ed then renderer.ClearMarking()) + fsi.OnRuntimeError.Add(fun _ -> if IEditor.isCurrent ed then renderer.ClearMarking()) + // fsi.OnFsiEvalError.Add(fun _ -> if IEditor.isCurrent ed then renderer.ClearMarking()) + fsi.OnCompletedOk.Add (fun evc -> + if IEditor.isCurrent ed then //this event will be hooked up for each tab so check id too + //IFeshLog.log.PrintfnColor 150 150 150 "Fsi.OnCompletedOk:%A" evc + //IFeshLog.log.PrintfnFsiErrorMsg "Fsi.OnCompletedOk:renderer.EvaluateFrom:%d" renderer.EvaluateFrom + match evc.amount with + |All |ContinueFromChanges -> renderer.MarkAllEvaluated() + |FsiSegment s -> + if s.startLine <= renderer.EvaluateFromLine then // only mark if the code before was evaluated already + let li = ed.Document.GetLineByOffset(s.startOffset + s.length).LineNumber + renderer.MarkEvaluatedTillLine(li) + ed.TextArea.TextView.Redraw() + else + IFeshLog.log.PrintfnDebugMsg "FsiSegment.startLine > renderer.EvaluateFromLine: %d > %d" s.startLine renderer.EvaluateFromLine ) - member _.SetLastChangeAt(off) = renderer.SetLastChangeAt(off) - - /// Offset of where evaluation should continue from - member _.EvaluateFrom = renderer.EvaluateFrom - - /// provide the index of the first unevaluated offset, this might be out of bound too if all doc is evaluated and will be checked - member _.MarkEvaluatedTillOffset(off) = renderer.MarkEvaluatedTillOffset(off) - - - - - + member _.SetLastChangeAt(lineIdx) = + if isActive then + renderer.SetLastChangeAt(lineIdx) + + /// Line where evaluation should continue from + member _.EvaluateFromLine = + renderer.EvaluateFromLine + + member _.MarkEvaluatedTillLineRedraw(lineIdx) = + if isActive then + renderer.MarkEvaluatedTillLine(lineIdx) + ed.TextArea.TextView.Redraw() + + + /// just the string literal used for settings file: "TrackEvaluatedCode" + static member SettingsStr = "TrackEvaluatedCode" + + static member onByDefault = false + + + + // let recomputeTopMostUnEvaluatedByIndent() = + // if topMostUnEvaluated = 0 then + // if isNull evaluatedCodeSeg then () // nothing evaluated yet, do nothing + // else evaluatedCodeSeg <- null // happens when there is a segment but the first char gets deleted + // else + // let len = doc.TextLength + // if topMostUnEvaluated >= len then + // evaluatedCodeSeg <- newSegmentTill(len-1)// recalculate just to be sure it hasn't changed + // ed.TextArea.TextView.Redraw() + + // elif isNull evaluatedCodeSeg || topMostUnEvaluated-1 <> evaluatedCodeSeg.EndOffset then + // let lastInEval = doc.GetCharAt(topMostUnEvaluated-1) + // //IFeshLog.log.PrintfnColor 144 222 100 "computeParagraphAndDraw:\r\n'%s'" ed.Text + + // // first try to look ahead if there is only white space till a paragraph starts. + // // in that case keep the current evaluated marking + // let rec keepMark prev i = + // if i = len then + // true + // else + // let ch = doc.GetCharAt(i) + // //IFeshLog.log.PrintfnColor 200 0 100 "prev: '%s' ch: '%s'" (formatChar prev)(formatChar ch) + // if isNonWhite ch then // non whitespace + // if prev = '\n' then // find a line that does not start with white space + // true //i-1 // there is only white space till 'i' where a paragraph starts + // else + // false //a non white but not at beginning of line + // else + // keepMark ch (i+1) + // let keep = keepMark lastInEval topMostUnEvaluated + // //IFeshLog.log.PrintfnColor 55 99 100 "keep is: '%b'" keep + + // //now search back needed since the next non white is at position 0 in line + // if keep then + // let mutable j = topMostUnEvaluated-1 + // while j>0 && isWhite (doc.GetCharAt(j)) do // to remove white space too + // j <- j-1 + // topMostUnEvaluated <- j+2 + // evaluatedCodeSeg <- newSegmentTill(j+1) + // else + // let rec searchBack after i = + // if i = -1 then 0 + // else + // let this = doc.GetCharAt(i) + // if this = '\n' && isNonWhite after then // find a line that does not start with white space + // //i // end segment will include line return + // let mutable j = i-1 + // while j>0 && isWhite (doc.GetCharAt(j)) do // to remove white space too + // j <- j-1 + // j+1 + // else + // searchBack this (i-1) + + // //IFeshLog.log.PrintfnColor 0 200 100 "topMostUnEvaluated-1: %d" (topMostUnEvaluated-1) + // let segmentEnd = + // let segEnd = searchBack lastInEval topMostUnEvaluated // GetCharAt cant be -1 because there is a check at the top + + // /// now include any attributes and comments in the lines above , and skip whitespace again + // let rec moveUp (ln:DocumentLine) = + // if ln.LineNumber = 1 then + // min segEnd ln.EndOffset // min() because segEnd might be smaller than ln.EndOffset + // else + // let st2 = doc.GetText(ln.Offset,2) + // let st5 = doc.GetText(ln.Offset,5) + // if st2 = "[<" || st2 = "//" then // for attributes comments + // moveUp(ln.PreviousLine) + // elif String.IsNullOrWhiteSpace(doc.GetText(ln)) then + // moveUp(ln.PreviousLine) + // elif st5 = "open " || st5 = "#if I" then // TODO: detect correctly if current end segment is inside a #if #elif #else block ! + // moveUp(ln.PreviousLine) + // else + // ln.EndOffset + // moveUp(doc.GetLineByOffset(segEnd)) + + + // if segmentEnd = 0 then + // topMostUnEvaluated <- 0 + // evaluatedCodeSeg <- null + // else + // topMostUnEvaluated <- segmentEnd+1 + // evaluatedCodeSeg <- newSegmentTill(segmentEnd) + + // ed.TextArea.TextView.Redraw() \ No newline at end of file diff --git a/Src/Editor/InteractionState.fs b/Src/Editor/InteractionState.fs index fd3d076..6be228e 100644 --- a/Src/Editor/InteractionState.fs +++ b/Src/Editor/InteractionState.fs @@ -60,7 +60,7 @@ module CodeLineTools = newLns.Add {offStart=stOff; indent=indent; len=len} loop (r+2) // +2 to jump over \r and \n - newLns.Add {offStart=0; indent=0; len=0} // ad dummy line at index 0 + newLns.Add {offStart=0; indent=0; len=0} // ad dummy line at index 0 , line 0 is always empty, line start at 1 in AvalonEdit loop (0) newLns @@ -93,6 +93,7 @@ module CodeLineTools = else ValueNone + /// Safe: checks correspondingId = changeId /// returns also none for bad indices member _.GetLineText(lineIdx, changeId): string voption = @@ -102,7 +103,6 @@ module CodeLineTools = else let l = lines.[lineIdx] ValueSome (fullCode.Substring(l.offStart,l.len)) - else ValueNone diff --git a/Src/Editor/Selection.fs b/Src/Editor/Selection.fs index 90b0fdd..3a110d9 100644 --- a/Src/Editor/Selection.fs +++ b/Src/Editor/Selection.fs @@ -158,11 +158,29 @@ module Selection = line.LineNumber, avaEdit.Document.GetText(line.Offset, line.Length) /// Offset at line End ( excluding \r and \n that probably follow - let currentLineEnd (avaEdit:TextEditor) : int= + let currentLineEndOffset (avaEdit:TextEditor) : int= let offset = avaEdit.CaretOffset let line = avaEdit.Document.GetLineByOffset(offset) line.Offset + line.Length + /// Offset at line End ( excluding \r and \n that probably follow + let currentLineIdx (avaEdit:TextEditor) : int= + let offset = avaEdit.CaretOffset + let line = avaEdit.Document.GetLineByOffset(offset) + line.LineNumber + + + let linesTillCursor(avaEdit:TextEditor) : CodeSegment = + let doc = avaEdit.Document + let en = doc.GetLineByOffset(avaEdit.SelectionStart + avaEdit.SelectionLength) + let t = doc.GetText(0,en.EndOffset) + { + text = t //+ Environment.NewLine // add line return so that EvaluationTracker.fs knows it is the full line and can jump to next line. + startLine = 1 + startOffset = 0 + length = t.Length //+ Environment.NewLine.Length + } + module SelectionForEval = /// Returns start line umber and selected text, or "" if none @@ -196,10 +214,10 @@ module SelectionForEval = let s = avaEdit.TextArea.Selection let t = avaEdit.SelectedText { - text = t + Environment.NewLine // add line return so that EvaluationTracker.fs knows it is the full line and can jump to next line. + text = t //+ Environment.NewLine // add line return so that EvaluationTracker.fs knows it is the full line and can jump to next line. TODO, not needed anymore ? startLine = s.StartPosition.Line // not st.LineNumber because this may just as wel be the last line startOffset = avaEdit.SelectionStart - length = t.Length + Environment.NewLine.Length + length = t.Length //+ Environment.NewLine.Length } @@ -210,10 +228,10 @@ module SelectionForEval = avaEdit.Select(0,en.EndOffset) let t = avaEdit.SelectedText { - text = t + Environment.NewLine // add line return so that EvaluationTracker.fs knows it is the full line and can jump to next line. + text = t //+ Environment.NewLine // add line return so that EvaluationTracker.fs knows it is the full line and can jump to next line. startLine = 1 startOffset = 0 - length = t.Length + Environment.NewLine.Length + length = t.Length //+ Environment.NewLine.Length } (* diff --git a/Src/Editor/SelectionHighlighter.fs b/Src/Editor/SelectionHighlighter.fs index 3c34ec2..02611eb 100644 --- a/Src/Editor/SelectionHighlighter.fs +++ b/Src/Editor/SelectionHighlighter.fs @@ -15,9 +15,9 @@ open Fesh.Editor.CodeLineTools module SelectionHighlighting = - let colorEditor = Brushes.PaleTurquoise |> AvalonLog.Brush.brighter 30 |> AvalonLog.Brush.freeze + let selColorEditor = Brushes.PaleTurquoise |> AvalonLog.Brush.brighter 30 |> AvalonLog.Brush.freeze - let colorLog = Brushes.Blue |> AvalonLog.Brush.brighter 220 |> AvalonLog.Brush.freeze + let selColorLog = Brushes.Blue |> AvalonLog.Brush.brighter 220 |> AvalonLog.Brush.freeze let colorInactive= Brushes.Gray |> AvalonLog.Brush.freeze let foundSelectionLogEv = new Event() @@ -85,7 +85,7 @@ type SelRedraw = /// Highlight-all-occurrences-of-selected-text in Editor type SelectionHighlighter (state:InteractionState) = - let action = new Action(fun el -> el.TextRunProperties.SetBackgroundBrush(colorEditor)) + let action = new Action(fun el -> el.TextRunProperties.SetBackgroundBrush(selColorEditor)) let mutable lastSkipOff = MarkAll let mutable lastWord = "" @@ -112,7 +112,7 @@ type SelectionHighlighter (state:InteractionState) = if f.EndOffset < off then // all following offset are bigger than this fold stop searching offsSearchFromIdx <- i // to search from this index on in next fold elif f.StartOffset < off && off < f.EndOffset then // this offset is the first within the range of the current fold - f.BackgroundColor <- colorEditor + f.BackgroundColor <- selColorEditor offsSearchFromIdx <- i // to search from this index on in next fold else loop (i+1) @@ -353,7 +353,7 @@ type SelectionHighlighter (state:InteractionState) = /// Highlight-all-occurrences-of-selected-text in Log type SelectionHighlighterLog (lg:TextEditor) = - let action = new Action(fun el -> el.TextRunProperties.SetBackgroundBrush(colorLog)) + let action = new Action(fun el -> el.TextRunProperties.SetBackgroundBrush(selColorLog)) let mutable lastSkipOff = MarkAll let mutable lastWord = "" diff --git a/Src/Editor/TypeInfo.fs b/Src/Editor/TypeInfo.fs index 7ad30f7..01b3a85 100644 --- a/Src/Editor/TypeInfo.fs +++ b/Src/Editor/TypeInfo.fs @@ -631,7 +631,7 @@ type TypeInfo private () = // see https://github.com/icsharpcode/AvalonEdit/blob/master/ICSharpCode.AvalonEdit/Editing/SelectionMouseHandler.cs#L477 match iEditor.FileCheckState with - | Checking -> () + | NotChecked | WaitForCompl _ | WaitForErr _ -> () | Done res when res.checkRes.HasFullTypeCheckInfo -> let av = iEditor.AvaEdit match Mouse.getOffset (e,av) with @@ -718,12 +718,12 @@ type TypeInfo private () = let fullName = if symbol.IsSome then symbol.Value.Symbol.FullName else "" let optArgs = if symbol.IsSome then namesOfOptnlArgs(symbol.Value) else ResizeArray(0) - let ttds = makeToolTipDataList (ttt, fullName ,optArgs) //TODO can this still be async ? + let tooltipDataList = makeToolTipDataList (ttt, fullName ,optArgs) //TODO can this still be async ? do! Async.SwitchToContext Fittings.SyncWpf.context - if List.isEmpty ttds then - tip.Content <- new TextBlock(Text = "No type info found for:\r\n'" + word + "'", FontSize = StyleState.fontSize * 0.65 ,FontFamily = StyleState.fontToolTip, Foreground = gray ) + if List.isEmpty tooltipDataList then + tip.Content <- new TextBlock(Text = "No type info found for:\r\n'" + word + "'", FontSize = StyleState.fontSize * 0.65 , FontFamily = StyleState.fontToolTip , Foreground = gray ) //ed.TypeInfoToolTip.IsOpen <- false else let sem, declLoc, dllLoc = @@ -748,7 +748,7 @@ type TypeInfo private () = sem , s.Symbol.DeclarationLocation , s.Symbol.Assembly.FileName let ed = {declListItem=None; semanticClass=sem; declLocation=declLoc; dllLocation=dllLoc } - let ttPanel = TypeInfo.getPanel (ttds, ed ) + let ttPanel = TypeInfo.getPanel (tooltipDataList, ed ) if tip.IsOpen then // showing the "loading" text till here. tip.Content <- ttPanel } |> Async.Start diff --git a/Src/Fsi.fs b/Src/Fsi.fs index ec6d85d..27d7290 100644 --- a/Src/Fsi.fs +++ b/Src/Fsi.fs @@ -64,6 +64,7 @@ type Fsi private (config:Config) = let canceledEv = new Event() let completedOkEv = new Event() let runtimeErrorEv = new Event() + let fsiEvalErrorEv = new Event() let isReadyEv = new Event() let resetEv = new Event() let modeChangedEv = new Event() @@ -147,8 +148,8 @@ type Fsi private (config:Config) = fun () -> // tryAbortInfo.Invoke(controlledExecution, null) |> ignore match (cts:?> ValueOption) with - | ValueNone -> - IFeshLog.log.PrintfnFsiErrorMsg "no cancellation token??" + | ValueNone -> // the token is none initially when creating a session. + IFeshLog.log.PrintfnFsiErrorMsg "No cancellation token for ControlledExecution found. Cancelling running scripts might not work." false | ValueSome ctk -> ctk.Cancel() @@ -316,7 +317,7 @@ type Fsi private (config:Config) = //fsiSession.Run() // don't call Run(), crashes app, done by WPF App.Run(). see https://github.com/dotnet/fsharp/issues/14486 fsiSession - let handeleEvaluationResult (evaluatedTo:Choice, errs: FSharpDiagnostic[], codeToEv:CodeToEval) = + let handeleEvaluationResult (evaluatedTo:Choice, diagnostics: FSharpDiagnostic[], codeToEv:CodeToEval) = // switch back to sync Thread: async{ match mode with @@ -327,9 +328,14 @@ type Fsi private (config:Config) = isReadyEv.Trigger() match evaluatedTo with //TODO move out of this thread? - |Choice1Of2 _ -> // _ = evaluatedToValue - completedOkEv.Trigger(codeToEv) - for e in errs do + |Choice1Of2 _evaluatedToValue -> + let errs = diagnostics |> Array.filter (fun e -> e.Severity = FSharpDiagnosticSeverity.Error ) + if errs.Length = 0 then + completedOkEv.Trigger(codeToEv) + else + fsiEvalErrorEv.Trigger(diagnostics.[0]) + + for e in diagnostics do match e.Severity with | FSharpDiagnosticSeverity.Error -> log.PrintfnAppErrorMsg "EvalInteractionNonThrowing returned Error:\r\n%s" e.Message | FSharpDiagnosticSeverity.Warning -> () //log.PrintfnInfoMsg "EvalInteractionNonThrowing returned Warning: %s" e.Message @@ -354,7 +360,7 @@ type Fsi private (config:Config) = runtimeErrorEv.Trigger(exn) log.PrintfnFsiErrorMsg "Compiler Error:" let es = - errs + diagnostics |> Array.map (sprintf "%A") |> Array.distinct for e in es do @@ -415,11 +421,12 @@ type Fsi private (config:Config) = match codeToEv.amount with |All -> avaEd.Text |ContinueFromChanges -> - match codeToEv.editor.EvaluateFrom with - |None -> avaEd.Text - |Some from -> + let fromLn = codeToEv.editor.EvaluateFromLine + if fromLn = 0 then avaEd.Text + else + let from = avaEd.Document.GetLineByNumber(fromLn).Offset let len = avaEd.Document.TextLength - from - if len > 0 then avaEd.Document.GetText(from , len ) + if len > 0 then avaEd.Document.GetText(from , len ) //|> (fun s -> printfn $"ContinueFromChanges ln: {fromLn}, off {from} to {len} :\r\n'{s}'" ; s) else "" // ContinueFromChanges reached end, all of document is evaluated | FsiSegment seg -> seg.text @@ -439,8 +446,7 @@ type Fsi private (config:Config) = //codeInEval <- Some codeToEv compilingEv.Trigger(codeToEv) // do always sync, to show "FSI is running" immediately - let asyncEval = async{ - + let asyncEval = async { // set context this or other async thread: match mode with |InSync -> @@ -471,7 +477,7 @@ type Fsi private (config:Config) = // new UnhandledExceptionEventHandler( (new ProcessCorruptedState(config)).Handler)) //https://stackoverflow.com/questions/14711633/my-c-sharp-application-is-returning-0xe0434352-to-windows-task-scheduler-but-it - // set current dir, file and Topline TODO + // set current dir, file and top line TODO // TODO https://github.com/dotnet/fsharp/blob/6b0719845c928361e63f6e38a9cce4ae7d621fbf/src/fsharp/fsi/fsi.fs#L2618 // change via reflection??? // let dummyScriptFileName = "input.fsx" @@ -486,14 +492,12 @@ type Fsi private (config:Config) = //setDir session fi //setFileAndLine session code.fromLine fi // TODO both fail ?? - evalSave(session, fsCode, codeToEv) - } Async.StartImmediate(asyncEval) - /// for init and reset! + /// used for initial loading and and reset! let initFsi(config:Config) :unit = match state with | Initializing -> log.PrintfnInfoMsg "FSI initialization can't be started because it is already in process.." @@ -501,61 +505,75 @@ type Fsi private (config:Config) = let prevState = state state <- Initializing async{ - //let timer = Fesh.Timer() - //timer.tic() - if config.Settings.GetBool ("asyncFsi", mode.IsAsync) then mode <- AsyncMode else mode <- InSync - match sessionOpt with - |None -> () - |Some session -> session.Interrupt() //TODO does this cancel running session correctly ?? // TODO how to dispose previous session ? Thread.Abort() ?? - - let fsiSession = createSession() - sessionOpt <- Some <| fsiSession + try + //let timer = Fesh.Timer() + //timer.tic() + if config.Settings.GetBool ("asyncFsi", mode.IsAsync) then mode <- AsyncMode + else mode <- InSync - //timer.stop() + match sessionOpt with + |Some session -> session.Interrupt() //TODO does this cancel running session correctly ?? // TODO how to dispose previous session ? Thread.Abort() ?? + |None -> () - // fsiSession.Run()// don't do this, covered by WPF app loop: https://github.com/dotnet/fsharp/issues/14486#issuecomment-1358310942 - // This Run call crashes the app when hosted in Rhino and Standalone too ! - // see https://github.com/dotnet/fsharp/issues/14486 - // and https://github.com/dotnet/fsharp/blob/main/src/Compiler/Interactive/fsi.fs#L3759 - // Is it needed to be able to cancel the evaluations in net7 and make the above net7cancellationToken work ?? - // see https://github.com/dotnet/fsharp/pull/14546 - // https://github.com/dotnet/fsharp/issues/14489 + let fsiSession = createSession() + sessionOpt <- Some <| fsiSession + //timer.stop() - match prevState with - |Initializing |Ready | Compiling |Evaluating -> log.PrintfnInfoMsg "FSharp Interactive session reset." // in %s" timer.tocEx - |NotLoaded -> () //log.PrintfnInfoMsg "FSharp 40.0 Interactive session created." // in %s" timer.tocEx + // fsiSession.Run()// don't do this, covered by WPF app loop: https://github.com/dotnet/fsharp/issues/14486#issuecomment-1358310942 + // This Run call crashes the app when hosted in Rhino and Standalone too ! + // see https://github.com/dotnet/fsharp/issues/14486 + // and https://github.com/dotnet/fsharp/blob/main/src/Compiler/Interactive/fsi.fs#L3759 + // Is it needed to be able to cancel the evaluations in net7 and make the above net7cancellationToken work ?? + // see https://github.com/dotnet/fsharp/pull/14546 + // https://github.com/dotnet/fsharp/issues/14489 - (* - if config.RunContext.IsHosted then - match mode with - |InSync -> log.PrintfnInfoMsg "FSharp Interactive will evaluate synchronously on UI Thread." - |Async472| Async60 -> log.PrintfnInfoMsg "FSharp Interactive will evaluate asynchronously on a new Thread with ApartmentState.STA." - else - log.PrintfnInfoMsg "FSharp Interactive will evaluate asynchronously on a new Thread with ApartmentState.STA." - *) - // TODO what happens in Abort if current state is NotLoaded - match mode with - |InSync -> () - |AsyncMode -> - abortThenMakeAndStartAsyncThread() - setAControlledExecutionCancellationToken() + match prevState with + |Initializing |Ready | Compiling |Evaluating -> log.PrintfnInfoMsg "FSharp Interactive session reset." // in %s" timer.tocEx + |NotLoaded -> () //log.PrintfnInfoMsg "Initial Interactive session created." // in %s" timer.tocEx - do! Async.SwitchToContext SyncWpf.context + (* + if config.RunContext.IsHosted then + match mode with + |InSync -> log.PrintfnInfoMsg "FSharp Interactive will evaluate synchronously on UI Thread." + |Async472| Async60 -> log.PrintfnInfoMsg "FSharp Interactive will evaluate asynchronously on a new Thread with ApartmentState.STA." + else + log.PrintfnInfoMsg "FSharp Interactive will evaluate asynchronously on a new Thread with ApartmentState.STA." + *) - match pendingEval with - |None -> - state <- Ready - isReadyEv.Trigger() - |Some ctE -> - pendingEval <- None - eval(ctE) + // TODO what happens in Abort if current state is NotLoaded + match mode with + |InSync -> () + |AsyncMode -> + abortThenMakeAndStartAsyncThread() + setAControlledExecutionCancellationToken() + + do! Async.SwitchToContext SyncWpf.context + + match pendingEval with + |None -> + state <- Ready + isReadyEv.Trigger() + |Some ctE -> + pendingEval <- None + eval(ctE) + + with e -> // for example a System.MissingMethodException when F# 8.0.2 is loaded but 8.0.4 required + state <- NotLoaded + sessionOpt <- None + log.PrintfnAppErrorMsg $"FSI initialization failed:\r\n{e}" + log.PrintfnFsiErrorMsg "Please report the issue on https://github.com/goswinr/Fesh/issues" + if config.RunContext.IsHosted then + let host = config.RunContext.HostName |> Option.defaultValue "" + log.PrintfnFsiErrorMsg $"Does the Hosting App {host} already hold a reference to an older version of FSharp.Core.dll ?" + log.PrintfnFsiErrorMsg $"Please try restarting the Hosting App {host}." + log.PrintfnFsiErrorMsg $"Then load the Fesh Editor first, that can solve assembly conflicts." + else + log.PrintfnFsiErrorMsg "Please try restarting Fesh." } |> Async.Start - - static let mutable singleInstance:Fsi option = None /// ensures only one instance is created @@ -680,6 +698,10 @@ type Fsi private (config:Config) = [] member this.OnRuntimeError = runtimeErrorEv.Publish + /// FSI evaluation returned an error + [] + member this.OnFsiEvalError = fsiEvalErrorEv.Publish + /// Interactive evaluation was canceled by user (e.g. by pressing Esc) [] member this.OnCanceled = canceledEv.Publish diff --git a/Src/Model.fs b/Src/Model.fs index b9c5f13..77a24df 100644 --- a/Src/Model.fs +++ b/Src/Model.fs @@ -9,6 +9,7 @@ open AvalonEditB.Folding open FSharp.Compiler.CodeAnalysis open FSharp.Compiler +open AvalonEditB.Document type IFeshLog = @@ -100,8 +101,10 @@ type FullCheckResults = { } type FileCheckState = - | Checking - | Done of FullCheckResults + | NotChecked + | WaitForCompl of prevRes:FullCheckResults + | WaitForErr of prevRes:FullCheckResults + | Done of res:FullCheckResults type FilePath = @@ -122,7 +125,7 @@ type IEditor = abstract member FilePath : FilePath // saving settings in config , like fold status abstract member IsComplWinOpen : bool // for checking when modifying keyboard events abstract member FoldingManager : FoldingManager // so that fsi can go to error location and unfold / - abstract member EvaluateFrom : int option // the line number to start evaluating from if EvaluationTracker is active + abstract member EvaluateFromLine : int // the line number to start evaluating from if EvaluationTracker is active [] module IEditor = @@ -165,7 +168,7 @@ type PositionInCode = { /// this line will include the character that trigger auto completion(dot or first letter) lineToCaret:string - row:int + lineIdx:int /// equal to amount of characters in lineToCaret column:int @@ -181,3 +184,14 @@ type CommandInfo = { tip:string } +[] +module AvalonEditTypeExtensions = + + type ISegment with + /// gives the offset at start and end + /// Length = till - from + static member inline FormTill(from, till) = + {new ISegment with + member _.Offset = from + member _.EndOffset = till + member _.Length = till - from } \ No newline at end of file diff --git a/Src/Util.fs b/Src/Util.fs index 4b70336..32e529a 100644 --- a/Src/Util.fs +++ b/Src/Util.fs @@ -135,7 +135,7 @@ module Str = else let mutable i = 0 let mutable ch = s.[p] - while p >= 0 && (Char.IsLetterOrDigit ch || ch = '_' || ch = '#' ) do // valid chars in F# names, # for compiler directives + while p >= 0 && (Char.IsLetterOrDigit ch || ch = '_'|| ch = ''' || ch = '`' || ch = '#' ) do // valid chars in F# names, # for compiler directives in completions i <- i+1 p <- p-1 if p >=0 then ch <- s.[p] diff --git a/Src/Views/Commands.fs b/Src/Views/Commands.fs index 4ec2f5b..f5b5305 100644 --- a/Src/Views/Commands.fs +++ b/Src/Views/Commands.fs @@ -29,15 +29,15 @@ type Commands (grid:TabsAndLog, statusBar:FeshStatusBar) = let fName() = tabs.Current.Editor.FilePath.FileName - let evalAllText() = fsi.Evaluate {editor=curr(); amount=All; logger=None; scriptName=fName()} - let evalAllTextSave() = tabs.SaveAsync(tabs.Current); fsi.Evaluate {editor=curr(); amount=All; logger=None; scriptName=fName()} - let evalAllTextSaveClear() = log.Clear(); tabs.SaveAsync(tabs.Current); fsi.Evaluate {editor=curr(); amount=All; logger=None; scriptName=fName()} - let evalContinue() = (if curr().FilePath.ExistsAsFile then tabs.SaveAsync(tabs.Current)); fsi.Evaluate {editor=curr(); amount=ContinueFromChanges; logger=None; scriptName=fName()} - let markEvaluated() = match curr().DrawingServices.evalTracker with |Some et -> et.MarkEvaluatedTillOffset(Selection.currentLineEnd tabs.CurrAvaEdit + 2 ) |None -> () + let markEvaluated() = curr().DrawingServices.evalTracker.MarkEvaluatedTillLineRedraw(Selection.currentLineIdx tabs.CurrAvaEdit ) // F2 + let evalTillCurLine() = fsi.Evaluate {editor=curr(); amount = FsiSegment <|Selection.linesTillCursor(tabs.CurrAvaEdit) ; logger=None; scriptName=fName()} //F3 + let evalContinue() = fsi.Evaluate {editor=curr(); amount=ContinueFromChanges; logger=None; scriptName=fName()} // F4 //(if curr().FilePath.ExistsAsFile then tabs.SaveAsync(tabs.Current)); + let evalAllText() = fsi.Evaluate {editor=curr(); amount=All; logger=None; scriptName=fName()} //F5 + let evalAllTextSave() = tabs.SaveAsync(tabs.Current); fsi.Evaluate {editor=curr(); amount=All; logger=None; scriptName=fName()} // F6 + let evalAllTextSaveClear() = log.Clear(); tabs.SaveAsync(tabs.Current); fsi.Evaluate {editor=curr(); amount=All; logger=None; scriptName=fName()} // F7 let evalSelectedLines() = fsi.Evaluate {editor=curr(); amount = FsiSegment <|SelectionForEval.expandSelectionToFullLines(tabs.CurrAvaEdit) ; logger=None; scriptName=fName()} let evalSelectedText() = fsi.Evaluate {editor=curr(); amount = FsiSegment <|SelectionForEval.current (tabs.CurrAvaEdit) ; logger=None; scriptName=fName()} // null or empty check is done in fsi.Evaluate - let evalTillCursor() = fsi.Evaluate {editor=curr(); amount = FsiSegment <|SelectionForEval.linesTillCursor(tabs.CurrAvaEdit) ; logger=None; scriptName=fName()} let goToError() = match ErrorUtil.getNextSegment(curr()) with Some s -> curr().Folds.GoToOffsetAndUnfold(s.Offset, s.Length, false)| None -> () let reset() = log.Clear(); Checker.Reset(); Fsi.GetOrCreate(config).Initialize() @@ -87,14 +87,14 @@ type Commands (grid:TabsAndLog, statusBar:FeshStatusBar) = member val SwapWordRight = {name= "Swap selected word right" ;gesture= "Alt + Right" ;cmd= mkCmdSimple (fun _ -> SwapWords.right tabs.CurrAvaEdit|> ignore ) ;tip="Swaps the currently selected word with the word on the right. A word may include any letter, digit, underscore or dot."} // FSI menu: - member val MarkEval = {name= "Mark as Evaluated till Current Line" ;gesture= "" ;cmd= mkCmdSimple (fun _ -> markEvaluated()) ;tip="Mark text till current line inclusive as evaluated." } - member val EvalContinue = {name= "Save, Continue Evaluation" ;gesture= "F4" ;cmd= mkCmdSimple (fun _ -> evalContinue()) ;tip="Saves the current file only if ist has a file path, then sends all changed or new lines after end of gray background text to FSharp Interactive." } + member val MarkEval = {name= "Mark as Evaluated till Current Line" ;gesture= "F2" ;cmd= mkCmdSimple (fun _ -> markEvaluated()) ;tip="Mark text till current line inclusive as evaluated." } + member val RunTextTillCursor = {name= "Evaluate till Current Line" ;gesture= "F3" ;cmd= mkCmdSimple (fun _ -> evalTillCurLine()) ;tip="Sends all lines till and including the current line to FSharp Interactive." } + member val EvalContinue = {name= "Continue Evaluation" ;gesture= "F4" ;cmd= mkCmdSimple (fun _ -> evalContinue()) ;tip="Sends all changed or new lines after the end of the gray background text to FSharp Interactive." } member val RunAllText = {name= "Evaluate All" ;gesture= "F5" ;cmd= mkCmdSimple (fun _ -> evalAllText() ) ;tip="Send all text in the current file to FSharp Interactive." } member val RunAllTextSave = {name= "Save, Evaluate All" ;gesture= "F6" ;cmd= mkCmdSimple (fun _ -> evalAllTextSave()) ;tip="First save current file, then send all it's text to FSharp Interactive." } member val RunAllTxSaveClear = {name= "Clear Log, Save, Evaluate All" ;gesture= "F7" ;cmd= mkCmdSimple (fun _ -> evalAllTextSaveClear()) ;tip="First clear Log, then save current file, then then send all text to FSharp Interactive,." } member val RunCurrentLines = {name= "Evaluate CurrentLines" ;gesture= "Ctrl + Enter" ;cmd= mkCmdSimple (fun _ -> evalSelectedLines()) ;tip="Sends the currently selected lines in the editor to FSharp Interactive.\r\nIncludes partially selected lines in full."} member val RunSelectedText = {name= "Evaluate Selected Text" ;gesture= "Alt + Enter" ;cmd= mkCmd isEse (fun _ -> evalSelectedText()) ;tip="Sends the currently selected text in the editor to FSharp Interactive." }// TODO mark evaluated code with gray background - member val RunTextTillCursor = {name= "Evaluate till Cursor" ;gesture= "F3" ;cmd= mkCmdSimple (fun _ -> evalTillCursor()) ;tip="Sends all lines till and including the current line to FSharp Interactive." } member val GoToError = {name= "Scroll to Errors" ;gesture= "Ctrl + E" ;cmd= mkCmdSimple (fun _ -> goToError()) ;tip="Scroll step by step through error segments. Unfold if needed." } @@ -102,8 +102,8 @@ type Commands (grid:TabsAndLog, statusBar:FeshStatusBar) = member val CancelFSI = {name= "Cancel FSI" ;gesture= "Ctrl + Break" ;cmd= mkCmd isAsy (fun _ -> fsi.CancelIfAsync()) ;tip="Cancel running FSI evaluation. Via " + if config.RunContext.IsRunningOnDotNetCore then "System.Runtime.ControlledExecution." else "Thread.Abort()" } member val ResetFSI = {name= "Reset FSI" ;gesture= "Ctrl + Alt + R" ;cmd= mkCmdSimple (fun _ -> reset()) ;tip="Clear all text from FSI Log window and reset FSharp Interactive and FSharp type checker." } member val ToggleSync = {name= "Toggle Sync / Async" ;gesture= "" ;cmd= mkCmdSimple (fun _ -> fsi.ToggleSync()) ;tip="Switch between synchronous and asynchronous evaluation in FSI, see status in StatusBar."} - member val CompileScriptSDK = {name= "Compile Script via dotnet SDK" ;gesture= "Ctrl + Alt + B" ;cmd= mkCmdSimple (fun _ -> compileScr(false)) ;tip="Creates an fsproj from template file in settings folder with current code (including unsaved changes) and build it via 'dotnet build'. dotnet SDK needs to be installed."} - member val CompileScriptMSB = {name= "Compile Script via MSBuild" ;gesture= "Ctrl + Shift + B";cmd= mkCmdSimple (fun _ -> compileScr(true)) ;tip="Creates an fsproj from template file in settings folder with current code (including unsaved changes) and build it via MSBuild. MSBuild or VisualStudio needs to be installed."} + member val CompileScriptSDK = {name= "Compile Script via dotnet SDK" ;gesture= "Ctrl + Alt + B" ;cmd= mkCmdSimple (fun _ -> compileScr(false)) ;tip="Creates an fsproj from template file in settings folder with current code (including unsaved changes) and tries to build it via 'dotnet build'.\r\nAll code must be in modules or behind an `#if INTERACTIVE directive`.\r\nThe dotnet SDK needs to be installed.\r\n See log window for output."} + member val CompileScriptMSB = {name= "Compile Script via MSBuild" ;gesture= "Ctrl + Shift + B";cmd= mkCmdSimple (fun _ -> compileScr(true)) ;tip="Creates an fsproj from template file in settings folder with current code (including unsaved changes) and tries to build it via MSBuild.\r\nAll code must be in modules or behind an `#if INTERACTIVE directive`.\r\n MSBuild or VisualStudio needs to be installed.\r\n See log window for output."} // View menu: member val ToggleSplit = {name= "Toggle Window Split" ;gesture= "" ;cmd= mkCmdSimple (fun _ -> grid.ToggleSplit()) ;tip="Toggle between vertical and horizontal window arrangement of Editor and Log View." } @@ -171,57 +171,60 @@ type Commands (grid:TabsAndLog, statusBar:FeshStatusBar) = // NOTE :-------------------------------------------------------------------- let allCustomCommands = [| //for setting up Key gestures below, excluding the ones already provided by avalonedit - this.NewTab - this.OpenFile - //this.OpenTemplateFile - this.Save - //this.Export - this.SaveAs - //this.SaveIncrementing - this.SaveAll - this.Close - //this.SaveLog - //this.SaveLogSel - this.Comment - this.UnComment - this.ToggleComment - //this.TrailWhite - this.ToggleBoolean - this.AlignCode - this.CollapseFolding - this.ExpandFolding - //this.SwapLineDown // handled via native keyboard hook see module KeyboardNative - //this.SwapLineUp // handled via native keyboard hook see module KeyboardNative - this.SelectLine - //this.SwapWordLeft // key gesture handled via previewKeyDown event in CursorBehavior module - //this.SwapWordRight // key gesture handled via previewKeyDown event in CursorBehavior module - //this.SelectLinesUp // implemented in AvalonEditB - //this.SelectLinesDown // implemented in AvalonEditB - this.RunAllText - this.RunAllTextSave - this.RunAllTxSaveClear - this.RunCurrentLines - this.RunSelectedText - //this.RunTextFromCursor - if config.Settings.GetBool("TrackEvaluatedCode", false) then this.RunTextTillCursor - if config.Settings.GetBool("TrackEvaluatedCode", false) then this.EvalContinue - this.GoToError - this.ClearLog - this.CancelFSI - this.ResetFSI - //if config.RunContext.IsHosted then this.ToggleSync - this.CompileScriptSDK - this.CompileScriptMSB - //this.ToggleSplit - this.ToggleLogSize - this.ToggleLogLineWrap - this.FontBigger - this.FontSmaller - this.PopOutToolTip - //this.SettingsFolder - //this.AppFolder - //this.ReloadXshdFile - |] + this.NewTab + this.OpenFile + //this.OpenTemplateFile + this.Save + //this.Export + this.SaveAs + //this.SaveIncrementing + this.SaveAll + this.Close + //this.SaveLog + //this.SaveLogSel + this.Comment + this.UnComment + this.ToggleComment + //this.TrailWhite + this.ToggleBoolean + this.AlignCode + this.CollapseFolding + this.ExpandFolding + //this.SwapLineDown // handled via native keyboard hook see module KeyboardNative + //this.SwapLineUp // handled via native keyboard hook see module KeyboardNative + this.SelectLine + //this.SwapWordLeft // key gesture handled via previewKeyDown event in CursorBehavior module + //this.SwapWordRight // key gesture handled via previewKeyDown event in CursorBehavior module + //this.SelectLinesUp // implemented in AvalonEditB + //this.SelectLinesDown // implemented in AvalonEditB + this.RunAllText + this.RunAllTextSave + this.RunAllTxSaveClear + this.RunCurrentLines + this.RunSelectedText + //this.RunTextFromCursor + if config.Settings.GetBool(EvaluationTracker.SettingsStr,EvaluationTracker.onByDefault) then + this.MarkEval + this.RunTextTillCursor + this.EvalContinue + + this.GoToError + this.ClearLog + this.CancelFSI + this.ResetFSI + //if config.RunContext.IsHosted then this.ToggleSync + this.CompileScriptSDK + this.CompileScriptMSB + //this.ToggleSplit + this.ToggleLogSize + this.ToggleLogLineWrap + this.FontBigger + this.FontSmaller + this.PopOutToolTip + //this.SettingsFolder + //this.AppFolder + //this.ReloadXshdFile + |] // these functions parse the KeyGesture from a string defined above. diff --git a/Src/Views/Editor.fs b/Src/Views/Editor.fs index f543257..38f3fae 100644 --- a/Src/Views/Editor.fs +++ b/Src/Views/Editor.fs @@ -77,7 +77,7 @@ type Editor private (code:string, config:Config, initialFilePath:FilePath) = se.WholeWords <- false // config.Settings.GetBool("SearchWholeWords", false) se - let mutable checkState = FileCheckState.Checking + let mutable checkState = FileCheckState.NotChecked let mutable filePath = initialFilePath let getFilePath() = filePath @@ -88,10 +88,11 @@ type Editor private (code:string, config:Config, initialFilePath:FilePath) = let folds = new Foldings(foldMg, state, getFilePath) let brackets = new BracketHighlighter( state) let semHiLi = new SemanticHighlighter(state) - let error = new ErrorHighlighter(state, foldMg, fun () -> compls.IsOpen ) let selHiLi = new SelectionHighlighter(state) - let evalTracker = - if config.Settings.GetBool("TrackEvaluatedCode", false) then Some <| EvaluationTracker(avaEdit,config) else None + let evalTracker = new EvaluationTracker(avaEdit,state, config) + let error = new ErrorHighlighter(state, foldMg, fun () -> compls.IsOpen ) + // if config.Settings.GetBool(EvaluationTracker.SettingsStr, EvaluationTracker.onByDefault) then Some <| EvaluationTracker(avaEdit,state, config) + // else None let drawServices :Redrawing.DrawingServices = { folds = folds @@ -131,15 +132,14 @@ type Editor private (code:string, config:Config, initialFilePath:FilePath) = member _.AvaEdit = avaEdit /// This CheckState is local to the current editor - member _.FileCheckState with get() = checkState and set(v) = checkState <- v + member _.FileCheckState with get() = checkState and set(v) = checkState <- v /// setting this alone does not change the tab header !! - member _.FilePath with get() = filePath and set (v)= filePath <- v + member _.FilePath with get() = filePath and set (v)= filePath <- v //member this.Log = config.Log member this.IsComplWinOpen = compls.IsOpen - //member _.EvaluateFrom = evalTracker.EvaluateFrom interface IEditor with member _.AvaEdit = avaEdit @@ -147,8 +147,7 @@ type Editor private (code:string, config:Config, initialFilePath:FilePath) = member _.FilePath = filePath // the interface is get only, it does not need a setter member _.IsComplWinOpen = compls.IsOpen member _.FoldingManager = foldMg - member _.EvaluateFrom = match evalTracker with Some et -> Some et.EvaluateFrom | None -> None - + member _.EvaluateFromLine = evalTracker.EvaluateFromLine member this.CloseToolTips() = this.TypeInfoTip.IsOpen <- false @@ -172,8 +171,7 @@ type Editor private (code:string, config:Config, initialFilePath:FilePath) = let _rulers = new ColumnRulers(avaEdit) // draw last , so on top? do foldings first avaEdit.Loaded.Add (fun _ -> new MagicScrollbar.ScrollBarEnhancer(avaEdit, ed.ErrorHighlighter) |> ignore ) - avaEdit.Drop.Add (fun e -> DragAndDrop.onTextArea( avaEdit, e)) - + avaEdit.Drop.Add (fun e -> DragAndDrop.onTextArea(avaEdit, e)) ed.Completions.OnShowing.Add(fun _ -> ed.CloseToolTips() ) @@ -197,15 +195,19 @@ type Editor private (code:string, config:Config, initialFilePath:FilePath) = // -------------------------keyboard events ----------------- // ---------------------------------------------------------- - avaEdit.TextArea.PreviewTextInput.Add ( fun e -> CursorBehavior.previewTextInput( avaEdit, e)) // A TextCompositionEventArgs that has a string , handling typing in rectangular selection - avaEdit.TextArea.AlternativeRectangularPaste <- Action( fun txt txtIsFromOtherRectSel -> RectangleSelection.paste(ed.AvaEdit, txt, txtIsFromOtherRectSel)) //TODO check txtIsFromOtherRectSel on pasting text with \r\n - avaEdit.PreviewKeyDown.Add (fun e -> KeyboardShortcuts.previewKeyDown( ed , e)) // A single Key event arg, indent and dedent, and change block selection delete behavior - + avaEdit.TextArea.AlternativeRectangularPaste <- Action(fun txt txtIsFromOtherRectSel -> RectangleSelection.paste(ed.AvaEdit, txt, txtIsFromOtherRectSel)) //TODO check txtIsFromOtherRectSel on pasting text with \r\n + avaEdit.TextArea.PreviewTextInput.Add ( fun e -> CursorBehavior.previewTextInput( avaEdit, e)) // A TextCompositionEventArgs that has a string , handling typing in rectangular selection + avaEdit.PreviewKeyDown.Add (fun e -> KeyboardShortcuts.previewKeyDown( ed , e)) // A single Key event arg, indent and dedent, and change block selection delete behavior // -------------React to doc changes and add Line transformers---------------- - avaEdit.Document.Changing.Add(DocChangeEvents.changing ed.State ) - avaEdit.Document.Changed.Add (DocChangeEvents.changed ed ed.DrawingServices ed.State) - avaEdit.Document.Changed.Add(fun a -> match ed.DrawingServices.evalTracker with Some et -> et.SetLastChangeAt a.Offset | None -> ()) + let drawServ = ed.DrawingServices + // match drawServ.evalTracker with + // |None -> () + // |Some evalTracker -> avaEdit.Document.Changed.Add( fun a -> evalTracker.SetLastChangeAt (a.Offset-a.RemovalLength)) + avaEdit.PreviewKeyDown.Add ( fun e -> DocChangeEvents.ctrlSpace (ed, drawServ, ed.State, e)) // to trigger completion window on ctrl+space without entering any character + avaEdit.Document.Changing.Add( fun a -> DocChangeEvents.changing (ed.State, a) ) + avaEdit.Document.Changed.Add ( fun a -> DocChangeEvents.changed (ed, drawServ, ed.State, a)) + // Check if closing and inserting from completion window is desired with currently typed character: avaEdit.TextArea.TextEntering.Add (compls.MaybeInsertOrClose) @@ -216,22 +218,13 @@ type Editor private (code:string, config:Config, initialFilePath:FilePath) = // avaEdit.Document.Changed.Add(fun a -> DocChangeEvents.logPerformance( a.InsertedText.Text)) // AutoHotKey SendInput of ßabcdefghijklmnopqrstuvwxyz£ - - // avaEdit.KeyDown.Add (fun k -> // close tooltips or clear selection on Escape key // match k.Key with // |Key.Escape -> // close ToolTips or if all are closed already ClearSelectionHighlight - // if ed.TypeInfoTip.IsOpen || ed.DrawingServices.errors.ToolTip.IsOpen then - // ed.CloseToolTips() - // else - // // - // ed.SelectionHighlighter.ClearAll() - // | _ -> () - // ) - + // if ed.TypeInfoTip.IsOpen || ed.DrawingServices.errors.ToolTip.IsOpen then ed.CloseToolTips() // + // else ed.SelectionHighlighter.ClearAll() + // | _ -> () ) - //avaEdit.KeyUp.Add (fun e -> if e.Key = Input.Key.Up then eprintfn "key up") - //avaEdit.KeyDown.Add (fun k -> printfn $"key:{k.Key} + {k.SystemKey}") ed ///additional constructor using default code diff --git a/Src/Views/Fesh.fs b/Src/Views/Fesh.fs index 9cfbaf6..7987fcc 100644 --- a/Src/Views/Fesh.fs +++ b/Src/Views/Fesh.fs @@ -63,7 +63,11 @@ type Fesh (config:Config,log:Log) = tabs.Fsi.OnRuntimeError.Add(fun _ -> let w = win // because it might be hidden manually, or not visible from the start ( e.g. current script is evaluated in Fesh.Rhino) - if w.Visibility <> Visibility.Visible || w.WindowState=WindowState.Minimized then win.Show() ) + if w.Visibility <> Visibility.Visible || w.WindowState=WindowState.Minimized then win.Show() ) + tabs.Fsi.OnFsiEvalError.Add(fun _ -> + let w = win // because it might be hidden manually, or not visible from the start ( e.g. current script is evaluated in Fesh.Rhino) + if w.Visibility <> Visibility.Visible || w.WindowState=WindowState.Minimized then win.Show() ) + member this.Config = config diff --git a/Src/Views/Menu.fs b/Src/Views/Menu.fs index e52a936..83f900c 100644 --- a/Src/Views/Menu.fs +++ b/Src/Views/Menu.fs @@ -14,6 +14,7 @@ open Fittings.DependencyProps open Fesh.Model open Fesh.Util open Fesh.Config +open Fesh.Editor open Fesh.Views.MenuUtil @@ -324,17 +325,17 @@ type Menu (config:Config,cmds:Commands, tabs:Tabs, statusBar:FeshStatusBar, log: menuItem cmds.BoxSelToLineEnd ] MenuItem(Header = "F_SI", ToolTip="FSharp Interactive code evaluation"),[ - menuItem cmds.RunAllText - menuItem cmds.RunAllTextSave - menuItem cmds.RunAllTxSaveClear - sep() menuItem cmds.RunCurrentLines menuItem cmds.RunSelectedText + if config.Settings.GetBool(EvaluationTracker.SettingsStr, EvaluationTracker.onByDefault) then + sep() + menuItem cmds.MarkEval + menuItem cmds.RunTextTillCursor + menuItem cmds.EvalContinue sep() - //menuItem cmds.RunTextFromCursor - if config.Settings.GetBool("TrackEvaluatedCode", false) then menuItem cmds.RunTextTillCursor - if config.Settings.GetBool("TrackEvaluatedCode", false) then menuItem cmds.EvalContinue - if config.Settings.GetBool("TrackEvaluatedCode", false) then menuItem cmds.MarkEval + menuItem cmds.RunAllText + menuItem cmds.RunAllTextSave + menuItem cmds.RunAllTxSaveClear sep() menuItem cmds.GoToError sep() diff --git a/Src/Views/StatusBar.fs b/Src/Views/StatusBar.fs index ac76dfc..7b697e0 100644 --- a/Src/Views/StatusBar.fs +++ b/Src/Views/StatusBar.fs @@ -132,7 +132,8 @@ type CheckerStatus (grid:TabsAndLog) as this = tip.VerticalOffset <- -6.0 this.ToolTip <- tip - | Checking -> + | WaitForCompl _ -> () + | WaitForErr _| NotChecked -> async{ do! Async.Sleep 200 // delay to only show check in progress massage if it takes long, otherwise just show results via on checked event match IEditor.current with @@ -140,13 +141,13 @@ type CheckerStatus (grid:TabsAndLog) as this = |Some e -> match e.FileCheckState with | Done _ -> () //now need to update, the newer call takes care of this. - | Checking -> + | WaitForCompl _ -> () // don't show when completion window is open + | WaitForErr _ | NotChecked -> if callCounter.Value = callID then lastErrCount <- -1 this.Text <- checkingTxt this.Background <- waitCol //originalBackGround this.ToolTip <- sprintf "Checking %s for Errors ..." tabs.Current.FormattedFileName - } |> Async.StartImmediate do @@ -271,7 +272,7 @@ type SelectedEditorTextStatus (grid:TabsAndLog) as this = else this.Inlines.Clear() this.Inlines.Add( $"%d{sel.Offsets.Count} of " ) - this.Inlines.Add( new Run (sel.Word, FontFamily = StyleState.fontEditor, Background = SelectionHighlighting.colorEditor)) + this.Inlines.Add( new Run (sel.Word, FontFamily = StyleState.fontEditor, Background = SelectionHighlighting.selColorEditor)) this.Inlines.Add( $" (%d{sel.Word.Length} Chars) " ) do @@ -316,7 +317,7 @@ type SelectedLogTextStatus (grid:TabsAndLog) as this = else this.Inlines.Clear() this.Inlines.Add( sprintf $"%d{hiLi.Offsets.Count} of ") - this.Inlines.Add( new Run (hiLi.Word, FontFamily = StyleState.fontEditor, Background = SelectionHighlighting.colorLog)) + this.Inlines.Add( new Run (hiLi.Word, FontFamily = StyleState.fontEditor, Background = SelectionHighlighting.selColorLog)) this.Inlines.Add( sprintf " (%d Chars) " hiLi.Word.Length) do diff --git a/Src/Views/Tabs.fs b/Src/Views/Tabs.fs index a6c5fcb..f0ed9de 100644 --- a/Src/Views/Tabs.fs +++ b/Src/Views/Tabs.fs @@ -78,7 +78,7 @@ type Tabs(config:Config, log:Log,feshWin:FeshWindow) = Environment.CurrentDirectory <- dir // to be able to use __SOURCE_DIRECTORY__ let ed = t.Editor - DocChangeMark.updateAllTransformersAsync(ed, ed.DrawingServices, ed.State, ed.State.Increment()) + DocChangeMark.updateAllTransformersAsync(ed, ed.DrawingServices, ed.State, ed.State.Increment(), 0) // TODO make sure to reset checker if it is currently still running from another file ?? // even though the error highlighter is only called if changeId is still the same