ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ" Vim indent file " Language: Erlang (http://www.erlang.org) " Author: Csaba Hoch " Contributors: Edwin Fine " Pawel 'kTT' Salata " Ricardo Catalinas Jiménez " Last Update: 2022-Sep-06 " License: Vim license " URL: https://github.com/vim-erlang/vim-erlang-runtime " Note About Usage: " This indentation script works best with the Erlang syntax file created by " Kreąimir Marľić (Kresimir Marzic) and maintained by Csaba Hoch. " Notes About Implementation: " " - LTI = Line to indent. " - The index of the first line is 1, but the index of the first column is 0. " Initialization {{{1 " ============== " Only load this indent file when no other was loaded " Vim 7 or later is needed if exists("b:did_indent") || version < 700 finish else let b:did_indent = 1 endif setlocal indentexpr=ErlangIndent() setlocal indentkeys+=0=end,0=of,0=catch,0=after,0=else,0=when,0=),0=],0=},0=>> let b:undo_indent = "setl inde< indk<" " Only define the functions once if exists("*ErlangIndent") finish endif let s:cpo_save = &cpo set cpo&vim " Logging library {{{1 " =============== " Purpose: " Logs the given string using the ErlangIndentLog function if it exists. " Parameters: " s: string function! s:Log(s) if exists("*ErlangIndentLog") call ErlangIndentLog(a:s) endif endfunction " Line tokenizer library {{{1 " ====================== " Indtokens are "indentation tokens". See their exact format in the " documentation of the s:GetTokensFromLine function. " Purpose: " Calculate the new virtual column after the given segment of a line. " Parameters: " line: string " first_index: integer -- the index of the first character of the segment " last_index: integer -- the index of the last character of the segment " vcol: integer -- the virtual column of the first character of the token " tabstop: integer -- the value of the 'tabstop' option to be used " Returns: " vcol: integer " Example: " " index: 0 12 34567 " " vcol: 0 45 89 " s:CalcVCol("\t'\tx', b", 1, 4, 4) -> 10 function! s:CalcVCol(line, first_index, last_index, vcol, tabstop) " We copy the relevant segment of the line, otherwise if the line were " e.g. `"\t", term` then the else branch below would consume the `", term` " part at once. let line = a:line[a:first_index : a:last_index] let i = 0 let last_index = a:last_index - a:first_index let vcol = a:vcol while 0 <= i && i <= last_index if line[i] ==# "\t" " Example (when tabstop == 4): " " vcol + tab -> next_vcol " 0 + tab -> 4 " 1 + tab -> 4 " 2 + tab -> 4 " 3 + tab -> 4 " 4 + tab -> 8 " " next_i - i == the number of tabs let next_i = matchend(line, '\t*', i + 1) let vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop call s:Log('new vcol after tab: '. vcol) else let next_i = matchend(line, '[^\t]*', i + 1) let vcol += next_i - i call s:Log('new vcol after other: '. vcol) endif let i = next_i endwhile return vcol endfunction " Purpose: " Go through the whole line and return the tokens in the line. " Parameters: " line: string -- the line to be examined " string_continuation: bool " atom_continuation: bool " Returns: " indtokens = [indtoken] " indtoken = [token, vcol, col] " token = string (examples: 'begin', '', '}') " vcol = integer (the virtual column of the first character of the token; " counting starts from 0) " col = integer (counting starts from 0) function! s:GetTokensFromLine(line, string_continuation, atom_continuation, \tabstop) let linelen = strlen(a:line) " The length of the line let i = 0 " The index of the current character in the line let vcol = 0 " The virtual column of the current character let indtokens = [] if a:string_continuation let i = matchend(a:line, '^\%([^"\\]\|\\.\)*"', 0) if i ==# -1 call s:Log(' Whole line is string continuation -> ignore') return [] else let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop) call add(indtokens, ['', vcol, i]) endif elseif a:atom_continuation let i = matchend(a:line, "^\\%([^'\\\\]\\|\\\\.\\)*'", 0) if i ==# -1 call s:Log(' Whole line is quoted atom continuation -> ignore') return [] else let vcol = s:CalcVCol(a:line, 0, i - 1, 0, a:tabstop) call add(indtokens, ['', vcol, i]) endif endif while 0 <= i && i < linelen let next_vcol = '' " Spaces if a:line[i] ==# ' ' let next_i = matchend(a:line, ' *', i + 1) " Tabs elseif a:line[i] ==# "\t" let next_i = matchend(a:line, '\t*', i + 1) " See example in s:CalcVCol let next_vcol = (vcol / a:tabstop + (next_i - i)) * a:tabstop " Comment elseif a:line[i] ==# '%' let next_i = linelen " String token: "..." elseif a:line[i] ==# '"' let next_i = matchend(a:line, '\%([^"\\]\|\\.\)*"', i + 1) if next_i ==# -1 call add(indtokens, ['', vcol, i]) else let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop) call add(indtokens, ['', vcol, i]) endif " Quoted atom token: '...' elseif a:line[i] ==# "'" let next_i = matchend(a:line, "\\%([^'\\\\]\\|\\\\.\\)*'", i + 1) if next_i ==# -1 call add(indtokens, ['', vcol, i]) else let next_vcol = s:CalcVCol(a:line, i, next_i - 1, vcol, a:tabstop) call add(indtokens, ['', vcol, i]) endif " Keyword or atom or variable token or number elseif a:line[i] =~# '[a-zA-Z_@0-9]' let next_i = matchend(a:line, \'[[:alnum:]_@:]*\%(\s*#\s*[[:alnum:]_@:]*\)\=', \i + 1) call add(indtokens, [a:line[(i):(next_i - 1)], vcol, i]) " Character token: $ (as in: $a) elseif a:line[i] ==# '$' call add(indtokens, ['$.', vcol, i]) let next_i = i + 2 " Dot token: . elseif a:line[i] ==# '.' let next_i = i + 1 if i + 1 ==# linelen || a:line[i + 1] =~# '[[:blank:]%]' " End of clause token: . (as in: f() -> ok.) call add(indtokens, ['', vcol, i]) else " Possibilities: " - Dot token in float: . (as in: 3.14) " - Dot token in record: . (as in: #myrec.myfield) call add(indtokens, ['.', vcol, i]) endif " Equal sign elseif a:line[i] ==# '=' " This is handled separately so that "=<<" will be parsed as " ['=', '<<'] instead of ['=<', '<']. Although Erlang parses it " currently in the latter way, that may be fixed some day. call add(indtokens, [a:line[i], vcol, i]) let next_i = i + 1 " Three-character tokens elseif i + 1 < linelen && \ index(['=:=', '=/='], a:line[i : i + 1]) != -1 call add(indtokens, [a:line[i : i + 1], vcol, i]) let next_i = i + 2 " Two-character tokens elseif i + 1 < linelen && \ index(['->', '<<', '>>', '||', '==', '/=', '=<', '>=', '?=', '++', \ '--', '::'], \ a:line[i : i + 1]) != -1 call add(indtokens, [a:line[i : i + 1], vcol, i]) let next_i = i + 2 " Other character: , ; < > ( ) [ ] { } # + - * / : ? = ! | else call add(indtokens, [a:line[i], vcol, i]) let next_i = i + 1 endif if next_vcol ==# '' let vcol += next_i - i else let vcol = next_vcol endif let i = next_i endwhile return indtokens endfunction " TODO: doc, handle "not found" case function! s:GetIndtokenAtCol(indtokens, col) let i = 0 while i < len(a:indtokens) if a:indtokens[i][2] ==# a:col return [1, i] elseif a:indtokens[i][2] > a:col return [0, s:IndentError('No token at col ' . a:col . ', ' . \'indtokens = ' . string(a:indtokens), \'', '')] endif let i += 1 endwhile return [0, s:IndentError('No token at col ' . a:col . ', ' . \'indtokens = ' . string(a:indtokens), \'', '')] endfunction " Stack library {{{1 " ============= " Purpose: " Push a token onto the parser's stack. " Parameters: " stack: [token] " token: string function! s:Push(stack, token) call s:Log(' Stack Push: "' . a:token . '" into ' . string(a:stack)) call insert(a:stack, a:token) endfunction " Purpose: " Pop a token from the parser's stack. " Parameters: " stack: [token] " token: string " Returns: " token: string -- the removed element function! s:Pop(stack) let head = remove(a:stack, 0) call s:Log(' Stack Pop: "' . head . '" from ' . string(a:stack)) return head endfunction " Library for accessing and storing tokenized lines {{{1 " ================================================= " The Erlang token cache: an `lnum -> indtokens` dictionary that stores the " tokenized lines. let s:all_tokens = {} let s:file_name = '' let s:last_changedtick = -1 " Purpose: " Clear the Erlang token cache if we have a different file or the file has " been changed since the last indentation. function! s:ClearTokenCacheIfNeeded() let file_name = expand('%:p') if file_name != s:file_name || \ b:changedtick != s:last_changedtick let s:file_name = file_name let s:last_changedtick = b:changedtick let s:all_tokens = {} endif endfunction " Purpose: " Return the tokens of line `lnum`, if that line is not empty. If it is " empty, find the first non-empty line in the given `direction` and return " the tokens of that line. " Parameters: " lnum: integer " direction: 'up' | 'down' " Returns: " result: [] -- the result is an empty list if we hit the beginning or end " of the file " | [lnum, indtokens] " lnum: integer -- the index of the non-empty line that was found and " tokenized " indtokens: [indtoken] -- the tokens of line `lnum` function! s:TokenizeLine(lnum, direction) call s:Log('Tokenizing starts from line ' . a:lnum) if a:direction ==# 'up' let lnum = prevnonblank(a:lnum) else " a:direction ==# 'down' let lnum = nextnonblank(a:lnum) endif " We hit the beginning or end of the file if lnum ==# 0 let indtokens = [] call s:Log(' We hit the beginning or end of the file.') " The line has already been parsed elseif has_key(s:all_tokens, lnum) let indtokens = s:all_tokens[lnum] call s:Log('Cached line ' . lnum . ': ' . getline(lnum)) call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - ")) " The line should be parsed now else " Parse the line let line = getline(lnum) let string_continuation = s:IsLineStringContinuation(lnum) let atom_continuation = s:IsLineAtomContinuation(lnum) let indtokens = s:GetTokensFromLine(line, string_continuation, \atom_continuation, &tabstop) let s:all_tokens[lnum] = indtokens call s:Log('Tokenizing line ' . lnum . ': ' . line) call s:Log(" Tokens in the line:\n - " . join(indtokens, "\n - ")) endif return [lnum, indtokens] endfunction " Purpose: " As a helper function for PrevIndToken and NextIndToken, the FindIndToken " function finds the first line with at least one token in the given " direction. " Parameters: " lnum: integer " direction: 'up' | 'down' " Returns: " result: [[], 0, 0] " -- the result is an empty list if we hit the beginning or end of " the file " | [indtoken, lnum, i] " -- the content, lnum and token index of the next (or previous) " indtoken function! s:FindIndToken(lnum, dir) let lnum = a:lnum while 1 let lnum += (a:dir ==# 'up' ? -1 : 1) let [lnum, indtokens] = s:TokenizeLine(lnum, a:dir) if lnum ==# 0 " We hit the beginning or end of the file return [[], 0, 0] elseif !empty(indtokens) " We found a non-empty line. If we were moving up, we return the last " token of this line. Otherwise we return the first token if this line. let i = (a:dir ==# 'up' ? len(indtokens) - 1 : 0) return [indtokens[i], lnum, i] endif endwhile endfunction " Purpose: " Find the token that directly precedes the given token. " Parameters: " lnum: integer -- the line of the given token " i: the index of the given token within line `lnum` " Returns: " result = [] -- the result is an empty list if the given token is the first " token of the file " | indtoken function! s:PrevIndToken(lnum, i) call s:Log(' PrevIndToken called: lnum=' . a:lnum . ', i =' . a:i) " If the current line has a previous token, return that if a:i > 0 return [s:all_tokens[a:lnum][a:i - 1], a:lnum, a:i - 1] else return s:FindIndToken(a:lnum, 'up') endif endfunction " Purpose: " Find the token that directly succeeds the given token. " Parameters: " lnum: integer -- the line of the given token " i: the index of the given token within line `lnum` " Returns: " result = [] -- the result is an empty list if the given token is the last " token of the file " | indtoken function! s:NextIndToken(lnum, i) call s:Log(' NextIndToken called: lnum=' . a:lnum . ', i =' . a:i) " If the current line has a next token, return that if len(s:all_tokens[a:lnum]) > a:i + 1 return [s:all_tokens[a:lnum][a:i + 1], a:lnum, a:i + 1] else return s:FindIndToken(a:lnum, 'down') endif endfunction " ErlangCalcIndent helper functions {{{1 " ================================= " Purpose: " This function is called when the parser encounters a syntax error. " " If we encounter a syntax error, we return " g:erlang_unexpected_token_indent, which is -1 by default. This means that " the indentation of the LTI will not be changed. " Parameter: " msg: string " token: string " stack: [token] " Returns: " indent: integer function! s:IndentError(msg, token, stack) call s:Log('Indent error: ' . a:msg . ' -> return') call s:Log(' Token = ' . a:token . ', ' . \' stack = ' . string(a:stack)) return g:erlang_unexpected_token_indent endfunction " Purpose: " This function is called when the parser encounters an unexpected token, " and the parser will return the number given back by UnexpectedToken. " " If we encounter an unexpected token, we return " g:erlang_unexpected_token_indent, which is -1 by default. This means that " the indentation of the LTI will not be changed. " Parameter: " token: string " stack: [token] " Returns: " indent: integer function! s:UnexpectedToken(token, stack) call s:Log(' Unexpected token ' . a:token . ', stack = ' . \string(a:stack) . ' -> return') return g:erlang_unexpected_token_indent endfunction if !exists('g:erlang_unexpected_token_indent') let g:erlang_unexpected_token_indent = -1 endif " Purpose: " Return whether the given line starts with a string continuation. " Parameter: " lnum: integer " Returns: " result: bool " Example: " f() -> % IsLineStringContinuation = false " "This is a % IsLineStringContinuation = false " multiline % IsLineStringContinuation = true " string". % IsLineStringContinuation = true function! s:IsLineStringContinuation(lnum) if has('syntax_items') return synIDattr(synID(a:lnum, 1, 0), 'name') =~# '^erlangString' else return 0 endif endfunction " Purpose: " Return whether the given line starts with an atom continuation. " Parameter: " lnum: integer " Returns: " result: bool " Example: " 'function with % IsLineAtomContinuation = true, but should be false " weird name'() -> % IsLineAtomContinuation = true " ok. % IsLineAtomContinuation = false function! s:IsLineAtomContinuation(lnum) if has('syntax_items') let syn_name = synIDattr(synID(a:lnum, 1, 0), 'name') return syn_name =~# '^erlangQuotedAtom' || \ syn_name =~# '^erlangQuotedRecord' else return 0 endif endfunction " Purpose: " Return whether the 'catch' token (which should be the `i`th token in line " `lnum`) is standalone or part of a try-catch block, based on the preceding " token. " Parameters: " lnum: integer " i: integer " Return: " is_standalone: bool function! s:IsCatchStandalone(lnum, i) call s:Log(' IsCatchStandalone called: lnum=' . a:lnum . ', i=' . a:i) let [prev_indtoken, _, _] = s:PrevIndToken(a:lnum, a:i) " If we hit the beginning of the file, it is not a catch in a try block if prev_indtoken == [] return 1 endif let prev_token = prev_indtoken[0] if prev_token =~# '^[A-Z_@0-9]' let is_standalone = 0 elseif prev_token =~# '[a-z]' if index(['after', 'and', 'andalso', 'band', 'begin', 'bnot', 'bor', 'bsl', \ 'bsr', 'bxor', 'case', 'catch', 'div', 'maybe', 'not', 'or', \ 'orelse', 'rem', 'try', 'xor'], prev_token) != -1 " If catch is after these keywords, it is standalone let is_standalone = 1 else " If catch is after another keyword (e.g. 'end') or an atom, it is " part of try-catch. " " Keywords: " - may precede 'catch': end " - may not precede 'catch': else fun if of receive when " - unused: cond let query let is_standalone = 0 endif elseif index([')', ']', '}', '', '', '', \ '', '$.'], prev_token) != -1 let is_standalone = 0 else " This 'else' branch includes the following tokens: " -> == /= =< < >= > ?= =:= =/= + - * / ++ -- :: < > ; ( [ { ? = ! . | let is_standalone = 1 endif call s:Log(' "catch" preceded by "' . prev_token . '" -> catch ' . \(is_standalone ? 'is standalone' : 'belongs to try-catch')) return is_standalone endfunction " Purpose: " This function is called when a begin-type element ('begin', 'case', " '[', '<<', etc.) is found. It asks the caller to return if the stack " if already empty. " Parameters: " stack: [token] " token: string " curr_vcol: integer " stored_vcol: integer " sw: integer -- number of spaces to be used after the begin element as " indentation " Returns: " result: [should_return, indent] " should_return: bool -- if true, the caller should return `indent` to Vim " indent -- integer function! s:BeginElementFoundIfEmpty(stack, token, curr_vcol, stored_vcol, sw) if empty(a:stack) if a:stored_vcol ==# -1 call s:Log(' "' . a:token . '" directly precedes LTI -> return') return [1, a:curr_vcol + a:sw] else call s:Log(' "' . a:token . \'" token (whose expression includes LTI) found -> return') return [1, a:stored_vcol] endif else return [0, 0] endif endfunction " Purpose: " This function is called when a begin-type element ('begin', 'case', '[', " '<<', etc.) is found, and in some cases when 'after' and 'when' is found. " It asks the caller to return if the stack is already empty. " Parameters: " stack: [token] " token: string " curr_vcol: integer " stored_vcol: integer " end_token: end token that belongs to the begin element found (e.g. if the " begin element is 'begin', the end token is 'end') " sw: integer -- number of spaces to be used after the begin element as " indentation " Returns: " result: [should_return, indent] " should_return: bool -- if true, the caller should return `indent` to Vim " indent -- integer function! s:BeginElementFound(stack, token, curr_vcol, stored_vcol, end_token, sw) " Return 'return' if the stack is empty let [ret, res] = s:BeginElementFoundIfEmpty(a:stack, a:token, a:curr_vcol, \a:stored_vcol, a:sw) if ret | return [ret, res] | endif if a:stack[0] ==# a:end_token call s:Log(' "' . a:token . '" pops "' . a:end_token . '"') call s:Pop(a:stack) if !empty(a:stack) && a:stack[0] ==# 'align_to_begin_element' call s:Pop(a:stack) if empty(a:stack) return [1, a:curr_vcol] else return [1, s:UnexpectedToken(a:token, a:stack)] endif else return [0, 0] endif else return [1, s:UnexpectedToken(a:token, a:stack)] endif endfunction " Purpose: " This function is called when we hit the beginning of a file or an " end-of-clause token -- i.e. when we found the beginning of the current " clause. " " If the stack contains an '->' or 'when', this means that we can return " now, since we were looking for the beginning of the clause. " Parameters: " stack: [token] " token: string " stored_vcol: integer " lnum: the line number of the "end of clause" mark (or 0 if we hit the " beginning of the file) " i: the index of the "end of clause" token within its own line " Returns: " result: [should_return, indent] " should_return: bool -- if true, the caller should return `indent` to Vim " indent -- integer function! s:BeginningOfClauseFound(stack, token, stored_vcol, lnum, i) if !empty(a:stack) && a:stack[0] ==# 'when' call s:Log(' BeginningOfClauseFound: "when" found in stack') call s:Pop(a:stack) if empty(a:stack) call s:Log(' Stack is ["when"], so LTI is in a guard -> return') return [1, a:stored_vcol + shiftwidth() + 2] else return [1, s:UnexpectedToken(a:token, a:stack)] endif elseif !empty(a:stack) && a:stack[0] ==# '->' call s:Log(' BeginningOfClauseFound: "->" found in stack') call s:Pop(a:stack) if empty(a:stack) call s:Log(' Stack is ["->"], so LTI is in function body -> return') return [1, a:stored_vcol + shiftwidth()] elseif a:stack[0] ==# ';' call s:Pop(a:stack) if !empty(a:stack) return [1, s:UnexpectedToken(a:token, a:stack)] endif if a:lnum ==# 0 " Set lnum and i to be NextIndToken-friendly let lnum = 1 let i = -1 else let lnum = a:lnum let i = a:i endif " Are we after a "-spec func() ...;" clause? let [next1_indtoken, next1_lnum, next1_i] = s:NextIndToken(lnum, i) if !empty(next1_indtoken) && next1_indtoken[0] =~# '-' let [next2_indtoken, next2_lnum, next2_i] = \s:NextIndToken(next1_lnum, next1_i) if !empty(next2_indtoken) && next2_indtoken[0] =~# 'spec' let [next3_indtoken, next3_lnum, next3_i] = \s:NextIndToken(next2_lnum, next2_i) if !empty(next3_indtoken) let [next4_indtoken, next4_lnum, next4_i] = \s:NextIndToken(next3_lnum, next3_i) if !empty(next4_indtoken) " Yes, we are. call s:Log(' Stack is ["->", ";"], so LTI is in a "-spec" ' . \'attribute -> return') return [1, next4_indtoken[1]] endif endif endif endif call s:Log(' Stack is ["->", ";"], so LTI is in a function head ' . \'-> return') return [1, a:stored_vcol] else return [1, s:UnexpectedToken(a:token, a:stack)] endif else return [0, 0] endif endfunction let g:erlang_indent_searchpair_timeout = 2000 " TODO function! s:SearchPair(lnum, curr_col, start, middle, end) call cursor(a:lnum, a:curr_col + 1) let [lnum_new, col1_new] = \searchpairpos(a:start, a:middle, a:end, 'bW', \'synIDattr(synID(line("."), col("."), 0), "name") ' . \'=~? "string\\|quotedatom\\|todo\\|comment\\|' . \'erlangmodifier"', \0, g:erlang_indent_searchpair_timeout) return [lnum_new, col1_new - 1] endfunction function! s:SearchEndPair(lnum, curr_col) return s:SearchPair( \ a:lnum, a:curr_col, \ '\C\<\%(case\|try\|begin\|receive\|if\|maybe\)\>\|' . \ '\\%(\s\|\n\|%.*$\|[A-Z_@][a-zA-Z_@]*\)*(', \ '', \ '\') endfunction " ErlangCalcIndent {{{1 " ================ " Purpose: " Calculate the indentation of the given line. " Parameters: " lnum: integer -- index of the line for which the indentation should be " calculated " stack: [token] -- initial stack " Return: " indent: integer -- if -1, that means "don't change the indentation"; " otherwise it means "indent the line with `indent` " number of spaces or equivalent tabs" function! s:ErlangCalcIndent(lnum, stack) let res = s:ErlangCalcIndent2(a:lnum, a:stack) call s:Log("ErlangCalcIndent returned: " . res) return res endfunction function! s:ErlangCalcIndent2(lnum, stack) let lnum = a:lnum let stored_vcol = -1 " Virtual column of the first character of the token that " we currently think we might align to. let mode = 'normal' let stack = a:stack let semicolon_abscol = '' " Walk through the lines of the buffer backwards (starting from the " previous line) until we can decide how to indent the current line. while 1 let [lnum, indtokens] = s:TokenizeLine(lnum, 'up') " Hit the start of the file if lnum ==# 0 let [ret, res] = s:BeginningOfClauseFound(stack, 'beginning_of_file', \stored_vcol, 0, 0) if ret | return res | endif return 0 endif let i = len(indtokens) - 1 let last_token_of_line = 1 while i >= 0 let [token, curr_vcol, curr_col] = indtokens[i] call s:Log(' Analyzing the following token: ' . string(indtokens[i])) if len(stack) > 256 " TODO: magic number return s:IndentError('Stack too long', token, stack) endif if token ==# '' let [ret, res] = s:BeginningOfClauseFound(stack, token, stored_vcol, \lnum, i) if ret | return res | endif if stored_vcol ==# -1 call s:Log(' End of clause directly precedes LTI -> return') return 0 else call s:Log(' End of clause (but not end of line) -> return') return stored_vcol endif elseif stack == ['prev_term_plus'] if token =~# '[a-zA-Z_@#]' || \ token ==# '' || token ==# '' || \ token ==# '' || token ==# '' call s:Log(' previous token found: curr_vcol + plus = ' . \curr_vcol . " + " . plus) return curr_vcol + plus endif elseif token ==# 'begin' let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, \stored_vcol, 'end', shiftwidth()) if ret | return res | endif " case EXPR of BRANCHES end " if BRANCHES end " try EXPR catch BRANCHES end " try EXPR after BODY end " try EXPR catch BRANCHES after BODY end " try EXPR of BRANCHES catch BRANCHES end " try EXPR of BRANCHES after BODY end " try EXPR of BRANCHES catch BRANCHES after BODY end " receive BRANCHES end " receive BRANCHES after BRANCHES end " maybe EXPR end " maybe EXPR else BRANCHES end " This branch is not Emacs-compatible elseif (index(['of', 'receive', 'after', 'if', 'else'], token) != -1 || \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i))) && \ !last_token_of_line && \ (empty(stack) || stack ==# ['when'] || stack ==# ['->'] || \ stack ==# ['->', ';']) " If we are after of/receive/etc, but these are not the last " tokens of the line, we want to indent like this: " " % stack == [] " receive stored_vcol, " LTI " " % stack == ['->', ';'] " receive stored_vcol -> " B; " LTI " " % stack == ['->'] " receive stored_vcol -> " LTI " " % stack == ['when'] " receive stored_vcol when " LTI " stack = [] => LTI is a condition " stack = ['->'] => LTI is a branch " stack = ['->', ';'] => LTI is a condition " stack = ['when'] => LTI is a guard if empty(stack) || stack == ['->', ';'] call s:Log(' LTI is in a condition after ' . \'"of/receive/after/if/else/catch" -> return') return stored_vcol elseif stack == ['->'] call s:Log(' LTI is in a branch after ' . \'"of/receive/after/if/else/catch" -> return') return stored_vcol + shiftwidth() elseif stack == ['when'] call s:Log(' LTI is in a guard after ' . \'"of/receive/after/if/else/catch" -> return') return stored_vcol + shiftwidth() else return s:UnexpectedToken(token, stack) endif elseif index(['case', 'if', 'try', 'receive', 'maybe'], token) != -1 " stack = [] => LTI is a condition " stack = ['->'] => LTI is a branch " stack = ['->', ';'] => LTI is a condition " stack = ['when'] => LTI is in a guard if empty(stack) " pass elseif (token ==# 'case' && stack[0] ==# 'of') || \ (token ==# 'if') || \ (token ==# 'maybe' && stack[0] ==# 'else') || \ (token ==# 'try' && (stack[0] ==# 'of' || \ stack[0] ==# 'catch' || \ stack[0] ==# 'after')) || \ (token ==# 'receive') " From the indentation point of view, the keyword " (of/catch/after/else/end) before the LTI is what counts, so " when we reached these tokens, and the stack already had " a catch/after/else/end, we didn't modify it. " " This way when we reach case/try/receive/maybe (i.e. now), " there is at most one of/catch/after/else/end token in the " stack. if token ==# 'case' || token ==# 'try' || \ (token ==# 'receive' && stack[0] ==# 'after') || \ (token ==# 'maybe' && stack[0] ==# 'else') call s:Pop(stack) endif if empty(stack) call s:Log(' LTI is in a condition; matching ' . \'"case/if/try/receive/maybe" found') let stored_vcol = curr_vcol + shiftwidth() elseif stack[0] ==# 'align_to_begin_element' call s:Pop(stack) let stored_vcol = curr_vcol elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';' call s:Log(' LTI is in a condition; matching ' . \'"case/if/try/receive/maybe" found') call s:Pop(stack) call s:Pop(stack) let stored_vcol = curr_vcol + shiftwidth() elseif stack[0] ==# '->' call s:Log(' LTI is in a branch; matching ' . \'"case/if/try/receive/maybe" found') call s:Pop(stack) let stored_vcol = curr_vcol + 2 * shiftwidth() elseif stack[0] ==# 'when' call s:Log(' LTI is in a guard; matching ' . \'"case/if/try/receive/maybe" found') call s:Pop(stack) let stored_vcol = curr_vcol + 2 * shiftwidth() + 2 endif endif let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, \stored_vcol, 'end', shiftwidth()) if ret | return res | endif elseif token ==# 'fun' let [next_indtoken, next_lnum, next_i] = s:NextIndToken(lnum, i) call s:Log(' Next indtoken = ' . string(next_indtoken)) if !empty(next_indtoken) && next_indtoken[0] =~# '^[A-Z_@]' " The "fun" is followed by a variable, so we might have a named fun: " "fun Fun() -> ok end". Thus we take the next token to decide " whether this is a function definition ("fun()") or just a function " reference ("fun Mod:Fun"). let [next_indtoken, _, _] = s:NextIndToken(next_lnum, next_i) call s:Log(' Next indtoken = ' . string(next_indtoken)) endif if !empty(next_indtoken) && next_indtoken[0] ==# '(' " We have an anonymous function definition " (e.g. "fun () -> ok end") " stack = [] => LTI is a condition " stack = ['->'] => LTI is a branch " stack = ['->', ';'] => LTI is a condition " stack = ['when'] => LTI is in a guard if empty(stack) call s:Log(' LTI is in a condition; matching "fun" found') let stored_vcol = curr_vcol + shiftwidth() elseif len(stack) > 1 && stack[0] ==# '->' && stack[1] ==# ';' call s:Log(' LTI is in a condition; matching "fun" found') call s:Pop(stack) call s:Pop(stack) elseif stack[0] ==# '->' call s:Log(' LTI is in a branch; matching "fun" found') call s:Pop(stack) let stored_vcol = curr_vcol + 2 * shiftwidth() elseif stack[0] ==# 'when' call s:Log(' LTI is in a guard; matching "fun" found') call s:Pop(stack) let stored_vcol = curr_vcol + 2 * shiftwidth() + 2 endif let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, \stored_vcol, 'end', shiftwidth()) if ret | return res | endif else " Pass: we have a function reference (e.g. "fun f/0") endif elseif token ==# '[' " Emacs compatibility let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, \stored_vcol, ']', 1) if ret | return res | endif elseif token ==# '<<' " Emacs compatibility let [ret, res] = s:BeginElementFound(stack, token, curr_vcol, \stored_vcol, '>>', 2) if ret | return res | endif elseif token ==# '(' || token ==# '{' let end_token = (token ==# '(' ? ')' : \token ==# '{' ? '}' : 'error') if empty(stack) " We found the opening paren whose block contains the LTI. let mode = 'inside' elseif stack[0] ==# end_token call s:Log(' "' . token . '" pops "' . end_token . '"') call s:Pop(stack) if !empty(stack) && stack[0] ==# 'align_to_begin_element' " We found the opening paren whose closing paren " starts LTI let mode = 'align_to_begin_element' else " We found the opening pair for a closing paren that " was already in the stack. let mode = 'outside' endif else return s:UnexpectedToken(token, stack) endif if mode ==# 'inside' || mode ==# 'align_to_begin_element' if last_token_of_line && i != 0 " Examples: {{{ " " mode == 'inside': " " my_func( " LTI " " [Variable, { " LTI " " mode == 'align_to_begin_element': " " my_func( " Params " ) % LTI " " [Variable, { " Terms " } % LTI " }}} let stack = ['prev_term_plus'] let plus = (mode ==# 'inside' ? 2 : 1) call s:Log(' "' . token . \'" token found at end of line -> find previous token') elseif mode ==# 'align_to_begin_element' " Examples: {{{ " " mode == 'align_to_begin_element' && !last_token_of_line " " my_func(stored_vcol " ) % LTI " " [Variable, {stored_vcol " } % LTI " " mode == 'align_to_begin_element' && i == 0 " " ( " stored_vcol " ) % LTI " " { " stored_vcol " } % LTI " }}} call s:Log(' "' . token . '" token (whose closing token ' . \'starts LTI) found -> return') return curr_vcol elseif stored_vcol ==# -1 " Examples: {{{ " " mode == 'inside' && stored_vcol == -1 && !last_token_of_line " " my_func( " LTI " [Variable, { " LTI " " mode == 'inside' && stored_vcol == -1 && i == 0 " " ( " LTI " " { " LTI " }}} call s:Log(' "' . token . \'" token (which directly precedes LTI) found -> return') return curr_vcol + 1 else " Examples: {{{ " " mode == 'inside' && stored_vcol != -1 && !last_token_of_line " " my_func(stored_vcol, " LTI " " [Variable, {stored_vcol, " LTI " " mode == 'inside' && stored_vcol != -1 && i == 0 " " (stored_vcol, " LTI " " {stored_vcol, " LTI " }}} call s:Log(' "' . token . \'" token (whose block contains LTI) found -> return') return stored_vcol endif endif elseif index(['end', ')', ']', '}', '>>'], token) != -1 " If we can be sure that there is synchronization in the Erlang " syntax, we use searchpair to make the script quicker. Otherwise we " just push the token onto the stack and keep parsing. " No synchronization -> no searchpair optimization if !exists('b:erlang_syntax_synced') call s:Push(stack, token) " We don't have searchpair optimization for '>>' elseif token ==# '>>' call s:Push(stack, token) elseif token ==# 'end' let [lnum_new, col_new] = s:SearchEndPair(lnum, curr_col) if lnum_new ==# 0 return s:IndentError('Matching token for "end" not found', \token, stack) else if lnum_new != lnum call s:Log(' Tokenize for "end" <<<<') let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up') call s:Log(' >>>> Tokenize for "end"') endif let [success, i] = s:GetIndtokenAtCol(indtokens, col_new) if !success | return i | endif let [token, curr_vcol, curr_col] = indtokens[i] call s:Log(' Match for "end" in line ' . lnum_new . ': ' . \string(indtokens[i])) endif else " token is one of the following: ')', ']', '}' call s:Push(stack, token) " We have to escape '[', because this string will be interpreted as a " regexp let open_paren = (token ==# ')' ? '(' : \token ==# ']' ? '\[' : \ '{') let [lnum_new, col_new] = s:SearchPair(lnum, curr_col, \open_paren, '', token) if lnum_new ==# 0 return s:IndentError('Matching token not found', \token, stack) else if lnum_new != lnum call s:Log(' Tokenize the opening paren <<<<') let [lnum, indtokens] = s:TokenizeLine(lnum_new, 'up') call s:Log(' >>>>') endif let [success, i] = s:GetIndtokenAtCol(indtokens, col_new) if !success | return i | endif let [token, curr_vcol, curr_col] = indtokens[i] call s:Log(' Match in line ' . lnum_new . ': ' . \string(indtokens[i])) " Go back to the beginning of the loop and handle the opening paren continue endif endif elseif token ==# ';' if empty(stack) call s:Push(stack, ';') elseif index([';', '->', 'when', 'end', 'after', 'catch', 'else'], \stack[0]) != -1 " Pass: " " - If the stack top is another ';', then one ';' is " enough. " - If the stack top is an '->' or a 'when', then we " should keep that, because they signify the type of the " LTI (branch, condition or guard). " - From the indentation point of view, the keyword " (of/catch/after/else/end) before the LTI is what counts, so " if the stack already has a catch/after/else/end, we don't " modify it. This way when we reach case/try/receive/maybe, " there will be at most one of/catch/after/else/end token in " the stack. else return s:UnexpectedToken(token, stack) endif elseif token ==# '->' if empty(stack) && !last_token_of_line call s:Log(' LTI is in expression after arrow -> return') return stored_vcol elseif empty(stack) || stack[0] ==# ';' || stack[0] ==# 'end' " stack = [';'] -> LTI is either a branch or in a guard " stack = ['->'] -> LTI is a condition " stack = ['->', ';'] -> LTI is a branch call s:Push(stack, '->') elseif index(['->', 'when', 'end', 'after', 'catch', 'else'], \stack[0]) != -1 " Pass: " " - If the stack top is another '->', then one '->' is " enough. " - If the stack top is a 'when', then we should keep " that, because this signifies that LTI is a in a guard. " - From the indentation point of view, the keyword " (of/catch/after/else/end) before the LTI is what counts, so " if the stack already has a catch/after/else/end, we don't " modify it. This way when we reach case/try/receive/maybe, " there will be at most one of/catch/after/else/end token in " the stack. else return s:UnexpectedToken(token, stack) endif elseif token ==# 'when' " Pop all ';' from the top of the stack while !empty(stack) && stack[0] ==# ';' call s:Pop(stack) endwhile if empty(stack) if semicolon_abscol != '' let stored_vcol = semicolon_abscol endif if !last_token_of_line " Example: " when A, " LTI let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol, \stored_vcol, shiftwidth()) if ret | return res | endif else " Example: " when " LTI call s:Push(stack, token) endif elseif index(['->', 'when', 'end', 'after', 'catch', 'else'], \stack[0]) != -1 " Pass: " - If the stack top is another 'when', then one 'when' is " enough. " - If the stack top is an '->' or a 'when', then we " should keep that, because they signify the type of the " LTI (branch, condition or guard). " - From the indentation point of view, the keyword " (of/catch/after/else/end) before the LTI is what counts, so " if the stack already has a catch/after/else/end, we don't " modify it. This way when we reach case/try/receive/maybe, " there will be at most one of/catch/after/else/end token in " the stack. else return s:UnexpectedToken(token, stack) endif elseif token ==# 'of' || token ==# 'after' || token ==# 'else' || \ (token ==# 'catch' && !s:IsCatchStandalone(lnum, i)) if token ==# 'after' || token ==# 'else' " If LTI is between an after/else and the corresponding 'end', then " let's return because calculating the indentation based on " after/else is enough. " " Example: " receive A after " LTI " maybe A else " LTI " " Note about Emacs compabitility {{{ " " It would be fine to indent the examples above the following way: " " receive A after " LTI " maybe A else " LTI " " We intend it the way above because that is how Emacs does it. " Also, this is a bit faster. " " We are still not 100% Emacs compatible because of placing the " 'end' after the indented blocks. " " Emacs example: " " receive A after " LTI " end, " maybe A else " LTI " end % Yes, it's here (in OTP 25.0, might change " % later) " " vim-erlang example: " " receive A after " LTI " end, " maybe A else " LTI " end " }}} let [ret, res] = s:BeginElementFoundIfEmpty(stack, token, curr_vcol, \stored_vcol, shiftwidth()) if ret | return res | endif endif if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when' call s:Push(stack, token) elseif stack[0] ==# 'catch' || stack[0] ==# 'after' || \stack[0] ==# 'else' || stack[0] ==# 'end' " Pass: From the indentation point of view, the keyword " (of/catch/after/end) before the LTI is what counts, so " if the stack already has a catch/after/end, we don't " modify it. This way when we reach case/try/receive, " there will be at most one of/catch/after/end token in " the stack. else return s:UnexpectedToken(token, stack) endif elseif token ==# '||' && empty(stack) && !last_token_of_line call s:Log(' LTI is in expression after "||" -> return') return stored_vcol else call s:Log(' Misc token, stack unchanged = ' . string(stack)) endif if empty(stack) || stack[0] ==# '->' || stack[0] ==# 'when' let stored_vcol = curr_vcol let semicolon_abscol = '' call s:Log(' Misc token when the stack is empty or has "->" ' . \'-> setting stored_vcol to ' . stored_vcol) elseif stack[0] ==# ';' let semicolon_abscol = curr_vcol call s:Log(' Setting semicolon-stored_vcol to ' . stored_vcol) endif let i -= 1 call s:Log(' Token processed. stored_vcol=' . stored_vcol) let last_token_of_line = 0 endwhile " iteration on tokens in a line call s:Log(' Line analyzed. stored_vcol=' . stored_vcol) if empty(stack) && stored_vcol != -1 && \ (!empty(indtokens) && indtokens[0][0] != '' && \ indtokens[0][0] != '') call s:Log(' Empty stack at the beginning of the line -> return') return stored_vcol endif let lnum -= 1 endwhile " iteration on lines endfunction " ErlangIndent function {{{1 " ===================== function! ErlangIndent() call s:ClearTokenCacheIfNeeded() let currline = getline(v:lnum) call s:Log('Indenting line ' . v:lnum . ': ' . currline) if s:IsLineStringContinuation(v:lnum) || s:IsLineAtomContinuation(v:lnum) call s:Log('String or atom continuation found -> ' . \'leaving indentation unchanged') return -1 endif " If the line starts with the comment, and so is the previous non-blank line if currline =~# '^\s*%' let lnum = prevnonblank(v:lnum - 1) if lnum ==# 0 call s:Log('First non-empty line of the file -> return 0.') return 0 else let ml = matchlist(getline(lnum), '^\(\s*\)%') " If the previous line also starts with a comment, then return the same " indentation that line has. Otherwise exit from this special "if" and " don't care that the current line is a comment. if !empty(ml) let new_col = s:CalcVCol(ml[1], 0, len(ml[1]) - 1, 0, &tabstop) call s:Log('Comment line after another comment line -> ' . \'use same indent: ' . new_col) return new_col endif endif endif let ml = matchlist(currline, \'^\(\s*\)\(\%(end\|of\|catch\|after\|else\)\>\|[)\]}]\|>>\)') " If the line has a special beginning, but not a standalone catch if !empty(ml) && !(ml[2] ==# 'catch' && s:IsCatchStandalone(v:lnum, 0)) let curr_col = len(ml[1]) " If we can be sure that there is synchronization in the Erlang " syntax, we use searchpair to make the script quicker. if ml[2] ==# 'end' && exists('b:erlang_syntax_synced') let [lnum, col] = s:SearchEndPair(v:lnum, curr_col) if lnum ==# 0 return s:IndentError('Matching token for "end" not found', \'end', []) else call s:Log(' Tokenize for "end" <<<<') let [lnum, indtokens] = s:TokenizeLine(lnum, 'up') call s:Log(' >>>> Tokenize for "end"') let [success, i] = s:GetIndtokenAtCol(indtokens, col) if !success | return i | endif let [token, curr_vcol, curr_col] = indtokens[i] call s:Log(' Match for "end" in line ' . lnum . ': ' . \string(indtokens[i])) return curr_vcol endif else call s:Log(" Line type = 'end'") let new_col = s:ErlangCalcIndent(v:lnum - 1, \[ml[2], 'align_to_begin_element']) endif else call s:Log(" Line type = 'normal'") let new_col = s:ErlangCalcIndent(v:lnum - 1, []) if currline =~# '^\s*when\>' let new_col += 2 endif endif if new_col < -1 call s:Log('WARNING: returning new_col == ' . new_col) return g:erlang_unexpected_token_indent endif return new_col endfunction " ErlangShowTokensInLine functions {{{1 " ================================ " These functions are useful during development. function! ErlangShowTokensInLine(line) echo "Line: " . a:line let indtokens = s:GetTokensFromLine(a:line, 0, 0, &tabstop) echo "Tokens:" for it in indtokens echo it endfor endfunction function! ErlangShowTokensInCurrentLine() return ErlangShowTokensInLine(getline('.')) endfunction " Cleanup {{{1 " ======= let &cpo = s:cpo_save unlet s:cpo_save " vim: sw=2 et fdm=marker