123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914(* Copyright (c) 2016-2017 David Kaloper Meršinjak. All rights reserved.
See LICENSE.md. *)letinvalid_argfmt=Format.kasprintfinvalid_argfmtlet(&.)fgx=f(gx)letbtw(x:int)ab=a<=x&&x<=bletbitnb=bland(1lsln)>0letmax(a:int)b=ifa>bthenaelsebletmin(a:int)b=ifa<bthenaelsebletis_C0x=x<0x20||x=0x7fandis_C1x=0x80<=x&&x<0xa0letis_ctrlx=is_C0x||is_C1xandis_asciix=x<0x80letrecconcatmz(@)xs=letrecaccum(@)=function|[]|[_]asxs->xs|a::b::xs->(a@b)::accum(@)xsinmatchxswith[]->z|[x]->x|xs->concatmz(@)(accum(@)xs)letreclinspcmz(@)xnf=matchnwith|0->z|1->fx|_->letm=n/2inlinspcmz(@)xmf@linspcmz(@)(x+m)(n-m)fletmemo(typea)?(hash=Hashtbl.hash)?(eq=(=))~sizef=letmoduleH=Ephemeron.K1.Make(structtypet=alet(hash,equal)=(hash,eq)end)inlett=H.createsizeinfunx->tryH.findtxwithNot_found->lety=fxinH.addtxy;ymoduleBuffer=structincludeBufferletbuf=Buffer.create1024letmkstringf=fbuf;letres=contentsbufinresetbuf;resletadd_decimalb=function|xwhenbtwx0999->letd1=x/100andd2=(xmod100)/10andd3=xmod10inifd1>0then0x30+d1|>Char.unsafe_chr|>add_charb;if(d1+d2)>0then0x30+d2|>Char.unsafe_chr|>add_charb;0x30+d3|>Char.unsafe_chr|>add_charb|x->string_of_intx|>add_stringbletadd_charsbcn=for_=1tondoadd_charbcdoneendmoduleString=structincludeStringletsub0cpsilen=ifi>0||len<lengthsthensubsilenelsesletof_chars_rev=function|[]->""|[c]->String.make1c|cs->letn=List.lengthcsinletrecgobsi=Bytes.(function|[]->unsafe_to_stringbs|x::xs->unsafe_setbsix;gobs(predi)xs)ingo(Bytes.createn)(n-1)csendmoduleOption=structletmapf=functionSomex->Some(fx)|_->Noneletgetdef=functionSomex->x|_->defletto_list=functionSomex->[x]|_->[]let(>>|)af=mapfalet(>>=)af=matchawithSomex->fx|_->NoneendmoduleText=structleterr_ctrlu=invalid_arg"Notty: control char: U+%02X, %S"(Char.codeu)leterr_malformed=invalid_arg"Notty: malformed UTF-8: %s, %S"typet=|Asciiofstring*int*int|Utf8ofstring*intarray*int*intletequalt1t2=match(t1,t2)with|(Utf8(s1,_,i1,n1),Utf8(s2,_,i2,n2))|(Ascii(s1,i1,n1),Ascii(s2,i2,n2))->i1=i2&&n1=n2&&s1=s2|_->falseletwidth=functionUtf8(_,_,_,w)->w|Ascii(_,_,w)->wletempty=Ascii("",0,0)letis_emptyt=widtht=0letgraphemesstr=letmoduleUuseg=Notty_grapheme_clusterinletseg=Uuseg.create()inletrecf(is,wasacc)ievt=matchUuseg.addsegevtwith|`Await|`End->acc|`Ucharu->f(is,w+Notty_uucp.tty_width_hintu)i`Await|`Boundary->letis=matchwwith0->is|1->i::is|_->i::(-1)::isinf(is,0)i`Awaitinletacc=Uutf.String.fold_utf_8(funacci->function|`Malformederr->err_malformederrstr|`Uchar_asu->facciu)([0],0)strinfacc(String.lengthstr)`End|>fst|>List.rev|>Array.of_list(*XXX*)letdead=' 'letto_bufferbuf=function|Ascii(s,off,w)->Buffer.add_substringbufsoffw|Utf8(s,ix,off,w)->letx1=matchix.(off)with|-1->Buffer.add_charbufdead;ix.(off+1)|x->xandx2=ix.(off+w)inBuffer.add_substringbufsx1@@(ifx2=-1thenix.(off+w-1)elsex2)-x1;ifx2=-1thenBuffer.add_charbufdeadletsubtxw=letw1=widthtinifw=0||x>=w1thenemptyelseletw=minw(w1-x)inifw=w1thentelsematchtwithAscii(s,off,_)->Ascii(s,off+x,w)|Utf8(s,ix,off,_)->Utf8(s,ix,off+x,w)letis_ascii_or_raise_ctrls=let(@!)si=String.unsafe_getsi|>Char.codeinletrecgosaccin=ifn=0thenaccelseletx=s@!iinifis_C0xthenerr_ctrls.[i]selseifx=0xc2&&n>1&&is_C1(s@!(i+1))thenerr_ctrls.[i+1]selsegos(acc&&is_asciix)(i+1)(n-1)ingostrue0(String.lengths)letof_asciis=Ascii(s,0,String.lengths)andof_unicodes=letx=graphemessinUtf8(s,x,0,Array.lengthx-1)letof_unicode=memo~eq:String.equal~size:128of_unicodeletof_string=function|""->empty|s->ifis_ascii_or_raise_ctrlsthenof_asciiselseof_unicodesletof_ucharsucs=of_string@@Buffer.mkstring@@funbuf->Array.iter(Buffer.add_utf_8_ucharbuf)ucsletreplicateuwu=ifis_ctrl(Uchar.to_intu)thenerr_ctrl(Uchar.unsafe_to_charu)"<repeated character>"elseifw<1thenemptyelseifis_ascii(Uchar.to_intu)thenof_ascii(String.makew(Uchar.unsafe_to_charu))elseof_unicode@@Buffer.mkstring@@funbuf->for_=1towdoBuffer.add_utf_8_ucharbufudoneletreplicatecwc=replicateuw(Uchar.of_charc)endmoduleA=structtypecolor=inttypestyle=inttypet={fg:color;bg:color;st:style}letequalt1t2=t1.fg=t2.fg&&t1.bg=t2.bg&&t1.st=t2.stletblack=0x01000000andred=0x01000001andgreen=0x01000002andyellow=0x01000003andblue=0x01000004andmagenta=0x01000005andcyan=0x01000006andwhite=0x01000007andlightblack=0x01000008andlightred=0x01000009andlightgreen=0x0100000aandlightyellow=0x0100000bandlightblue=0x0100000candlightmagenta=0x0100000dandlightcyan=0x0100000eandlightwhite=0x0100000flettagc=(cland0x03000000)lsr24letrgb~r~g~b=ifr<0||g<0||b<0||r>5||g>5||b>5theninvalid_arg"Notty.A.rgb %d %d %d: channel out of range"rgbelse0x01000000lor(r*36+g*6+b+16)letgraylevel=iflevel<0||level>23theninvalid_arg"Notty.A.gray %d: level out of range"levelelse0x01000000lor(level+232)letrgb_888~r~g~b=ifr<0||g<0||b<0||r>255||g>255||b>255theninvalid_arg"Notty.A.rgb_888 %d %d %d: channel out of range"rgbelse0x02000000lor((rlsl16)lor(glsl8)lorb)letix=xland0xffandrx=xlsr16land0xffandgx=xlsr8land0xffandbx=xland0xffletbold=1anditalic=2andunderline=4andblink=8andreverse=16letempty={fg=0;bg=0;st=0}let(++)a1a2=ifa1==emptythena2elseifa2==emptythena1else{fg=(matcha2.fgwith0->a1.fg|x->x);bg=(matcha2.bgwith0->a1.bg|x->x);st=a1.stlora2.st}letfgfg={emptywithfg}letbgbg={emptywithbg}letstst={emptywithst}endmoduleI=structtypedim=int*inttypet=|Empty|SegmentofA.t*Text.t|Hcomposeof(t*t)*dim|Vcomposeof(t*t)*dim|Zcomposeof(t*t)*dim|Hcropof(t*int*int)*dim|Vcropof(t*int*int)*dim|Voidofdimletwidth=function|Empty->0|Segment(_,text)->Text.widthtext|Hcompose(_,(w,_))->w|Vcompose(_,(w,_))->w|Zcompose(_,(w,_))->w|Hcrop(_,(w,_))->w|Vcrop(_,(w,_))->w|Void(w,_)->w[@@inline]letheight=function|Empty->0|Segment_->1|Hcompose(_,(_,h))->h|Vcompose(_,(_,h))->h|Zcompose(_,(_,h))->h|Hcrop(_,(_,h))->h|Vcrop(_,(_,h))->h|Void(_,h)->h[@@inline]letequalt1t2=letreceqt1t2=match(t1,t2)with|(Empty,Empty)->true|(Segment(a1,t1),Segment(a2,t2))->A.equala1a2&&Text.equalt1t2|(Hcompose((a,b),_),Hcompose((c,d),_))|(Vcompose((a,b),_),Vcompose((c,d),_))|(Zcompose((a,b),_),Zcompose((c,d),_))->eqac&&eqbd|(Hcrop((a,i1,n1),_),Hcrop((b,i2,n2),_))|(Vcrop((a,i1,n1),_),Vcrop((b,i2,n2),_))->i1=i2&&n1=n2&&eqab|(Void(a,b),Void(c,d))->a=c&&b=d|_->falseinwidtht1=widtht2&&heightt1=heightt2&&eqt1t2letempty=Emptylet(<|>)t1t2=match(t1,t2)with|(_,Empty)->t1|(Empty,_)->t2|_->letw=widtht1+widtht2andh=max(heightt1)(heightt2)inHcompose((t1,t2),(w,h))let(<->)t1t2=match(t1,t2)with|(_,Empty)->t1|(Empty,_)->t2|_->letw=max(widtht1)(widtht2)andh=heightt1+heightt2inVcompose((t1,t2),(w,h))let(</>)t1t2=match(t1,t2)with|(_,Empty)->t1|(Empty,_)->t2|_->letw=max(widtht1)(widtht2)andh=max(heightt1)(heightt2)inZcompose((t1,t2),(w,h))letvoidwh=ifw<1&&h<1thenEmptyelseVoid(max0w,max0h)letlincropinvcropvoid(++)initfiniimg=match(init>=0,fini>=0)with|(true,true)->cropinitfiniimg|(true,_)->cropinit0img++void(-fini)|(_,true)->void(-init)++crop0finiimg|_->void(-init)++img++void(-fini)lethcrop=letctorleftrightimg=leth=heightimgandw=widthimg-left-rightinifw>0thenHcrop((img,left,right),(w,h))elsevoidwhinlincropinvctor(funw->voidw0)(<|>)letvcrop=letctortopbottomimg=letw=widthimgandh=heightimg-top-bottominifh>0thenVcrop((img,top,bottom),(w,h))elsevoidwhinlincropinvctor(void0)(<->)letcrop?(l=0)?(r=0)?(t=0)?(b=0)img=letimg=ifl<>0||r<>0thenhcroplrimgelseimginift<>0||b<>0thenvcroptbimgelseimglethpadleftrightimg=hcrop(-left)(-right)imgletvpadtopbottomimg=vcrop(-top)(-bottom)imgletpad?(l=0)?(r=0)?(t=0)?(b=0)img=crop~l:(-l)~r:(-r)~t:(-t)~b:(-b)imglethcat=concatmempty(<|>)letvcat=concatmempty(<->)letzcatxs=List.fold_right(</>)xsemptylettextattrtx=ifText.is_emptytxthenvoid01elseSegment(attr,tx)letstringattrs=textattr(Text.of_strings)letucharsattra=textattr(Text.of_ucharsa)lettabulatemnf=letm=maxm0andn=maxn0inlinspcmempty(<->)0n(funy->linspcmempty(<|>)0m(funx->fxy))letcharsctorattrcwh=ifw<1||h<1thenvoidwhelseletline=textattr(ctorwc)intabulate1h(fun__->line)letchar=charsText.replicatecletuchar=charsText.replicateulethsnap?(align=`Middle)wimg=letoff=widthimg-winmatchalignwith|`Left->hcrop0offimg|`Right->hcropoff0img|`Middle->letw1=off/2inhcropw1(off-w1)imgletvsnap?(align=`Middle)himg=letoff=heightimg-hinmatchalignwith|`Top->vcrop0offimg|`Bottom->vcropoff0img|`Middle->leth1=off/2invcroph1(off-h1)imgmoduleFmt=structopenFormattypestag+=AttrofA.tletpushrx=r:=x::!rletpopr=r:=(match!rwith_::xs->xs|_->[])lettop_ar=match!rwitha::_->a|_->A.emptyletcreate()=letimg,line,attr=refempty,refempty,ref[]inletfmt=formatter_of_out_functions{out_flush=(fun()->img:=!img<->!line;line:=empty;attr:=[]);out_newline=(fun()->img:=!img<->!line;line:=void01);out_string=(funsin->line:=!line<|>string(top_aattr)String.(sub0cpsin))(* Not entirely clear; either or both could be void: *);out_spaces=(funw->line:=!line<|>char(top_aattr)' 'w1);out_indent=(funw->line:=!line<|>char(top_aattr)' 'w1)}inpp_set_formatter_stag_functionsfmt{(pp_get_formatter_stag_functionsfmt())withmark_open_stag=(functionAttra->pushattrA.(top_aattr++a);""|_->"");mark_close_stag=(fun_->popattr;"")};pp_set_mark_tagsfmttrue;fmt,fun()->leti=!imginimg:=empty;line:=empty;attr:=[];iletppf,reset=create()letkstrf?(attr=A.empty)?(w=1000000)kformat=letm=ref0inletf1_()=m:=pp_get_marginppf();pp_set_marginppfw;pp_open_stagppf(Attrattr)andk_=pp_print_flushppf();pp_set_marginppf!m;reset()|>kinkfprintfkppf("%a"^^format)f1()letstrf?attr?wformat=kstrf?attr?w(funi->i)formatletattrattrffmtx=pp_open_stagfmt(Attrattr);ffmtx;pp_close_stagfmt()endletkstrf,strf,pp_attr=Fmt.(kstrf,strf,attr)endmoduleOperation=structtypet=End|Skipofint*t|TextofA.t*Text.t*tletskipnk=ifn=0thenkelsematchkwithEnd->End|Skip(m,k)->Skip(m+n,k)|_->Skip(n,k)[@@inline]letrecscanxwrowik=letopenIinmatchiwith|Empty|Void_->skipwk|Segment_whenrow>0->skipwk|Segment(attr,text)->lett=Text.subtextxwinletw1=Text.widthtinletp=ifw>w1thenskip(w-w1)kelsekinifw1>0thenText(attr,t,p)elsep|Hcompose((i1,i2),_)->letw1=widthi1andw2=widthi2inifx>=w1+w2thenskipwkelseifx>=w1thenscan(x-w1)wrowi2kelseifx+w<=w1thenscanxwrowi1kelsescanx(w1-x)rowi1@@scan0(w-w1+x)rowi2@@k|Vcompose((i1,i2),_)->leth1=heighti1andh2=heighti2inifrow>=h1+h2thenskipwkelseifrow>=h1thenscanxw(row-h1)i2kelsescanxwrowi1k|Zcompose((i1,i2),_)->letrecstitchxwrowi=function|End->scanxwrowiEnd|Text(a,t,ops)asopss->letw1=Text.widthtinifw1>=wthenopsselseText(a,t,stitch(x+w1)(w-w1)rowiops)|Skip(w1,ops)->scanxw1rowi@@ifw1>=wthenopselsestitch(x+w1)(w-w1)rowiopsinstitchxwrowi2@@scanxwrowi1@@k|Hcrop((i,left,_),(w1,_))->ifx>=w1thenskipwkelseifx+w<=w1thenscan(x+left)wrowikelsescan(x+left)(w1-x)rowi@@skip(w-w1+x)k|Vcrop((i,top,_),(_,h1))->ifrow<h1thenscanxw(top+row)ikelseskipwkletof_image(x,y)(w,h)i=List.inith(funoff->scanx(x+w)(y+off)iEnd)endmoduleCap=structtypeop=Buffer.t->unitlet(&)op1op2buf=op1buf;op2buftypet={skip:int->op;sgr:A.t->op;newline:op;clreol:op;cursvis:bool->op;cursat:int->int->op;cubcuf:int->op;cuucud:int->op;cr:op;altscr:bool->op;mouse:bool->op;bpaste:bool->op}let((<|),(<.),(<!))=Buffer.(add_string,add_char,add_decimal)letsts=[";1";";3";";4";";5";";7"]letsgr{A.fg;bg;st}buf=buf<|"\x1b[0";letrgb888bufx=buf<!A.rx;buf<.';';buf<!A.gx;buf<.';';buf<!A.bxin(matchA.tagfgwith0->()|1->letc=A.ifginifc<8then(buf<.';';buf<!(c+30))elseifc<16then(buf<.';';buf<!(c+82))else(buf<|";38;5;";buf<!c)|_->buf<|";38;2;";rgb888buffg);(matchA.tagbgwith0->()|1->letc=A.ibginifc<8then(buf<.';';buf<!(c+40))elseifc<16then(buf<.';';buf<!(c+92))else(buf<|";48;5;";buf<!c)|_->buf<|";48;2;";rgb888bufbg);ifst<>0then(letrecgofxs=match(f,xs)with|(0,_)|(_,[])->()|(_,x::xs)->iffland1>0thenbuf<|x;go(flsr1)xsingoststs);buf<.'m'letansi={skip=(funnb->b<|"\x1b[0m";Buffer.add_charsb' 'n);newline=(funb->b<|"\x1bE");altscr=(funxb->b<|ifxthen"\x1b[?1049h"else"\x1b[?1049l");cursat=(funwhb->b<|"\x1b[";b<!h;b<.';';b<!w;b<.'H');cubcuf=(funxb->b<|"\x1b[";b<!absx;b<.ifx<0then'D'else'C');cuucud=(funyb->b<|"\x1b[";b<!absy;b<.ify<0then'A'else'B');cr=(funb->b<|"\x1b[1G");clreol=(funb->b<|"\x1b[K");cursvis=(funxb->b<|ifxthen"\x1b[34h\x1b[?25h"else"\x1b[?25l");mouse=(funxb->b<|ifxthen"\x1b[?1000;1002;1005;1015;1006h"else"\x1b[?1000;1002;1005;1015;1006l");bpaste=(funxb->b<|ifxthen"\x1b[?2004h"else"\x1b[?2004l");sgr}letno0_=()andno1__=()andno2___=()letdumb={skip=(funnb->Buffer.add_charsb' 'n);newline=(funb->b<|"\n");altscr=no1;cursat=no2;cubcuf=no1;cuucud=no1;cr=no0;clreol=no0;cursvis=no1;sgr=no1;mouse=no1;bpaste=no1}leterasecapbuf=cap.sgrA.emptybuf;cap.clreolbuf(* KEEP ETA-LONG. *)letcursat0capwh=cap.cursat(maxw0+1)(maxh0+1)endmoduleRender=structopenCapopenOperationletskip_opcapbufn=cap.skipnbuflettext_opcapbufax=cap.sgrabuf;Text.to_bufferbufxletreclinecapbuf=functionEnd->erasecapbuf|Skip(n,End)->erasecapbuf;skip_opcapbufn|Text(a,x,End)->erasecapbuf;text_opcapbufax|Skip(n,ops)->skip_opcapbufn;linecapbufops|Text(a,x,ops)->text_opcapbufax;linecapbufopsletreclinescapbuf=function[]->()|[ln]->linecapbufln;cap.sgrA.emptybuf|ln::lns->linecapbufln;cap.newlinebuf;linescapbuflnsletto_bufferbufcapoffdimimg=Operation.of_imageoffdimimg|>linescapbufletppcapppfimg=letopenFormatinletbuf=Buffer.create(I.widthimg*2)inleth,w=I.(heightimg,widthimg|>min(pp_get_marginppf()))inletimg=I.(img</>vpad(h-1)0(charA.empty' 'w1))inpp_open_vboxppf0;fory=0toh-1doBuffer.clearbuf;to_bufferbufcap(0,y)(w,1)img;pp_print_asppfw(Buffer.contentsbuf);ify<h-1thenpp_print_cutppf()done;pp_close_boxppf()letpp_image=ppCap.ansiletpp_attrppfa=letstring_=I.stringA.emptyinpp_imageppfI.(string_"<"<|>stringa"ATTR"<|>string_">")endmoduleUnescape=structtypespecial=[`Escape|`Enter|`Tab|`Backspace|`Insert|`Delete|`Home|`End|`Arrowof[`Up|`Down|`Left|`Right]|`Pageof[`Up|`Down]|`Functionofint]typebutton=[`Left|`Middle|`Right|`Scrollof[`Up|`Down]]typemods=[`Meta|`Ctrl|`Shift]listtypekey=[special|`UcharofUchar.t|`ASCIIofchar]*modstypemouse=[`Pressofbutton|`Drag|`Release]*(int*int)*modstypepaste=[`Start|`End]typeevent=[`Keyofkey|`Mouseofmouse|`Pasteofpaste]typeesc=C0ofchar|C1ofchar|SS2ofchar|CSIofstring*intlist*char|Esc_Mofint*int*int|UcharofUchar.tletuchar=function`Ucharu->u|`ASCIIc->Uchar.of_charcletcsi=letopenOptioninletrecprivacc=function|x::xswhenbtwx0x3c0x3f->priv(Char.unsafe_chrx::acc)xs|xs->param(String.of_chars_revacc)None[]xsandparamprvpps=function|x::xswhenbtwx0x300x39->paramprv(Some(get0p*10+x-0x30))psxs|0x3b::xs->paramprvNone(get0p::ps)xs|xs->codeprv(List.rev(to_listp@ps))xsandcodeprvps=function(* Conflate two classes because urxvt... *)|x::xswhenbtwx0x200x2f||btwx0x400x7e->Some(CSI(prv,ps,(Char.chrx)),xs)|_->Noneinpriv[]letrecdemux=letchr=Char.chrinfunction|0x1b::0x5b::0x4d::a::b::c::xs->Esc_M(a,b,c)::demuxxs|0x1b::0x5b::xs|0x9b::xs->let(r,xs)=csixs|>Option.get(C1'\x5b',xs)inr::demuxxs|0x1b::0x4f::x::xs|0x8f::x::xswhenis_asciix->SS2(chrx)::demuxxs|0x1b::x::xswhenis_C1(x+0x40)->C1(chrx)::demuxxs|x::xswhenis_C1x->C1(chr(x-0x40))::demuxxs|x::xswhenis_C0x->C0(chrx)::demuxxs|x::xs->Uchar(Uchar.unsafe_of_intx)::demuxxs|[]->[]letxtrm_mod_flags=function|2->Some[`Shift]|3->Some[`Meta]|4->Some[`Shift;`Meta]|5->Some[`Ctrl]|6->Some[`Shift;`Ctrl]|7->Some[`Meta;`Ctrl]|8->Some[`Shift;`Meta;`Ctrl]|_->Noneletmods_xtrm=function|[1;p]->xtrm_mod_flagsp|[]->Some[]|_->Noneletmods_rxvt=function|'~'->Some[]|'$'->Some[`Shift]|'^'->Some[`Ctrl]|'@'->Some[`Ctrl;`Shift]|_->Noneletmods_commonpscode=match(ps,code)with|([],'~')->Some[]|([],c)->mods_rxvtc|([p],'~')->xtrm_mod_flagsp|_->Noneletmouse_pp=letbtn=matchpland3with|0whenbit6p->`Scroll`Up|0->`Left|1whenbit6p->`Scroll`Down|1->`Middle|2whenbit6p->`ALL(* `Scroll `Left *)|2->`Right|3whenbit6p->`ALL(* `Scroll `Right *)|_->`ALLanddrag=bit5pandmods=(ifbit3pthen[`Meta]else[])@(ifbit4pthen[`Ctrl]else[])in(btn,drag,mods)letkeykmods=Some(`Key(k,mods))letevent_of_control_code=letopenOptioninfunction|UcharuwhenUchar.to_intu|>is_ascii->Some(`Key(`ASCII(Uchar.unsafe_to_charu),[]))|Ucharu->Some(`Key(`Ucharu,[]))|C0'\x1b'->key`Escape[]|C0('\b'|'\x7f')->key`Backspace[]|C0'\n'->key`Enter[]|C0'\t'->key`Tab[]|C0x->key(`ASCIIChar.(codex+0x40|>unsafe_chr))[`Ctrl]|C1x->key(`ASCIIx)[`Meta]|CSI("",[],'Z')->key`Tab[`Shift]|CSI("",p,'A')->mods_xtrmp>>=key(`Arrow`Up)|CSI("",p,'B')->mods_xtrmp>>=key(`Arrow`Down)|CSI("",p,'C')->mods_xtrmp>>=key(`Arrow`Right)|CSI("",p,'D')->mods_xtrmp>>=key(`Arrow`Left)|CSI("",[],'a')->key(`Arrow`Up)[`Shift]|CSI("",[],'b')->key(`Arrow`Down)[`Shift]|CSI("",[],'c')->key(`Arrow`Right)[`Shift]|CSI("",[],'d')->key(`Arrow`Left)[`Shift]|SS2('A'|'a')->key(`Arrow`Up)[`Ctrl]|SS2('B'|'b')->key(`Arrow`Down)[`Ctrl]|SS2('C'|'c')->key(`Arrow`Right)[`Ctrl]|SS2('D'|'d')->key(`Arrow`Left)[`Ctrl]|CSI("",5::p,c)->mods_commonpc>>=key(`Page`Up)|CSI("",6::p,c)->mods_commonpc>>=key(`Page`Down)|CSI("",2::p,c)->mods_commonpc>>=key`Insert|CSI("",3::p,c)->mods_commonpc>>=key`Delete|CSI("",[4],'h')->key`Insert[]|CSI("",[],'L')->key`Insert[`Ctrl]|CSI("",[],'P')->key`Delete[]|CSI("",[],'M')->key`Delete[`Ctrl]|CSI("",p,'H')->mods_xtrmp>>=key`Home|CSI("",[7|1],c)->mods_rxvtc>>=key`Home|CSI("",p,'F')->mods_xtrmp>>=key`End|CSI("",[8|4],c)->mods_rxvtc>>=key`End|CSI("",[],'J')->key`End[`Ctrl]|SS2('P'..'S'asc)->key(`Function(Char.codec-0x4f))[]|CSI("",p,('P'..'S'asc))->mods_xtrmp>>=key(`Function(Char.codec-0x4f))|CSI("",k::p,c)whenbtwk1115||btwk1721||btwk2326->mods_commonpc>>=key(`Function((k-10)-(k-10)/6))|CSI("<",[p;x;y],('M'|'m'asc))->let(btn,drag,mods)=mouse_ppin(match(c,btn,drag)with|('M',(#buttonasb),false)->Some(`Pressb)|('M',#button,true)->Some`Drag|('m',#button,false)->Some`Release(* | ('M', `ALL , true) -> Some `Move *)|_->None)>>|fune->`Mouse(e,(x-1,y-1),mods)|CSI("",[p;x;y],'M')|Esc_M(p,x,y)asevt->let(x,y)=matchevtwithEsc_M_->x-32,y-32|_->x,yand(btn,drag,mods)=mouse_p(p-32)in(match(btn,drag)with|(#buttonasb,false)->Some(`Pressb)|(#button,true)->Some`Drag|(`ALL,false)->Some`Release(* | (`ALL , true) -> Some `Move *)|_->None)>>|fune->`Mouse(e,(x-1,y-1),mods)|CSI("",[200],'~')->Some(`Paste`Start)|CSI("",[201],'~')->Some(`Paste`End)|CSI_|SS2_->Noneletrecevents=function|C0'\x1b'::cc::ccs->(matchevent_of_control_codeccwith|Some(`Key(k,mods))->`Key(k,`Meta::mods)::eventsccs|Some_->`Key(`Escape,[])::events(cc::ccs)|None->eventsccs)|cc::ccs->(event_of_control_codecc|>Option.to_list)@eventsccs|[]->[]letdecode=events&.demux&.List.mapUchar.to_inttypet=(eventlist*bool)refletcreate()=ref([],false)letnextt=match!twith|(#eventase::es,eof)->t:=(es,eof);e|([],false)->`Await|_->`Endletlist_of_utf8bufil=letfcs_=function`Ucharc->c::cs|_->csinString.sub0cp(Bytes.unsafe_to_stringbuf)il|>Uutf.String.fold_utf_8f[]|>List.revletinputtbufil=t:=match!twith|(es,false)whenl>0->(es@(list_of_utf8bufil|>decode),false)|(es,_)->(es,true)letpendingt=match!twith([],false)->false|_->trueendmoduleTmachine=structopenCap(* XXX This is sad. This should be a composable, stateless transducer. *)typet={cap:Cap.t;mutablewrite:Buffer.t->unit;mutablecurs:(int*int)option;mutabledim:(int*int);mutableimage:I.t;mutabledead:bool}letemittop=ift.deadtheninvalid_arg"Notty: use of released terminal"elset.write<-t.write&opletcursorcap=function|None->cap.cursvisfalse|Some(w,h)->cap.cursvistrue&cursat0capwhletcreate~mouse~bpastecap={cap;curs=None;dim=(0,0);image=I.empty;dead=false;write=cap.altscrtrue&cursorcapNone&cap.mousemouse&cap.bpastebpaste}letreleaset=ift.deadthenfalseelse(emitt(t.cap.altscrfalse&t.cap.cursvistrue&t.cap.mousefalse&t.cap.bpastefalse);t.dead<-true;true)letoutputtbuf=t.writebuf;t.write<-ignoreletrefresh({dim;image;_}ast)=emitt(cursort.capNone&cursat0t.cap00&(funbuf->Render.to_bufferbuft.cap(0,0)dimimage)&cursort.capt.curs)letset_sizetdim=t.dim<-dimletimagetimage=t.image<-image;refreshtletcursortcurs=t.curs<-curs;emitt(cursort.capcurs)letsizet=t.dimletdeadt=t.deadendmoduleDirect=structletshow_cursorbufcapx=cap.Cap.cursvisxbufandmove_cursorbufcapcmd=matchcmdwith|`To(w,h)->Cap.cursat0capwhbuf|`Home->cap.Cap.crbuf|`By(x,y)->Cap.(ifx<>0thencap.cubcufxbuf;ify<>0thencap.cuucudybuf)endtypeattr=A.ttypeimage=I.tmoduleInfix=structlet((<->),(<|>),(</>))=I.((<->),(<|>),(</>))let(++)=A.(++)end