123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174open!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(* CR yminsky: Maybe turn this into just ac all that just reads the next
input, rather than one that creates a pipe. *)(* 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(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=(* CR yminsky: Here, we're defeating the purpose of Async's idioms
for avoiding operating on closed FDs. To do something better,
we'd need to adjust the API of Notty's Gen_output functor, or
replicate it. *)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.flushedwend)