From 43f151fc1922e9713f5131cb79ae25f40417ac06 Mon Sep 17 00:00:00 2001 From: lynnux Date: Mon, 12 Sep 2022 00:48:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=87=AA=E5=8A=A8=E6=9B=B4?= =?UTF-8?q?=E6=96=B0zip=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- bin/list.txt | 7 +- packages/lsp/eglot-master.zip | Bin 38147 -> 0 bytes packages/lsp/eglot.el | 3181 +++++++++++++++++++++++++++++++++ settings/package_extra.el | 94 +- setup.py | 39 +- 6 files changed, 3255 insertions(+), 69 deletions(-) delete mode 100644 packages/lsp/eglot-master.zip create mode 100644 packages/lsp/eglot.el diff --git a/.gitignore b/.gitignore index d877f5a..dbd586f 100644 --- a/.gitignore +++ b/.gitignore @@ -67,7 +67,6 @@ packages/tree-sitter/lisp/* /.dap-breakpoints /packages/lsp/dap-mode-master/* /packages/lsp/lsp-mode-master/* -/packages/lsp/eglot.el /packages/lsp/bui.el-master/* /packages/lsp/lsp-treemacs-master/* /.extension/ @@ -100,3 +99,5 @@ packages/minibuffer/compat.el-master/* /packages/citre/citre-master /packages/tools/elfeed-master/ /dired-history +/packages/dired/dired-hacks-master/ +/packages/projectile/rg.el-master/ diff --git a/bin/list.txt b/bin/list.txt index c02edd9..0bcc327 100644 --- a/bin/list.txt +++ b/bin/list.txt @@ -2,12 +2,7 @@ emacs-win32-launcher.exe es.exe (Everything的命令接口,还支持中文!) fd.exe - global.exe - gozilla.exe - gtags-cscope.exe - gtags.exe - htags.exe - pop_select.dll (ctrl-tab实现,rust牛逼!) + pop_select.dll readtags.exe (ctags) rg.exe diff --git a/packages/lsp/eglot-master.zip b/packages/lsp/eglot-master.zip deleted file mode 100644 index 1fddb5cb884dfad167c746a7ed6002429daeeee8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38147 zcmV)LK)JtAO9KQ7000000C5qmQ~&?~000000000001W^M0A*)vZ*(nfVRLk4axYa> z1pooZAbw&{O9KQ7000080C5qmRQdeoqQ#Q{0E0vV02KfU0A*)vZ*(nfVRLk4axZ0P zY;SZfWo%Vc1pooZAbw)hz3p-%$F(5%dl2&wY3Rx*$SC%QY}tk^+Z3Cvrq$i#kZiXV zYE82U6p0dnDyXVJHa#&B^BVheV;^PSWS?ZuSAJDx0mW|Zja|mQRs+a9nR)W$$&=^v z>#x67>GiUx_tK?`<5(Rmvox>ORZ*&!Nq)Uau2XfEmbYoCPRgP#X2nuPFV9ZKs-kZz zrQ$DQwM;)`vt$`xX8AnJucxa18owV50RBO-zAv-uMXjQPv3mOW$)DniSO@0KS>3CwqUw&-3Tf5rB%k}R*`|I-Kbd2{-5sup#HS?%&a0`K75QzN1MX(&L@fn#6}CA^W;gKf_%!`xla(p7jiPjw%qlf} z`qkc(;aEjKS4Cc~XKMIl@A2M~-{V(T%llPwlj2uTxzV~Teokk#8b03pa_=#I^Px#@^E}hvPA!^L&w2>MC2N3jVuJ%32jy>buu()FIA?QEonB zn=9O+O15aK?u(6@CAlioc~;eBcDbnmRIQSH{s;(WRm`)idjtaC!EOT0)QeQrX}PL! zB>aHjze{05lBGJ?TrRVjdYR4Ayh>F9bHZ<`MLJiP1P6O`lSvAj17qAWk*F{A7y@vj ziV}fDNsTivRk6l?W0>weY*B5y23BqtI9FLtU>C(27PA0ou)Mo$xm1^_+EnS)W;wzB z&{(}aI)8Ee=3MQ+{z1LnKRw-leg4DO&=|-TeocWy2voLO!#SHPSW=nf^*zj-$o25_ z;01Kq|MuwR(fJQBfA#$6{Pp44nRB;fg;huuyiPK8; z5<0MZMH>VdoTqRima^MFz>Zchx8+ke=Q^hhT}^DA8P2uMT#tq?|r2L`9L z09MMo3gi!5U{v#TCh!@7K&RCj7Jz7z69WL?2wh#lYOWXw6(S12B7N-7>P@m--cL03 z%EISGS|R1AJcXsgX28yrn>@$aEQ=X-v8)LAZjM|Ln$f=Kh7JIAVGisK`}-)T+&F1`EY=&P2fkgS!qh;5`@-+k?w*mejD+ zb+J~PHR6HD578bk;U0ozzrsTZ3;;%lqtDBSK~`y!SIqN(i1n_0fxCXSsW)Z%1#@o3 zr|{Fn+t)9$yvpXOA$ko&ZjLWhvZ;#|Fi*lEpcTj}RquvCTCn-^cSA%IG0?LUyC1mc zxhja&(q%UxsxMc>g;jEiM-6_>N<>?Y17@_4^uufcgav!J2E-S085VX5CUgrEg)M;w zGiWfw>BB6R_Z6Umh?mG4Z@Bmh&>GKE_)mr-B6(lP^%@iX0h@`-xlL0qQTK2GX* zT>$YyyJUr&iq=vYeo(04CTAQBg+>L*z`GWkdS2WyISWl8qw|h#tXnxC=`#?)9UO#`&ama-D6CYq5puUE4#uDn!R3N&k=HUJx%o&_4H4Bg9zG1$bh21D3VxXVm7g!$J0NJ0iE&} zA;*p`Q9N4%cIZ69|8M9*r*jAb6|Cd>emv%G5zTl=O^0J|W9T~(TF^C9aH|JH#YQWj11q%xG(_F8giZ)44<+HFsI$pts|Mh zpXkoQUjVx+#OW--Qx*Z&x`nB)lJW-mLwp4c66h#KeivWDWp-1=Ibi2DUEbT~B1FWO z@N!7=dDZ@a{wT?2#50r!MsXgV1DS32CV{cz*nrO8cl3D-td18*TX*JS}W z6SJi}Tf*vxM|d?ReBY56*ZK65N8jEMx=n$;ud)xYgTz|sD+WGF)*#=`%#?80jj%$P zGYrOyu7JFpPq-69kO9~DtPq)I2HcCz0RZ3$;>LH#J$r-0WVTT0GDWJxD{}wk`Qhp7 z{qv)L9-fiZs8Fm2a3`n7-<|HiLfI?0kFxzdPI8+Z*E{vvQHjlT}I* zD9WBZiGlvD*1Vq(NdS~OP2mR5ad@)~m|T(LS-++uvNw2(=Qzu6iyLJ6bhn$v^x7y1 zh@Nh=|Lxhy{`m_UVmuk}4E2|=R1!!pt73`RTqiIY5$5&cXo$O{8UT(f7(2=|86Y_l zX58CqOq(7plhx%sQPFStH3bg>ryCzGpuT^c{~g}=PvXa4#ZSJR%EMEy2L285IKM{d zo<>qZUIfwr3X=k3rp0m)R1$b+(MuB~>`e|*rpS|cJ+R!bdkJa8>4$W-sgd?cfEd9c z1la?h)T<592tHZxfE0=wrm!0tM47G%_yYMpND0g?QV^e_2r-{s;ma+;tFQnm?}{i0 zIHya=mMw<=;3zFoY9&sEK-Ve(F`NGl|I{o}^VYO1E#}7o=-2`#GJK6R1^Ybuyc*#> zqTv3^=E@?Y8g|buS|w``;b1e=XCvTX$}V>}QNyt_tq9u9J&N(cYJ?@5You;O7y?h6 zWh9|>bj=LF0kkW;U0|)j9qtdU0UKup*smK%-`S5ubDYtGM@H;bW?=p#Yn#nW-v9oS zF}u{!B}#7L!B4k9grp6ZC-8k`wk9x?Y%kA7_wJeywThCeDrQ@!9BG+6zmCf^*@3p< zkF?D{@q39_1Jbt0;CAor{H9w-o5mLkWZK@jEcruJ(M3uBPkR>m{deq?#r|g_kQyXC$RCOCa%ZQ;`tru=k0=BZUv|PAIH>;b%SZ+D#=7d56`hh*hMY z{qf14yYK*rYteIa`W`=x)@l!L#%hd=)zEhbcw*H-igf zrXOPj^sVog)f(o9{~ap)Om+xbL16NIrXT%WF*fS^bz05JY|We4gIgp1oL--X#%f_V z@*=5j(&h32gXAkSRYS9Z!GIy{7AKo|M^t(YoWO2Z((mGjIF)gbf%RudvgJu4Vw6a7=g@1K!+Frz1 zryqpHqbcEofQNsf$w8Fl*`;pPlM~J}pgj;F`)yH)b9R^leSVJu1W-U-CV9nNZrJ~_ zzBe}nLZnZhcKheq2M6)^sV~F3o;yMd(;%AE&>%KcUp;>Or(W9xA~#}!fn5FhvBNj; zr=F+uY6H)@^2Sa@z98<|Z#m12L&Er>Z^wUU&L01}=Y(ffwL@YD3ArNWE;ik=$0(CK zEk05hUDTGYc1?)dZl96Yw ze%_Ss3roJ}OMkNpD1B}F7ppv@uZV&61hR$^o5$JtZX1swUHgmTPR!lxULdAyaWkk~ zqo-DD?+x~IMY^7h?Z2WsSJA8ee;l92ua2J`!aNR+U!U(EoR8Hy$x1Z#+g}ISJk9Ar z&jVItu7T zu@bqk2H(BGz*z`UP6R$)N$*Bw-5{j>gLg!n$8wqGbfN$_c#e#SHy5cH0+S zy#*Sd{qXABo`W=nPrz&b2chQ)0I2x!`X z&SKmU4BuP(p0WXS)5mOmDMd1LNKmo?d%GZVG6fS8BCE6Vx(wJP<15 z7Uk>+*+BqJ&}*rOhVG&Y0gUS{>xXVlZ!KHRk7BI(X^c91{VgB{RWo`X*oJTh-=`md zMryW!(zM8&gSI`87Vs5W+=>SqP<{?F9;(pRoBC0)snHgX-VJ$5o>X?HTeDG9@>@uj z*}v()9Fy6kQpYFfN5`+xa-(?fH)~ks)@fJTujUHQR4wjo*dG@pejDoDFgV`3q58+! z@oQCFk`rNu>q9kHU2X^mWK)(ov6qj0V5`|CPs)2^!I`j?rZGu}TVy$+Y?iWFmfdBH z`3mQO*rl0x$a`^ocCJp2PtVnl=LaXT*-$aR(a)|CFCpDKb)c`;MU|0K5>=hoa=^7O z57KtwyH~*=NGqox^fK`?p3%~mke?M=$BT8E$9W1z0YnuTI89a~1PmQFYarpd@wBo| zLxhhspOyD(Huja`Kr>6$$z`^LS*APbM!%xfkFSnT4-GOV4!I<>i+FSsZB#U8w-7=j zhnjQ_oBABdW`cIhq^?uc>Jk~Ok~wh(Qk3G4Mqw?e!by;mmPen<1gK#_T3JJ*?6G-a zran?P>>&&6@e)Mvn*1r5?E<&YH>*qJLrlSDl}c34G@?wkB2!(3DqXlI74039u*Ny@ zHULT}W5}xD^I}9?K{=aW4qon`oyD*BU*Uyy^!n(WEU7|k&W}uS#BO0k;BwhDqMJO) zU|F*z& zW?UEIwu1_Gbk&P3U5uE)W^g+;$$|wt=7vDX4H0XI+Lp_m@YsWX2 zn{FfPXBK6gXvuE{K+=A)1>0hn#2D#5s<;heGza+?4NuvX?10N${i7=Rj`sdLaMQz#QgTa-A3GKNi=?b-Y3PgEIyKLC5Oh9WyWPLhCP6iVE1| z^hJUHGQh|Qw8!{!WT)gX;tjvont+<3m~fNE^`d0I?k!WYQ0cNpzZu|>{+wo;s)lnR zk6O6mpoh30e>ou6rMxb2L+OUUXXv_x@A)Z$xMlx)19UwG+FjnE`+8SD^0LDp(NTV$ z&d1_spDibY9NoD)dnQ*kVctl+a=B~Y)oL=Bl}WV#&IAY$c}HD=D1Z-2bk}Z+S8~f% zIODn)6giIL;f2|jy<)5@wadulj)&JBak7=M_=|@#9PJ80&c*Qzt!S|8vqh1?PDYmn zY!@utUIl+J-qfDe1#ZX?00CA;99vZ8zM+O2kuHi;UN9Zpf zBPFzY!^7r^R+W)cJ+`E{g%%DfA_Uw|#ruhXvAw%;#(QWw)qOsTgG5Q*hBWro)}et0 zsj2H{*I4Ap?1oDfg9cTQL%Rd3wU-n9Zo!)j_`AN3UFUn2-_Q{fJqvS`Vv(gUM0BZ; zkLf2VeD@eGioJAC+%$P?)G92O|DPiq1Z}*_wIAk{zVF%dT+xqmQ27Agzyq5CA>gQ3 z9SLbK+$GA&f+PZrBWBP1D$eH1l(tnp-unYifn7&x3_6iN9Ln<)jU zU|Su8$-bfe4jcwi#GWEH}Cj z*~9*Tu$29q9)zfmaiexn7Q2QwSd3b@GK2TtXm1eFAna?1VIS!S<#eg)d)O6pim%Aq zi-KWb&*=#|3c&{4C7UH41U6Lwxe*LSpf8guo3S_7f0ck47PK)w=WE_*B5-j!lPAFq z#w(GFI6hsY=n2TC!{II3pou*7`U&BIX3tsbtsE6bE%&CE>gDNXngc{(+rnH~fB5<$&V zn(H~7wG&{mD|ic(4vTenJ-FTHz;Wz$n|=DSiOS!%c7^Bn2WS6ew_SYy(IE)g?AE^h z*`+kS*Dj6le!l@GPoJLhAj^&2`7b^4_`P5rl~VKtZ*QKh*J*9{_n=tXb?_Ez;^)0R z)hyNS?{i>TvRRLt8|ZGcr7*qKdRz52+b#XQzoi1^4}G^4pPC$dW_GpLb65}5)xPn3 z4G;c5{wI1X!_#$vu6?928^g4L6QiJh@2c|6>(m6;w47ffZQrvo8yw<5ui=;QMAng1 ze*^*Nq8T{>GBqp!P0K@5NWrYqh`Ma}u>>DWoA&PERReT7(t zO9Wt}Es*d3MY@D77KTjGehG@VHrUU*9!Z6m>65}Y1r2`@iRckwP~<+pKB8%z)rk3M ziiccH?N(N5iZYR!>a|R>>V#y4((%IwU?7xSZ%Uf$5xlZggwU!PoWeI{rlu9YE8HFo zFOtE5jO40^etIzT9e$~`hLs0It~wrbx=b$9kaWe(_39`w0Q}dD+QYAnW@Ji=Y>Zsl zZkwJ~MOh;YR8tU?0NecUEQkBQZvOi@g%a-oh`vCFgmAdiGbZA5`L5z!$m#%&xP%@x255TDSnQ?+OP%aoB8-Y56KCZ#L@#2}su@PHJ`A$lL+$uIbYP|L{(N z(~UG9+!ST?sd$Fvl?t=p?w!x!lx z>8~RpKZi1rm~C@>D)q#b)AW?h2^~F<+sqo%IwNb?rc7S|C&mTq&y{X|o0>LCzA%Un z!0EFWz=<@3Y8y-v$bEpM`WA9n3axH&wTV3bho_0s38A>&KFBOH!u@$-r-%RT&C%)MGx5{df2m!F%nx|O=^x3d z0XveB^ZUae-X5Pm^K98y_}Cb5WHW2U;B^kqdom(p>@5BhPBmH&B!W!L+7$e%ghvdL z`GV7|XnVX_3#;E&-)18T-NB|SG41#RTHlXdeco*E`wB*00dq&cx|DHZ!nOw zM8Y{Py7zu0c}MVEPK>pC0AH3#MnRxgWGM%Z1?)COlN=p<8dK3}*33vdSnk&YcdyY0 zPjav*A1tyHdW~=B{3E9rWch5lLB~LLkL1DH3kvRWcrEC&&7XWffnTt{xrcn(*)RMM}Qs?MoAaqJ-AIS?ux6w|Qd(1z9$$JV7Iu^0G zI7RmTj?8k`f#zPjS{cpAu7**QRJ#L!C zfJ+{7MpQs2Iyx?e_Mu^24WZVd2+G*0#iic@Im&hFokZo)tNJZ3h9J{7M&6SBGCQRF zy%JRqH+pAAMke!7VAEc43YiJ{22f{knx1%3%p!qhE=!YTXIbBF% zRJb@&4)JTyk|aRSmvRN%sM|5}5@8Q;eSJ2X&F7`th{VWmRD|tqwhAS(;~0~Fpv3U} z=wGi6rlTP7O{aQjpllr zoM{=Sh}hY`(-?$Lm^uEh;JBHO1%4U}bImD5Bs`hbaEg0{KLL`c^LWr2g*^lbC9oZ1 zRtTd&zgF<+M)E1#u>V!7w`CSlkXuM58K)UE=cnbD05y&44g9CZJsrA%=|Mt_0OW8*+l===;HHmzl#|qxI=sp=OjBc_DIpU zA~N&U6c9L`ot+#WsN_1qxB%t9iT9ZH!xxiWNgs6?^}UXi$#zqeTX~#+MkOUtjae4a ze@k)Bc4jGF}c5i{Su0v^B$&wbX=v6kjsyVm5;+{Z1aVnf5h<{is`k&vHnCuk=Fh-f&G!4``LKev?BAjj89F}P? zH8nSOnn#S8xWH!=yB^5nf9U+;1|>&Vd8oB~YjWqfi2{Rf_9!4^G?r?GR{6USluX=N zPxNE0frG7*#pLfq7i}Rzxv*PUjY5iv(WEi&tsufX^lRLC9YB8tS)&88tEFgzYQviR zosOROi_Z#%vmVc6fGv`sU#L&FLZ54SD6J zFm`{-ssV;GODJfjOBwKz9)#5Zh8+~cbUQ8<|E_RPy*=`l{^>3;ggg0rQyqkVq!OY7 zu*bwluH1~-W;<*aCzY0bD<^+sQy6YtLD;{+?!Wn%JJ}xT#@*Z;4A;(HC*OG2=dSxz zxBG}|R{pRoMlRtUaNMx`gA!BggwoYl)h0NmQ=r5-t-AJ5M*a+#Ou(q90a_Rg$7v#; zUR`F_8}!q^OzS(0KbfT^x+1V9FJyDPkcTL%mw5L)cadR05^@Qo zp}7A14366z31Ni}hdPe^^Rv!Z;LhjABu$S-t$&rfQ9-n?6AH9w#-NfP9 z*>A?|;2XF`*_@KyrV>I1@5s%STt!CuV9n&WZ=S2A4pq-lTaT|gWM^5uH{5pD)$Vn6 z=|~K%tLsutVEnNt!6mAVrniP);A`}gh@~=*o-+0_(n5#0K#jb`5&=cYj04m-fI;s}%R>4ISwz`jT3g6Hrayuz>3y0Kcq z9<+|7#8ZrL+{5?rsKtCqWQA;y%^6y3VD=k!W@n#uW_wsQPN}!ple0I_oycke7pE>F zFQN(n#ugxdc0l490HXV9(KEir@@v^l=OKW(Ect;b@)yNwvRz)NpIjtaoP0se)!E_c zKMzkwm3nn}{^IzVHoNmqoa~?OgQz&%F{EqG|JZRo7`+CUA`P+jztRKi&(;MChuyDk zWK6ff!@=>ZSNpG@nQS$0ULC$Z-!i73M=93%bYy56$W5t4S)^bpT=?)Jn1xA%YH{}k zP(;3Rh&bVR#McZKpwlcFsbn5YqLfO(OD>#K%CL5A(9qd9)|||IY@c{MRzo=D8Tem0 zAu0X|@^PiTmSf{Ve%~aHEI=RrMrk0*0U{KG6?Am8>CrO}i-f`Vl^*838Rxwn$e@zd zic-}EPMyvHAp>vnYO}@!LRgc`P6BPX?R!srwmWgM$^ZY26+8c57cY`U*Nrvs4%J&N zH&S*Ix4xrHf^+9EhJpvmP_K#a(SOrB?GYUK(QAk$jPA*8cuON;E!t)DBAU+c;Z2au zJQx2dWwE?Umem%;nw=R9dJ#1pl(+c{O+i-UW*i)hfQt(z}NkAo%acp)Rn8wLUN* z9b#knQ-2sgy4{*sw;@dw;>1(`biI(=Ktx|IZvAxu3YvPNk);=`}@8X2Oqk>KMsG{WV4&A z>_cDIp&@Y9gp!BYf9{bZ)vSInjJ+(!m}VDLnckIvtKOhEB7Ug-05=<(nkPE$rj}PJsik-L)nd=IcX+@xrVX}Je;>; zY4YP|$5Zt-r7%o+Pqz~lP_DTLK*gjCl)84V{RQQgFis-dN}9)SeEVaYLl7t&aARd*fA81qn$ujHlds$#fqv1+}h zoB9`{z~l%mZ7`G10#JK|-ExgtQB^j>5awCgc6H=KfMMPSc2X}ew-LTc1SGnZ! z5)+UDO*%og*sIBjE~1HEMO+qK{gQl+(Az-&prgI1MP-^ebssq;cr$4=7e@(asU|y% z86k%Yz}WE{nGY}r9ws=KCdMzsskk6u#Iqixa~!NN9LIMZ7CSc)+`r8(~3j#LP$d8`s%>R1T6lmNrj02X(aW#&?fn0UIq&Of8n%ElL z-2EM6dI?l$P?JmXs`!X8v0K+z4rCch(uu|e=zp>+j;XFZR#jpRW(&ld+e3 z-;^`JEaNbg9vI&;c&(x*ft2}$1K(abPl4wn31a;$&<${5WPFG0m$L56mCipa(C7*U zfU%o$mQpB}n@%bZ&V(m<#g5dd5DwG(t7MI5sxC~3`rG~UgBOQqocuSaR35d^*349f z%GIhi)sEX!cITxz3nj7@qiuMyFJ+3lUXW8DXgeV_fD1u+HnlLL#U{#_-MEy4voY&U zxh|@1Uf>U*=?12d6{Wrc!UIcY?#-pn#7S9%&`r-x>D^5c{up%0`D5_iE=Qv>MN#(D z+cH^GB^4PM7tPtCSF24fCR;L@lC#E%G-DK#F1?X(O^RE@m-j{-*g-Eix9`!(hU&0z zAI!=7PcEC_B{Gp|;vO`!;F;XY+TdoB-@SLr7AT##n~+f&HxjnQofN>zv7AEUndIPw zR__gt{Ce7Wf$BCk?Eq}Qcp^yfXiaXIS;g7Xv7|YbBf`|eghf+LhW!{!a^t~sbP2!O zpwk?Vk12k*h=C60FDXG3^VTd^=SQy&kKde|Y|I1wg-Ln}D|xc_2Pc{LGs&4uMg{Z` zGvyo*=D;cN1CNvCQi&%9Ck!|_J*3sg-@bYN{E*`$boGj;QG1-@d!5=r$pG*VZ(d`_ zA7lDuN=}OzuXUzpM3q!u(rlo(8n}|EyR|GfYKal|M@wQLL%k|7ir*gkSsOzVJe-pTw#?Tcvl-SlH@;U=xCFpuM8fv z+B)3f)qHK$!EhXE$Ewo0&NV>yv28}w4kLobshhwYj(AUOg4X35fYtoDAS<5 zROR+nGwxzNF?w1=mhbFlG1#GzEV39&pT7KrC=* zhZv49XG#Et+~6KqVKOJopk@SLy2X{rkY?ltc5r3)c}Km0bQjGF<}sR7!Xk(A_yfO| z(be&`$mVLkSugQoO>HR-6!mKk$Csu&Ow>oaWeXOHfi7UuCQ#G!h{Nkqb9+xYY`%EX_T_YeLx~OyKM>r6v zNDDkx92h7!&jx<&1cUq1QDblBDb{^4KjMG&*k`G(2Ph=|NLUOh=g60g7kflPK{uV zZ=jA+HF!(x4cjob3=>6o5tZvtoZjoAZobU&>j@X(KoL${rRnps0EuWA;#dimxi`8G zIOVR64Ou@2yE^qWUF}+#*0+g1>v+5pcnehaETUU(Oa2g#TJ|YiQATZDQi1*1QB^Zm zBe+$vTg?&H30?`oNVoL{s0<^e^lW54W^zsWUmP@JkS4LBnl480LO$9mbJm5EsxDIZ zQ6RZB=Ih%?;oL~;kcKbmTXO-jW<-}0qjHT3ORDY-1ly5Ye$|w#MSt^u{h(_qVPOQR zB0HZ9+zuJm%43O4m(C>UI>V9Hm#Mo77_CDL zN3pW0D(r5FJdwwfq%2K!;Fdq?OKbgzzO12)F}XQhz!luQ@T?gUCWhwhadbR|$97u7 zG{Lk2=W$mGbB-CSm{nC`S=N$rhQAK&5vRZ8Ubd9AK=LWfzyQVhLP-3Tskfxpah+dQC zksAI;+xe3^qa@a*bT05`#)9(*=Ul7y>iU~%zNW22=$?Tnd^e8AtcPvFaJzcTrRLjz zZl-T)S!7KZTQ6IEVYEL-)JorU9NRvJtz4UctY>M6zMUapMB9q^WQDyeL z-@5U|D#}8WQJHfnUvium8Q-ZMZiK~_C-lWOaO)&1Il4WQQfw3xpVh28tWBlwPL}}M zPl^Au*z?xqAhi>Uvw9bymoS@{?;Mxa?_li1L;Ot_vvnhoaJGLz)cke&7sSn9$5S;7 zZ6TkR=9uzhbG(QvfTwiOe%Tc8eC6ef7>Sj))zH-Bo<2a$>HOJpr&OkxYeXvLw4CyIHJm*rS z7=ar-P}s7JMLkn4pulSCkbRl=0n4K&H#uT+q^kin9=;7!HPm!^irQxfXMISo-il^y zyaKpJW|=NAz7gK07uu;x;E$yI@dke#m|v0A#abe+M(`@usgo*Q4tYsMp9UJ=sN@qX z&zRggf7zrZC-LW(-Z)mgKCp8 zYRO5?_P8o699!EQFyr}3=*&wk64Ey*ao`5J#_A%%+>jMTg6^q_n6YCsIx8{0{`%|B zKKl&ME*91Bw$^#Po0q0tU)Ooq&HTDmBd_>@XtBt~Ze={$#MR@ZEb3y0rGo%#V`JDH zoO?CmNn1*~lgzq=M4TH4XPwT+R7O=aD_qVSiLjt5h~!Mo8dZ2WckERx7TA3;9a2a;^ z$ZCaq;JYD0W3L$aWj5h(x98brnCMB4WW5&d6SU zNrg2)W@UECHKsd=%Jmf(G_4(84QT6yt5aS`m1yBoVLWU2E4sYsj3nqDRZua-LKhu3 zMYD_!SdpKTI171xL;;@$Nmm^Ob=Yu+j3RCF6g{x-!L!@1Cz4z~^gw8KHf}PKp=Pa` zfb<22ayUX@X#N$`salBcW4N_)0Bas-FXb?cv)(43Ppp$7vgsP$Cx7#l3@xK+V-*3- z8e0+d%Dr@IEeDR)iW|NOdeo1(iUVc}yNtGl{jKhiwyxYgpfb+4laB(t8wemKn71ZGArszFS89)-$BxM-Xh8rCA{v^J3aL&o6BY z@Mc<@ogG{qx9j%S^q@2QoX&qU4A`W-x0CuB8P**Z+pJJ#)f_15T=XnJ(=+F^b3(ruWs=TX?QQqQ+lfdAkwF z-j0$0zxr(Cfg$_IppEhlPa@w?B5LpecAKwTN~&VZWHf2hW7lJ^NCussrj5=bC7I8A zFLflB1e=6f9L4wYb3MzRWfk13_u3g%S~#&#gEy6R+%<0>EUTB5coW|y*^-`3Of(*x z9sDK4RvRRg={t0X$71M&0iBVKZTRlp7nF8q?+fk=pSP-iY>K;Td&+<*dLVo@lIC0> zRTLzyLdv@$^~&aqc%KHF7mH`7qs72eIDqKignVWFvwaq z1T$hRV-tenRZ?fS13cTPjAnN+$A8QUxf=Q?pg=O=Q(AtW+c|^=NCFZ)Nm%#{MbTl# zxs>VIo{;2znF2~uu5PNMM((i6MTOdvtXj|m37Cd{>AA?D3a7-tU|R}SmNg*_>|-AI zssmPZOGYLySaU?LSVeF51@s2tDMim5E)c8{LB10%x?x3!3BDh7GyrArt*X=;0u#CB z#*3@%xHVhNjkZ1PH{x#BK!jxC^>~+&HKeWc5hh?Fn@QgRZ|-8(j@4Gz7ys>~eTDEJ#!fR10$v-OCi8Q$P#% z6AObZs`!dt73kv48IlEL4(qQeq8BHEs#thMe^3<_mp2KN`ScH~#ZV37nSTRl9sJJ) z95k-&HLPGYs!Z! z7@Ezc^OmKCd2qUX6z~$hnPa7n<<}Z#j3{Hq9_xo`&&m^W9L4tEO=}G2aAe zGV-DbEoAhlc3tvH+gUk$uyG{l622yvW%6C2`Cu^XJnMpYWb*DxGciR@fWF+(%1j zGdWkS>tthTfb+jwi{nS^^zbju*oVIkf5rWw`V&&CloOh3*<(*l^vDmRs8$%pB_>g* z%Vhm9!t^%&VO{N(?e52j?`AOS0a$RdJ%ouROeXH4k6toz#K6Zs8yTjv z1%?K-LWPsj!DzP!0gJZs^cQoDvE%kOXojD*J3rpq-4bXRcps{j=WzRO3*1Fpc8kr& zTW7<6oLr1~h{(J5jA3b7;v%CX6hn9Ppts*>Q(wh@iqlmxt4Q+OzQRIm z$y-~=)7Z_~&TpY!=hEC(UwmH4EBlL~kJcT?AgP1Xlb2iEoF;$wh`s3uRIMq3+Ys7S zK6WB5yrj)e3gzufa}C`MX>@zYum^>&$t$A)*2#`VkHxbw>MU!ESHzb3@&oCNd=VWSe zMOdSGiMh;xj&46?^FlL}c)F#CqxYzDc@w?1WEy_xcu;+>ynGjpF3qq+FUB$0)MOws zeY})JjZSUbM(E`b=oqjpG#_ovE?se?TZ9DxQ*Lrn@WfQFf{-`bs*>^)w-OsqBS=7Y zKCc#-H%+N?)EIeX((dz_(W>bFtPZE2_@4>>8%6CUN825Tw9zCod0UVg7R7RIvV-+;J<`y6s>yml0+Fqir;J35ZtFq! z-$)*HJFmHj1(J0ZD0J)n0o}c#P%zT2Q7Za-b@8aG7SpSu`1@s2P9I@zxJPyI=rZ~D z-uiyD3m&OgFPgn3sw&8R>AEa#DlC<=70wj(CMN(w_JZ_K4|<$Ll-Yc~U5B!J4sW`W zP1#lzgC3k$^&I2dI4)?GZcXBRERrdw5e#{$Pvc+r_me3Q!* zb^d79*$M6v3n%L0E}mCdV6#V)-2khC!?)Na5G@nrizHrB^zIq6r{YdfHv0-mCTNVr zyq)Qs^5f|el30xEem5|aBTkZNZSN569(udds3{?CNj0(iG&Tc@JDNq3L~yNaau`1k z;4TOlJQV8N=V?mHr{$*l_|Z`srlUcLI{I88fsy)Ze*5b_rTwqCp)Od8UklVJNr=3oLlP<~W=fKb@2t@Qru#|cTEzS4Od8H$+lJL< zxODy-?T1exW;r~sOe#M5Mmxj58vx_cwu&fiw~wyP8Q^niHP7Nkxo*7-{+o5_tbT{dIS>YhODcXtOoP zR_aULRU++R-n-d zW++#;X&uiutJVF3*w$31^LbDwOkbgfB<|rqA3h>hg?{WOkHA&vaPAIWar-ZFswl8| z+M-Smv_*^3O!R0vdq=^zSNNqGw&=9|Y`gj}XgKW?xZT=3_5Gs)wgr?PI;2Tsm=#s^Z&|pox+e$9urT^I5*#uc00^G z+kHT{`$S{9j4^rz`H^kltwp#TWmq^rn$>RBGPFM z-K}r(gvP472&K`uB_lj5@^qqsv(oSL>Noxo@?k`cbI7kV!@HWiu5%`L&gDsUZW|q4 z52o-U+`JW>L6h(2eD0DqUpzY!?w}al$u^s9HHDe5cnY-Nozllbu(t`bxGp3$|Arh- zLQ>9urJ@Mz*IuD-73I-SEAlYjKRNo_7#lQ%Ax4>A0T<~u(hfau>4icIdVYNRYX96+ zus>tbL6ELvLdoS*pI8Aa|B)lml*csbbxh)w{JdithJZhQB9v{nV-|1WH{_Cf7}-O;$?c9E=3=qy&-h)jSUEDu5HjlK*) zNWE1LSk5o|pd^p|pT2tB_f5OPpc0?#SILL*m^~MteC1UYek%Wb+GZs%VL& zWBmL#fB)@g|8emB(d+Nj@$={Lw?D+kuMaI!rn5zYp?7I1PYAh<5khob;E{p`H0|wb z6Yb`&xW zkrYq};*rHqL4~Th5)eJf2L(UwH#vDBy*Yo5bV4fCO`g?o{3(~@tj7FLw^_9zy#hkT zqBm#7fG|ySqO2pDT)yOFmbAl8`lFvWuP#Vo$O$8)SF z%o$B+bYM`J1*cLMmm`|h;9@q~Q!k3UlvCtjX#-vNV@}n0t_n=l&3M6#)WpeV7=5|C z=c+)VidcP_;lMCMb+K!gw=qSZc6KvGzdc^f_+OSbgm{Gi;Wdwfhb&p}p{tP?UMNL8 zxUea1H#x91r-fTJ!E{B*hVzVK17e+DFkO7XV1zn!&JwX{&$BCv7_0eNX^E54=#UB# z?lHDvL&rL0ZxWqsXbqPdRl#7F&r1K0*0VijN`5^3^H&pwVZQhy2_}{aFpvCQHzX14ir%6z3Up;=-}L@Fo&-9)e7@;k8MhR_4eiJr z`eS`WuY$4_1M&m4EW%yegk)5|YUg-i1I1*}s|DBe_nxM+} zR1n8^eoTu?d}5S+y02-)c(rzdkpvElix8^x`60kW4a67X#xU@D-&T37-!i^ z4>%r&G_>xuiJ0Q+F~Uog(+}C3bd{7hy7Z1*rtbZTcWQj{^5~4zwTI~D$CoXoCo@o9 z?Vo;scsdcm4>>7;)hTQ&4W%F8POA(B1?h^sZQ!Ohk9g*a@PYk=~;%kgEUy@bl+e$F!fmPZqtn3o`Prum%iq>DeZJgFtfr*{*GrzhzszwzK`_> zEo>4wYvI#59jGfZPH~$uWA{p`qml9vt?UEx77Jw%_zHcFF|uM)E(2YRbA5vMFn0Uf zlegI$z48MkR5lG*hSjqn3oSWb_w z?kUTzo2UkBkglzND}E&1G<7rSYUG8DO{R|?`P+*5!{>7ps9?)+|A&w0{mq(A_3BK$ z&GI>}qN?wg47u`swoKkEbW`S#b%}qvldj>qHA=ZTmW`jZUQ@^(3X^NlAmVzFexMti zf+hSuxV&r3y2cf*P-dYj#q6RSSWGx@y6HabOw#YYl@LQb9a|zRr%i+p9?OPJNB)e1 z(_AMYD=pWO^e&hLL>`r#*vf<)B3wDHL{(%Zpy+h3f&evp;-_Nkoq|p8A7}a_dhq+k zg!HUh*aTY*0BGpt9o^)xr#gKweSelEd!CW1v5vs{vK9l$dOQ|voBB}%-%ChFjRfbM6-2Hn%da~4p8;bR45R+UDo+N|-h zDyl@>giAEiuhr;2sbb1l=vNETgw5F*yIRrr;^HquwHL^`Lb`)CYqSmGe`$TsS&N8b z(wh@_O*&6zxO+#HBWgFIwq#J3Ko27Sw5-ALVJ)*7D zAswfjUb%}H#OET=?*_JI8^sN=A^IZ>cTdiCqKufB$rO6we|GkG?^OV@d8yc0HC?S^ z&wHDAo}?eq6NLTD>BUEywoCZ&2D9UFLP1+XF9Ipi_Wq=0nc1eQ;VHhmQ=}7=4INR{Ye5d| zoG*-uM4$$Ns}S6wB4%&!L$Q%xlKGsI1L>$)PJYXLU~pk-ZNJE_7fY;=a>7ZA%MoWV zrf^tW%v&vVI}|E_*QAt0uqrqy+>{wP5|~4-Q}nMHo@cWg&g_(@RHk7BLLXill*)^1 zH)gpjhea&La`$Im;c@uC1!grGqL&B!SI@d-G1c7I+j( zlgI%;%ok`$+X?5p0+DJHJp%yyp=WQxum=LadG=5+87|)76QKBC^j^wrnUo(1;yEV1 zGntL5A;v=M4?8;>dKFTx{DSjC)lJ!*FkecMNdlMY9F7jCGy3hpa7?t;(23~k3F}bm z@sNr*;jCUnyz_K_)(g!<`5-0fZZF}VyZ46g@D>L6dp!JoL7;%bX0tZ5Q%sll0FcL$ zLgLDFaey7?=kVJDpq;Yl%o|12ho2undCM%np$*nkaAxxoe%lG^Sqicd8PbX+258qNPGqn+-*{_ZfO zBtAns*-ffJd+xLTON1vgC7&Qq0dSdK+mMo=s#_jFlup~}=q8u%(vdfZhdyHq%szfG zKj^i>Ujs;`vQnbdkTM7ssdE(3v#S^_kulJH4AP%qI6(!Qq!%~2sW~k_+N{u@MN9!3 z(!rjb9=|%_QX$WdU!TWE&*Oi*IXjQfUmT)=TIUfh(-G&rq;l8V*_thsQql@rl4(@P zC#(0AfEQyUHbw+W5fn4P zANNtkY9W&psJsoh)LXCDN8|w26=W6aZ0W3>&;;6#UGkahI(T}Xg8%?=4STJ9?yBGT z`QaiyZ5~z2(omOgSV#%Krp=MVzu}dl_4|J+MQW z@UA(ST~ua?pI76*siCe)qV3?@0BIeUbQ$`U7~0JxAF312u`ND6R3W$V^H$GUy@HlA zr_KgW*wv?z5_o%gyLDkRm!4|QUT4lufVVlySvN`bc?w$&`@^yAqsjzYQa~$+GZ&un zUewRNLf;R9?MOQmm=g1BEQ2omdkl{;AZ7Z7oIvPXufRJ?6gTSqEr}9rXS+nlKS>O^ zT;iZN)-m)IY6ZFAw($dX-EZb89Qd{6gR5k%lSHxh8Kwi@873g=7TgzX`XitrK{GSE zC9(+tpVU9$UQwkZ{C_tfm!Nee&y`cFYbLP|4{BT`k?w(aoGd2mnRBc6eE zc`q?VSdnIrvtH{`w_MH|2xXd3VoJtqq z0tnDA!NhRza^EbI(iED?>Orvv834qHI-M`k*{J54=~QTI4L3dP3!~{OnTaGm!J=Uc ztR(cD_j;oK1=HYU#5_^1G=&~8aKuV>1gTPbGghS6QLIhGLQ@syoyaxrFVh8VCFZO` za3o{U24JZ5GC{j3I#CR~wZV%DWbtTIu}>8iJ<|!VBvx%SM&})nTQ+OXw>?0$8w^v~ zX(eJH#g`0V)OcY56QY8rz@uz=KT8-JDRS(fmk>4(gxxb`p)zhRX}b&HBq`_PEMXI| z`<&_%XyU4i0UZ^t*aUdsG4C?qiGb-6?TRG8$>b@njhw$yCZmZN>-neZqcA63MCX)1+x1`SI<0R(@|L^RFSKl7LH2jlp7tbIl zu-Zr1y_)#qvGNz6Lc7X;5l-3AAU;c-{$cty6`k<%{b4$hjX(_V$G?%bK5ZYp{vuJAuYL*bYKeMPgZbIPt7TqAc-#cL3 z0E^R!L8V+zVD2&2QE&i&GwYlNUeXsG>%2@DejejMgS4&8^8(Jj+V6$pr` z^6Z?Co{m%`LEE7=n>^A(Z^sYXd&>Hgq49=#9jNi?0Q$_<({8rvK!eb}eW*Wxh?`Zb zs&si3vn|-iY53O;NO<7f@>d2(^9Q$o;O;Un25TX-nCg5~di8tIh8Hs&-E+%pnLCo@ zvC(P#uvub8fjYhJ&JX51hn#NkkHx0Sl4=W5h{X6ckIot~dYNJpVrI}ED|$ya*lMnb|{RxKi$ZiyiDSF>b&)uU%!6w5kWqh0PT-U+b>bmwcD@i$F@qu3x6_Avdz#CJ(^FF61DH2i(W~`uDo=wyoyD*Jlve zLvGJ)vw7gBxiHRS~BagD>UnTso!a&3bdWL?1GF7MrSr9I6eBCbyc_*0UNNkWKbQ z!#G;a0x1KUM_E(>y5`+QpxsCT(dwutRNQ&7wQfrlKj{pR*H%&2q`WMy(M8Ft+!m zpxKWpscS5%dZ2%t9lwrGPYxX5!*Qos#l8THr9+hJ%Kd4J@kEKJBB(ayzsayPZeXk} z+%U38zdWkcMXP}u*RgFRD@KQ@jRfqRAj(i54Zq4;o!nYLYCusye=_U%zDHn<1ZzHDF*&-Lm4%PP2Y2F zX*!tAQ?nxo#^YKIf--G_YF7WZ?K?LXTLFts$lZ8?o+z~~SN5Pw+1L~idjV_Q2Fo-- z_w%IQ__dA?@COdWftNfTPCT1@Y=5PK$1HVdj)3C`Td+lHmX6sM*NLH?6NMrgo4L3U z@8&uw1Eyd`4aF~uAHqd6O(|rlqXF+0<}Vv{ff$upb#vboM(g$gEjAU1$LN3o6w2Oi za3-;qZmwK}i5Z5y_?Ao5F1&rQe=d$jdU$c{5t9vhzKewD!WyPSUv8wdQQSP{58M@v zBNTxFPXI2L!~sqeo1E(p?D4hT#lySM0-?oyyE?JOlZ7;&|6Rc%MD`xk!pSxh=}%5e@ViI;5=9Ax$7ns{5E8Mr@AJbHO8qVjf>{ zLo&J9e=~a*UtFTyYi&`@`JkX|K>5mC(2 zq%o#}T}w_+SRBI|#<7_&%0+BS)^5alLgepnBNwV6t0;+EJ74qOPstG9>VpPaN0FIG zHXMl>nQH#%iEG9wITUR1lRG)r0o%^ae%qd!{f&YuK6++>$Oms`p=bh3-&GcvX2+ea zeAk|;20>`z2^JS_L?6#Qt1=+*FXx-uNn2?Ows?T zJxl-hPn>8+q>MM$R-lhFSwG~bJ=sRzPn>N>fKmJGV-lY-#nfPPbNVrbNmT=5*J3;> z3G|O+%dEUu44w!FWb{2iUhsy&q#ws3DCE#pHzsl?Wp&5DG=>sD>^bg_*cgfecDWr3 zl4-mSdXDaxQ&$v?nc3L_e*k)%`f!Us8&+Dnq3BVNzNrJZDMWUM@R!I}lx^XM9{E(J!Aq!fAR}W_56gl7IU2G5*^oo84Sx zA3}rtBENmYDBHw2>2IaFR|RlPn=c=k-X@nKut>e5X!s0^p`9GeHKASX|6ru;p5XuX?;}2J z6Z{`sRh-Tig__uq33l(InLps|%7~>2Swb=a_93NmOH*PqijXyVA^v$FW;L`LCey}4 z(%0}`gn>Yz+%=umnA#u^KjdZVLZSGlnzAlXM$CSgvz4TE>1^~>&fsOsFGSyY!RvVu zP2Oj7HJKNbW@KIB^R!Mdja4W=Pqof~x0YMh<|x%X0NqMAn@Eeow-_Q!F0yoO zN3ptsRxYOz(T>S^DjV+n?oik$Kw?fyOV+G_n2KjTWsaosrSEcv_GGG_aomPlnJ5ub z7dp8cMsj5Og@-gT+Kd*loQYDE$UJ^>P!ojXj=&mhq*UGVA{tJe8HbUtye@rk{t&Et zcsD%sL}GwOpI0LX>25L*an2{C(+s`!e-iQ^jDAP#=>W)~I~vb_=dG}(!tVsz@@NRV z5x%s;0}Khykex)bCHu%ZyfWzuEgJ!LXfdj-5bP=26r!Q zfVVRUVVgY3t2?TOi#$PY!OEnhrt|nN*R>wT%{Us1xm8xps7P@W(;+{HXb9qbryw%? zNe!LC5?VvxX{OnH*#fU0AIUAfxLg9u zTV4vN*f_)5#tr}2aiACe8qbqDvArCJTKoYAbDg-_6@bXFj+(qIH;%W2G9MT23LSbv z86PpgDVg*n&}A7zF69boli!6Vf4aZ+Y{IATRlUZ-BN#}w2S@XF>JN`q)^VxJrG)K1 z?p|_%wXZ0?lX@_zz_Z3U4_ZZ0k4f-cEzQHdjWvUoO;vGpDkqfEsApI5u&*ecwv<@` zLcrjxv|be2_hkW`dzr>=7Zl{*iwq!be~T#iEhW^9$x=(9!TfL~0KhJ8a@4uHfc*3- zD7Z?@7^gM_p?LToL%%8(kJmyAhlNqjmC-z_cp@@n%zY}OAHejkUTtbOU#~LC1=JIS zX06vug$K=qqm$ z;6uj@Uif+u6ORbxTP(|F>aR@fI(N`Q*HMbzOY128WvSRM{|Hsrue4~z#6q0ZsobmS z#G}!6ci(kTSNL&M7@F|eITaOCFwi%jSL11qvQYdUp~%s{e8W?G0wRuE{>T6GR`KA& zvkOq02gdU+Rhm0md$OD8-pbB@5#&u0ynU0J9c!Q!*8p8nCU9iHfy>9R-i4bkX? zj4)c^;_jQS9`z~v_tEdCdUZGjDL1V!Sre>#O{MNzSMR6`5$(Cy zpnsN7U0I%J0It|pDx^k&qua6f<_MpS2+ugQ`{rGwH6W4}^4IKi5X_knGGb|~WbbSx1F&R%tXRD^P^`7-HtTsh{dJqY+>g2jRF_M`}hx&q?XN|}}`xV-*E}fK7`gdO+5h}$oxf{I8DEpgp`|VbE zq55>)jki|A*)iQdx&XIWWiJBoH=&D|Oi7(SQ%%E;ZJKnDLX zHNGh3&3+&-!2|r06mQ#d>4pCEWtHu4!M@-XBc^zm*2a?!9g_BlvSG;EE zuRDg52Hl!-aGQm=Gtfl9I%NYIf&uA2NYMBu5GB?rkm&AxWr{xX1l{U{#{dPdw zKE!W4H>dA4CcZ1pa+XI~%`+l@HAYZ66%3zmf6idX-O7Yi# zj&>CJE~?-(cz#m2ObIlk00{bFR-`0i^NdEiZU&h?8mH{k8FQ?%xz@Gnfsfa$%22*L zkyOaW!BQ#$1IrE^9)qNj_Q(_bnIz$%`z&&>D;@1OZr4^{{m|y1X{{>8Wi{9H0R4FI z_0GN@kJ>w{-|gOJZc*)Z0`kJxX}PR{%bD=C<)=R4Qb?B*`buIY+@G;Uu_cfNB+HFjjf zGyF1P9gT`hauRB14%p*3vMDOmjJJeWKGAsnZws7nb1Sb*Q$LaUBZhIhVa06yl5m6h zT7l{qlMe>~h{FkTdE7$xKXtQSc;D4}`vz#+M6`9QT@C|VChJg>E$a$+g?g~-bhV9u z@aJAL$C*X_5J#)fHReHF+5Vjgq_8e4M5jjoKm=^3R0*fYcRbLBk3$Ps`$WkMpzaD zGbfPuMZr~lVq4Xxo$n;DB>4*VA~vDg;#tDJ$rQ4&GkU7CY>?JlTQ@ofY?F~SYS@{I z=zo6v)r9k=zC6RqhsYAr)f%63M5qBi%K1HhX{~R|ySPe!i77*KH%M;IvsgVkGU32gc9gCGivFSEY)_J z?M0N5O~D`Wz*d@b7)gN7cC!>(I=MyUmMcz-O{He!N=%_SzU%Ip?odo>0_g1D^Cs^M zt!mcZdziYbNh@`OxcaKkh{$Y`?i$tX2?nskKO6PCw+_!mCJ--ipwa{yt$BjqBTk>T z=c^|2&A8e4$u_yCOlNq7a;g%Oqvw>q)sa^E$E4C4D8E91muFFG6nKKQcIdGwgUET} z9&>7&$AeYG>bxTFO+pU8S_V21QGb#@C<{lHhvg8Wn~(M3mo%Mq&^v7i*? z&e!2!e9Lf|tnkW+ps0Q`7Syd=5EC^kH?xd1v@@xa4Y&G=)m1OgPIO<=(mi73mzE=( zENn^brIx@X{{>zum+36Qg!}>p1qMICC+bY@jV&IX+9-8ddWYjx3Z0d8XI6C0A0_j7 zIv?*TwU3FAN?MdIJ%v(k6lgn81mle3QY1`h2^5VJZgF+O;K zCCuWNhp)dof5COYbD2C|V+Ut5n@gyP58hFWuNFDg8McD$A+>=^bg%oT7GRLHy|>?j(q&`t3OWFTk@2ITP&-o@Ham7=OaZ) z^H(Vr9+?<0-3%aE-X-^ykbFg_>26T~8zN|x0|8euqFG$fWJhpZ&azduOv>f`gzZSP zB3Y)@jC?Wg3es*{5On?vOy`6ZiI!LyKDjQF^-qU%b2_Q=3DZqlH4Qc`~o3#!u zaN1QV*@7sfL*(wq`USCy2wuWKz`5bJ`$?h-0EDVqoj!a^#!EDGeS ze3r&PgHTC@Vf1yIl!CzM900E5Q)dqxu za_Hw2y2Q#cyiW4}pvVOLtP2^!hL3J7flxBL^aIxAL6oFYIFI9wD154sPLqmNQ;>SP zvNVFxzaSgjFlC$T15F2XU2wJ#c9JAM^Nr%u>>XwP8o9wXRzT0JN9;dB?Z))cK=T+j ziv68@sx2f2I2POqu{X`Flk@*3yYw3_s8154eMCCOrH8CPxcb(ewPl{W*K3MV>O11S zWV#Cr#?usIS;_Z$Bk?7E-r<&P2)7b&fu(%vYBqLs4lw1G`-7gHm$|hG#MNVAdRuaC z3;t{ZF7vrwnr-N|BfF-P%u8#|$n631%vhhf?ax3LNv-6QTl(o|Uq|Y5n)|ND)po40 z^rcmJz&l>-AAJ8C^)dlQBiXIym{SX*V8)rmyp36WrM0|L5C=9nuf znE>z4XCfA_xZpFURFj)f!^_cHtQK6dfB+!C8S!q|&WRG?SzUBLq(Cx@lKE{0_YM}@ z`fSt;qL4ZTzOadJben!#cKbaXlbO=Sj`wR;IH298H)Ak(3kJt@?^iR|v>!gGq3&`v z>qRi* zUa4Py{q<*`eTEsnmqjx7!zMcxHmZ`VR?>H}qpzE95y7ZI;(?DLC#~rj#bkI9Md4l} zlmE8RsZIxPNv7qTwq9aLc21{oBWAK7L`WJZ$#jFf_#?BV>hnha;dD~kVN2mP7}E#Q z<5U;yW4=!XYu!Bkl_YfDQ@2ij{*K*g9~9$4gaVMz&RPl39S2KVZ9T!_PGG#crx8Fs5H6W@&3L^#N5&i+Czb7QdvCFH^embic=tPH1D#UUq3YEg zX>c%K&Flu}+hCYgj5ak|lv}XY>uQ}nH4Av)(YJp)Yb7fy4czI``tb-MhHrLBW*+;w z>jzr~&=S30*V4dHPj-XpJD_D;K0z?!Xe-;c$_&P?iORfi3zL`4(dc$Rn4(}VrXFT- z#3Bf^j(=Ep;!bCxXV51E=7#*^a7S~>Ik8p^=;P)WooIKxHybmY&Wz;cc0lDrOl{Z3 z?rCSWgBGx{_p<9$s~W>$`<`b);k!P(ujUPGZh8C*8}|D+N^o~FADAkH8L8Ia|*peS^`uM|U1d)6z zL4X%OPQ^vpAGNA-#Z^o5nY~h|$RDdEwf4ujH zun*(5;*48PU5JGGYI1)imV;C9RVr|eogY6to+^3eyCjFL-M|hBdcZa46p3Ub8<<1; z(XOHvL|<>t_P#WV=Axvds}V}x26U8TU8BjMrW>QcbckT~23Yc`Qm2O}$EWA<^VcLf zCuPYO1f>$AB+=quna;<2!`tL=14(_5dVy+tN`0Hwcd01gB%xeZnY70fJLX`O+}i=H zV1%Su;ld~zgI0sCb!M#MYH-oOf@2xi5kGE-Vc6=>v&N(Omut%Fj6ZD04!TUy^+d)g zqvvX)nq>PRwg-tgEDQJeNLM^uI3ZL)O3}WDSosGD1#??yEkQ)mzc4*Lv7MM{1$fdy zJ7P<=Scet~dGoO3F&AqHdIGlk(U`_Af@3%|qgQ?h!gRYUy923WId72Sn+mCC8VZ?* zZd!3pwSOuz2HDFIV5`zk-ZNGHZIR-^&RSB1uXJDoJ2M9D)u>( z#_VuwYh(Is#8=@!oZ!(ck1i({)EKoYBMB{FE>r;>kTbWV2FCDML!GQVHuC^cMZPLZ zoyq3^Y3|ya+c=K=|E&8E6S#Z;WPp_HTpq9;S(fQo7fDo+bbKyRHkZJXf(2kZFkd&+2vMCbS>3R2b_jLdIs+tj!%^55zP~*sWtf++bXvAy1hbNC8A3r=l zIR%yL>yvMOqSM+CBJ1|0(Z`WKE7`=z?jjkmQt-mQ(_3cvi%%?q( zaxMqE6=46sF|sxO8Mq&j(8v_=jUN_gW@P%U2GFq~d$!>kaf*jV$GYNUu-bb|T6 zw^Q9Fv?)S+HX@$%9z#Z|%izsEx}o6Gd3^S)U#q7No*a+V_ot6=shxsP+~L9b<`x(W zLewCO)7mcBxWM9z3%yX9AybmP<+9Lkr(oAboJ8w)dsO+rBH~mztQ^cenz;^;w5Ko% zE#qm&U#^#;j0`U24n`**ZGI1cnf5yefF6WffSZ*I4npobE|^Cvs)_Qg(VJg&*0-6Jp9BP;w1GyNWZI5XmpH2L=C!xd!x7+bx^zjCwfG8B0&CXGMhdBpYo+=_-ht{>ym7 z@~a?9I_%i)sT6{g{Z_;%-VnYk7+qh1=LTjjSkpF4lI%9na8Ymes@}LOi03WGt5WhX zsh+w6oA*u%wXgB%#CIFp#-UxsEh+hkthcWjaMpcC4uftb0Ju4qJ;AcTkW(1 zw*&raK6G(=*z}oCcTLOGGl)cqtlh!xVlEnlN3U#5=L>9sEIv9k34()IfZ|OXI}5mi zvt!~M+u$Jat~UuSX!(rrbLW9Sna?o4WKEv#!5Wv9hp^fE^b4N&lj0_Z&3%?{j*l`u8~BLn%v zoivv+CSE#0ZV2@Fu&0}#&dO4C$dYXuU#F*%9T<_4WWi(aZPdIqcQo~nC+!L)(7PwrWW<7El-Zm zzdQM6q>lgR{Pe;2*@M#uPtHbKzb-ZNd-&klgRdVwesun$kIkJlV0zL!<}}XT&9X~R z#Hm~t9|^(BBh?P3#SFZc*1C#u>i(Vsv-UcjdPhB%efb+zt9zT<5og{YyQxGM%_#8C9E{!xD)HxYFc>^$HvSk|zTr*i*)7YeslZJKY%iO5*=XYi*6bR0|#$biuz?vXY19H}crBf1#LQUNg4< zISw{v6JrggoHtYJAIa~+#}aX1rXgR1arc-JIS=lXFom} zp@5xi)`5!BE=E(yizv9XvDKuT7iPn-Eli|T{x>z!uPNP5Kg+S(&Qvz#e^W#KMzqMA zZeH|^oT+{eqBNUCn{Z+~Hp5x=U0Xpex4C`Q$}RkcI`#Wpz~UvS!DPOgE_O3LxOP`; zeYTnE8)s9`c5Gchc;U)M6z!Wn{e47(&@-7xKeB;*L)IkaxQlPW1RgJSBARl()w<;$2qEw*~|4^+-V8+A z4KVEe#?N2h*!fJZ`{X4A`zMOGB3ljLZ4A7|xN2YyLn1vg2>_*jFdEJVFujgDY{DPu z{AjcmfK(ewouX!Q#;5`mYpVGVy+B`0dpKoO>uxOF6Y0?FtpyX0 zAxSHJnRumdygiUVfBw0G++Opo79ehFYlAe71R(jU-3p*Fw+P`t5DA#(QZtxC5vGVI zI~8^ad5DpvWI4Z2yajm8SjxUiX)H9(3$Zm}F~(c=91-UcY-hTA&%Na%S54da7G5Ho z@QhAZH<0txzfinuvReYX1BVaQS}`>i`VJeo%iFzP#5}=lQD8F7OZ)43GHiwX4O-KbP#_bS*DiEQG)7Y$mo0>wTLtVK@iPLl7NMr7j9-Jb9Mg z%f+17k5i~?!#g)MJ47w*GxF}gN_{rLYQgv+ zTrO=r187;;C8Lz|L-YL|I+fv)CIG&(47u+mk9j8C9N*s8_&BGB>C^-h zhBp_^#+%sgK$w;+lKr}5TN0n74V27uM;qX(+CeNP|2wX(#|U#`)s8-y_M8wvH~yBO zHNf)^%Zu}$V{F71W(_aMlGGdc3PKc0NhIr1>SxePHzW59e;F;9*I87zO&bc+_5IeBL z4qcx%{Y=nfiyRk4VM}dcZ?wbPjfWlw90I0dF<&SE83>&N_LvoK3Gvi2Wg1=@qcvC8 z%KBZI?MrE6FPT8uf0%yql(fT_w2=bjF_zA14ZuSwM-`5jT-w3!=>HH4H*yu0uUhiS z_EgsA$j!6e+=uX?MB4r93Y?Ft=_|8r2yzC#4VD<@&b8{EZ_*2i0NwsuY_REEGfSL+}B@@UR#&fe1kiA+g%CPim6A$!t*~uK+}(x{t%LMhNLaOQ&h-Kib;tQsm@}_*d9#`wYIOC*IG@%OV8<#|q4Nkcj%E0qg zX|LSfd_{Jo%eV7wi#!Km4%=NFkH)fG{>opfK19YrB)b+&-WJoKEr1g0 zYJ*2ji0jGQtbl^KCeUjO!Ufzi^Oa;bAE|Ox?>2-`3BAiv+3Z%eppEk`c!QAW2yJPV zpf^Pwkl^vxT?II9bo7S2- zShCb(On*m)oAfh)c-YFsFF6DtVH8H0j_H+7hRA5s25Y!Q+~3Ps?HIX%mqXq{03T=M z@^k*EW=mz_RA9!Ps3dR9wqcHVTb228s%N`yD$p^__fi8s-te$Lo6f z6uJ)>$B0L(EL=tsPKszeP{54zk--a8^GXU|F+uo${7bF}@gU@;EFPP@BEu<-h`B_1 zya}NT9;7l=Ypp?VLidyxqtLpHo2jSCAq%c8@yj^U1&Qb%kHO<3%@G&MgXrl9` zSZyun1Lc3{>4It?^fc$$VJ3(sGNi%5nV>ZpwoJY$`>zq-1fr%eXA<^3KdBC9z4xcP zA-;t34A_H15GOvt$Y0K4w%1*TPgZkkYWtqnYN*|_l)A#mUbH%A%ucV-4 z97(CyFzNuRbr-0}#m@;x@7&&osPz`Ryg%J}zc{yyAKKphu5dCqJ{;EcQJ*&W&VFfVOUu3c zB3Q8!P^(?-u$UH3LSm)mkCH^dtpFo0A<@n*DJ2+MmP)_IRo>vX z%F0FgOWJo!{4gjNTGeEiVv4YP^ZWlj zHnz26CeR~zmn+qqsGc`e1Hfz{XALU-Q9q?>=d1SIPGmra)>WN6Ak7y;)w@_3uo~Xz zdP2=OMVlT$Wnisv1LaNrJOww$)1!Z@*+CpW1$=*ub1|OT-~fbk#W=9gCPK!a!;}F2 z8PFI45UWI7B2m1MLTodVX755tYTtp1Pm42HfLpDPdC?H$#^Tv92yU+gb(S0cOgaF+c>a9=tT&Q6SVdzsAgX1TMhH$W4L zwFYRZ*lDtS4ZnUG*pf1!CJ{jZbFx%Az=tc6Q>Aq?@I?OHgD1L0Z~{1(fcF1?k)c z3F-cPpLu4!_r1@|U32F8``{;b8ZsrAVV!VFa z_9!D88nMRoYWJ4a=~~)$l)(P(qd~AQLepZ4V9GhqE|}(sQt%uxt04c&JM4wh zC^HXB%6OA0gHiO%4zg!~Jo^aoN_-K@uex5k!-VHyslyYy$`KV6)smB7km~O3=Mvej zweCOghRMBxVV}zU2QJ^ZYmRtkwvMFGBbP2-h}=1otdSqyNl@VH3a3-|pkwz?Z|dVp zT4W6{&d4_zv*g-h!ZFSy{d+&IEb{|g&SRP7k33vFM5StPAaAqRHy^lBu(j(b#4u}H z8#>oK1gSJmU(adAi?7l9S%B>Yb)89no(9ekbC6(&d?Pd4P%6Eb^rbVGY~aoWY46 zkm9D!o@48fbr$wK{+JhGkvah+;FXw0!#R{IMOl8$mvVX+^lCeO^Vq*0pa4a{ll{4_!6&cwYX zDPShjK9O>u%(dy%36$Qh{EGxD&)>9a3otK)Wvz6p^8(6pSm3UuYH%pQsR-8c+eJ^W zKFwmBX?1{)L?AEkBY)FPUm~|N-s&UX{-OXCu|X(mwVMlzDXT@BqT?J~mitiN2$!<} zRiiEsBz~0rT$k6+Hi?c9^5C_BI4_vrqejQxr(1fL;-n&Ip|!!#{>6){Q5d99RD^oce%4hL;E-6v5#i+-2SITISF{6X<~@&wJHxT;_~ z0c_$(@r;E*5pZwpQR1p49wV66%#yZ%k#+g}sosK0~Y zASqb_WpFg~(G#LLTkZ@GatX}r5WH4k3y7GblTceogR_2=Q|ro)lH zFH_8ZLc?p{^nJ;B<@qrtYmbv6AtQ*r(_7~!YpI~TNHK2jVrQ{XAk=(F99?9e4q-## z_`;z)vzD8~pHF1e{1zmLG3mkt)W`ZQzVne9Jkf#g#B8TZekz!sIX777`9TAZ=mDOrH4y{n}&LKqG21u zUB}S8RA2ehdAZ?ZNVnvcn1i)R9o<6nRo4xlAX~mVW2O^#qw<~QbzZ)usfoI49)jZY z_8+L|B`$&klvu*}G3lz$4{;qk2C&5my=?df?{Dieb&%@@hVxMpXxz|~0f*P^bbrsR zmxn{g2%#OV3uJt7SvJTfy^c*AI&KSA$g?qR5fk-gA}UOGojZ`{lNffz5rw=|gXUW& zP|M6rq+2QASxJOVffnCHB2J3#K*(J!5+_e5r2=ICUgmCV{F08>^2rTDuj>8r#mU}y zziyX0_8v<|?KoRV(rUb=*Yu25lv6=|W#yw!a6#c5rygfE8a-8mE4M`&O)ABMb(HF& zfwcSFjZbDhI`XUTb`N2!b3>jcl$8_m&iXQ>6#tw+kUlG!r_W!u89n`dnf%xDO^x#S zws=Wh`tRhh6C!+h@dEPxZD^V42&)=t%nN`X4ncxUr}_3J3qO7@bCe)omSS-ZMFsNX2;^}djj|JCz{n_>*DQ(g%@ zDf`y8ulYf zqI}V^p#_?|G?a7#uHPj&H9tQ!H)q=|pfFsx1K8p8Kx1mUwA%L~kzt!*)p< zsU1z&HCJ1<%*qd9Thw}wz=Bn4N7x6(Q>S=;TER`d3X9cfJ93eQWkda?G-!+-=9)d0v<_4CD`YiNvJV<; zG2EUHV{3e?7Q2`KTqXdS_@@(z(@DV|;uLi!$2(kbe%P zP_EVbq<4{&@xar(diz5$I~CSX(c&632AoVuLZzf8BjbDZ8$QWxfeI@266@F*l{lSw zQTo^nDu+i!AE0&&Svyx8@vE*;-zTCVF!<=Lu)Y_Yu1@7k`$6<<_*{BoB{c6I}+gN1@(SoG8;ywXSqWH*DS&Bdu zqFxTH9Q1`{-!fonIHi>JH42|KB-G0ZY6J>^WmCZknQZG6Kso2d{R`UZ4eF_%`tZ)Wsl!s9Tv5m_z=BY?@4$HY*^2!@Noiz7gDE87Kfw?KTkP0p-ezOCt5UgGdVuLs|-$PLX{&86uh&I zq}ZyTvdC9Szzv`4uJjr2jo}mwPdP}g@{W@`(HW?9ktm59=QE90Xw$tSELJoKg6cfi zyqjESW-+!4?;{!(*;$C#zbi3HbiVn5z_Sf>lPw)|d3;hGim!^T;OU{V{-uc>{;+AG zSI5%F>vGxowJ|9FbNSMneL4rFN%{Ln7q)(%R>T=j%#XDRJ+J%e)Yk#>1R>J!h@)>D-3d>yr3v|4Q+R2_1FFOHX8dlaEDeZ< zeNR*P!eXc8XKVGj8S7!w zg6S8Pd|1LVfnwD1SxfjWy!Medjd}ZxSm+4Y;mYbIJnhSRu#DeTLZ*vTou zmq^mX;BTEkCJBkPj+YfWazjF0_%kWv;VPuZIJG2NV`!^X>z48*V-J})_Vkml)oS=_G6hgN23=$Tu4B$mLqI6NaT zSQm*JYAWV>YCtm<95;Pq&I$ZF)m9Zx#1-l{mEY1o@Sa=i)gl7RgCthf*-7;x6Cg*d z9Z_t&R3w~tBP~B6P31;A4}PgM2El(?_$ZHz`)0%&VHwTIX2{ICuu3R|8R5ad*;oWP97O(DLYhdbG!1PJLt z`EtzAPe{CJm)*-1IuK~bAOx>=pT0^QSwFxN)%{*~x*?XP?OeX-dAGg)4y<8v;B-x_ z>wY)q2ZKlQHh(SZ7Q|b3NhrQg)S=-ZcD-dgLv7FY0G!Iy1I^22M&h}tOtp}&CmvV6 z%FOLo4Hu75@~yOz$1Je*SqeCl9=K-(+P+`C^c1{Ket25=<@D3X-uW?~d0jX^?5Wb< zd+z;(-_N+{XV@@%p^tAC`O1p2fkW<2ZG+yfr511I!yJXHKdC7iHKv;`n#pw~^qgQ3 zQd2nwTHePdsBm=NI3GqeMUqaXe#`X35uS|w#a#g@BR8(Od-hqyUca&ik7Ip!mlM{M z3#s@K31HR!nR4q)qIr~!kvr#taaOR%{l#}jWM)6hYyEY~lfl_;= z$}c2ul(^qdu{VzzXM*|~qRJ><;6~ybGAj)Cpo~kgOumhXu|ec?>f$#9%CED_PCGSL zk9O0orae||Z_{nnWEGcQmWHG1IA40)(>;#*5{9vphm$kzwF5(tiz-TViS72~-)mtWzqNr+fDqVs&rTj81qKl(k>I!8pV^98*Z#xeB3zq z^tdbj5|qJqGeK32`sv$fJe#P-Q89<-ip6#~v%P1iq!`#$*USys&?kY8Ib&&j%84)CV zju4-QGNov?)2wis*v|^OoD-u($&PgijFcPDvEHxJe5e}kn<4{-T(k0lP?nd4Z@OH0 z59v&BQ7>a~eFpVh>tKO%y?fBk`c8J7x%lviHYSms#WBd*nv9`;NoUHMOgE-(D&pIx zk&w52DOKy0R-AEl0^x3^J#`=XIjlS5P)-vTO=0$wb5_Q(pO^OM^d#<9NbIOy+nq)l zGBrgTDR>74q+q{rB~014;7<>Ce%hy;o6=l4VsfH7lG@U+!s5JU-SCs{2Ba9Ds3~MM zdARe8&s)yG2YT5XeZe8!j-Lgiv^%B>U^A(Q1@Mm$-<9ShLFBv;EOy<^!`F;DY}8zd z8yvXjT6ez7x=>Hz7ZCrCT@TYYs|trxX|&Hd-9q>&1~mkjqc6#~nH2}rMlKOsT^2T_ z9b-{{61>r2+X9`qtF=k6zyG3GZgub?3}B-p%z4a7qd@_MiJxSo%y5F~0hdYW#%#UtooV1Z)ngRS;sD`bW_K z=6S1k1<`1}@9|2+&FBIq!{8gK z-ZF+=zo#FFm~xz^mPz+hbkGAty_cYN*ejMb&0I-T>Yk8%AJ$St^Fx{T2AM9A#x1cv z;wd^`ZajfJ8q|+C{nKOC7I}Mq-pg%n9V_TrA>G^vHKpRFzrIpwTW0(2EaX)XPZH5(!?SKLMmgegMm*sVm4Ao z;5fN5S}r*0hQ2|Z@=RrH%BkF!qf+#i^`c$ka5&j%d%2dfq^}>ts<0vn@1ZI%^*Z@g zXzx;%IE=cX4$zH*HxzW|o7_z&vZN^S=hz%P#R=Alr z195A6K`!g<(4O;8Rs3rdom2grgq?EBSX4rQ%Qjq5dw|f?rQRxU?(OfZ4aS?iYFe&Y z=4kutY^gO=zo4W2pOh!HiDm(Xntt|Nj9kM+&VS$4$V-1%lV6|oIv61J&zD>DO}SKF z5W=iH)(u(a(p=e_c{{N1^Ir*AJUU{kSpYs?)`j_7#{mdkpo-HBbZ?{nyybg`=JSu;Ij&hi{9|rIPh3CI2YocJDRYevjB_%QCf?02E0$AJ$^gWW4nHvo0` z9qOre2*9~eNqflUOH#vh)=A*kKl>Nec#mTRGqgJw?K*vozZZ0QXrUE{6_SF={;G$x zR*83$iSO8(%_6=%nV1X=Sryqlq56TX&!A2uEV!Cl-8&n%W*5d3a@DXt{nkBXJywbM zMJqwUV-)NjSL+233jlZk_>Z}fRqFkd{OgDS`2S|4|3=$4Z{NL#I9qzWw{;iL*TV$> zds%|4{-1&O599J?tFSu_0DviB0jU0E0RJI~{`LRE_^05vefwW5T+n~EV*Opof1_AB w0KfyV5wjH$5wjD9LWPCIt!(T>>}*A?1Z^aQ?5xCXg+;}n;+7&}mg1KG2W_}Fr~m)} diff --git a/packages/lsp/eglot.el b/packages/lsp/eglot.el new file mode 100644 index 0000000..e8f060c --- /dev/null +++ b/packages/lsp/eglot.el @@ -0,0 +1,3181 @@ +;;; eglot.el --- Client for Language Server Protocol (LSP) servers -*- lexical-binding: t; -*- + +;; Copyright (C) 2018-2022 Free Software Foundation, Inc. + +;; Version: 1.8 +;; Author: Jo茫o T谩vora +;; Maintainer: Jo茫o T谩vora +;; URL: https://github.com/joaotavora/eglot +;; Keywords: convenience, languages +;; Package-Requires: ((emacs "26.1") (jsonrpc "1.0.14") (flymake "1.2.1") (project "0.3.0") (xref "1.0.1") (eldoc "1.11.0") (seq "2.23")) + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; Eglot ("Emacs Polyglot") is an Emacs LSP client that stays out of +;; your way. +;; +;; Typing M-x eglot should be enough to get you started, but here's a +;; little info (see the accompanying README.md or the URL for more). +;; +;; M-x eglot starts a server via a shell command guessed from +;; `eglot-server-programs', using the current major mode (for whatever +;; language you're programming in) as a hint. If it can't guess, it +;; prompts you in the minibuffer for these things. Actually, the +;; server does not need to be running locally: you can connect to a +;; running server via TCP by entering a syntax. +;; +;; If the connection is successful, you should see an `eglot' +;; indicator pop up in your mode-line. More importantly, this means +;; that current *and future* file buffers of that major mode *inside +;; your current project* automatically become \"managed\" by the LSP +;; server. In other words, information about their content is +;; exchanged periodically to provide enhanced code analysis using +;; `xref-find-definitions', `flymake-mode', `eldoc-mode', +;; `completion-at-point', among others. +;; +;; To "unmanage" these buffers, shutdown the server with +;; M-x eglot-shutdown. +;; +;; To start an eglot session automatically when a foo-mode buffer is +;; visited, you can put this in your init file: +;; +;; (add-hook 'foo-mode-hook 'eglot-ensure) + +;;; Code: + +(require 'json) +(require 'imenu) +(require 'cl-lib) +(require 'project) +(require 'url-parse) +(require 'url-util) +(require 'pcase) +(require 'compile) ; for some faces +(require 'warnings) +(require 'flymake) +(require 'xref) +(eval-when-compile + (require 'subr-x)) +(require 'jsonrpc) +(require 'filenotify) +(require 'ert) +(require 'array) + +;; ElDoc is preloaded in Emacs, so `require'-ing won't guarantee we are +;; using the latest version from GNU Elpa when we load eglot.el. Use an +;; heuristic to see if we need to `load' it in Emacs < 28. +(if (and (< emacs-major-version 28) + (not (boundp 'eldoc-documentation-strategy))) + (load "eldoc") + (require 'eldoc)) + +;; Similar issue as above for Emacs 26.3 and seq.el. +(if (< emacs-major-version 27) + (load "seq") + (require 'seq)) + +;; forward-declare, but don't require (Emacs 28 doesn't seem to care) +(defvar markdown-fontify-code-blocks-natively) +(defvar company-backends) +(defvar company-tooltip-align-annotations) + + + +;;; User tweakable stuff +(defgroup eglot nil + "Interaction with Language Server Protocol servers." + :prefix "eglot-" + :group 'applications) + +(defun eglot-alternatives (alternatives) + "Compute server-choosing function for `eglot-server-programs'. +Each element of ALTERNATIVES is a string PROGRAM or a list of +strings (PROGRAM ARGS...) where program names an LSP server +program to start with ARGS. Returns a function of one argument. +When invoked, that function will return a list (ABSPATH ARGS), +where ABSPATH is the absolute path of the PROGRAM that was +chosen (interactively or automatically)." + (lambda (&optional interactive) + ;; JT@2021-06-13: This function is way more complicated than it + ;; could be because it accounts for the fact that + ;; `eglot--executable-find' may take much longer to execute on + ;; remote files. + (let* ((listified (cl-loop for a in alternatives + collect (if (listp a) a (list a)))) + (err (lambda () + (error "None of '%s' are valid executables" + (mapconcat #'car listified ", "))))) + (cond (interactive + (let* ((augmented (mapcar (lambda (a) + (let ((found (eglot--executable-find + (car a) t))) + (and found + (cons (car a) (cons found (cdr a)))))) + listified)) + (available (remove nil augmented))) + (cond ((cdr available) + (cdr (assoc + (completing-read + "[eglot] More than one server executable available:" + (mapcar #'car available) + nil t nil nil (car (car available))) + available #'equal))) + ((cdr (car available))) + (t + ;; Don't error when used interactively, let the + ;; Eglot prompt the user for alternative (github#719) + nil)))) + (t + (cl-loop for (p . args) in listified + for probe = (eglot--executable-find p t) + when probe return (cons probe args) + finally (funcall err))))))) + +(defvar eglot-server-programs `((rust-mode . ,(eglot-alternatives '("rust-analyzer" "rls"))) + (cmake-mode . ("cmake-language-server")) + (vimrc-mode . ("vim-language-server" "--stdio")) + (python-mode + . ,(eglot-alternatives + '("pylsp" "pyls" ("pyright-langserver" "--stdio")))) + ((js-mode typescript-mode) + . ("typescript-language-server" "--stdio")) + (sh-mode . ("bash-language-server" "start")) + ((php-mode phps-mode) + . ("php" "vendor/felixfbecker/\ +language-server/bin/php-language-server.php")) + ((c++-mode c-mode) . ,(eglot-alternatives + '("clangd" "ccls"))) + (((caml-mode :language-id "ocaml") + (tuareg-mode :language-id "ocaml") reason-mode) + . ("ocamllsp")) + (ruby-mode + . ("solargraph" "socket" "--port" :autoport)) + (haskell-mode + . ("haskell-language-server-wrapper" "--lsp")) + (elm-mode . ("elm-language-server")) + (mint-mode . ("mint" "ls")) + (kotlin-mode . ("kotlin-language-server")) + (go-mode . ("gopls")) + ((R-mode ess-r-mode) . ("R" "--slave" "-e" + "languageserver::run()")) + (java-mode . ("jdtls")) + (dart-mode . ("dart" "language-server" + "--client-id" "emacs.eglot-dart")) + (elixir-mode . ("language_server.sh")) + (ada-mode . ("ada_language_server")) + (scala-mode . ("metals-emacs")) + (racket-mode . ("racket" "-l" "racket-langserver")) + ((tex-mode context-mode texinfo-mode bibtex-mode) + . ("digestif")) + (erlang-mode . ("erlang_ls" "--transport" "stdio")) + (yaml-mode . ("yaml-language-server" "--stdio")) + (nix-mode . ("rnix-lsp")) + (gdscript-mode . ("localhost" 6008)) + ((fortran-mode f90-mode) . ("fortls")) + (futhark-mode . ("futhark" "lsp")) + (lua-mode . ("lua-lsp")) + (zig-mode . ("zls")) + (css-mode . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio") ("css-languageserver" "--stdio")))) + (html-mode . ,(eglot-alternatives '(("vscode-html-language-server" "--stdio") ("html-languageserver" "--stdio")))) + (json-mode . ,(eglot-alternatives '(("vscode-json-language-server" "--stdio") ("json-languageserver" "--stdio")))) + (dockerfile-mode . ("docker-langserver" "--stdio")) + (clojure-mode . ("clojure-lsp")) + (csharp-mode . ("omnisharp" "-lsp")) + (purescript-mode . ("purescript-language-server" "--stdio"))) + "How the command `eglot' guesses the server to start. +An association list of (MAJOR-MODE . CONTACT) pairs. MAJOR-MODE +identifies the buffers that are to be managed by a specific +language server. The associated CONTACT specifies how to connect +to a server for those buffers. + +MAJOR-MODE can be: + +* In the most common case, a symbol such as `c-mode'; + +* A list (MAJOR-MODE-SYMBOL :LANGUAGE-ID ID) where + MAJOR-MODE-SYMBOL is the aforementioned symbol and ID is a + string identifying the language to the server; + +* A list combining the previous two alternatives, meaning + multiple major modes will be associated with a single server + program. + +CONTACT can be: + +* In the most common case, a list of strings (PROGRAM [ARGS...]). + PROGRAM is called with ARGS and is expected to serve LSP requests + over the standard input/output channels. + +* A list (PROGRAM [ARGS...] :initializationOptions OPTIONS), + whereupon PROGRAM is called with ARGS as in the first option, + and the LSP \"initializationOptions\" JSON object is + constructed from OPTIONS. If OPTIONS is a unary function, it + is called with the server instance and should return a JSON + object. + +* A list (HOST PORT [TCP-ARGS...]) where HOST is a string and + PORT is a positive integer for connecting to a server via TCP. + Remaining ARGS are passed to `open-network-stream' for + upgrading the connection with encryption or other capabilities. + +* A list (PROGRAM [ARGS...] :autoport [MOREARGS...]), whereupon a + combination of previous options is used. First, an attempt is + made to find an available server port, then PROGRAM is launched + with ARGS; the `:autoport' keyword substituted for that number; + and MOREARGS. Eglot then attempts to establish a TCP + connection to that port number on the localhost. + +* A cons (CLASS-NAME . INITARGS) where CLASS-NAME is a symbol + designating a subclass of `eglot-lsp-server', for representing + experimental LSP servers. INITARGS is a keyword-value plist + used to initialize the object of CLASS-NAME, or a plain list + interpreted as the previous descriptions of CONTACT. In the + latter case that plain list is used to produce a plist with a + suitable :PROCESS initarg to CLASS-NAME. The class + `eglot-lsp-server' descends from `jsonrpc-process-connection', + which you should see for the semantics of the mandatory + :PROCESS argument. + +* A function of a single argument producing any of the above + values for CONTACT. The argument's value is non-nil if the + connection was requested interactively (e.g. from the `eglot' + command), and nil if it wasn't (e.g. from `eglot-ensure'). If + the call is interactive, the function can ask the user for + hints on finding the required programs, etc. Otherwise, it + should not ask the user for any input, and return nil or signal + an error if it can't produce a valid CONTACT.") + +(defface eglot-highlight-symbol-face + '((t (:inherit bold))) + "Face used to highlight the symbol at point.") + +(defface eglot-mode-line + '((t (:inherit font-lock-constant-face :weight bold))) + "Face for package-name in EGLOT's mode line.") + +(defface eglot-diagnostic-tag-unnecessary-face + '((t (:inherit shadow))) + "Face used to render unused or unnecessary code.") + +(defface eglot-diagnostic-tag-deprecated-face + '((t . (:inherit shadow :strike-through t))) + "Face used to render deprecated or obsolete code.") + +(defcustom eglot-autoreconnect 3 + "Control ability to reconnect automatically to the LSP server. +If t, always reconnect automatically (not recommended). If nil, +never reconnect automatically after unexpected server shutdowns, +crashes or network failures. A positive integer number says to +only autoreconnect if the previous successful connection attempt +lasted more than that many seconds." + :type '(choice (boolean :tag "Whether to inhibit autoreconnection") + (integer :tag "Number of seconds"))) + +(defcustom eglot-connect-timeout 30 + "Number of seconds before timing out LSP connection attempts. +If nil, never time out." + :type 'number) + +(defcustom eglot-sync-connect 3 + "Control blocking of LSP connection attempts. +If t, block for `eglot-connect-timeout' seconds. A positive +integer number means block for that many seconds, and then wait +for the connection in the background. nil has the same meaning +as 0, i.e. don't block at all." + :type '(choice (boolean :tag "Whether to inhibit autoreconnection") + (integer :tag "Number of seconds"))) + +(defcustom eglot-autoshutdown nil + "If non-nil, shut down server after killing last managed buffer." + :type 'boolean) + +(defcustom eglot-send-changes-idle-time 0.5 + "Don't tell server of changes before Emacs's been idle for this many seconds." + :type 'number) + +(defcustom eglot-events-buffer-size 2000000 + "Control the size of the Eglot events buffer. +If a number, don't let the buffer grow larger than that many +characters. If 0, don't use an event's buffer at all. If nil, +let the buffer grow forever." + :type '(choice (const :tag "No limit" nil) + (integer :tag "Number of characters"))) + +(defcustom eglot-confirm-server-initiated-edits 'confirm + "Non-nil if server-initiated edits should be confirmed with user." + :type '(choice (const :tag "Don't show confirmation prompt" nil) + (symbol :tag "Show confirmation prompt" 'confirm))) + +(defcustom eglot-extend-to-xref nil + "If non-nil, activate Eglot in cross-referenced non-project files." + :type 'boolean) + +(defcustom eglot-menu-string "eglot" + "String displayed in mode line when Eglot is active." + :type 'string) + +(defvar eglot-withhold-process-id nil + "If non-nil, Eglot will not send the Emacs process id to the language server. +This can be useful when using docker to run a language server.") + +;; Customizable via `completion-category-overrides'. +(when (assoc 'flex completion-styles-alist) + (add-to-list 'completion-category-defaults '(eglot (styles flex basic)))) + + +;;; Constants +;;; +(defconst eglot--symbol-kind-names + `((1 . "File") (2 . "Module") + (3 . "Namespace") (4 . "Package") (5 . "Class") + (6 . "Method") (7 . "Property") (8 . "Field") + (9 . "Constructor") (10 . "Enum") (11 . "Interface") + (12 . "Function") (13 . "Variable") (14 . "Constant") + (15 . "String") (16 . "Number") (17 . "Boolean") + (18 . "Array") (19 . "Object") (20 . "Key") + (21 . "Null") (22 . "EnumMember") (23 . "Struct") + (24 . "Event") (25 . "Operator") (26 . "TypeParameter"))) + +(defconst eglot--kind-names + `((1 . "Text") (2 . "Method") (3 . "Function") (4 . "Constructor") + (5 . "Field") (6 . "Variable") (7 . "Class") (8 . "Interface") + (9 . "Module") (10 . "Property") (11 . "Unit") (12 . "Value") + (13 . "Enum") (14 . "Keyword") (15 . "Snippet") (16 . "Color") + (17 . "File") (18 . "Reference") (19 . "Folder") (20 . "EnumMember") + (21 . "Constant") (22 . "Struct") (23 . "Event") (24 . "Operator") + (25 . "TypeParameter"))) + +(defconst eglot--tag-faces + `((1 . eglot-diagnostic-tag-unnecessary-face) + (2 . eglot-diagnostic-tag-deprecated-face))) + +(defconst eglot--{} (make-hash-table) "The empty JSON object.") + +(defun eglot--executable-find (command &optional remote) + "Like Emacs 27's `executable-find', ignore REMOTE on Emacs 26." + (if (>= emacs-major-version 27) (executable-find command remote) + (executable-find command))) + + +;;; Message verification helpers +;;; +(eval-and-compile + (defvar eglot--lsp-interface-alist + `( + (CodeAction (:title) (:kind :diagnostics :edit :command :isPreferred)) + (ConfigurationItem () (:scopeUri :section)) + (Command ((:title . string) (:command . string)) (:arguments)) + (CompletionItem (:label) + (:kind :detail :documentation :deprecated :preselect + :sortText :filterText :insertText :insertTextFormat + :textEdit :additionalTextEdits :commitCharacters + :command :data :tags)) + (Diagnostic (:range :message) (:severity :code :source :relatedInformation :codeDescription :tags)) + (DocumentHighlight (:range) (:kind)) + (FileSystemWatcher (:globPattern) (:kind)) + (Hover (:contents) (:range)) + (InitializeResult (:capabilities) (:serverInfo)) + (Location (:uri :range)) + (LocationLink (:targetUri :targetRange :targetSelectionRange) (:originSelectionRange)) + (LogMessageParams (:type :message)) + (MarkupContent (:kind :value)) + (ParameterInformation (:label) (:documentation)) + (Position (:line :character)) + (Range (:start :end)) + (Registration (:id :method) (:registerOptions)) + (ResponseError (:code :message) (:data)) + (ShowMessageParams (:type :message)) + (ShowMessageRequestParams (:type :message) (:actions)) + (SignatureHelp (:signatures) (:activeSignature :activeParameter)) + (SignatureInformation (:label) (:documentation :parameters :activeParameter)) + (SymbolInformation (:name :kind :location) + (:deprecated :containerName)) + (DocumentSymbol (:name :range :selectionRange :kind) + ;; `:containerName' isn't really allowed , but + ;; it simplifies the impl of `eglot-imenu'. + (:detail :deprecated :children :containerName)) + (TextDocumentEdit (:textDocument :edits) ()) + (TextEdit (:range :newText)) + (VersionedTextDocumentIdentifier (:uri :version) ()) + (WorkspaceEdit () (:changes :documentChanges)) + ) + "Alist (INTERFACE-NAME . INTERFACE) of known external LSP interfaces. + +INTERFACE-NAME is a symbol designated by the spec as +\"interface\". INTERFACE is a list (REQUIRED OPTIONAL) where +REQUIRED and OPTIONAL are lists of KEYWORD designating field +names that must be, or may be, respectively, present in a message +adhering to that interface. KEY can be a keyword or a cons (SYM +TYPE), where type is used by `cl-typep' to check types at +runtime. + +Here's what an element of this alist might look like: + + (Command ((:title . string) (:command . string)) (:arguments))")) + +(eval-and-compile + (defvar eglot-strict-mode (if load-file-name '() + '(disallow-non-standard-keys + ;; Uncomment these two for fun at + ;; compile-time or with flymake-mode. + ;; + ;; enforce-required-keys + ;; enforce-optional-keys + )) + "How strictly to check LSP interfaces at compile- and run-time. + +Value is a list of symbols (if the list is empty, no checks are +performed). + +If the symbol `disallow-non-standard-keys' is present, an error +is raised if any extraneous fields are sent by the server. At +compile-time, a warning is raised if a destructuring spec +includes such a field. + +If the symbol `enforce-required-keys' is present, an error is +raised if any required fields are missing from the message sent +from the server. At compile-time, a warning is raised if a +destructuring spec doesn't use such a field. + +If the symbol `enforce-optional-keys' is present, nothing special +happens at run-time. At compile-time, a warning is raised if a +destructuring spec doesn't use all optional fields. + +If the symbol `disallow-unknown-methods' is present, Eglot warns +on unknown notifications and errors on unknown requests.")) + +(cl-defun eglot--check-object (interface-name + object + &optional + (enforce-required t) + (disallow-non-standard t) + (check-types t)) + "Check that OBJECT conforms to INTERFACE. Error otherwise." + (cl-destructuring-bind + (&key types required-keys optional-keys &allow-other-keys) + (eglot--interface interface-name) + (when-let ((missing (and enforce-required + (cl-set-difference required-keys + (eglot--plist-keys object))))) + (eglot--error "A `%s' must have %s" interface-name missing)) + (when-let ((excess (and disallow-non-standard + (cl-set-difference + (eglot--plist-keys object) + (append required-keys optional-keys))))) + (eglot--error "A `%s' mustn't have %s" interface-name excess)) + (when check-types + (cl-loop + for (k v) on object by #'cddr + for type = (or (cdr (assoc k types)) t) ;; FIXME: enforce nil type? + unless (cl-typep v type) + do (eglot--error "A `%s' must have a %s as %s, but has %s" + interface-name ))) + t)) + +(eval-and-compile + (defun eglot--keywordize-vars (vars) + (mapcar (lambda (var) (intern (format ":%s" var))) vars)) + + (defun eglot--ensure-type (k) (if (consp k) k (cons k t))) + + (defun eglot--interface (interface-name) + (let* ((interface (assoc interface-name eglot--lsp-interface-alist)) + (required (mapcar #'eglot--ensure-type (car (cdr interface)))) + (optional (mapcar #'eglot--ensure-type (cadr (cdr interface))))) + (list :types (append required optional) + :required-keys (mapcar #'car required) + :optional-keys (mapcar #'car optional)))) + + (defun eglot--check-dspec (interface-name dspec) + "Check destructuring spec DSPEC against INTERFACE-NAME." + (cl-destructuring-bind (&key required-keys optional-keys &allow-other-keys) + (eglot--interface interface-name) + (cond ((or required-keys optional-keys) + (let ((too-many + (and + (memq 'disallow-non-standard-keys eglot-strict-mode) + (cl-set-difference + (eglot--keywordize-vars dspec) + (append required-keys optional-keys)))) + (ignored-required + (and + (memq 'enforce-required-keys eglot-strict-mode) + (cl-set-difference + required-keys (eglot--keywordize-vars dspec)))) + (missing-out + (and + (memq 'enforce-optional-keys eglot-strict-mode) + (cl-set-difference + optional-keys (eglot--keywordize-vars dspec))))) + (when too-many (byte-compile-warn + "Destructuring for %s has extraneous %s" + interface-name too-many)) + (when ignored-required (byte-compile-warn + "Destructuring for %s ignores required %s" + interface-name ignored-required)) + (when missing-out (byte-compile-warn + "Destructuring for %s is missing out on %s" + interface-name missing-out)))) + (t + (byte-compile-warn "Unknown LSP interface %s" interface-name)))))) + +(cl-defmacro eglot--dbind (vars object &body body) + "Destructure OBJECT, binding VARS in BODY. +VARS is ([(INTERFACE)] SYMS...) +Honour `eglot-strict-mode'." + (declare (indent 2) (debug (sexp sexp &rest form))) + (let ((interface-name (if (consp (car vars)) + (car (pop vars)))) + (object-once (make-symbol "object-once")) + (fn-once (make-symbol "fn-once"))) + (cond (interface-name + (eglot--check-dspec interface-name vars) + `(let ((,object-once ,object)) + (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once + (eglot--check-object ',interface-name ,object-once + (memq 'enforce-required-keys eglot-strict-mode) + (memq 'disallow-non-standard-keys eglot-strict-mode) + (memq 'check-types eglot-strict-mode)) + ,@body))) + (t + `(let ((,object-once ,object) + (,fn-once (lambda (,@vars) ,@body))) + (if (memq 'disallow-non-standard-keys eglot-strict-mode) + (cl-destructuring-bind (&key ,@vars) ,object-once + (funcall ,fn-once ,@vars)) + (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once + (funcall ,fn-once ,@vars)))))))) + + +(cl-defmacro eglot--lambda (cl-lambda-list &body body) + "Function of args CL-LAMBDA-LIST for processing INTERFACE objects. +Honour `eglot-strict-mode'." + (declare (indent 1) (debug (sexp &rest form))) + (let ((e (cl-gensym "jsonrpc-lambda-elem"))) + `(lambda (,e) (eglot--dbind ,cl-lambda-list ,e ,@body)))) + +(cl-defmacro eglot--dcase (obj &rest clauses) + "Like `pcase', but for the LSP object OBJ. +CLAUSES is a list (DESTRUCTURE FORMS...) where DESTRUCTURE is +treated as in `eglot-dbind'." + (declare (indent 1) (debug (sexp &rest (sexp &rest form)))) + (let ((obj-once (make-symbol "obj-once"))) + `(let ((,obj-once ,obj)) + (cond + ,@(cl-loop + for (vars . body) in clauses + for vars-as-keywords = (eglot--keywordize-vars vars) + for interface-name = (if (consp (car vars)) + (car (pop vars))) + for condition = + (cond (interface-name + (eglot--check-dspec interface-name vars) + ;; In this mode, in runtime, we assume + ;; `eglot-strict-mode' is partially on, otherwise we + ;; can't disambiguate between certain types. + `(ignore-errors + (eglot--check-object + ',interface-name ,obj-once + t + (memq 'disallow-non-standard-keys eglot-strict-mode) + t))) + (t + ;; In this interface-less mode we don't check + ;; `eglot-strict-mode' at all: just check that the object + ;; has all the keys the user wants to destructure. + `(null (cl-set-difference + ',vars-as-keywords + (eglot--plist-keys ,obj-once))))) + collect `(,condition + (cl-destructuring-bind (&key ,@vars &allow-other-keys) + ,obj-once + ,@body))) + (t + (eglot--error "%S didn't match any of %S" + ,obj-once + ',(mapcar #'car clauses))))))) + + +;;; API (WORK-IN-PROGRESS!) +;;; +(cl-defmacro eglot--when-live-buffer (buf &rest body) + "Check BUF live, then do BODY in it." (declare (indent 1) (debug t)) + (let ((b (cl-gensym))) + `(let ((,b ,buf)) (if (buffer-live-p ,b) (with-current-buffer ,b ,@body))))) + +(cl-defmacro eglot--when-buffer-window (buf &body body) + "Check BUF showing somewhere, then do BODY in it." (declare (indent 1) (debug t)) + (let ((b (cl-gensym))) + `(let ((,b ,buf)) + ;;notice the exception when testing with `ert' + (when (or (get-buffer-window ,b) (ert-running-test)) + (with-current-buffer ,b ,@body))))) + +(cl-defmacro eglot--widening (&rest body) + "Save excursion and restriction. Widen. Then run BODY." (declare (debug t)) + `(save-excursion (save-restriction (widen) ,@body))) + +(cl-defgeneric eglot-handle-request (server method &rest params) + "Handle SERVER's METHOD request with PARAMS.") + +(cl-defgeneric eglot-handle-notification (server method &rest params) + "Handle SERVER's METHOD notification with PARAMS.") + +(cl-defgeneric eglot-execute-command (server command arguments) + "Ask SERVER to execute COMMAND with ARGUMENTS.") + +(cl-defgeneric eglot-initialization-options (server) + "JSON object to send under `initializationOptions'." + (:method (s) + (let ((probe (plist-get (eglot--saved-initargs s) :initializationOptions))) + (cond ((functionp probe) (funcall probe s)) + (probe) + (t eglot--{}))))) + +(cl-defgeneric eglot-register-capability (server method id &rest params) + "Ask SERVER to register capability METHOD marked with ID." + (:method + (_s method _id &rest _params) + (eglot--warn "Server tried to register unsupported capability `%s'" + method))) + +(cl-defgeneric eglot-unregister-capability (server method id &rest params) + "Ask SERVER to register capability METHOD marked with ID." + (:method + (_s method _id &rest _params) + (eglot--warn "Server tried to unregister unsupported capability `%s'" + method))) + +(cl-defgeneric eglot-client-capabilities (server) + "What the EGLOT LSP client supports for SERVER." + (:method (s) + (list + :workspace (list + :applyEdit t + :executeCommand `(:dynamicRegistration :json-false) + :workspaceEdit `(:documentChanges t) + :didChangeWatchedFiles + `(:dynamicRegistration + ,(if (eglot--trampish-p s) :json-false t)) + :symbol `(:dynamicRegistration :json-false) + :configuration t + :workspaceFolders t) + :textDocument + (list + :synchronization (list + :dynamicRegistration :json-false + :willSave t :willSaveWaitUntil t :didSave t) + :completion (list :dynamicRegistration :json-false + :completionItem + `(:snippetSupport + ,(if (eglot--snippet-expansion-fn) + t + :json-false) + :deprecatedSupport t + :tagSupport (:valueSet [1])) + :contextSupport t) + :hover (list :dynamicRegistration :json-false + :contentFormat + (if (fboundp 'gfm-view-mode) + ["markdown" "plaintext"] + ["plaintext"])) + :signatureHelp (list :dynamicRegistration :json-false + :signatureInformation + `(:parameterInformation + (:labelOffsetSupport t) + :activeParameterSupport t)) + :references `(:dynamicRegistration :json-false) + :definition (list :dynamicRegistration :json-false + :linkSupport t) + :declaration (list :dynamicRegistration :json-false + :linkSupport t) + :implementation (list :dynamicRegistration :json-false + :linkSupport t) + :typeDefinition (list :dynamicRegistration :json-false + :linkSupport t) + :documentSymbol (list + :dynamicRegistration :json-false + :hierarchicalDocumentSymbolSupport t + :symbolKind `(:valueSet + [,@(mapcar + #'car eglot--symbol-kind-names)])) + :documentHighlight `(:dynamicRegistration :json-false) + :codeAction (list + :dynamicRegistration :json-false + :codeActionLiteralSupport + '(:codeActionKind + (:valueSet + ["quickfix" + "refactor" "refactor.extract" + "refactor.inline" "refactor.rewrite" + "source" "source.organizeImports"])) + :isPreferredSupport t) + :formatting `(:dynamicRegistration :json-false) + :rangeFormatting `(:dynamicRegistration :json-false) + :rename `(:dynamicRegistration :json-false) + :publishDiagnostics (list :relatedInformation :json-false + ;; TODO: We can support :codeDescription after + ;; adding an appropriate UI to + ;; Flymake. + :codeDescriptionSupport :json-false + :tagSupport + `(:valueSet + [,@(mapcar + #'car eglot--tag-faces)]))) + :experimental eglot--{}))) + +(cl-defgeneric eglot-workspace-folders (server) + "Return workspaceFolders for SERVER." + (let ((project (eglot--project server))) + (vconcat + (mapcar (lambda (dir) + (list :uri (eglot--path-to-uri dir) + :name (abbreviate-file-name dir))) + `(,(project-root project) ,@(project-external-roots project)))))) + +(defclass eglot-lsp-server (jsonrpc-process-connection) + ((project-nickname + :documentation "Short nickname for the associated project." + :accessor eglot--project-nickname + :reader eglot-project-nickname) + (major-mode + :documentation "Major mode symbol." + :accessor eglot--major-mode) + (language-id + :documentation "Language ID string for the mode." + :accessor eglot--language-id) + (capabilities + :documentation "JSON object containing server capabilities." + :accessor eglot--capabilities) + (server-info + :documentation "JSON object containing server info." + :accessor eglot--server-info) + (shutdown-requested + :documentation "Flag set when server is shutting down." + :accessor eglot--shutdown-requested) + (project + :documentation "Project associated with server." + :accessor eglot--project) + (spinner + :documentation "List (ID DOING-WHAT DONE-P) representing server progress." + :initform `(nil nil t) :accessor eglot--spinner) + (inhibit-autoreconnect + :initform t + :documentation "Generalized boolean inhibiting auto-reconnection if true." + :accessor eglot--inhibit-autoreconnect) + (file-watches + :documentation "Map ID to list of WATCHES for `didChangeWatchedFiles'." + :initform (make-hash-table :test #'equal) :accessor eglot--file-watches) + (managed-buffers + :documentation "List of buffers managed by server." + :accessor eglot--managed-buffers) + (saved-initargs + :documentation "Saved initargs for reconnection purposes." + :accessor eglot--saved-initargs) + (inferior-process + :documentation "Server subprocess started automatically." + :accessor eglot--inferior-process)) + :documentation + "Represents a server. Wraps a process for LSP communication.") + + +;;; Process management +(defvar eglot--servers-by-project (make-hash-table :test #'equal) + "Keys are projects. Values are lists of processes.") + +(defun eglot-shutdown (server &optional _interactive timeout preserve-buffers) + "Politely ask SERVER to quit. +Interactively, read SERVER from the minibuffer unless there is +only one and it's managing the current buffer. + +Forcefully quit it if it doesn't respond within TIMEOUT seconds. +TIMEOUT defaults to 1.5 seconds. Don't leave this function with +the server still running. + +If PRESERVE-BUFFERS is non-nil (interactively, when called with a +prefix argument), do not kill events and output buffers of +SERVER." + (interactive (list (eglot--read-server "Shutdown which server" + (eglot-current-server)) + t nil current-prefix-arg)) + (eglot--message "Asking %s politely to terminate" (jsonrpc-name server)) + (unwind-protect + (progn + (setf (eglot--shutdown-requested server) t) + (jsonrpc-request server :shutdown nil :timeout (or timeout 1.5)) + (jsonrpc-notify server :exit nil)) + ;; Now ask jsonrpc.el to shut down the server. + (jsonrpc-shutdown server (not preserve-buffers)) + (unless preserve-buffers (kill-buffer (jsonrpc-events-buffer server))))) + +(defun eglot-shutdown-all (&optional preserve-buffers) + "Politely ask all language servers to quit, in order. +PRESERVE-BUFFERS as in `eglot-shutdown', which see." + (interactive (list current-prefix-arg)) + (cl-loop for ss being the hash-values of eglot--servers-by-project + do (cl-loop for s in ss do (eglot-shutdown s nil preserve-buffers)))) + +(defun eglot--on-shutdown (server) + "Called by jsonrpc.el when SERVER is already dead." + ;; Turn off `eglot--managed-mode' where appropriate. + (dolist (buffer (eglot--managed-buffers server)) + (let (;; Avoid duplicate shutdowns (github#389) + (eglot-autoshutdown nil)) + (eglot--when-live-buffer buffer (eglot--managed-mode-off)))) + ;; Kill any expensive watches + (maphash (lambda (_id watches) + (mapcar #'file-notify-rm-watch watches)) + (eglot--file-watches server)) + ;; Kill any autostarted inferior processes + (when-let (proc (eglot--inferior-process server)) + (delete-process proc)) + ;; Sever the project/server relationship for `server' + (setf (gethash (eglot--project server) eglot--servers-by-project) + (delq server + (gethash (eglot--project server) eglot--servers-by-project))) + (cond ((eglot--shutdown-requested server) + t) + ((not (eglot--inhibit-autoreconnect server)) + (eglot--warn "Reconnecting after unexpected server exit.") + (eglot-reconnect server)) + ((timerp (eglot--inhibit-autoreconnect server)) + (eglot--warn "Not auto-reconnecting, last one didn't last long.")))) + +(defun eglot--all-major-modes () + "Return all known major modes." + (let ((retval)) + (mapatoms (lambda (sym) + (when (plist-member (symbol-plist sym) 'derived-mode-parent) + (push sym retval)))) + retval)) + +(defvar eglot--command-history nil + "History of CONTACT arguments to `eglot'.") + +(defun eglot--lookup-mode (mode) + "Lookup `eglot-server-programs' for MODE. +Return (LANGUAGE-ID . CONTACT-PROXY). If not specified, +LANGUAGE-ID is determined from MODE." + (cl-loop + for (modes . contact) in eglot-server-programs + thereis (cl-some + (lambda (spec) + (cl-destructuring-bind (probe &key language-id &allow-other-keys) + (if (consp spec) spec (list spec)) + (and (provided-mode-derived-p mode probe) + (cons + (or language-id + (or (get mode 'eglot-language-id) + (get spec 'eglot-language-id) + (string-remove-suffix "-mode" (symbol-name mode)))) + contact)))) + (if (or (symbolp modes) (keywordp (cadr modes))) + (list modes) modes)))) + +(defun eglot--guess-contact (&optional interactive) + "Helper for `eglot'. +Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID). If INTERACTIVE is +non-nil, maybe prompt user, else error as soon as something can't +be guessed." + (let* ((guessed-mode (if buffer-file-name major-mode)) + (managed-mode + (cond + ((and interactive + (or (>= (prefix-numeric-value current-prefix-arg) 16) + (not guessed-mode))) + (intern + (completing-read + "[eglot] Start a server to manage buffers of what major mode? " + (mapcar #'symbol-name (eglot--all-major-modes)) nil t + (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil))) + ((not guessed-mode) + (eglot--error "Can't guess mode to manage for `%s'" (current-buffer))) + (t guessed-mode))) + (lang-id-and-guess (eglot--lookup-mode guessed-mode)) + (language-id (or (car lang-id-and-guess) + (string-remove-suffix "-mode" (symbol-name guessed-mode)))) + (guess (cdr lang-id-and-guess)) + (guess (if (functionp guess) + (funcall guess interactive) + guess)) + (class (or (and (consp guess) (symbolp (car guess)) + (prog1 (unless current-prefix-arg (car guess)) + (setq guess (cdr guess)))) + 'eglot-lsp-server)) + (program (and (listp guess) + (stringp (car guess)) + ;; A second element might be the port of a (host, port) + ;; pair, but in that case it is not a string. + (or (null (cdr guess)) (stringp (cadr guess))) + (car guess))) + (base-prompt + (and interactive + "Enter program to execute (or :): ")) + (program-guess + (and program + (combine-and-quote-strings (cl-subst ":autoport:" + :autoport guess)))) + (prompt + (and base-prompt + (cond (current-prefix-arg base-prompt) + ((null guess) + (format "[eglot] Sorry, couldn't guess for `%s'!\n%s" + managed-mode base-prompt)) + ((and program + (not (file-name-absolute-p program)) + (not (eglot--executable-find program t))) + (concat (format "[eglot] I guess you want to run `%s'" + program-guess) + (format ", but I can't find `%s' in PATH!" program) + "\n" base-prompt))))) + (contact + (or (and prompt + (let ((s (read-shell-command + prompt + program-guess + 'eglot-command-history))) + (if (string-match "^\\([^\s\t]+\\):\\([[:digit:]]+\\)$" + (string-trim s)) + (list (match-string 1 s) + (string-to-number (match-string 2 s))) + (cl-subst + :autoport ":autoport:" (split-string-and-unquote s) + :test #'equal)))) + guess + (eglot--error "Couldn't guess for `%s'!" managed-mode)))) + (list managed-mode (eglot--current-project) class contact language-id))) + +(defvar eglot-lsp-context) +(put 'eglot-lsp-context 'variable-documentation + "Dynamically non-nil when searching for projects in LSP context.") + +(defvar eglot--servers-by-xrefed-file + (make-hash-table :test 'equal :weakness 'value)) + +(defun eglot--current-project () + "Return a project object for Eglot's LSP purposes. +This relies on `project-current' and thus on +`project-find-functions'. Functions in the latter +variable (which see) can query the value `eglot-lsp-context' to +decide whether a given directory is a project containing a +suitable root directory for a given LSP server's purposes." + (let ((eglot-lsp-context t)) + (or (project-current) `(transient . ,default-directory)))) + +;;;###autoload +(defun eglot (managed-major-mode project class contact language-id + &optional interactive) + "Manage a project with a Language Server Protocol (LSP) server. + +The LSP server of CLASS is started (or contacted) via CONTACT. +If this operation is successful, current *and future* file +buffers of MANAGED-MAJOR-MODE inside PROJECT become \"managed\" +by the LSP server, meaning information about their contents is +exchanged periodically to provide enhanced code-analysis via +`xref-find-definitions', `flymake-mode', `eldoc-mode', +`completion-at-point', among others. + +Interactively, the command attempts to guess MANAGED-MAJOR-MODE +from current buffer, CLASS and CONTACT from +`eglot-server-programs' and PROJECT from +`project-find-functions'. The search for active projects in this +context binds `eglot-lsp-context' (which see). + +If it can't guess, the user is prompted. With a single +\\[universal-argument] prefix arg, it always prompt for COMMAND. +With two \\[universal-argument] prefix args, also prompts for +MANAGED-MAJOR-MODE. + +PROJECT is a project object as returned by `project-current'. + +CLASS is a subclass of `eglot-lsp-server'. + +CONTACT specifies how to contact the server. It is a +keyword-value plist used to initialize CLASS or a plain list as +described in `eglot-server-programs', which see. + +LANGUAGE-ID is the language ID string to send to the server for +MANAGED-MAJOR-MODE, which matters to a minority of servers. + +INTERACTIVE is t if called interactively." + (interactive (append (eglot--guess-contact t) '(t))) + (let* ((current-server (eglot-current-server)) + (live-p (and current-server (jsonrpc-running-p current-server)))) + (if (and live-p + interactive + (y-or-n-p "[eglot] Live process found, reconnect instead? ")) + (eglot-reconnect current-server interactive) + (when live-p (ignore-errors (eglot-shutdown current-server))) + (eglot--connect managed-major-mode project class contact language-id)))) + +(defun eglot-reconnect (server &optional interactive) + "Reconnect to SERVER. +INTERACTIVE is t if called interactively." + (interactive (list (eglot--current-server-or-lose) t)) + (when (jsonrpc-running-p server) + (ignore-errors (eglot-shutdown server interactive nil 'preserve-buffers))) + (eglot--connect (eglot--major-mode server) + (eglot--project server) + (eieio-object-class-name server) + (eglot--saved-initargs server) + (eglot--language-id server)) + (eglot--message "Reconnected!")) + +(defvar eglot--managed-mode) ; forward decl + +;;;###autoload +(defun eglot-ensure () + "Start Eglot session for current buffer if there isn't one." + (let ((buffer (current-buffer))) + (cl-labels + ((maybe-connect + () + (remove-hook 'post-command-hook #'maybe-connect nil) + (eglot--when-live-buffer buffer + (unless eglot--managed-mode + (apply #'eglot--connect (eglot--guess-contact)))))) + (when buffer-file-name + (add-hook 'post-command-hook #'maybe-connect 'append nil))))) + +(defun eglot-events-buffer (server) + "Display events buffer for SERVER. +Use current server's or first available Eglot events buffer." + (interactive (list (eglot-current-server))) + (let ((buffer (if server (jsonrpc-events-buffer server) + (cl-find "\\*EGLOT.*events\\*" + (buffer-list) + :key #'buffer-name :test #'string-match)))) + (if buffer (display-buffer buffer) + (eglot--error "Can't find an Eglot events buffer!")))) + +(defun eglot-stderr-buffer (server) + "Display stderr buffer for SERVER." + (interactive (list (eglot--current-server-or-lose))) + (display-buffer (jsonrpc-stderr-buffer server))) + +(defun eglot-forget-pending-continuations (server) + "Forget pending requests for SERVER." + (interactive (list (eglot--current-server-or-lose))) + (jsonrpc-forget-pending-continuations server)) + +(defvar eglot-connect-hook + '(eglot-signal-didChangeConfiguration) + "Hook run after connecting in `eglot--connect'.") + +(defvar eglot-server-initialized-hook + '() + "Hook run after a `eglot-lsp-server' instance is created. + +That is before a connection was established. Use +`eglot-connect-hook' to hook into when a connection was +successfully established and the server on the other side has +received the initializing configuration. + +Each function is passed the server as an argument") + +(defun eglot--cmd (contact) + "Helper for `eglot--connect'." + (if (file-remote-p default-directory) + ;; TODO: this seems like a bug, although it鈥檚 everywhere. For + ;; some reason, for remote connections only, over a pipe, we + ;; need to turn off line buffering on the tty. + ;; + ;; Not only does this seem like there should be a better way, + ;; but it almost certainly doesn鈥檛 work on non-unix systems. + (list "sh" "-c" + (string-join (cons "stty raw > /dev/null;" + (mapcar #'shell-quote-argument contact)) + " ")) + contact)) + +(defvar-local eglot--cached-server nil + "A cached reference to the current EGLOT server.") + +(defun eglot--connect (managed-major-mode project class contact language-id) + "Connect to MANAGED-MAJOR-MODE, LANGUAGE-ID, PROJECT, CLASS and CONTACT. +This docstring appeases checkdoc, that's all." + (let* ((default-directory (project-root project)) + (nickname (file-name-base (directory-file-name default-directory))) + (readable-name (format "EGLOT (%s/%s)" nickname managed-major-mode)) + autostart-inferior-process + server-info + (contact (if (functionp contact) (funcall contact) contact)) + (initargs + (cond ((keywordp (car contact)) contact) + ((integerp (cadr contact)) + (setq server-info (list (format "%s:%s" (car contact) + (cadr contact)))) + `(:process ,(lambda () + (apply #'open-network-stream + readable-name nil + (car contact) (cadr contact) + (cddr contact))))) + ((and (stringp (car contact)) (memq :autoport contact)) + (setq server-info (list "")) + `(:process ,(lambda () + (pcase-let ((`(,connection . ,inferior) + (eglot--inferior-bootstrap + readable-name + contact))) + (setq autostart-inferior-process inferior) + connection)))) + ((stringp (car contact)) + (let* ((probe (cl-position-if #'keywordp contact)) + (more-initargs (and probe (cl-subseq contact probe))) + (contact (cl-subseq contact 0 probe))) + `(:process + ,(lambda () + (let ((default-directory default-directory)) + (make-process + :name readable-name + :command (setq server-info (eglot--cmd contact)) + :connection-type 'pipe + :coding 'utf-8-emacs-unix + :noquery t + :stderr (get-buffer-create + (format "*%s stderr*" readable-name)) + :file-handler t))) + ,@more-initargs))))) + (spread (lambda (fn) (lambda (server method params) + (let ((eglot--cached-server server)) + (apply fn server method (append params nil)))))) + (server + (apply + #'make-instance class + :name readable-name + :events-buffer-scrollback-size eglot-events-buffer-size + :notification-dispatcher (funcall spread #'eglot-handle-notification) + :request-dispatcher (funcall spread #'eglot-handle-request) + :on-shutdown #'eglot--on-shutdown + initargs)) + (cancelled nil) + (tag (make-symbol "connected-catch-tag"))) + (when server-info + (jsonrpc--debug server "Running language server: %s" + (string-join server-info " "))) + (setf (eglot--saved-initargs server) initargs) + (setf (eglot--project server) project) + (setf (eglot--project-nickname server) nickname) + (setf (eglot--major-mode server) managed-major-mode) + (setf (eglot--language-id server) language-id) + (setf (eglot--inferior-process server) autostart-inferior-process) + (run-hook-with-args 'eglot-server-initialized-hook server) + ;; Now start the handshake. To honour `eglot-sync-connect' + ;; maybe-sync-maybe-async semantics we use `jsonrpc-async-request' + ;; and mimic most of `jsonrpc-request'. + (unwind-protect + (condition-case _quit + (let ((retval + (catch tag + (jsonrpc-async-request + server + :initialize + (list :processId + (unless (or eglot-withhold-process-id + (file-remote-p default-directory) + (eq (jsonrpc-process-type server) + 'network)) + (emacs-pid)) + ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py' + ;; into `/path/to/baz.py', so LSP groks it. + :rootPath (file-local-name + (expand-file-name default-directory)) + :rootUri (eglot--path-to-uri default-directory) + :initializationOptions (eglot-initialization-options + server) + :capabilities (eglot-client-capabilities server) + :workspaceFolders (eglot-workspace-folders server)) + :success-fn + (eglot--lambda ((InitializeResult) capabilities serverInfo) + (unless cancelled + (push server + (gethash project eglot--servers-by-project)) + (setf (eglot--capabilities server) capabilities) + (setf (eglot--server-info server) serverInfo) + (jsonrpc-notify server :initialized eglot--{}) + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + ;; No need to pass SERVER as an argument: it has + ;; been registered in `eglot--servers-by-project', + ;; so that it can be found (and cached) from + ;; `eglot--maybe-activate-editing-mode' in any + ;; managed buffer. + (eglot--maybe-activate-editing-mode))) + (setf (eglot--inhibit-autoreconnect server) + (cond + ((booleanp eglot-autoreconnect) + (not eglot-autoreconnect)) + ((cl-plusp eglot-autoreconnect) + (run-with-timer + eglot-autoreconnect nil + (lambda () + (setf (eglot--inhibit-autoreconnect server) + (null eglot-autoreconnect))))))) + (let ((default-directory (project-root project)) + (major-mode managed-major-mode)) + (hack-dir-local-variables-non-file-buffer) + (run-hook-with-args 'eglot-connect-hook server)) + (eglot--message + "Connected! Server `%s' now managing `%s' buffers \ +in project `%s'." + (or (plist-get serverInfo :name) + (jsonrpc-name server)) + managed-major-mode + (eglot-project-nickname server)) + (when tag (throw tag t)))) + :timeout eglot-connect-timeout + :error-fn (eglot--lambda ((ResponseError) code message) + (unless cancelled + (jsonrpc-shutdown server) + (let ((msg (format "%s: %s" code message))) + (if tag (throw tag `(error . ,msg)) + (eglot--error msg))))) + :timeout-fn (lambda () + (unless cancelled + (jsonrpc-shutdown server) + (let ((msg (format "Timed out after %s seconds" + eglot-connect-timeout))) + (if tag (throw tag `(error . ,msg)) + (eglot--error msg)))))) + (cond ((numberp eglot-sync-connect) + (accept-process-output nil eglot-sync-connect)) + (eglot-sync-connect + (while t (accept-process-output nil 30))))))) + (pcase retval + (`(error . ,msg) (eglot--error msg)) + (`nil (eglot--message "Waiting in background for server `%s'" + (jsonrpc-name server)) + nil) + (_ server))) + (quit (jsonrpc-shutdown server) (setq cancelled 'quit))) + (setq tag nil)))) + +(defun eglot--inferior-bootstrap (name contact &optional connect-args) + "Use CONTACT to start a server, then connect to it. +Return a cons of two process objects (CONNECTION . INFERIOR). +Name both based on NAME. +CONNECT-ARGS are passed as additional arguments to +`open-network-stream'." + (let* ((port-probe (make-network-process :name "eglot-port-probe-dummy" + :server t + :host "localhost" + :service 0)) + (port-number (unwind-protect + (process-contact port-probe :service) + (delete-process port-probe))) + inferior connection) + (unwind-protect + (progn + (setq inferior + (make-process + :name (format "autostart-inferior-%s" name) + :stderr (format "*%s stderr*" name) + :noquery t + :command (cl-subst + (format "%s" port-number) :autoport contact))) + (setq connection + (cl-loop + repeat 10 for i from 1 + do (accept-process-output nil 0.5) + while (process-live-p inferior) + do (eglot--message + "Trying to connect to localhost and port %s (attempt %s)" + port-number i) + thereis (ignore-errors + (apply #'open-network-stream + (format "autoconnect-%s" name) + nil + "localhost" port-number connect-args)))) + (cons connection inferior)) + (cond ((and (process-live-p connection) + (process-live-p inferior)) + (eglot--message "Done, connected to %s!" port-number)) + (t + (when inferior (delete-process inferior)) + (when connection (delete-process connection)) + (eglot--error "Could not start and connect to server%s" + (if inferior + (format " started with %s" + (process-command inferior)) + "!"))))))) + + +;;; Helpers (move these to API?) +;;; +(defun eglot--error (format &rest args) + "Error out with FORMAT with ARGS." + (error "[eglot] %s" (apply #'format format args))) + +(defun eglot--message (format &rest args) + "Message out with FORMAT with ARGS." + (message "[eglot] %s" (apply #'format format args))) + +(defun eglot--warn (format &rest args) + "Warning message with FORMAT and ARGS." + (apply #'eglot--message (concat "(warning) " format) args) + (let ((warning-minimum-level :error)) + (display-warning 'eglot (apply #'format format args) :warning))) + +(defun eglot-current-column () (- (point) (point-at-bol))) + +(defvar eglot-current-column-function #'eglot-lsp-abiding-column + "Function to calculate the current column. + +This is the inverse operation of +`eglot-move-to-column-function' (which see). It is a function of +no arguments returning a column number. For buffers managed by +fully LSP-compliant servers, this should be set to +`eglot-lsp-abiding-column' (the default), and +`eglot-current-column' for all others.") + +(defun eglot-lsp-abiding-column (&optional lbp) + "Calculate current COLUMN as defined by the LSP spec. +LBP defaults to `line-beginning-position'." + (/ (- (length (encode-coding-region (or lbp (line-beginning-position)) + ;; Fix github#860 + (min (point) (point-max)) 'utf-16 t)) + 2) + 2)) + +(defun eglot--pos-to-lsp-position (&optional pos) + "Convert point POS to LSP position." + (eglot--widening + (list :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE + :character (progn (when pos (goto-char pos)) + (funcall eglot-current-column-function))))) + +(defvar eglot-move-to-column-function #'eglot-move-to-lsp-abiding-column + "Function to move to a column reported by the LSP server. + +According to the standard, LSP column/character offsets are based +on a count of UTF-16 code units, not actual visual columns. So +when LSP says position 3 of a line containing just \"aXbc\", +where X is a multi-byte character, it actually means `b', not +`c'. However, many servers don't follow the spec this closely. + +For buffers managed by fully LSP-compliant servers, this should +be set to `eglot-move-to-lsp-abiding-column' (the default), and +`eglot-move-to-column' for all others.") + +(defun eglot-move-to-column (column) + "Move to COLUMN without closely following the LSP spec." + ;; We cannot use `move-to-column' here, because it moves to *visual* + ;; columns, which can be different from LSP columns in case of + ;; `whitespace-mode', `prettify-symbols-mode', etc. (github#296, + ;; github#297) + (goto-char (min (+ (line-beginning-position) column) + (line-end-position)))) + +(defun eglot-move-to-lsp-abiding-column (column) + "Move to COLUMN abiding by the LSP spec." + (save-restriction + (cl-loop + with lbp = (line-beginning-position) + initially + (narrow-to-region lbp (line-end-position)) + (move-to-column column) + for diff = (- column + (eglot-lsp-abiding-column lbp)) + until (zerop diff) + do (condition-case eob-err + (forward-char (/ (if (> diff 0) (1+ diff) (1- diff)) 2)) + (end-of-buffer (cl-return eob-err)))))) + +(defun eglot--lsp-position-to-point (pos-plist &optional marker) + "Convert LSP position POS-PLIST to Emacs point. +If optional MARKER, return a marker instead" + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (forward-line (min most-positive-fixnum + (plist-get pos-plist :line))) + (unless (eobp) ;; if line was excessive leave point at eob + (let ((tab-width 1) + (col (plist-get pos-plist :character))) + (unless (wholenump col) + (eglot--warn + "Caution: LSP server sent invalid character position %s. Using 0 instead." + col) + (setq col 0)) + (funcall eglot-move-to-column-function col))) + (if marker (copy-marker (point-marker)) (point))))) + +(defconst eglot--uri-path-allowed-chars + (let ((vec (copy-sequence url-path-allowed-chars))) + (aset vec ?: nil) ;; see github#639 + vec) + "Like `url-path-allows-chars' but more restrictive.") + +(defun eglot--path-to-uri (path) + "URIfy PATH." + (let ((truepath (file-truename path))) + (concat "file://" + ;; Add a leading "/" for local MS Windows-style paths. + (if (and (eq system-type 'windows-nt) + (not (file-remote-p truepath))) + "/") + (url-hexify-string + ;; Again watch out for trampy paths. + (directory-file-name (file-local-name truepath)) + eglot--uri-path-allowed-chars)))) + +(defun eglot--uri-to-path (uri) + "Convert URI to file path, helped by `eglot--current-server'." + (when (keywordp uri) (setq uri (substring (symbol-name uri) 1))) + (let* ((server (eglot-current-server)) + (remote-prefix (and server (eglot--trampish-p server))) + (retval (url-unhex-string (url-filename (url-generic-parse-url uri)))) + ;; Remove the leading "/" for local MS Windows-style paths. + (normalized (if (and (not remote-prefix) + (eq system-type 'windows-nt) + (cl-plusp (length retval))) + (substring retval 1) + retval))) + (concat remote-prefix normalized))) + +(defun eglot--snippet-expansion-fn () + "Compute a function to expand snippets. +Doubles as an indicator of snippet support." + (and (boundp 'yas-minor-mode) + (symbol-value 'yas-minor-mode) + 'yas-expand-snippet)) + +(defun eglot--format-markup (markup) + "Format MARKUP according to LSP's spec." + (pcase-let ((`(,string ,mode) + (if (stringp markup) (list markup 'gfm-view-mode) + (list (plist-get markup :value) + (pcase (plist-get markup :kind) + ("markdown" 'gfm-view-mode) + ("plaintext" 'text-mode) + (_ major-mode)))))) + (with-temp-buffer + (setq-local markdown-fontify-code-blocks-natively t) + (insert string) + (let ((inhibit-message t) + (message-log-max nil)) + (ignore-errors (delay-mode-hooks (funcall mode)))) + (font-lock-ensure) + (string-trim (buffer-string))))) + +(define-obsolete-variable-alias 'eglot-ignored-server-capabilites + 'eglot-ignored-server-capabilities "1.8") + +(defcustom eglot-ignored-server-capabilities (list) + "LSP server capabilities that Eglot could use, but won't. +You could add, for instance, the symbol +`:documentHighlightProvider' to prevent automatic highlighting +under cursor." + :type '(set + :tag "Tick the ones you're not interested in" + (const :tag "Documentation on hover" :hoverProvider) + (const :tag "Code completion" :completionProvider) + (const :tag "Function signature help" :signatureHelpProvider) + (const :tag "Go to definition" :definitionProvider) + (const :tag "Go to type definition" :typeDefinitionProvider) + (const :tag "Go to implementation" :implementationProvider) + (const :tag "Go to declaration" :implementationProvider) + (const :tag "Find references" :referencesProvider) + (const :tag "Highlight symbols automatically" :documentHighlightProvider) + (const :tag "List symbols in buffer" :documentSymbolProvider) + (const :tag "List symbols in workspace" :workspaceSymbolProvider) + (const :tag "Execute code actions" :codeActionProvider) + (const :tag "Code lens" :codeLensProvider) + (const :tag "Format buffer" :documentFormattingProvider) + (const :tag "Format portion of buffer" :documentRangeFormattingProvider) + (const :tag "On-type formatting" :documentOnTypeFormattingProvider) + (const :tag "Rename symbol" :renameProvider) + (const :tag "Highlight links in document" :documentLinkProvider) + (const :tag "Decorate color references" :colorProvider) + (const :tag "Fold regions of buffer" :foldingRangeProvider) + (const :tag "Execute custom commands" :executeCommandProvider))) + +(defun eglot--server-capable (&rest feats) + "Determine if current server is capable of FEATS." + (unless (cl-some (lambda (feat) + (memq feat eglot-ignored-server-capabilities)) + feats) + (cl-loop for caps = (eglot--capabilities (eglot--current-server-or-lose)) + then (cadr probe) + for (feat . more) on feats + for probe = (plist-member caps feat) + if (not probe) do (cl-return nil) + if (eq (cadr probe) :json-false) do (cl-return nil) + if (not (listp (cadr probe))) do (cl-return (if more nil (cadr probe))) + finally (cl-return (or (cadr probe) t))))) + +(defun eglot--range-region (range &optional markers) + "Return region (BEG . END) that represents LSP RANGE. +If optional MARKERS, make markers." + (let* ((st (plist-get range :start)) + (beg (eglot--lsp-position-to-point st markers)) + (end (eglot--lsp-position-to-point (plist-get range :end) markers))) + (cons beg end))) + +(defun eglot--read-server (prompt &optional dont-if-just-the-one) + "Read a running Eglot server from minibuffer using PROMPT. +If DONT-IF-JUST-THE-ONE and there's only one server, don't prompt +and just return it. PROMPT shouldn't end with a question mark." + (let ((servers (cl-loop for servers + being hash-values of eglot--servers-by-project + append servers)) + (name (lambda (srv) + (format "%s/%s" (eglot-project-nickname srv) + (eglot--major-mode srv))))) + (cond ((null servers) + (eglot--error "No servers!")) + ((or (cdr servers) (not dont-if-just-the-one)) + (let* ((default (when-let ((current (eglot-current-server))) + (funcall name current))) + (read (completing-read + (if default + (format "%s (default %s)? " prompt default) + (concat prompt "? ")) + (mapcar name servers) + nil t + nil nil + default))) + (cl-find read servers :key name :test #'equal))) + (t (car servers))))) + +(defun eglot--trampish-p (server) + "Tell if SERVER's project root is `file-remote-p'." + (file-remote-p (project-root (eglot--project server)))) + +(defun eglot--plist-keys (plist) "Get keys of a plist." + (cl-loop for (k _v) on plist by #'cddr collect k)) + + +;;; Minor modes +;;; +(defvar eglot-mode-map + (let ((map (make-sparse-keymap))) + (define-key map [remap display-local-help] #'eldoc-doc-buffer) + map)) + +(defvar-local eglot--current-flymake-report-fn nil + "Current flymake report function for this buffer.") + +(defvar-local eglot--saved-bindings nil + "Bindings saved by `eglot--setq-saving'.") + +(defvar eglot-stay-out-of '() + "List of Emacs things that Eglot should try to stay of. +Each element is a string, a symbol, or a regexp which is matched +against a variable's name. Examples include the string +\"company\" or the symbol `xref'. + +Before Eglot starts \"managing\" a particular buffer, it +opinionatedly sets some peripheral Emacs facilities, such as +Flymake, Xref and Company. These overriding settings help ensure +consistent Eglot behaviour and only stay in place until +\"managing\" stops (usually via `eglot-shutdown'), whereupon the +previous settings are restored. + +However, if you wish for Eglot to stay out of a particular Emacs +facility that you'd like to keep control of add an element to +this list and Eglot will refrain from setting it. + +For example, to keep your Company customization use + +(add-to-list 'eglot-stay-out-of 'company)") + +(defun eglot--stay-out-of-p (symbol) + "Tell if EGLOT should stay of of SYMBOL." + (cl-find (symbol-name symbol) eglot-stay-out-of + :test (lambda (s thing) + (let ((re (if (symbolp thing) (symbol-name thing) thing))) + (string-match re s))))) + +(defmacro eglot--setq-saving (symbol binding) + `(unless (or (not (boundp ',symbol)) (eglot--stay-out-of-p ',symbol)) + (push (cons ',symbol (symbol-value ',symbol)) eglot--saved-bindings) + (setq-local ,symbol ,binding))) + +(defun eglot-managed-p () + "Tell if current buffer is managed by EGLOT." + eglot--managed-mode) + +(defvar eglot-managed-mode-hook nil + "A hook run by EGLOT after it started/stopped managing a buffer. +Use `eglot-managed-p' to determine if current buffer is managed.") + +(define-minor-mode eglot--managed-mode + "Mode for source buffers managed by some EGLOT project." + :init-value nil :lighter nil :keymap eglot-mode-map + (cond + (eglot--managed-mode + (add-hook 'after-change-functions 'eglot--after-change nil t) + (add-hook 'before-change-functions 'eglot--before-change nil t) + (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) + ;; Prepend "didClose" to the hook after the "nonoff", so it will run first + (add-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose nil t) + (add-hook 'before-revert-hook 'eglot--signal-textDocument/didClose nil t) + (add-hook 'after-revert-hook 'eglot--after-revert-hook nil t) + (add-hook 'before-save-hook 'eglot--signal-textDocument/willSave nil t) + (add-hook 'after-save-hook 'eglot--signal-textDocument/didSave nil t) + (unless (eglot--stay-out-of-p 'xref) + (add-hook 'xref-backend-functions 'eglot-xref-backend nil t)) + (add-hook 'completion-at-point-functions #'eglot-completion-at-point nil t) + (add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t) + (add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t) + (add-hook 'pre-command-hook 'eglot--pre-command-hook nil t) + (eglot--setq-saving eldoc-documentation-functions + '(eglot-signature-eldoc-function + eglot-hover-eldoc-function)) + (eglot--setq-saving eldoc-documentation-strategy + #'eldoc-documentation-enthusiast) + (eglot--setq-saving xref-prompt-for-identifier nil) + (eglot--setq-saving flymake-diagnostic-functions '(eglot-flymake-backend)) + (eglot--setq-saving company-backends '(company-capf)) + (eglot--setq-saving company-tooltip-align-annotations t) + (unless (eglot--stay-out-of-p 'imenu) + (add-function :before-until (local 'imenu-create-index-function) + #'eglot-imenu)) + (unless (eglot--stay-out-of-p 'flymake) (flymake-mode 1)) + (unless (eglot--stay-out-of-p 'eldoc) (eldoc-mode 1)) + (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server)))) + (t + (remove-hook 'after-change-functions 'eglot--after-change t) + (remove-hook 'before-change-functions 'eglot--before-change t) + (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t) + (remove-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose t) + (remove-hook 'before-revert-hook 'eglot--signal-textDocument/didClose t) + (remove-hook 'after-revert-hook 'eglot--after-revert-hook t) + (remove-hook 'before-save-hook 'eglot--signal-textDocument/willSave t) + (remove-hook 'after-save-hook 'eglot--signal-textDocument/didSave t) + (remove-hook 'xref-backend-functions 'eglot-xref-backend t) + (remove-hook 'completion-at-point-functions #'eglot-completion-at-point t) + (remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t) + (remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t) + (remove-hook 'pre-command-hook 'eglot--pre-command-hook t) + (cl-loop for (var . saved-binding) in eglot--saved-bindings + do (set (make-local-variable var) saved-binding)) + (remove-function (local 'imenu-create-index-function) #'eglot-imenu) + (when eglot--current-flymake-report-fn + (eglot--report-to-flymake nil) + (setq eglot--current-flymake-report-fn nil)) + (let ((server eglot--cached-server)) + (setq eglot--cached-server nil) + (when server + (setf (eglot--managed-buffers server) + (delq (current-buffer) (eglot--managed-buffers server))) + (when (and eglot-autoshutdown + (null (eglot--managed-buffers server))) + (eglot-shutdown server)))))) + ;; Note: the public hook runs before the internal eglot--managed-mode-hook. + (run-hooks 'eglot-managed-mode-hook)) + +(defun eglot--managed-mode-off () + "Turn off `eglot--managed-mode' unconditionally." + (eglot--managed-mode -1)) + +(defun eglot-current-server () + "Return logical EGLOT server for current buffer, nil if none." + (setq eglot--cached-server + (or eglot--cached-server + (cl-find major-mode + (gethash (eglot--current-project) eglot--servers-by-project) + :key #'eglot--major-mode) + (and eglot-extend-to-xref + buffer-file-name + (gethash (expand-file-name buffer-file-name) + eglot--servers-by-xrefed-file))))) + +(defun eglot--current-server-or-lose () + "Return current logical EGLOT server connection or error." + (or (eglot-current-server) + (jsonrpc-error "No current JSON-RPC connection"))) + +(defvar-local eglot--diagnostics nil + "Flymake diagnostics for this buffer.") + +(defvar revert-buffer-preserve-modes) +(defun eglot--after-revert-hook () + "Eglot's `after-revert-hook'." + (when revert-buffer-preserve-modes (eglot--signal-textDocument/didOpen))) + +(defun eglot--maybe-activate-editing-mode () + "Maybe activate `eglot--managed-mode'. + +If it is activated, also signal textDocument/didOpen." + (unless eglot--managed-mode + ;; Called when `revert-buffer-in-progress-p' is t but + ;; `revert-buffer-preserve-modes' is nil. + (when (and buffer-file-name (eglot-current-server)) + (setq eglot--diagnostics nil) + (eglot--managed-mode) + (eglot--signal-textDocument/didOpen)))) + +(add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode) +(add-hook 'after-change-major-mode-hook 'eglot--maybe-activate-editing-mode) + +(defun eglot-clear-status (server) + "Clear the last JSONRPC error for SERVER." + (interactive (list (eglot--current-server-or-lose))) + (setf (jsonrpc-last-error server) nil)) + + +;;; Mode-line, menu and other sugar +;;; +(defvar eglot--mode-line-format `(:eval (eglot--mode-line-format))) + +(put 'eglot--mode-line-format 'risky-local-variable t) + +(defun eglot--mouse-call (what) + "Make an interactive lambda for calling WHAT from mode-line." + (lambda (event) + (interactive "e") + (let ((start (event-start event))) (with-selected-window (posn-window start) + (save-excursion + (goto-char (or (posn-point start) + (point))) + (call-interactively what) + (force-mode-line-update t)))))) + +(defun eglot-manual () "Open on-line documentation." + (interactive) (browse-url "https://github.com/joaotavora/eglot#readme")) + +(easy-menu-define eglot-menu nil "Eglot" + `("Eglot" + ;; Commands for getting information and customization. + ["Read manual" eglot-manual] + ["Customize Eglot" (lambda () (interactive) (customize-group "eglot"))] + "--" + ;; xref like commands. + ["Find definitions" xref-find-definitions + :help "Find definitions of identifier at point" + :active (eglot--server-capable :definitionProvider)] + ["Find references" xref-find-references + :help "Find references to identifier at point" + :active (eglot--server-capable :referencesProvider)] + ["Find symbols in workspace (apropos)" xref-find-apropos + :help "Find symbols matching a query" + :active (eglot--server-capable :workspaceSymbolProvider)] + ["Find declaration" eglot-find-declaration + :help "Find declaration for identifier at point" + :active (eglot--server-capable :declarationProvider)] + ["Find implementation" eglot-find-implementation + :help "Find implementation for identifier at point" + :active (eglot--server-capable :implementationProvider)] + ["Find type definition" eglot-find-typeDefinition + :help "Find type definition for identifier at point" + :active (eglot--server-capable :typeDefinitionProvider)] + "--" + ;; LSP-related commands (mostly Eglot's own commands). + ["Rename symbol" eglot-rename + :active (eglot--server-capable :renameProvider)] + ["Format buffer" eglot-format-buffer + :active (eglot--server-capable :documentFormattingProvider)] + ["Format active region" eglot-format + :active (and (region-active-p) + (eglot--server-capable :documentRangeFormattingProvider))] + ["Show Flymake diagnostics for buffer" flymake-show-buffer-diagnostics] + ["Show Flymake diagnostics for project" flymake-show-project-diagnostics] + ["Show Eldoc documentation at point" eldoc-doc-buffer] + "--" + ["All possible code actions" eglot-code-actions + :active (eglot--server-capable :codeActionProvider)] + ["Organize imports" eglot-code-action-organize-imports + :visible (eglot--server-capable :codeActionProvider)] + ["Extract" eglot-code-action-extract + :visible (eglot--server-capable :codeActionProvider)] + ["Inline" eglot-code-action-inline + :visible (eglot--server-capable :codeActionProvider)] + ["Rewrite" eglot-code-action-rewrite + :visible (eglot--server-capable :codeActionProvider)] + ["Quickfix" eglot-code-action-quickfix + :visible (eglot--server-capable :codeActionProvider)])) + +(easy-menu-define eglot-server-menu nil "Monitor server communication" + '("Debugging the server communication" + ["Reconnect to server" eglot-reconnect] + ["Quit server" eglot-shutdown] + "--" + ["LSP events buffer" eglot-events-buffer] + ["Server stderr buffer" eglot-stderr-buffer] + ["Customize event buffer size" + (lambda () + (interactive) + (customize-variable 'eglot-events-buffer-size))])) + +(defun eglot--mode-line-props (thing face defs &optional prepend) + "Helper for function `eglot--mode-line-format'. +Uses THING, FACE, DEFS and PREPEND." + (cl-loop with map = (make-sparse-keymap) + for (elem . rest) on defs + for (key def help) = elem + do (define-key map `[mode-line ,key] (eglot--mouse-call def)) + concat (format "%s: %s" key help) into blurb + when rest concat "\n" into blurb + finally (return `(:propertize ,thing + face ,face + keymap ,map help-echo ,(concat prepend blurb) + mouse-face mode-line-highlight)))) + +(defun eglot--mode-line-format () + "Compose the EGLOT's mode-line." + (pcase-let* ((server (eglot-current-server)) + (nick (and server (eglot-project-nickname server))) + (pending (and server (hash-table-count + (jsonrpc--request-continuations server)))) + (`(,_id ,doing ,done-p ,_detail) (and server (eglot--spinner server))) + (last-error (and server (jsonrpc-last-error server)))) + (append + `(,(propertize + eglot-menu-string + 'face 'eglot-mode-line + 'mouse-face 'mode-line-highlight + 'help-echo "Eglot: Emacs LSP client\nmouse-1: Display minor mode menu" + 'keymap (let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] eglot-menu) + map))) + (when nick + `(":" + ,(propertize + nick + 'face 'eglot-mode-line + 'mouse-face 'mode-line-highlight + 'help-echo (format "Project '%s'\nmouse-1: LSP server control menu" nick) + 'keymap (let ((map (make-sparse-keymap))) + (define-key map [mode-line down-mouse-1] eglot-server-menu) + map)) + ,@(when last-error + `("/" ,(eglot--mode-line-props + "error" 'compilation-mode-line-fail + '((mouse-3 eglot-clear-status "Clear this status")) + (format "An error occurred: %s\n" (plist-get last-error + :message))))) + ,@(when (and doing (not done-p)) + `("/" ,(eglot--mode-line-props doing + 'compilation-mode-line-run '()))) + ,@(when (cl-plusp pending) + `("/" ,(eglot--mode-line-props + (format "%d" pending) 'warning + '((mouse-3 eglot-forget-pending-continuations + "Forget pending continuations")) + "Number of outgoing, \ +still unanswered LSP requests to the server\n")))))))) + +(add-to-list 'mode-line-misc-info + `(eglot--managed-mode (" [" eglot--mode-line-format "] "))) + + +;;; Flymake customization +;;; +(put 'eglot-note 'flymake-category 'flymake-note) +(put 'eglot-warning 'flymake-category 'flymake-warning) +(put 'eglot-error 'flymake-category 'flymake-error) + +(defalias 'eglot--make-diag 'flymake-make-diagnostic) +(defalias 'eglot--diag-data 'flymake-diagnostic-data) + +(cl-loop for i from 1 + for type in '(eglot-note eglot-warning eglot-error ) + do (put type 'flymake-overlay-control + `((mouse-face . highlight) + (priority . ,(+ 50 i)) + (keymap . ,(let ((map (make-sparse-keymap))) + (define-key map [mouse-1] + (eglot--mouse-call 'eglot-code-actions)) + map))))) + + +;;; Protocol implementation (Requests, notifications, etc) +;;; +(cl-defmethod eglot-handle-notification + (_server method &key &allow-other-keys) + "Handle unknown notification." + (unless (or (string-prefix-p "$" (format "%s" method)) + (not (memq 'disallow-unknown-methods eglot-strict-mode))) + (eglot--warn "Server sent unknown notification method `%s'" method))) + +(cl-defmethod eglot-handle-request + (_server method &key &allow-other-keys) + "Handle unknown request." + (when (memq 'disallow-unknown-methods eglot-strict-mode) + (jsonrpc-error "Unknown request method `%s'" method))) + +(cl-defmethod eglot-execute-command + (server command arguments) + "Execute COMMAND on SERVER with `:workspace/executeCommand'. +COMMAND is a symbol naming the command." + (jsonrpc-request server :workspace/executeCommand + `(:command ,(format "%s" command) :arguments ,arguments))) + +(cl-defmethod eglot-handle-notification + (_server (_method (eql window/showMessage)) &key type message) + "Handle notification window/showMessage." + (eglot--message (propertize "Server reports (type=%s): %s" + 'face (if (<= type 1) 'error)) + type message)) + +(cl-defmethod eglot-handle-request + (_server (_method (eql window/showMessageRequest)) &key type message actions) + "Handle server request window/showMessageRequest." + (let* ((actions (append actions nil)) ;; gh#627 + (label (completing-read + (concat + (format (propertize "[eglot] Server reports (type=%s): %s" + 'face (if (<= type 1) 'error)) + type message) + "\nChoose an option: ") + (or (mapcar (lambda (obj) (plist-get obj :title)) actions) + '("OK")) + nil t (plist-get (elt actions 0) :title)))) + (if label `(:title ,label) :null))) + +(cl-defmethod eglot-handle-notification + (_server (_method (eql window/logMessage)) &key _type _message) + "Handle notification window/logMessage.") ;; noop, use events buffer + +(cl-defmethod eglot-handle-notification + (_server (_method (eql telemetry/event)) &rest _any) + "Handle notification telemetry/event.") ;; noop, use events buffer + +(cl-defmethod eglot-handle-notification + (_server (_method (eql textDocument/publishDiagnostics)) &key uri diagnostics + &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode' + "Handle notification publishDiagnostics." + (cl-flet ((eglot--diag-type (sev) + (cond ((null sev) 'eglot-error) + ((<= sev 1) 'eglot-error) + ((= sev 2) 'eglot-warning) + (t 'eglot-note))) + (mess (source code message) + (concat source (and code (format " [%s]" code)) ": " message))) + (if-let ((buffer (find-buffer-visiting (eglot--uri-to-path uri)))) + (with-current-buffer buffer + (cl-loop + for diag-spec across diagnostics + collect (eglot--dbind ((Diagnostic) range code message severity source tags) + diag-spec + (setq message (mess source code message)) + (pcase-let + ((`(,beg . ,end) (eglot--range-region range))) + ;; Fallback to `flymake-diag-region' if server + ;; botched the range + (when (= beg end) + (if-let* ((st (plist-get range :start)) + (diag-region + (flymake-diag-region + (current-buffer) (1+ (plist-get st :line)) + (plist-get st :character)))) + (setq beg (car diag-region) end (cdr diag-region)) + (eglot--widening + (goto-char (point-min)) + (setq beg + (point-at-bol + (1+ (plist-get (plist-get range :start) :line)))) + (setq end + (point-at-eol + (1+ (plist-get (plist-get range :end) :line))))))) + (eglot--make-diag + (current-buffer) beg end + (eglot--diag-type severity) + message `((eglot-lsp-diag . ,diag-spec)) + (when-let ((faces + (cl-loop for tag across tags + when (alist-get tag eglot--tag-faces) + collect it))) + `((face . ,faces)))))) + into diags + finally (cond (eglot--current-flymake-report-fn + (eglot--report-to-flymake diags)) + (t + (setq eglot--diagnostics diags))))) + (cl-loop + with path = (expand-file-name (eglot--uri-to-path uri)) + for diag-spec across diagnostics + collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec + (setq message (mess source code message)) + (let* ((start (plist-get range :start)) + (line (1+ (plist-get start :line))) + (char (1+ (plist-get start :character)))) + (eglot--make-diag + path (cons line char) nil (eglot--diag-type severity) message))) + into diags + finally + (setq flymake-list-only-diagnostics + (assoc-delete-all path flymake-list-only-diagnostics #'string=)) + (push (cons path diags) flymake-list-only-diagnostics))))) + +(cl-defun eglot--register-unregister (server things how) + "Helper for `registerCapability'. +THINGS are either registrations or unregisterations (sic)." + (cl-loop + for thing in (cl-coerce things 'list) + do (eglot--dbind ((Registration) id method registerOptions) thing + (apply (cl-ecase how + (register 'eglot-register-capability) + (unregister 'eglot-unregister-capability)) + server (intern method) id registerOptions)))) + +(cl-defmethod eglot-handle-request + (server (_method (eql client/registerCapability)) &key registrations) + "Handle server request client/registerCapability." + (eglot--register-unregister server registrations 'register)) + +(cl-defmethod eglot-handle-request + (server (_method (eql client/unregisterCapability)) + &key unregisterations) ;; XXX: "unregisterations" (sic) + "Handle server request client/unregisterCapability." + (eglot--register-unregister server unregisterations 'unregister)) + +(cl-defmethod eglot-handle-request + (_server (_method (eql workspace/applyEdit)) &key _label edit) + "Handle server request workspace/applyEdit." + (eglot--apply-workspace-edit edit eglot-confirm-server-initiated-edits)) + +(cl-defmethod eglot-handle-request + (server (_method (eql workspace/workspaceFolders))) + "Handle server request workspace/workspaceFolders." + (eglot-workspace-folders server)) + +(defun eglot--TextDocumentIdentifier () + "Compute TextDocumentIdentifier object for current buffer." + `(:uri ,(eglot--path-to-uri (or buffer-file-name + (ignore-errors + (buffer-file-name + (buffer-base-buffer))))))) + +(defvar-local eglot--versioned-identifier 0) + +(defun eglot--VersionedTextDocumentIdentifier () + "Compute VersionedTextDocumentIdentifier object for current buffer." + (append (eglot--TextDocumentIdentifier) + `(:version ,eglot--versioned-identifier))) + +(defun eglot--TextDocumentItem () + "Compute TextDocumentItem object for current buffer." + (append + (eglot--VersionedTextDocumentIdentifier) + (list :languageId + (eglot--language-id (eglot--current-server-or-lose)) + :text + (eglot--widening + (buffer-substring-no-properties (point-min) (point-max)))))) + +(defun eglot--TextDocumentPositionParams () + "Compute TextDocumentPositionParams." + (list :textDocument (eglot--TextDocumentIdentifier) + :position (eglot--pos-to-lsp-position))) + +(defvar-local eglot--last-inserted-char nil + "If non-nil, value of the last inserted character in buffer.") + +(defun eglot--post-self-insert-hook () + "Set `eglot--last-inserted-char', maybe call on-type-formatting." + (setq eglot--last-inserted-char last-input-event) + (let ((ot-provider (eglot--server-capable :documentOnTypeFormattingProvider))) + (when (and ot-provider + (ignore-errors ; github#906, some LS's send empty strings + (or (eq last-input-event + (seq-first (plist-get ot-provider :firstTriggerCharacter))) + (cl-find last-input-event + (plist-get ot-provider :moreTriggerCharacter) + :key #'seq-first)))) + (eglot-format (point) nil last-input-event)))) + +(defun eglot--pre-command-hook () + "Reset `eglot--last-inserted-char'." + (setq eglot--last-inserted-char nil)) + +(defun eglot--CompletionParams () + (append + (eglot--TextDocumentPositionParams) + `(:context + ,(if-let (trigger (and (characterp eglot--last-inserted-char) + (cl-find eglot--last-inserted-char + (eglot--server-capable :completionProvider + :triggerCharacters) + :key (lambda (str) (aref str 0)) + :test #'char-equal))) + `(:triggerKind 2 :triggerCharacter ,trigger) `(:triggerKind 1))))) + +(defvar-local eglot--recent-changes nil + "Recent buffer changes as collected by `eglot--before-change'.") + +(cl-defmethod jsonrpc-connection-ready-p ((_server eglot-lsp-server) _what) + "Tell if SERVER is ready for WHAT in current buffer." + (and (cl-call-next-method) (not eglot--recent-changes))) + +(defvar-local eglot--change-idle-timer nil "Idle timer for didChange signals.") + +(defun eglot--before-change (beg end) + "Hook onto `before-change-functions' with BEG and END." + (when (listp eglot--recent-changes) + ;; Records BEG and END, crucially convert them into LSP + ;; (line/char) positions before that information is lost (because + ;; the after-change thingy doesn't know if newlines were + ;; deleted/added). Also record markers of BEG and END + ;; (github#259) + (push `(,(eglot--pos-to-lsp-position beg) + ,(eglot--pos-to-lsp-position end) + (,beg . ,(copy-marker beg nil)) + (,end . ,(copy-marker end t))) + eglot--recent-changes))) + +(defun eglot--after-change (beg end pre-change-length) + "Hook onto `after-change-functions'. +Records BEG, END and PRE-CHANGE-LENGTH locally." + (cl-incf eglot--versioned-identifier) + (pcase (and (listp eglot--recent-changes) + (car eglot--recent-changes)) + (`(,lsp-beg ,lsp-end + (,b-beg . ,b-beg-marker) + (,b-end . ,b-end-marker)) + ;; github#259 and github#367: With `capitalize-word' or somesuch, + ;; `before-change-functions' always records the whole word's + ;; `b-beg' and `b-end'. Similarly, when coalescing two lines + ;; into one, `fill-paragraph' they mark the end of the first line + ;; up to the end of the second line. In both situations, args + ;; received here contradict that information: `beg' and `end' + ;; will differ by 1 and will likely only encompass the letter + ;; that was capitalized or, in the sentence-joining situation, + ;; the replacement of the newline with a space. That's we keep + ;; markers _and_ positions so we're able to detect and correct + ;; this. We ignore `beg', `len' and `pre-change-len' and send + ;; "fuller" information about the region from the markers. I've + ;; also experimented with doing this unconditionally but it seems + ;; to break when newlines are added. + (if (and (= b-end b-end-marker) (= b-beg b-beg-marker) + (or (/= beg b-beg) (/= end b-end))) + (setcar eglot--recent-changes + `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker) + ,(buffer-substring-no-properties b-beg-marker + b-end-marker))) + (setcar eglot--recent-changes + `(,lsp-beg ,lsp-end ,pre-change-length + ,(buffer-substring-no-properties beg end))))) + (_ (setf eglot--recent-changes :emacs-messup))) + (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) + (let ((buf (current-buffer))) + (setq eglot--change-idle-timer + (run-with-idle-timer + eglot-send-changes-idle-time + nil (lambda () (eglot--when-live-buffer buf + (when eglot--managed-mode + (eglot--signal-textDocument/didChange) + (setq eglot--change-idle-timer nil)))))))) + +;; HACK! Launching a deferred sync request with outstanding changes is a +;; bad idea, since that might lead to the request never having a +;; chance to run, because `jsonrpc-connection-ready-p'. +(advice-add #'jsonrpc-request :before + (cl-function (lambda (_proc _method _params &key + deferred &allow-other-keys) + (when (and eglot--managed-mode deferred) + (eglot--signal-textDocument/didChange)))) + '((name . eglot--signal-textDocument/didChange))) + +(defvar-local eglot-workspace-configuration () + "Alist of (SECTION . VALUE) entries configuring the LSP server. +SECTION should be a keyword or a string, value can be anything +that can be converted to JSON.") + +;;;###autoload +(put 'eglot-workspace-configuration 'safe-local-variable 'listp) + +(defun eglot-signal-didChangeConfiguration (server) + "Send a `:workspace/didChangeConfiguration' signal to SERVER. +When called interactively, use the currently active server" + (interactive (list (eglot--current-server-or-lose))) + (jsonrpc-notify + server :workspace/didChangeConfiguration + (list + :settings + (or (cl-loop for (section . v) in eglot-workspace-configuration + collect (if (keywordp section) + section + (intern (format ":%s" section))) + collect v) + eglot--{})))) + +(cl-defmethod eglot-handle-request + (server (_method (eql workspace/configuration)) &key items) + "Handle server request workspace/configuration." + (apply #'vector + (mapcar + (eglot--lambda ((ConfigurationItem) scopeUri section) + (with-temp-buffer + (let* ((uri-path (eglot--uri-to-path scopeUri)) + (default-directory + (if (and (not (string-empty-p uri-path)) + (file-directory-p uri-path)) + (file-name-as-directory uri-path) + (project-root (eglot--project server))))) + (setq-local major-mode (eglot--major-mode server)) + (hack-dir-local-variables-non-file-buffer) + (alist-get section eglot-workspace-configuration + nil nil + (lambda (wsection section) + (string= + (if (keywordp wsection) + (substring (symbol-name wsection) 1) + wsection) + section)))))) + items))) + +(defun eglot--signal-textDocument/didChange () + "Send textDocument/didChange to server." + (when eglot--recent-changes + (let* ((server (eglot--current-server-or-lose)) + (sync-capability (eglot--server-capable :textDocumentSync)) + (sync-kind (if (numberp sync-capability) sync-capability + (plist-get sync-capability :change))) + (full-sync-p (or (eq sync-kind 1) + (eq :emacs-messup eglot--recent-changes)))) + (jsonrpc-notify + server :textDocument/didChange + (list + :textDocument (eglot--VersionedTextDocumentIdentifier) + :contentChanges + (if full-sync-p + (vector `(:text ,(eglot--widening + (buffer-substring-no-properties (point-min) + (point-max))))) + (cl-loop for (beg end len text) in (reverse eglot--recent-changes) + ;; github#259: `capitalize-word' and commands based + ;; on `casify_region' will cause multiple duplicate + ;; empty entries in `eglot--before-change' calls + ;; without an `eglot--after-change' reciprocal. + ;; Weed them out here. + when (numberp len) + vconcat `[,(list :range `(:start ,beg :end ,end) + :rangeLength len :text text)])))) + (setq eglot--recent-changes nil) + (setf (eglot--spinner server) (list nil :textDocument/didChange t)) + (jsonrpc--call-deferred server)))) + +(defun eglot--signal-textDocument/didOpen () + "Send textDocument/didOpen to server." + (setq eglot--recent-changes nil eglot--versioned-identifier 0) + (jsonrpc-notify + (eglot--current-server-or-lose) + :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem)))) + +(defun eglot--signal-textDocument/didClose () + "Send textDocument/didClose to server." + (with-demoted-errors + "[eglot] error sending textDocument/didClose: %s" + (jsonrpc-notify + (eglot--current-server-or-lose) + :textDocument/didClose `(:textDocument ,(eglot--TextDocumentIdentifier))))) + +(defun eglot--signal-textDocument/willSave () + "Send textDocument/willSave to server." + (let ((server (eglot--current-server-or-lose)) + (params `(:reason 1 :textDocument ,(eglot--TextDocumentIdentifier)))) + (when (eglot--server-capable :textDocumentSync :willSave) + (jsonrpc-notify server :textDocument/willSave params)) + (when (eglot--server-capable :textDocumentSync :willSaveWaitUntil) + (ignore-errors + (eglot--apply-text-edits + (jsonrpc-request server :textDocument/willSaveWaitUntil params + :timeout 0.5)))))) + +(defun eglot--signal-textDocument/didSave () + "Send textDocument/didSave to server." + (eglot--signal-textDocument/didChange) + (jsonrpc-notify + (eglot--current-server-or-lose) + :textDocument/didSave + (list + ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this. + :text (buffer-substring-no-properties (point-min) (point-max)) + :textDocument (eglot--TextDocumentIdentifier)))) + +(defun eglot-flymake-backend (report-fn &rest _more) + "A Flymake backend for Eglot. +Calls REPORT-FN (or arranges for it to be called) when the server +publishes diagnostics. Between calls to this function, REPORT-FN +may be called multiple times (respecting the protocol of +`flymake-backend-functions')." + (cond (eglot--managed-mode + (setq eglot--current-flymake-report-fn report-fn) + (eglot--report-to-flymake eglot--diagnostics)) + (t + (funcall report-fn nil)))) + +(defun eglot--report-to-flymake (diags) + "Internal helper for `eglot-flymake-backend'." + (save-restriction + (widen) + (funcall eglot--current-flymake-report-fn diags + ;; If the buffer hasn't changed since last + ;; call to the report function, flymake won't + ;; delete old diagnostics. Using :region + ;; keyword forces flymake to delete + ;; them (github#159). + :region (cons (point-min) (point-max)))) + (setq eglot--diagnostics diags)) + +(defun eglot-xref-backend () "EGLOT xref backend." 'eglot) + +(defvar eglot--temp-location-buffers (make-hash-table :test #'equal) + "Helper variable for `eglot--handling-xrefs'.") + +(defvar eglot-xref-lessp-function #'ignore + "Compare two `xref-item' objects for sorting.") + +(cl-defmacro eglot--collecting-xrefs ((collector) &rest body) + "Sort and handle xrefs collected with COLLECTOR in BODY." + (declare (indent 1) (debug (sexp &rest form))) + (let ((collected (cl-gensym "collected"))) + `(unwind-protect + (let (,collected) + (cl-flet ((,collector (xref) (push xref ,collected))) + ,@body) + (setq ,collected (nreverse ,collected)) + (sort ,collected eglot-xref-lessp-function)) + (maphash (lambda (_uri buf) (kill-buffer buf)) eglot--temp-location-buffers) + (clrhash eglot--temp-location-buffers)))) + +(defun eglot--xref-make-match (name uri range) + "Like `xref-make-match' but with LSP's NAME, URI and RANGE. +Try to visit the target file for a richer summary line." + (pcase-let* + ((file (eglot--uri-to-path uri)) + (visiting (or (find-buffer-visiting file) + (gethash uri eglot--temp-location-buffers))) + (collect (lambda () + (eglot--widening + (pcase-let* ((`(,beg . ,end) (eglot--range-region range)) + (bol (progn (goto-char beg) (point-at-bol))) + (substring (buffer-substring bol (point-at-eol))) + (hi-beg (- beg bol)) + (hi-end (- (min (point-at-eol) end) bol))) + (add-face-text-property hi-beg hi-end 'xref-match + t substring) + (list substring (1+ (current-line)) (eglot-current-column) + (- end beg)))))) + (`(,summary ,line ,column ,length) + (cond + (visiting (with-current-buffer visiting (funcall collect))) + ((file-readable-p file) (with-current-buffer + (puthash uri (generate-new-buffer " *temp*") + eglot--temp-location-buffers) + (insert-file-contents file) + (funcall collect))) + (t ;; fall back to the "dumb strategy" + (let* ((start (cl-getf range :start)) + (line (1+ (cl-getf start :line))) + (start-pos (cl-getf start :character)) + (end-pos (cl-getf (cl-getf range :end) :character))) + (list name line start-pos (- end-pos start-pos))))))) + (setf (gethash (expand-file-name file) eglot--servers-by-xrefed-file) + (eglot--current-server-or-lose)) + (xref-make-match summary (xref-make-file-location file line column) length))) + +(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql eglot))) + (eglot--error "Cannot (yet) provide reliable completion table for LSP symbols")) + +(cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot))) + ;; JT@19/10/09: This is a totally dummy identifier that isn't even + ;; passed to LSP. The reason for this particular wording is to + ;; construct a readable message "No references for LSP identifier at + ;; point.". See https://github.com/joaotavora/eglot/issues/314 + "LSP identifier at point.") + +(defvar eglot--lsp-xref-refs nil + "`xref' objects for overriding `xref-backend-references''s.") + +(cl-defun eglot--lsp-xrefs-for-method (method &key extra-params capability) + "Make `xref''s for METHOD, EXTRA-PARAMS, check CAPABILITY." + (unless (eglot--server-capable + (or capability + (intern + (format ":%sProvider" + (cadr (split-string (symbol-name method) + "/")))))) + (eglot--error "Sorry, this server doesn't do %s" method)) + (let ((response + (jsonrpc-request + (eglot--current-server-or-lose) + method (append (eglot--TextDocumentPositionParams) extra-params)))) + (eglot--collecting-xrefs (collect) + (mapc + (lambda (loc-or-loc-link) + (let ((sym-name (symbol-name (symbol-at-point)))) + (eglot--dcase loc-or-loc-link + (((LocationLink) targetUri targetSelectionRange) + (collect (eglot--xref-make-match sym-name + targetUri targetSelectionRange))) + (((Location) uri range) + (collect (eglot--xref-make-match sym-name + uri range)))))) + (if (vectorp response) response (and response (list response))))))) + +(cl-defun eglot--lsp-xref-helper (method &key extra-params capability ) + "Helper for `eglot-find-declaration' & friends." + (let ((eglot--lsp-xref-refs (eglot--lsp-xrefs-for-method + method + :extra-params extra-params + :capability capability))) + (if eglot--lsp-xref-refs + (xref-find-references "LSP identifier at point.") + (eglot--message "%s returned no references" method)))) + +(defun eglot-find-declaration () + "Find declaration for SYM, the identifier at point." + (interactive) + (eglot--lsp-xref-helper :textDocument/declaration)) + +(defun eglot-find-implementation () + "Find implementation for SYM, the identifier at point." + (interactive) + (eglot--lsp-xref-helper :textDocument/implementation)) + +(defun eglot-find-typeDefinition () + "Find type definition for SYM, the identifier at point." + (interactive) + (eglot--lsp-xref-helper :textDocument/typeDefinition)) + +(cl-defmethod xref-backend-definitions ((_backend (eql eglot)) _identifier) + (eglot--lsp-xrefs-for-method :textDocument/definition)) + +(cl-defmethod xref-backend-references ((_backend (eql eglot)) _identifier) + (or + eglot--lsp-xref-refs + (eglot--lsp-xrefs-for-method + :textDocument/references :extra-params `(:context (:includeDeclaration t))))) + +(cl-defmethod xref-backend-apropos ((_backend (eql eglot)) pattern) + (when (eglot--server-capable :workspaceSymbolProvider) + (eglot--collecting-xrefs (collect) + (mapc + (eglot--lambda ((SymbolInformation) name location) + (eglot--dbind ((Location) uri range) location + (collect (eglot--xref-make-match name uri range)))) + (jsonrpc-request (eglot--current-server-or-lose) + :workspace/symbol + `(:query ,pattern)))))) + +(defun eglot-format-buffer () + "Format contents of current buffer." + (interactive) + (eglot-format nil nil)) + +(defun eglot-format (&optional beg end on-type-format) + "Format region BEG END. +If either BEG or END is nil, format entire buffer. +Interactively, format active region, or entire buffer if region +is not active. + +If non-nil, ON-TYPE-FORMAT is a character just inserted at BEG +for which LSP on-type-formatting should be requested." + (interactive (and (region-active-p) (list (region-beginning) (region-end)))) + (pcase-let ((`(,method ,cap ,args) + (cond + ((and beg on-type-format) + `(:textDocument/onTypeFormatting + :documentOnTypeFormattingProvider + ,`(:position ,(eglot--pos-to-lsp-position beg) + :ch ,(string on-type-format)))) + ((and beg end) + `(:textDocument/rangeFormatting + :documentRangeFormattingProvider + (:range ,(list :start (eglot--pos-to-lsp-position beg) + :end (eglot--pos-to-lsp-position end))))) + (t + '(:textDocument/formatting :documentFormattingProvider nil))))) + (unless (eglot--server-capable cap) + (eglot--error "Server can't format!")) + (eglot--apply-text-edits + (jsonrpc-request + (eglot--current-server-or-lose) + method + (cl-list* + :textDocument (eglot--TextDocumentIdentifier) + :options (list :tabSize tab-width + :insertSpaces (if indent-tabs-mode :json-false t) + :insertFinalNewline (if require-final-newline t :json-false) + :trimFinalNewlines (if delete-trailing-lines t :json-false)) + args) + :deferred method)))) + +(defun eglot-completion-at-point () + "EGLOT's `completion-at-point' function." + ;; Commit logs for this function help understand what's going on. + (when-let (completion-capability (eglot--server-capable :completionProvider)) + (let* ((server (eglot--current-server-or-lose)) + (sort-completions + (lambda (completions) + (cl-sort completions + #'string-lessp + :key (lambda (c) + (or (plist-get + (get-text-property 0 'eglot--lsp-item c) + :sortText) + ""))))) + (metadata `(metadata (category . eglot) + (display-sort-function . ,sort-completions))) + resp items (cached-proxies :none) + (proxies + (lambda () + (if (listp cached-proxies) cached-proxies + (setq resp + (jsonrpc-request server + :textDocument/completion + (eglot--CompletionParams) + :deferred :textDocument/completion + :cancel-on-input t)) + (setq items (append + (if (vectorp resp) resp (plist-get resp :items)) + nil)) + (setq cached-proxies + (mapcar + (jsonrpc-lambda + (&rest item &key label insertText insertTextFormat + &allow-other-keys) + (let ((proxy + (cond ((and (eql insertTextFormat 2) + (eglot--snippet-expansion-fn)) + (string-trim-left label)) + ((and insertText + (not (string-empty-p insertText))) + insertText) + (t + (string-trim-left label))))) + (unless (zerop (length proxy)) + (put-text-property 0 1 'eglot--lsp-item item proxy)) + proxy)) + items))))) + (resolved (make-hash-table)) + (resolve-maybe + ;; Maybe completion/resolve JSON object `lsp-comp' into + ;; another JSON object, if at all possible. Otherwise, + ;; just return lsp-comp. + (lambda (lsp-comp) + (or (gethash lsp-comp resolved) + (setf (gethash lsp-comp resolved) + (if (and (eglot--server-capable :completionProvider + :resolveProvider) + (plist-get lsp-comp :data)) + (jsonrpc-request server :completionItem/resolve + lsp-comp :cancel-on-input t) + lsp-comp))))) + (bounds (bounds-of-thing-at-point 'symbol))) + (list + (or (car bounds) (point)) + (or (cdr bounds) (point)) + (lambda (probe pred action) + (cond + ((eq action 'metadata) metadata) ; metadata + ((eq action 'lambda) ; test-completion + (test-completion probe (funcall proxies))) + ((eq (car-safe action) 'boundaries) nil) ; boundaries + ((null action) ; try-completion + (try-completion probe (funcall proxies))) + ((eq action t) ; all-completions + (all-completions + "" + (funcall proxies) + (lambda (proxy) + (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) + (filterText (plist-get item :filterText))) + (and (or (null pred) (funcall pred proxy)) + (string-prefix-p + probe (or filterText proxy) completion-ignore-case)))))))) + :annotation-function + (lambda (proxy) + (eglot--dbind ((CompletionItem) detail kind) + (get-text-property 0 'eglot--lsp-item proxy) + (let* ((detail (and (stringp detail) + (not (string= detail "")) + detail)) + (annotation + (or detail + (cdr (assoc kind eglot--kind-names))))) + (when annotation + (concat " " + (propertize annotation + 'face 'font-lock-function-name-face)))))) + :company-kind + ;; Associate each lsp-item with a lsp-kind symbol. + (lambda (proxy) + (when-let* ((lsp-item (get-text-property 0 'eglot--lsp-item proxy)) + (kind (alist-get (plist-get lsp-item :kind) + eglot--kind-names))) + (intern (downcase kind)))) + :company-deprecated + (lambda (proxy) + (when-let ((lsp-item (get-text-property 0 'eglot--lsp-item proxy))) + (or (seq-contains-p (plist-get lsp-item :tags) + 1) + (eq t (plist-get lsp-item :deprecated))))) + :company-docsig + ;; FIXME: autoImportText is specific to the pyright language server + (lambda (proxy) + (when-let* ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy)) + (data (plist-get (funcall resolve-maybe lsp-comp) :data)) + (import-text (plist-get data :autoImportText))) + import-text)) + :company-doc-buffer + (lambda (proxy) + (let* ((documentation + (let ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy))) + (plist-get (funcall resolve-maybe lsp-comp) :documentation))) + (formatted (and documentation + (eglot--format-markup documentation)))) + (when formatted + (with-current-buffer (get-buffer-create " *eglot doc*") + (erase-buffer) + (insert formatted) + (current-buffer))))) + :company-require-match 'never + :company-prefix-length + (save-excursion + (when (car bounds) (goto-char (car bounds))) + (when (listp completion-capability) + (looking-back + (regexp-opt + (cl-coerce (cl-getf completion-capability :triggerCharacters) 'list)) + (line-beginning-position)))) + :exit-function + (lambda (proxy status) + (when (memq status '(finished exact)) + ;; To assist in using this whole `completion-at-point' + ;; function inside `completion-in-region', ensure the exit + ;; function runs in the buffer where the completion was + ;; triggered from. This should probably be in Emacs itself. + ;; (github#505) + (with-current-buffer (if (minibufferp) + (window-buffer (minibuffer-selected-window)) + (current-buffer)) + (eglot--dbind ((CompletionItem) insertTextFormat + insertText textEdit additionalTextEdits label) + (funcall + resolve-maybe + (or (get-text-property 0 'eglot--lsp-item proxy) + ;; When selecting from the *Completions* + ;; buffer, `proxy' won't have any properties. + ;; A lookup should fix that (github#148) + (get-text-property + 0 'eglot--lsp-item + (cl-find proxy (funcall proxies) :test #'string=)))) + (let ((snippet-fn (and (eql insertTextFormat 2) + (eglot--snippet-expansion-fn)))) + (cond (textEdit + ;; Undo (yes, undo) the newly inserted completion. + ;; If before completion the buffer was "foo.b" and + ;; now is "foo.bar", `proxy' will be "bar". We + ;; want to delete only "ar" (`proxy' minus the + ;; symbol whose bounds we've calculated before) + ;; (github#160). + (delete-region (+ (- (point) (length proxy)) + (if bounds + (- (cdr bounds) (car bounds)) + 0)) + (point)) + (eglot--dbind ((TextEdit) range newText) textEdit + (pcase-let ((`(,beg . ,end) + (eglot--range-region range))) + (delete-region beg end) + (goto-char beg) + (funcall (or snippet-fn #'insert) newText))) + (when (cl-plusp (length additionalTextEdits)) + (eglot--apply-text-edits additionalTextEdits))) + (snippet-fn + ;; A snippet should be inserted, but using plain + ;; `insertText'. This requires us to delete the + ;; whole completion, since `insertText' is the full + ;; completion's text. + (delete-region (- (point) (length proxy)) (point)) + (funcall snippet-fn (or insertText label))))) + (eglot--signal-textDocument/didChange) + (eldoc))))))))) + +(defun eglot--hover-info (contents &optional range) + (let ((heading (and range (pcase-let ((`(,beg . ,end) (eglot--range-region range))) + (concat (buffer-substring beg end) ": ")))) + (body (mapconcat #'eglot--format-markup + (if (vectorp contents) contents (list contents)) "\n"))) + (when (or heading (cl-plusp (length body))) (concat heading body)))) + +(defun eglot--sig-info (sigs active-sig sig-help-active-param) + (cl-loop + for (sig . moresigs) on (append sigs nil) for i from 0 + concat + (eglot--dbind ((SignatureInformation) label documentation parameters activeParameter) sig + (with-temp-buffer + (save-excursion (insert label)) + (let ((active-param (or activeParameter sig-help-active-param)) + params-start params-end) + ;; Ad-hoc attempt to parse label as () + (when (looking-at "\\([^(]+\\)(\\([^)]+\\))") + (setq params-start (match-beginning 2) params-end (match-end 2)) + (add-face-text-property (match-beginning 1) (match-end 1) + 'font-lock-function-name-face)) + (when (eql i active-sig) + ;; Decide whether to add one-line-summary to signature line + (when (and (stringp documentation) + (string-match "[[:space:]]*\\([^.\r\n]+[.]?\\)" + documentation)) + (setq documentation (match-string 1 documentation)) + (unless (string-prefix-p (string-trim documentation) label) + (goto-char (point-max)) + (insert ": " (eglot--format-markup documentation)))) + ;; Decide what to do with the active parameter... + (when (and (eql i active-sig) active-param + (< -1 active-param (length parameters))) + (eglot--dbind ((ParameterInformation) label documentation) + (aref parameters active-param) + ;; ...perhaps highlight it in the formals list + (when params-start + (goto-char params-start) + (pcase-let + ((`(,beg ,end) + (if (stringp label) + (let ((case-fold-search nil)) + (and (re-search-forward + (concat "\\<" (regexp-quote label) "\\>") + params-end t) + (list (match-beginning 0) (match-end 0)))) + (mapcar #'1+ (append label nil))))) + (if (and beg end) + (add-face-text-property + beg end + 'eldoc-highlight-function-argument)))) + ;; ...and/or maybe add its doc on a line by its own. + (when documentation + (goto-char (point-max)) + (insert "\n" + (propertize + (if (stringp label) + label + (apply #'buffer-substring (mapcar #'1+ label))) + 'face 'eldoc-highlight-function-argument) + ": " (eglot--format-markup documentation)))))) + (buffer-string)))) + when moresigs concat "\n")) + +(defun eglot-signature-eldoc-function (cb) + "A member of `eldoc-documentation-functions', for signatures." + (when (eglot--server-capable :signatureHelpProvider) + (let ((buf (current-buffer))) + (jsonrpc-async-request + (eglot--current-server-or-lose) + :textDocument/signatureHelp (eglot--TextDocumentPositionParams) + :success-fn + (eglot--lambda ((SignatureHelp) + signatures activeSignature activeParameter) + (eglot--when-buffer-window buf + (funcall cb + (unless (seq-empty-p signatures) + (eglot--sig-info signatures + activeSignature + activeParameter))))) + :deferred :textDocument/signatureHelp)) + t)) + +(defun eglot-hover-eldoc-function (cb) + "A member of `eldoc-documentation-functions', for hover." + (when (eglot--server-capable :hoverProvider) + (let ((buf (current-buffer))) + (jsonrpc-async-request + (eglot--current-server-or-lose) + :textDocument/hover (eglot--TextDocumentPositionParams) + :success-fn (eglot--lambda ((Hover) contents range) + (eglot--when-buffer-window buf + (let ((info (unless (seq-empty-p contents) + (eglot--hover-info contents range)))) + (funcall cb info :buffer t)))) + :deferred :textDocument/hover)) + (eglot--highlight-piggyback cb) + t)) + +(defvar eglot--highlights nil "Overlays for textDocument/documentHighlight.") + +(defun eglot--highlight-piggyback (_cb) + "Request and handle `:textDocument/documentHighlight'." + ;; FIXME: Obviously, this is just piggy backing on eldoc's calls for + ;; convenience, as shown by the fact that we just ignore cb. + (let ((buf (current-buffer))) + (when (eglot--server-capable :documentHighlightProvider) + (jsonrpc-async-request + (eglot--current-server-or-lose) + :textDocument/documentHighlight (eglot--TextDocumentPositionParams) + :success-fn + (lambda (highlights) + (mapc #'delete-overlay eglot--highlights) + (setq eglot--highlights + (eglot--when-buffer-window buf + (mapcar + (eglot--lambda ((DocumentHighlight) range) + (pcase-let ((`(,beg . ,end) + (eglot--range-region range))) + (let ((ov (make-overlay beg end))) + (overlay-put ov 'face 'eglot-highlight-symbol-face) + (overlay-put ov 'modification-hooks + `(,(lambda (o &rest _) (delete-overlay o)))) + ov))) + highlights)))) + :deferred :textDocument/documentHighlight) + nil))) + +(defun eglot-imenu () + "EGLOT's `imenu-create-index-function'." + (cl-labels + ((visit (_name one-obj-array) + (imenu-default-goto-function + nil (car (eglot--range-region + (eglot--dcase (aref one-obj-array 0) + (((SymbolInformation) location) + (plist-get location :range)) + (((DocumentSymbol) selectionRange) + selectionRange)))))) + (unfurl (obj) + (eglot--dcase obj + (((SymbolInformation)) (list obj)) + (((DocumentSymbol) name children) + (cons obj + (mapcar + (lambda (c) + (plist-put + c :containerName + (let ((existing (plist-get c :containerName))) + (if existing (format "%s::%s" name existing) + name)))) + (mapcan #'unfurl children))))))) + (mapcar + (pcase-lambda (`(,kind . ,objs)) + (cons + (alist-get kind eglot--symbol-kind-names "Unknown") + (mapcan (pcase-lambda (`(,container . ,objs)) + (let ((elems (mapcar (lambda (obj) + (list (plist-get obj :name) + `[,obj] ;; trick + #'visit)) + objs))) + (if container (list (cons container elems)) elems))) + (seq-group-by + (lambda (e) (plist-get e :containerName)) objs)))) + (seq-group-by + (lambda (obj) (plist-get obj :kind)) + (mapcan #'unfurl + (jsonrpc-request (eglot--current-server-or-lose) + :textDocument/documentSymbol + `(:textDocument + ,(eglot--TextDocumentIdentifier)) + :cancel-on-input non-essential)))))) + +(defun eglot--apply-text-edits (edits &optional version) + "Apply EDITS for current buffer if at VERSION, or if it's nil." + (unless (or (not version) (equal version eglot--versioned-identifier)) + (jsonrpc-error "Edits on `%s' require version %d, you have %d" + (current-buffer) version eglot--versioned-identifier)) + (atomic-change-group + (let* ((change-group (prepare-change-group)) + (howmany (length edits)) + (reporter (make-progress-reporter + (format "[eglot] applying %s edits to `%s'..." + howmany (current-buffer)) + 0 howmany)) + (done 0)) + (mapc (pcase-lambda (`(,newText ,beg . ,end)) + (let ((source (current-buffer))) + (with-temp-buffer + (insert newText) + (let ((temp (current-buffer))) + (with-current-buffer source + (save-excursion + (save-restriction + (narrow-to-region beg end) + + ;; On emacs versions < 26.2, + ;; `replace-buffer-contents' is buggy - it calls + ;; change functions with invalid arguments - so we + ;; manually call the change functions here. + ;; + ;; See emacs bugs #32237, #32278: + ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32237 + ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32278 + (let ((inhibit-modification-hooks t) + (length (- end beg)) + (beg (marker-position beg)) + (end (marker-position end))) + (run-hook-with-args 'before-change-functions + beg end) + (replace-buffer-contents temp) + (run-hook-with-args 'after-change-functions + beg (+ beg (length newText)) + length)))) + (progress-reporter-update reporter (cl-incf done))))))) + (mapcar (eglot--lambda ((TextEdit) range newText) + (cons newText (eglot--range-region range 'markers))) + (reverse edits))) + (undo-amalgamate-change-group change-group) + (progress-reporter-done reporter)))) + +(defun eglot--apply-workspace-edit (wedit &optional confirm) + "Apply the workspace edit WEDIT. If CONFIRM, ask user first." + (eglot--dbind ((WorkspaceEdit) changes documentChanges) wedit + (let ((prepared + (mapcar (eglot--lambda ((TextDocumentEdit) textDocument edits) + (eglot--dbind ((VersionedTextDocumentIdentifier) uri version) + textDocument + (list (eglot--uri-to-path uri) edits version))) + documentChanges))) + (cl-loop for (uri edits) on changes by #'cddr + do (push (list (eglot--uri-to-path uri) edits) prepared)) + (if (or confirm + (cl-notevery #'find-buffer-visiting + (mapcar #'car prepared))) + (unless (y-or-n-p + (format "[eglot] Server wants to edit:\n %s\n Proceed? " + (mapconcat #'identity (mapcar #'car prepared) "\n "))) + (eglot--error "User cancelled server edit"))) + (cl-loop for edit in prepared + for (path edits version) = edit + do (with-current-buffer (find-file-noselect path) + (eglot--apply-text-edits edits version)) + finally (eldoc) (eglot--message "Edit successful!"))))) + +(defun eglot-rename (newname) + "Rename the current symbol to NEWNAME." + (interactive + (list (read-from-minibuffer + (format "Rename `%s' to: " (or (thing-at-point 'symbol t) + "unknown symbol")) + nil nil nil nil + (symbol-name (symbol-at-point))))) + (unless (eglot--server-capable :renameProvider) + (eglot--error "Server can't rename!")) + (eglot--apply-workspace-edit + (jsonrpc-request (eglot--current-server-or-lose) + :textDocument/rename `(,@(eglot--TextDocumentPositionParams) + :newName ,newname)) + current-prefix-arg)) + +(defun eglot--region-bounds () + "Region bounds if active, else bounds of things at point." + (if (use-region-p) `(,(region-beginning) ,(region-end)) + (let ((boftap (bounds-of-thing-at-point 'sexp))) + (list (car boftap) (cdr boftap))))) + +(defun eglot-code-actions (beg &optional end action-kind) + "Offer to execute actions of ACTION-KIND between BEG and END. +If ACTION-KIND is nil, consider all kinds of actions. +Interactively, default BEG and END to region's bounds else BEG is +point and END is nil, which results in a request for code actions +at point. With prefix argument, prompt for ACTION-KIND." + (interactive + `(,@(eglot--region-bounds) + ,(and current-prefix-arg + (completing-read "[eglot] Action kind: " + '("quickfix" "refactor.extract" "refactor.inline" + "refactor.rewrite" "source.organizeImports"))))) + (unless (eglot--server-capable :codeActionProvider) + (eglot--error "Server can't execute code actions!")) + (let* ((server (eglot--current-server-or-lose)) + (actions + (jsonrpc-request + server + :textDocument/codeAction + (list :textDocument (eglot--TextDocumentIdentifier) + :range (list :start (eglot--pos-to-lsp-position beg) + :end (eglot--pos-to-lsp-position end)) + :context + `(:diagnostics + [,@(cl-loop for diag in (flymake-diagnostics beg end) + when (cdr (assoc 'eglot-lsp-diag + (eglot--diag-data diag))) + collect it)] + ,@(when action-kind `(:only [,action-kind])))) + :deferred t)) + (menu-items + (or (cl-loop for action across actions + ;; Do filtering ourselves, in case the `:only' + ;; didn't go through. + when (or (not action-kind) + (equal action-kind (plist-get action :kind))) + collect (cons (plist-get action :title) action)) + (apply #'eglot--error + (if action-kind `("No \"%s\" code actions here" ,action-kind) + `("No code actions here"))))) + (preferred-action (cl-find-if + (lambda (menu-item) + (plist-get (cdr menu-item) :isPreferred)) + menu-items)) + (default-action (car (or preferred-action (car menu-items)))) + (action (if (and action-kind (null (cadr menu-items))) + (cdr (car menu-items)) + (if (listp last-nonmenu-event) + (x-popup-menu last-nonmenu-event `("Eglot code actions:" + ("dummy" ,@menu-items))) + (cdr (assoc (completing-read + (format "[eglot] Pick an action (default %s): " + default-action) + menu-items nil t nil nil default-action) + menu-items)))))) + (eglot--dcase action + (((Command) command arguments) + (eglot-execute-command server (intern command) arguments)) + (((CodeAction) edit command) + (when edit (eglot--apply-workspace-edit edit)) + (when command + (eglot--dbind ((Command) command arguments) command + (eglot-execute-command server (intern command) arguments))))))) + +(defmacro eglot--code-action (name kind) + "Define NAME to execute KIND code action." + `(defun ,name (beg &optional end) + ,(format "Execute '%s' code actions between BEG and END." kind) + (interactive (eglot--region-bounds)) + (eglot-code-actions beg end ,kind))) + +(eglot--code-action eglot-code-action-organize-imports "source.organizeImports") +(eglot--code-action eglot-code-action-extract "refactor.extract") +(eglot--code-action eglot-code-action-inline "refactor.inline") +(eglot--code-action eglot-code-action-rewrite "refactor.rewrite") +(eglot--code-action eglot-code-action-quickfix "quickfix") + + +;;; Dynamic registration +;;; +(cl-defmethod eglot-register-capability + (server (method (eql workspace/didChangeWatchedFiles)) id &key watchers) + "Handle dynamic registration of workspace/didChangeWatchedFiles." + (eglot-unregister-capability server method id) + (let* (success + (globs (mapcar + (eglot--lambda ((FileSystemWatcher) globPattern) + (eglot--glob-compile globPattern t t)) + watchers)) + (dirs-to-watch + (delete-dups (mapcar #'file-name-directory + (project-files + (eglot--project server)))))) + (cl-labels + ((handle-event + (event) + (pcase-let ((`(,desc ,action ,file ,file1) event)) + (cond + ((and (memq action '(created changed deleted)) + (cl-find file globs :test (lambda (f g) (funcall g f)))) + (jsonrpc-notify + server :workspace/didChangeWatchedFiles + `(:changes ,(vector `(:uri ,(eglot--path-to-uri file) + :type ,(cl-case action + (created 1) + (changed 2) + (deleted 3))))))) + ((eq action 'renamed) + (handle-event `(,desc 'deleted ,file)) + (handle-event `(,desc 'created ,file1))))))) + (unwind-protect + (progn + (dolist (dir dirs-to-watch) + (push (file-notify-add-watch dir '(change) #'handle-event) + (gethash id (eglot--file-watches server)))) + (setq + success + `(:message ,(format "OK, watching %s directories in %s watchers" + (length dirs-to-watch) (length watchers))))) + (unless success + (eglot-unregister-capability server method id)))))) + +(cl-defmethod eglot-unregister-capability + (server (_method (eql workspace/didChangeWatchedFiles)) id) + "Handle dynamic unregistration of workspace/didChangeWatchedFiles." + (mapc #'file-notify-rm-watch (gethash id (eglot--file-watches server))) + (remhash id (eglot--file-watches server)) + (list t "OK")) + + +;;; Glob heroics +;;; +(defun eglot--glob-parse (glob) + "Compute list of (STATE-SYM EMITTER-FN PATTERN)." + (with-temp-buffer + (save-excursion (insert glob)) + (cl-loop + with grammar = '((:** "\\*\\*/?" eglot--glob-emit-**) + (:* "\\*" eglot--glob-emit-*) + (:? "\\?" eglot--glob-emit-?) + (:{} "{[^][*{}]+}" eglot--glob-emit-{}) + (:range "\\[\\^?[^][/,*{}]+\\]" eglot--glob-emit-range) + (:literal "[^][,*?{}]+" eglot--glob-emit-self)) + until (eobp) + collect (cl-loop + for (_token regexp emitter) in grammar + thereis (and (re-search-forward (concat "\\=" regexp) nil t) + (list (cl-gensym "state-") emitter (match-string 0))) + finally (error "Glob '%s' invalid at %s" (buffer-string) (point)))))) + +(defun eglot--glob-compile (glob &optional byte-compile noerror) + "Convert GLOB into Elisp function. Maybe BYTE-COMPILE it. +If NOERROR, return predicate, else erroring function." + (let* ((states (eglot--glob-parse glob)) + (body `(with-current-buffer (get-buffer-create " *eglot-glob-matcher*") + (erase-buffer) + (save-excursion (insert string)) + (cl-labels ,(cl-loop for (this that) on states + for (self emit text) = this + for next = (or (car that) 'eobp) + collect (funcall emit text self next)) + (or (,(caar states)) + (error "Glob done but more unmatched text: '%s'" + (buffer-substring (point) (point-max))))))) + (form `(lambda (string) ,(if noerror `(ignore-errors ,body) body)))) + (if byte-compile (byte-compile form) form))) + +(defun eglot--glob-emit-self (text self next) + `(,self () (re-search-forward ,(concat "\\=" (regexp-quote text))) (,next))) + +(defun eglot--glob-emit-** (_ self next) + `(,self () (or (ignore-errors (save-excursion (,next))) + (and (re-search-forward "\\=/?[^/]+/?") (,self))))) + +(defun eglot--glob-emit-* (_ self next) + `(,self () (re-search-forward "\\=[^/]") + (or (ignore-errors (save-excursion (,next))) (,self)))) + +(defun eglot--glob-emit-? (_ self next) + `(,self () (re-search-forward "\\=[^/]") (,next))) + +(defun eglot--glob-emit-{} (arg self next) + (let ((alternatives (split-string (substring arg 1 (1- (length arg))) ","))) + `(,self () + (or ,@(cl-loop for alt in alternatives + collect `(re-search-forward ,(concat "\\=" alt) nil t)) + (error "Failed matching any of %s" ',alternatives)) + (,next)))) + +(defun eglot--glob-emit-range (arg self next) + (when (eq ?! (aref arg 1)) (aset arg 1 ?^)) + `(,self () (re-search-forward ,(concat "\\=" arg)) (,next))) + + +;;; Obsolete +;;; + +(make-obsolete-variable 'eglot--managed-mode-hook + 'eglot-managed-mode-hook "1.6") +(provide 'eglot) + +;; Local Variables: +;; bug-reference-bug-regexp: "\\(github#\\([0-9]+\\)\\)" +;; bug-reference-url-format: "https://github.com/joaotavora/eglot/issues/%s" +;; checkdoc-force-docstrings-flag: nil +;; End: + +;;; eglot.el ends here diff --git a/settings/package_extra.el b/settings/package_extra.el index 0cbf4e5..021b385 100644 --- a/settings/package_extra.el +++ b/settings/package_extra.el @@ -1,4 +1,4 @@ -;; 闈炲畼鏂硅嚜甯ackages鐨勮缃 -*- lexical-binding: t -*- +锘;; 闈炲畼鏂硅嚜甯ackages鐨勮缃 -*- lexical-binding: t -*- ;; benchmark: 浣跨敤profiler-start鍜宲rofiler-report鏉ユ煡鐪嬩細褰卞搷emacs鎬ц兘锛屽閫犳垚鍗¢】鐨勫懡浠ょ瓑 ;; 鎷栨參gui娴嬭瘯锛欳-x 3寮涓や釜绐楀彛锛屾墦寮涓嶅悓鐨刡uffer锛孋-s鎼滅储鍙兘鍑虹幇姣旇緝澶氱殑璇嶏紝娴嬭瘯鍑篸oom modeline鍜宼abbar ruler姣旇緝鎱 @@ -11,7 +11,7 @@ ;; 浠ュ悗鐨勫穿婧冮棶棰橀兘鍙互鍙傝冭繖涓鐞嗭紝涓鑸槸eln-cache閲屾湁tmp娌$紪璇戝ソ閫犳垚emacs閫鍑烘椂宕╂簝 (eval-when-compile - (add-to-list 'load-path "~/.emacs.d/packages/use-package") + (add-to-list 'load-path "~/.emacs.d/packages/use-package/use-package-master") (require 'use-package)) (add-to-list 'load-path @@ -26,36 +26,71 @@ (interactive "P") (time-convert (file-attribute-modification-time (file-attributes file)) 'integer)) -(defun ensure-latest (zip) - "鑷姩鏇存柊zip鍖" +(defun straight--build-compile (dir) + "鏍稿績灏辨槸璋冪敤byte-recompile-directory锛屼絾鍙湁璺ㄨ繘绋嬫墠涓嶆姤閿" + (let* ( + (emacs (concat invocation-directory invocation-name)) + (program (format "(let ((default-directory %S)) + (normal-top-level-add-subdirs-to-load-path) + (byte-recompile-directory %S 0 'force))" + dir dir)) + (args (list "-Q" "-L" dir "--batch" "--eval" program))) + (with-current-buffer (get-buffer-create "*straight-byte-compilation*") + (insert (format "\n$ %s %s %s \\\n %S\n" emacs + (string-join (cl-subseq args 0 3) " ") + (string-join (cl-subseq args 3 5) " ") + program))) + (apply #'call-process + `(,emacs nil "*straight-byte-compilation*" nil ,@args)))) +(defun ensure-latest (zip &optional no-compile) + "鑷姩鏇存柊zip鍖咃紝zip閲岀殑鐩綍缁勭粐鏈夋牸寮忚姹傦紝鐩存帴鐢╣it涓嬭浇鐨剒ip灏卞彲浠ヤ簡" (interactive "P") (let* ((check-file (expand-file-name (concat ".cache/" (file-name-nondirectory zip)) user-emacs-directory)) (expand-zip (expand-file-name zip)) (extdir (file-name-directory expand-zip)) (target-dir (concat extdir (file-name-base expand-zip)))) (when (file-newer-than-file-p expand-zip check-file) - (delete-directory target-dir t nil) ;鍏堝垹闄 + (delete-directory target-dir t nil) ;鍏堝垹闄ょ洰褰 (call-process-shell-command (concat "unzip " expand-zip " -d " extdir)) - (let ((default-directory target-dir)) - (call-interactively 'byte-recompile-directory nil (read-kbd-macro "C-n"))) - - ;; (byte-recompile-directory target-dir 0) - ;; (call-process-shell-command (concat "emacs -Q --batch --eval " - ;; (format "(let ((default-directory %S)) - ;; (normal-top-level-add-subdirs-to-load-path) - ;; (byte-recompile-directory %S 0 'force))" - ;; target-dir target-dir))) - - ;; (message target-dir) - ;; 鐢╰ouch鏇存柊check-file鐨勬椂闂 + (unless no-compile + (message (format "Building %s ..." target-dir)) + (straight--build-compile target-dir)) + ;; 鍒涘缓绌虹殑鏃堕棿鎴虫枃浠 (unless (file-exists-p (expand-file-name ".cache" user-emacs-directory)) (make-directory (expand-file-name ".cache" user-emacs-directory) t)) - ;; (call-process-shell-command (concat "touch " check-file " -r " expand-zip)) - ) + (call-process-shell-command (concat "touch " check-file " -r " expand-zip)) + ))) + +(when nil + ;; 娴嬭瘯鍙戠幇瀵瑰惎鍔ㄩ熷害杩樻槸鏈夊奖鍝嶇殑锛岃繖閲屾墜鍔ㄦ墽琛屾洿鏂板氨鍙互浜 + ;; tree-sistter閭d釜鐢ㄦ牴鐩綍鐨剆etup.py涓嬭浇bin鍜岃В鍘 + (progn + (ensure-latest "~/.emacs.d/themes/diff-hl-master.zip") + (ensure-latest "~/.emacs.d/themes/themes-master.zip") + (ensure-latest "~/.emacs.d/packages/citre/citre-master.zip") + (ensure-latest "~/.emacs.d/packages/corfu/corfu-main.zip") + (ensure-latest "~/.emacs.d/packages/dired/dired-hacks-master.zip") + (ensure-latest "~/.emacs.d/packages/expand-region/expand-region.el-master.zip") + (ensure-latest "~/.emacs.d/packages/easy-kill/easy-kill-extras.el-master.zip" t) + (ensure-latest "~/.emacs.d/packages/multiple-cursors/multiple-cursors.el-master.zip") + (ensure-latest "~/.emacs.d/packages/minibuffer/vertico-main.zip") + (ensure-latest "~/.emacs.d/packages/minibuffer/embark-master.zip") + (ensure-latest "~/.emacs.d/packages/minibuffer/consult-main.zip") + (ensure-latest "~/.emacs.d/packages/minibuffer/compat.el-master.zip") + (ensure-latest "~/.emacs.d/packages/magit/magit-master.zip") + (ensure-latest "~/.emacs.d/packages/lsp/lsp-bridge-master.zip") + (ensure-latest "~/.emacs.d/packages/org/emacs-maple-preview-master.zip") + (ensure-latest "~/.emacs.d/packages/org/org-roam.zip") + (ensure-latest "~/.emacs.d/packages/org/emacsql-master.zip") + (ensure-latest "~/.emacs.d/packages/projectile/rg.el-master.zip") + (ensure-latest "~/.emacs.d/packages/tools/elfeed-master.zip") + (ensure-latest "~/.emacs.d/packages/use-package/use-package-master.zip") + (ensure-latest "~/.emacs.d/packages/yasnippet/yasnippet-snippets-master.zip") + ) ) -;; (ensure-latest "~/.emacs.d/settings/settings.zip") +;; (ensure-latest "~/.emacs.d/settings/test.zip") ;; F1 v鏌ョ湅鍙橀噺 sanityinc/require-times锛屾甯镐竴椤靛氨鏄剧ず瀹屼簡锛岀洰鍓11涓寘 ;; 鏈変釜鍑犲勾鍓嶇殑灏佽娌℃湁鐢紝鐩存帴鐢ㄧ殑鍘熺増鐨 https://github.com/purcell/emacs.d/blob/master/lisp/init-benchmarking.el (use-package init-benchmarking @@ -108,6 +143,7 @@ _q_uit (put 'dired-find-alternate-file 'disabled nil) ;; 閬垮厤浣跨敤璇ュ嚱鏁版椂鎻愮ず (global-set-key [remap dired] 'dired-jump) ;; 鐩存帴鎵撳紑buffer鎵鍦ㄧ洰褰曪紝鏃犻』纭鐩綍 (use-package dired-recent + :load-path "~/.emacs.d/packages/dired/dired-hacks-master" :commands(dired-recent-mode dired-recent-open) :init (setq dired-recent-mode-map nil);; 绂佹瀹冩敞鍐孋-x C-d @@ -407,8 +443,7 @@ _q_uit ("f" consult-find nil :color blue) ("e" consult-everything nil :color blue) ("c" files-recent-changed nil :color blue) ;; 杩欎釜鍙湁session鎵嶆湁鐨勶紝recentf娌℃湁 - ("v" files-recent-visited nil :color blue) - ("a" find-file-at-point nil :color blue) + ("v" files-recent-visited nil :color blue) ("a" find-file-at-point nil :color blue) ("r" files-recent-visited nil :color blue) ("p" prj-find-file nil :color blue) ("d" dired-recent-open nil :color blue) @@ -424,8 +459,7 @@ _q_uit " _a_: all _t_: type _m_: mode _RET_: this buffer -_q_uit -" +_q_uit" ("a" all-occur nil :color blue) ("t" type-occur nil :color blue) ("m" mode-occur nil :color blue) @@ -691,6 +725,7 @@ _c_: hide comment _q_uit :load-path "~/.emacs.d/packages/corfu/corfu-main" :commands(global-corfu-mode corfu-mode) :init + (setq corfu-cycle t corfu-auto t corfu-auto-prefix 1 @@ -929,7 +964,7 @@ _c_: hide comment _q_uit ;; expand-region琚 easy-kill鐨別asy-mark鏇挎崲浜嗭紝浣嗚淇濈暀浼氳璋冪敤 (use-package expand-region - :load-path "~/.emacs.d/packages/expand-region" + :load-path "~/.emacs.d/packages/expand-region/expand-region.el-master" :commands(er/expand-region er/contract-region) :init (global-set-key (kbd "C-S-t") 'er/contract-region) @@ -972,7 +1007,7 @@ _c_: hide comment _q_uit ;;; 绫讳技sublime鐨勫鍏夋爣鍔熻兘(浠閿洿鍍忔槸visual code) (use-package multiple-cursors - :load-path "~/.emacs.d/packages/multiple-cursors" + :load-path "~/.emacs.d/packages/multiple-cursors/multiple-cursors.el-master" :init ;; mc璇㈤棶浣犵殑鍛戒护閮戒繚瀛樺湪杩欓噷闈簡 (setq mc/list-file "~/.emacs.d/packages/multiple-cursors/my-cmds.el") @@ -1476,6 +1511,7 @@ _c_: hide comment _q_uit consult-find ;; minad璇磃d涓嶅お鎴愮啛锛屽氨鐢╢ind鍚 ) :init + (setq consult-line-start-from-top nil ;; nil鍓嶉潰琛屼細鎺掑悗闈紝浣唗鍒濆琛屾槸鏈鍓嶉潰閭d釜 consult-line-point-placement 'match-beginning ; jump鍚庤烦鍒板尮閰嶈瘝鐨勫紑澶 @@ -1489,6 +1525,7 @@ _c_: hide comment _q_uit (add-to-list 'process-coding-system-alist '("[rR][gG]" . (utf-8 . gbk-dos))) ;; rg鏀寔涓枃 (add-to-list 'process-coding-system-alist '("es" gbk . gbk)) (add-to-list 'process-coding-system-alist '("[fF][iI][nN][dD]" . (utf-8 . gbk-dos))) ;; find鏀寔涓枃 + ;; https://github.com/phikal/compat.el (use-package compat :defer t @@ -1790,6 +1827,7 @@ Copy Buffer Name: _f_ull, _d_irectoy, n_a_me ? ) )) + (add-to-list 'load-path "~/.emacs.d/packages/easy-kill/easy-kill-extras.el-master") (require 'easy-kill-er) (require 'extra-things) (require 'easy-kill-extras) @@ -1958,6 +1996,7 @@ Copy Buffer Name: _f_ull, _d_irectoy, n_a_me ? ad-do-it) (modify-coding-system-alist 'process "[cC][mM][dD][pP][rR][oO][xX][yY]" cmdproxy-old-encoding)))) + ;; TODO: ctags鐢熸垚濂藉儚杩樺惈鏈夊閮ㄥ紩鐢紵鍙﹀--exclude闇瑕佽嚜宸卞姞涓 ;; 娴嬭瘯闂锛歺ref绌虹櫧澶勪細鍗℃锛岃ˉ鍏ㄦ椂涔熶細鍗℃emacs(灏ゅ叾鏄痚l鏂囦欢鍐欐敞閲婄殑鏃跺欙紝浼氬垱寤簆rocess骞舵彁绀哄け璐) ;; 鎵浠ョ洰鍓嶄粎鐢ㄥ畠鏉ュ垱寤篢AGS鏂囦欢 @@ -1967,6 +2006,7 @@ Copy Buffer Name: _f_ull, _d_irectoy, n_a_me ? :load-path "~/.emacs.d/packages/citre/citre-master" :commands(citre-create-tags-file citre-update-this-tags-file) :init + (setq citre-default-create-tags-file-location 'project-cache citre-use-project-root-when-creating-tags t citre-tags-file-per-project-cache-dir "" ;; 寮哄埗tag鏀惧埌鏍圭洰褰曪紝闇閰嶅悎鍚庨潰璁剧疆 @@ -2174,6 +2214,7 @@ Copy Buffer Name: _f_ull, _d_irectoy, n_a_me ? :load-path "~/.emacs.d/packages/projectile" :commands(rg-define-search) :init + (add-to-list 'load-path "~/.emacs.d/packages/projectile/rg.el-master") (defun my/rg-dwim() (interactive) ;; type涓篴ll锛屼笉鐒秇灏变細褰撴垚c浠庤屽拷鐣ヤ簡cpp鏂囦欢銆傝鎸囧畾绫诲瀷鍙互鍦╮g buffer鎸塮淇敼 @@ -2243,6 +2284,7 @@ Copy Buffer Name: _f_ull, _d_irectoy, n_a_me ? :load-path "~/.emacs.d/packages/lsp/lsp-bridge-master" :commands(lsp-bridge-mode) :init + (setq acm-enable-english-helper nil ;; english瀛楀吀澶ぇ宸茬粡鍒犻櫎浜 ) (defun lsp-ensure() diff --git a/setup.py b/setup.py index 061a26b..e4b327d 100644 --- a/setup.py +++ b/setup.py @@ -14,35 +14,9 @@ zip_list = [ # http寮澶寸殑鎺掑墠闈㈠鏋滄湁闂灏辩粓姝簡 ("https://ghproxy.com/https://github.com/emacs-tree-sitter/tree-sitter-langs/releases/download/0.11.3/tree-sitter-grammars.x86_64-pc-windows-msvc.v0.11.3.tar.gz", "packages/tree-sitter/langs/bin"), - ("packages/company-mode/company-mode-master.zip", "packages/company-mode", True), - ("packages/dired/dired-hacks-master.zip", "packages/dired", True), - ("packages/easy-kill/easy-kill-extras.el-master.zip", "packages/easy-kill", True), - ("packages/expand-region/expand-region.el-master.zip", "packages/expand-region", True), - ("packages/helm/emacs-async-master.zip", "packages/helm"), - ("packages/helm/helm-master.zip", "packages/helm"), - ("packages/lsp/bui.el-master.zip", "packages/lsp"), - ("packages/lsp/dap-mode-master.zip", "packages/lsp"), - ("packages/lsp/eglot-master.zip", "packages/lsp", True), - ("packages/lsp/lsp-mode-master.zip", "packages/lsp"), - ("packages/lsp/lsp-pyright-master.zip", "packages/lsp"), - ("packages/lsp/lsp-treemacs-master.zip", "packages/lsp"), - ("packages/magit/magit-master.zip", "packages/magit"), - ("packages/multiple-cursors/multiple-cursors.el-master.zip", "packages/multiple-cursors", True), - ("packages/projectile/rg.el-master.zip", "packages/projectile", True), - ("packages/treemacs/treemacs-master.zip", "packages/treemacs", True), ("packages/tree-sitter/elisp-tree-sitter-master.zip", "packages/tree-sitter", True), ("packages/tree-sitter/langs/tree-sitter-langs-master.zip", "packages/tree-sitter/langs", True), ("packages/tree-sitter/langs/elisp.zip", "packages/tree-sitter/langs", True), - ("packages/use-package/use-package-master.zip", "packages/use-package", True), - ("packages/yasnippet/yasnippet-snippets-master.zip", "packages/yasnippet"), - ("themes/all-the-icons.el-master.zip", "themes"), - ("themes/themes-master.zip", "themes"), - ("themes/nyan-mode-master.zip", "themes"), - ("themes/diff-hl-master.zip", "themes"), - ("packages/org/emacs-maple-preview-master.zip", "packages/org"), - ("packages/org/org-roam.zip", "packages/org"), - ("packages/org/emacsql-master.zip", "packages/org"), - ] class ZipTar(): def __init__(self, path): @@ -117,12 +91,6 @@ def unzip(zf): zip_file.close() bin_list = [ - ("http://adoxa.altervista.org/global/glo668wb.zip",[("bin/global.exe", "bin/global.exe"), - ("bin/gozilla.exe","bin/gozilla.exe"), - ("bin/gtags-cscope.exe","bin/gtags-cscope.exe"), - ("bin/gtags.exe","bin/gtags.exe"), - ("bin/htags.exe","bin/htags.exe"), - ]), ("https://ghproxy.com/https://github.com/BurntSushi/ripgrep/releases/download/13.0.0/ripgrep-13.0.0-x86_64-pc-windows-msvc.zip", [("ripgrep-13.0.0-x86_64-pc-windows-msvc/rg.exe", "bin/rg.exe")]), ("https://ghproxy.com/https://github.com/universal-ctags/ctags-win32/releases/download/2022-03-16%2Fp5.9.20220306.0-11-g4492555f/ctags-2022-03-16_p5.9.20220306.0-11-g4492555f-x64.zip", [ ("ctags.exe", "bin/ctags.exe"), @@ -134,7 +102,7 @@ def unzip(zf): ("https://ghproxy.com/https://github.com/lynnux/.emacs.d/releases/download/20220501/winbin64.zip", [("emacs-win32-launcher.exe", "bin/emacs-win32-launcher.exe"), ("pop_select.dll", "bin/pop_select.dll"), - ("emacs_beacon.dll", "bin/emacs_beacon.dll") + ("es.exe", "bin/es.exe") ]), ("https://ghproxy.com/https://github.com/lynnux/rgpre/releases/download/2022.03.22/rgpre-windows-x86_64.zip", [("rgpre.exe", "bin/rgpre.exe")]) @@ -178,9 +146,8 @@ def main(): unzip_el() print(''' 鎿嶄綔瀹屾垚锛岃繕鏈変互涓嬪伐浣: - 1.瀹夎瀛椾綋锛歵hemes/all-the-icons.el-master/fonts - 2.缂栬瘧elc锛屾墦寮init.el鎵цC-u 0 M-x byte-recompile-directory锛 - 瀹屾垚鍚庡垹闄ackages\tabbar\tabbar.elc锛屽垹闄ackages\easy-kill涓嬮櫎easy-kill.elc澶栫殑elc鏂囦欢 + 1.缂栬瘧elc锛屾墦寮init.el鎵цC-u 0 M-x byte-recompile-directory + 2.鎵撳紑 ''') os.system("pause")