123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169open!Baseopen!AsyncincludestructopenNottymoduleUnescape=UnescapemoduleTmachine=TmachineendmoduleWinch_listener=structletwaiting=ref[]externalwinch_number:unit->int="caml_notty_winch_number"[@@noalloc]letsigwinch=Async.Signal.of_caml_int(winch_number())letsetup_winch=lazy(Signal.handle[sigwinch]~f:(fun(_:Signal.t)->List.iter!waiting~f:(funi->Ivar.filli());waiting:=[]))letwinch()=forcesetup_winch;leti=Ivar.create()inwaiting:=i::!waiting;Ivar.readiendmoduleTerm=structletbsize=1024(* Call [f] function repeatedly as input is received from the
stream. *)letinput_pipe~nosigreader=let`Revertrevert=letfd=Unix.Fd.file_descr_exn(Reader.fdreader)inNotty_unix.Private.setup_tcattr~nosigfdinletflt=Notty.Unescape.create()inletibuf=Bytes.createbsizeinlet(r,w)=Pipe.create()inletrecloop()=matchUnescape.nextfltwith|#Unescape.eventasr->(* As long as there are events to read without blocking, dump
them all into the pipe. *)ifPipe.is_closedwthenreturn()else(Pipe.write_without_pushbackwr;loop())|`End->return()|`Await->(* Don't bother issuing a new read until the pipe has space to
write *)let%bind()=Pipe.pushbackwinmatch%bindReader.readreaderibufwith|`Eof->return()|(`Okn)->Unescape.inputfltibuf0n;loop()in(* Some error handling to make sure that we call revert if the pipe fails *)letmonitor=Monitor.create~here:[%here]~name:"Notty input pipe"()indon't_wait_for(Deferred.ignore_m(Monitor.get_next_errormonitor)>>|revert);don't_wait_for(Scheduler.within'~monitorloop);don't_wait_for(Pipe.closedr>>|revert);rtypet={writer:Writer.t;tmachine:Tmachine.t;buf:Buffer.t;fds:Fd.t*Fd.t;events:[Unescape.event|`Resizeof(int*int)]Pipe.Reader.t;stop:(unit->unit)}letwritet=Buffer.cleart.buf;Tmachine.outputt.tmachinet.buf;Writer.writet.writer(Buffer.contentst.buf);Writer.flushedt.writerletrefresht=Tmachine.refresht.tmachine;writetletimagetimage=Tmachine.imaget.tmachineimage;writetletcursortcurs=Tmachine.cursort.tmachinecurs;writetletset_sizetdim=Tmachine.set_sizet.tmachinedimletsizet=Tmachine.sizet.tmachineletreleaset=ifTmachine.releaset.tmachinethen(t.stop();writet)elsereturn()letresize_pipe_and_update_tmachinetmachinewriter=let(r,w)=Pipe.create()indon't_wait_for(match%bindUnix.isatty(Writer.fdwriter)with|false->Pipe.closew;return()|true->letrecloop()=let%bind()=Winch_listener.winch()inmatchFd.with_file_descr(Writer.fdwriter)Notty_unix.winsizewith|`Already_closed|`Error_->return()|`Oksize->matchsizewith|None->(* Note 100% clear that this is the right behavior,
since it's not clear why one would receive None from
winsize at all. In any case, causing further resizes
should cause an app to recover if there's a temporary
inability to read the size. *)loop()|Somesize->ifPipe.is_closedwthenreturn()else(Tmachine.set_sizetmachinesize;let%bind()=Pipe.writew(`Resizesize)inloop())inlet%map()=loop()inPipe.closew);rletcreate?(dispose=true)?(nosig=true)?(mouse=true)?(bpaste=true)?(reader=(forceReader.stdin))?(writer=(forceWriter.stdout))()=let(cap,size)=Fd.with_file_descr_exn(Writer.fdwriter)(funfd->(Notty_unix.Private.cap_for_fdfd,Notty_unix.winsizefd))inlettmachine=Tmachine.create~mouse~bpastecapinletinput_pipe=input_pipe~nosigreaderinletresize_pipe=resize_pipe_and_update_tmachinetmachinewriterinletevents=Pipe.interleave[input_pipe;resize_pipe]inletstop()=Pipe.close_readeventsinletbuf=Buffer.create4096inletfds=(Reader.fdreader,Writer.fdwriter)inlett={tmachine;writer;events;stop;buf;fds}inOption.itersize~f:(set_sizet);ifdisposethenShutdown.at_shutdown(fun()->releaset);don't_wait_for(let%bind()=Pipe.closedeventsinreleaset);let%map()=writetintleteventst=t.eventsendincludeNotty_unix.Private.Gen_output(structtypefd=Writer.tlazy_tandk=unitDeferred.tletdef=Writer.stdoutletto_fdw=matchFd.with_file_descr(Writer.fd(forcew))Fn.idwith|`Already_closed|`Error_->raise_s[%message"Couldn't obtain FD"]|`Okx->xletwrite(((lazyw)))buf=letbytes=Buffer.contents_bytesbufinWriter.write_byteswbytes~pos:0~len:(Bytes.lengthbytes);Writer.flushedw[@@ocaml.warning"-68"]end)