1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393(*
* lTerm.ml
* --------
* Copyright : (c) 2011, Jeremie Dimino <jeremie@dimino.org>
* Licence : BSD3
*
* This file is a part of Lambda-Term.
*)openCamomileLibraryDefault.CamomileopenLwt_reactopenLTerm_geomletreturn,(>>=)=Lwt.return,Lwt.(>>=)letuspace=UChar.of_char' '(* +-----------------------------------------------------------------+
| TTYs sizes |
+-----------------------------------------------------------------+ *)externalget_size_from_fd:Unix.file_descr->size="lt_term_get_size_from_fd"externalset_size_from_fd:Unix.file_descr->size->unit="lt_term_set_size_from_fd"letget_size_from_fdfd=Lwt_unix.check_descriptorfd;get_size_from_fd(Lwt_unix.unix_file_descrfd)letset_size_from_fdfdsize=Lwt_unix.check_descriptorfd;set_size_from_fd(Lwt_unix.unix_file_descrfd)size(* +-----------------------------------------------------------------+
| The terminal type |
+-----------------------------------------------------------------+ *)exceptionNot_a_ttylet()=Printexc.register_printer(function|Not_a_tty->Some"terminal is not a tty"|_->None)moduleInt_map=Map.Make(structtypet=intletcompareab=a-bend)typet={model:string;colors:int;windows:bool;bold_is_bright:bool;color_map:LTerm_color_mappings.map;(* Informations. *)mutableraw_mode:bool;(* Whether the terminal is currently in raw mode. *)mutableincoming_fd:Lwt_unix.file_descr;mutableoutgoing_fd:Lwt_unix.file_descr;(* File descriptors. *)mutableic:Lwt_io.input_channel;mutableoc:Lwt_io.output_channel;(* Channels. *)mutableinput_stream:charLwt_stream.t;(* Stream of characters read from the terminal. *)mutablenext_event:LTerm_event.tLwt.toption;(* Thread reading the next event from the terminal. We cannot cancel
the reading of an event, so we keep the last thread to reuse it
in case the user cancels [read_event]. *)mutableread_event:bool;(* Whether a thread is currently reading an event. *)mutablelast_reported_size:size;(* The last size reported by [read_event]. *)mutablesize:size;(* The current size of the terminal. *)incoming_encoding:CharEncoding.t;outgoing_encoding:CharEncoding.t;(* Characters encodings. *)outgoing_is_utf8:bool;(* Whether the outgoing encoding is UTF-8. *)notify:LTerm_event.tLwt_condition.t;(* Condition used to send a spontaneous event. *)mutableevent:unitevent;(* Event which handles SIGWINCH. *)mutableincoming_is_a_tty:bool;mutableoutgoing_is_a_tty:bool;(* Whether input/output are tty devices. *)mutableescape_time:float;(* Time to wait before returning the escape key. *)}(* +-----------------------------------------------------------------+
| Signals |
+-----------------------------------------------------------------+ *)letresize_event,send_resize=E.create()letsend_resize()=send_resize()let()=matchLTerm_unix.sigwinchwith|None->(* Check for size when something happen. *)ignore(LTerm_dlist.add_lsend_resize(LTerm_dlist.create()))|Somesignum->tryignore(Lwt_unix.on_signalsignum(fun_->send_resize()))withNot_found->ignore(LTerm_dlist.add_lsend_resize(LTerm_dlist.create()))(* +-----------------------------------------------------------------+
| Creation |
+-----------------------------------------------------------------+ *)letdefault_model=trySys.getenv"TERM"withNot_found->"dumb"letcolors_of_term=function|"Eterm-256color"->256|"Eterm-88color"->88|"gnome-256color"->256|"iTerm.app"->256|"konsole-256color"->256|"mlterm-256color"->256|"mrxvt-256color"->256|"putty-256color"->256|"rxvt-256color"->256|"rxvt-88color"->88|"rxvt-unicode-256color"->256|"rxvt-unicode"->88|"screen-256color"->256|"screen-256color-bce"->256|"screen-256color-bce-s"->256|"screen-256color-s"->256|"st-256color"->256|"vte-256color"->256|"xterm-256color"->256|"xterm+256color"->256|"xterm-88color"->88|"xterm+88color"->88|_->16exceptionNo_such_encodingofstringletchar_encoding_of_namename=tryCharEncoding.of_namenamewithNot_found->raise(No_such_encodingname)(* UTF-8 on windows. *)let()=CharEncoding.alias"CP65001""UTF-8"letempty_stream=Lwt_stream.from(fun()->returnNone)letcreate?(windows=Sys.win32)?(model=default_model)?incoming_encoding?outgoing_encodingincoming_fdincoming_channeloutgoing_fdoutgoing_channel=Lwt.catch(fun()->(* Colors stuff. *)letcolors=ifwindowsthen16elsecolors_of_termmodelinletbold_is_bright=matchmodelwith|"linux"(* The linux frame buffer *)|"xterm-color"(* The MacOS-X terminal *)->true|_->falseinletcolor_map=matchcolorswith|16->LTerm_color_mappings.colors_16|88->LTerm_color_mappings.colors_88|256->LTerm_color_mappings.colors_256|n->Printf.ksprintffailwith"LTerm.create: unknown number of colors (%d)"nin(* Encodings. *)letincoming_encoding=char_encoding_of_name(matchincoming_encodingwith|Somename->name|None->ifwindowsthenPrintf.sprintf"CP%d"(LTerm_windows.get_console_cp())elseLTerm_unix.system_encoding)andoutgoing_encoding=char_encoding_of_name(matchoutgoing_encodingwith|Somename->name|None->ifwindowsthenPrintf.sprintf"CP%d"(LTerm_windows.get_console_output_cp())elseLTerm_unix.system_encoding)in(* Check if fds are ttys. *)Lwt_unix.isattyincoming_fd>>=funincoming_is_a_tty->Lwt_unix.isattyoutgoing_fd>>=funoutgoing_is_a_tty->(* Create the terminal. *)letterm={model;colors;windows;bold_is_bright;color_map;raw_mode=false;incoming_fd;outgoing_fd;ic=incoming_channel;oc=outgoing_channel;input_stream=empty_stream;next_event=None;read_event=false;incoming_encoding;outgoing_encoding;outgoing_is_utf8=CharEncoding.name_ofoutgoing_encoding="UTF-8";notify=Lwt_condition.create();event=E.never;incoming_is_a_tty;outgoing_is_a_tty;escape_time=0.1;size={rows=0;cols=0};last_reported_size={rows=0;cols=0};}interm.input_stream<-Lwt_stream.from(fun()->Lwt_io.read_char_optterm.ic);(* Setup initial size and size updater. *)ifterm.outgoing_is_a_ttythenbeginletcheck_size()=letsize=get_size_from_fdterm.outgoing_fdinifsize<>term.sizethenbeginterm.size<-size;Lwt_condition.signalterm.notify(LTerm_event.Resizesize)endinterm.size<-get_size_from_fdterm.outgoing_fd;term.last_reported_size<-term.size;term.event<-E.mapcheck_sizeresize_eventend;returnterm)Lwt.failletset_io?incoming_fd?incoming_channel?outgoing_fd?outgoing_channelterm=letgetoptx=matchoptwith|Somex->x|None->xinletincoming_fd=getincoming_fdterm.incoming_fdandoutgoing_fd=getoutgoing_fdterm.outgoing_fdandincoming_channel=getincoming_channelterm.icandoutgoing_channel=getoutgoing_channelterm.ocin(* Check if fds are ttys. *)Lwt_unix.isattyincoming_fd>>=funincoming_is_a_tty->Lwt_unix.isattyoutgoing_fd>>=funoutgoing_is_a_tty->(* Apply changes. *)term.incoming_fd<-incoming_fd;term.outgoing_fd<-outgoing_fd;term.ic<-incoming_channel;term.oc<-outgoing_channel;term.incoming_is_a_tty<-incoming_is_a_tty;term.outgoing_is_a_tty<-outgoing_is_a_tty;return()letmodelt=t.modelletcolorst=t.colorsletwindowst=t.windowsletis_a_ttyt=t.incoming_is_a_tty&&t.outgoing_is_a_ttyletincoming_is_a_ttyt=t.incoming_is_a_ttyletoutgoing_is_a_ttyt=t.outgoing_is_a_ttyletescape_timet=t.escape_timeletset_escape_timettime=t.escape_time<-timeletsizeterm=ifterm.outgoing_is_a_ttythenbeginletsize=get_size_from_fdterm.outgoing_fdinifsize<>term.sizethenbeginterm.size<-size;Lwt_condition.signalterm.notify(LTerm_event.Resizesize)end;sizeendelseraiseNot_a_ttyletget_sizeterm=Lwt.catch(fun()->return(sizeterm))Lwt.failletset_size__=Lwt.fail(Failure"LTerm.set_size is deprecated")(* +-----------------------------------------------------------------+
| Events |
+-----------------------------------------------------------------+ *)classoutput_single(cell:UChar.toptionref)=objectmethodputchar=cell:=Somecharmethodflush()=()methodclose_out()=()endletread_charterm=beginLwt_stream.getterm.input_stream>>=funbyte_opt->matchbyte_optwith|Somebyte->returnbyte|None->Lwt.failEnd_of_fileend>>=funfirst_byte->letcell=refNoneinletoutput=newCharEncoding.convert_uchar_outputterm.incoming_encoding(newoutput_singlecell)inletrecloopst=match!cellwith|Somechar->returnchar|None->Lwt_stream.nextst>>=funbyte->assert(output#output(Bytes.make1byte)01=1);output#flush();loopstinLwt.catch(fun()->assert(output#output(Bytes.make1first_byte)01=1);Lwt_stream.parseterm.input_streamloop)(function|CharEncoding.Malformed_code|Lwt_stream.Empty->return(UChar.of_charfirst_byte)|exn->Lwt.failexn)>>=funchar->return(LTerm_event.Key{LTerm_key.control=false;LTerm_key.meta=false;LTerm_key.shift=false;LTerm_key.code=LTerm_key.Charchar;})letrecnext_eventterm=ifterm.windowsthenLTerm_windows.read_console_inputterm.incoming_fd>>=funinput->matchinputwith|LTerm_windows.Resize->ifterm.outgoing_is_a_ttythenletsize=get_size_from_fdterm.outgoing_fdinifsize<>term.sizethenbeginterm.size<-size;return(LTerm_event.Resizesize)endelsenext_eventtermelsenext_eventterm|LTerm_windows.Keykey->return(LTerm_event.Keykey)|LTerm_windows.Mousemouse->letwindow=(LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fd).LTerm_windows.windowinreturn(LTerm_event.Mouse{mousewithLTerm_mouse.row=mouse.LTerm_mouse.row-window.row1;LTerm_mouse.col=mouse.LTerm_mouse.col-window.col1;})elseLTerm_unix.parse_event~escape_time:term.escape_timeterm.incoming_encodingterm.input_streamletwrap_next_eventnext_eventterm=matchterm.next_eventwith|Somethread->thread|None->(* Create a non-cancelable thread. *)letwaiter,wakener=Lwt.wait()interm.next_event<-Somewaiter;(* Connect the [next_event term] thread to [waiter]. *)ignore(Lwt.try_bind(fun()->next_eventterm)(funv->term.next_event<-None;Lwt.wakeupwakenerv;return())(fune->term.next_event<-None;Lwt.wakeup_exnwakenere;return()));waiterletread_eventterm=ifterm.read_eventthenLwt.fail(Failure"LTerm.read_event: cannot read events from two thread at the same time")elseifterm.size<>term.last_reported_sizethenbeginterm.last_reported_size<-term.size;return(LTerm_event.Resizeterm.last_reported_size)endelsebeginterm.read_event<-true;Lwt.finalize(fun()->ifterm.incoming_is_a_ttythenLwt.pick[wrap_next_eventnext_eventterm;Lwt_condition.waitterm.notify]>>=funev->matchevwith|LTerm_event.Resizesize->term.last_reported_size<-size;return(LTerm_event.Resizesize)|ev->returnevelsewrap_next_eventread_charterm)(fun()->term.read_event<-false;return())end(* +-----------------------------------------------------------------+
| Modes |
+-----------------------------------------------------------------+ *)typemode=|Mode_fake|Mode_unixofUnix.terminal_io|Mode_windowsofLTerm_windows.console_modeletenter_raw_modeterm=ifterm.incoming_is_a_ttythenifterm.raw_modethenreturnMode_fakeelseifterm.windowsthenbeginletmode=LTerm_windows.get_console_modeterm.incoming_fdinLTerm_windows.set_console_modeterm.incoming_fd{modewithLTerm_windows.cm_echo_input=false;LTerm_windows.cm_line_input=false;LTerm_windows.cm_mouse_input=true;LTerm_windows.cm_processed_input=false;LTerm_windows.cm_window_input=true;};term.raw_mode<-true;return(Mode_windowsmode)endelsebeginLwt_unix.tcgetattrterm.incoming_fd>>=funattr->Lwt_unix.tcsetattrterm.incoming_fdUnix.TCSAFLUSH{attrwith(* Inspired from Python-3.0/Lib/tty.py: *)Unix.c_brkint=false;Unix.c_inpck=false;Unix.c_istrip=false;Unix.c_ixon=false;Unix.c_csize=8;Unix.c_parenb=false;Unix.c_echo=false;Unix.c_icanon=false;Unix.c_vmin=1;Unix.c_vtime=0;Unix.c_isig=false;}>>=fun()->term.raw_mode<-true;return(Mode_unixattr)endelseLwt.failNot_a_ttyletleave_raw_modetermmode=ifterm.incoming_is_a_ttythenmatchmodewith|Mode_fake->return()|Mode_unixattr->term.raw_mode<-false;Lwt_unix.tcsetattrterm.incoming_fdUnix.TCSAFLUSHattr|Mode_windowsmode->term.raw_mode<-false;LTerm_windows.set_console_modeterm.incoming_fdmode;return()elseLwt.failNot_a_ttyletenable_mouseterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenreturn()elseLwt_io.writeterm.oc"\027[?1000h"elseLwt.failNot_a_ttyletdisable_mouseterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenreturn()elseLwt_io.writeterm.oc"\027[?1000l"elseLwt.failNot_a_tty(* +-----------------------------------------------------------------+
| Cursor |
+-----------------------------------------------------------------+ *)letshow_cursorterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletsize,_=LTerm_windows.get_console_cursor_infoterm.outgoing_fdinLTerm_windows.set_console_cursor_infoterm.outgoing_fdsizetrue;return()endelseLwt_io.writeterm.oc"\027[?25h"elseLwt.failNot_a_ttylethide_cursorterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletsize,_=LTerm_windows.get_console_cursor_infoterm.outgoing_fdinLTerm_windows.set_console_cursor_infoterm.outgoing_fdsizefalse;return()endelseLwt_io.writeterm.oc"\027[?25l"elseLwt.failNot_a_ttyletgototermcoord=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginLwt_io.flushterm.oc>>=fun()->letwindow=(LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fd).LTerm_windows.windowinLTerm_windows.set_console_cursor_positionterm.outgoing_fd{row=window.row1+coord.row;col=window.col1+coord.col;};return()endelsebeginLwt_io.fprintterm.oc"\027[H">>=fun()->(ifcoord.row>0thenLwt_io.fprintfterm.oc"\027[%dB"coord.rowelsereturn())>>=fun()->(ifcoord.col>0thenLwt_io.fprintfterm.oc"\027[%dC"coord.colelsereturn())>>=fun()->return()endelseLwt.failNot_a_ttyletmovetermrowscols=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginLwt_io.flushterm.oc>>=fun()->letpos=(LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fd).LTerm_windows.cursor_positioninLTerm_windows.set_console_cursor_positionterm.outgoing_fd{row=pos.row+rows;col=pos.col+cols;};return()endelsebeginmatchrowswith|nwhenn<0->Lwt_io.fprintfterm.oc"\027[%dA"(-n)|nwhenn>0->Lwt_io.fprintfterm.oc"\027[%dB"n|_->return()end>>=fun()->beginmatchcolswith|nwhenn<0->Lwt_io.fprintfterm.oc"\027[%dD"(-n)|nwhenn>0->Lwt_io.fprintfterm.oc"\027[%dC"n|_->return()endelseLwt.failNot_a_tty(* +-----------------------------------------------------------------+
| Erasing text |
+-----------------------------------------------------------------+ *)letclear_screenterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletinfo=LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fdinlet_=LTerm_windows.fill_console_output_characterterm.outgoing_fduspace(info.LTerm_windows.size.cols*info.LTerm_windows.size.rows){row=0;col=0}inreturn()endelseLwt_io.writeterm.oc"\027[2J"elseLwt.failNot_a_ttyletclear_screen_nextterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletinfo=LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fdinlet_=LTerm_windows.fill_console_output_characterterm.outgoing_fduspace(info.LTerm_windows.size.cols*(info.LTerm_windows.size.rows-info.LTerm_windows.cursor_position.row)+info.LTerm_windows.size.cols-info.LTerm_windows.cursor_position.col)info.LTerm_windows.cursor_positioninreturn()endelseLwt_io.writeterm.oc"\027[J"elseLwt.failNot_a_ttyletclear_screen_prevterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletinfo=LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fdinlet_=LTerm_windows.fill_console_output_characterterm.outgoing_fduspace(info.LTerm_windows.size.cols*info.LTerm_windows.cursor_position.row+info.LTerm_windows.cursor_position.col){row=0;col=0}inreturn()endelseLwt_io.writeterm.oc"\027[1J"elseLwt.failNot_a_ttyletclear_lineterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletinfo=LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fdinlet_=LTerm_windows.fill_console_output_characterterm.outgoing_fduspaceinfo.LTerm_windows.size.cols{row=info.LTerm_windows.cursor_position.row;col=0}inreturn()endelseLwt_io.writeterm.oc"\027[2K"elseLwt.failNot_a_ttyletclear_line_nextterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletinfo=LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fdinlet_=LTerm_windows.fill_console_output_characterterm.outgoing_fduspace(info.LTerm_windows.size.cols-info.LTerm_windows.cursor_position.col)info.LTerm_windows.cursor_positioninreturn()endelseLwt_io.writeterm.oc"\027[K"elseLwt.failNot_a_ttyletclear_line_prevterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletinfo=LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fdinlet_=LTerm_windows.fill_console_output_characterterm.outgoing_fduspaceinfo.LTerm_windows.cursor_position.col{row=info.LTerm_windows.cursor_position.row;col=0}inreturn()endelseLwt_io.writeterm.oc"\027[1K"elseLwt.failNot_a_tty(* +-----------------------------------------------------------------+
| State |
+-----------------------------------------------------------------+ *)letsave_stateterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenreturn()elseLwt_io.writeterm.oc"\027[?1049h"elseLwt.failNot_a_ttyletload_stateterm=ifterm.outgoing_is_a_ttythenifterm.windowsthenreturn()elseLwt_io.writeterm.oc"\027[?1049l"elseLwt.failNot_a_tty(* +-----------------------------------------------------------------+
| String recoding |
+-----------------------------------------------------------------+ *)letvline=UChar.of_char'|'letvlline=UChar.of_char'+'letdlcorner=UChar.of_char'+'leturcorner=UChar.of_char'+'lethuline=UChar.of_char'+'lethdline=UChar.of_char'+'letvrline=UChar.of_char'+'lethline=UChar.of_char'-'letcross=UChar.of_char'+'letulcorner=UChar.of_char'+'letdrcorner=UChar.of_char'+'letquestion=UChar.of_char'?'moduleUNF=UNF.Make(UText)(* Map characters that cannot be encoded to ASCII ones. *)letmap_charchar=matchUChar.codecharwith|0x2500->hline|0x2501->hline|0x2502->vline|0x2503->vline|0x2504->hline|0x2505->hline|0x2506->vline|0x2507->vline|0x2508->hline|0x2509->hline|0x250a->vline|0x250b->vline|0x250c->drcorner|0x250d->drcorner|0x250e->drcorner|0x250f->drcorner|0x2510->dlcorner|0x2511->dlcorner|0x2512->dlcorner|0x2513->dlcorner|0x2514->urcorner|0x2515->urcorner|0x2516->urcorner|0x2517->urcorner|0x2518->ulcorner|0x2519->ulcorner|0x251a->ulcorner|0x251b->ulcorner|0x251c->vrline|0x251d->vrline|0x251e->vrline|0x251f->vrline|0x2520->vrline|0x2521->vrline|0x2522->vrline|0x2523->vrline|0x2524->vlline|0x2525->vlline|0x2526->vlline|0x2527->vlline|0x2528->vlline|0x2529->vlline|0x252a->vlline|0x252b->vlline|0x252c->hdline|0x252d->hdline|0x252e->hdline|0x252f->hdline|0x2530->hdline|0x2531->hdline|0x2532->hdline|0x2533->hdline|0x2534->huline|0x2535->huline|0x2536->huline|0x2537->huline|0x2538->huline|0x2539->huline|0x253a->huline|0x253b->huline|0x253c->cross|0x253d->cross|0x253e->cross|0x253f->cross|0x2540->cross|0x2541->cross|0x2542->cross|0x2543->cross|0x2544->cross|0x2545->cross|0x2546->cross|0x2547->cross|0x2548->cross|0x2549->cross|0x254a->cross|0x254b->cross|0x254c->hline|0x254d->hline|0x254e->vline|0x254f->vline|0x2550->hline|0x2551->vline|_->matchUNF.nfd_decomposecharwith|char::_->ifUChar.codechar<=127thencharelsequestion|[]->questionclassoutput_to_bufferbufres=objectmethodoutputstrofslen=Buffer.add_subbytesbufstrofslen;lenmethodflush()=()methodclose_out()=res:=Buffer.contentsbufendletencode_stringtermstr=ifterm.outgoing_is_utf8then(* Do not recode [str] if the output is UTF-8. *)strelseletbuf=Buffer.create(String.lengthstr)inletres=ref""inletoutput=newCharEncoding.uchar_output_channel_ofterm.outgoing_encoding(newoutput_to_bufferbufres)inletrecloopofs=ifofs=String.lengthstrthenbeginoutput#close_out();!resendelsebeginletch,ofs=Zed_utf8.unsafe_extract_nextstrofsin(tryoutput#putchwithCharEncoding.Out_of_range|UChar.Out_of_range->output#put(map_charch));loopofsendinloop0letencode_chartermch=ifterm.outgoing_is_utf8thenZed_utf8.singletonchelsebeginletres=ref""inletoutput=newCharEncoding.uchar_output_channel_ofterm.outgoing_encoding(newoutput_to_buffer(Buffer.create8)res)in(tryoutput#putchwithCharEncoding.Out_of_range|UChar.Out_of_range->output#put(map_charch));output#close_out();!resend(* +-----------------------------------------------------------------+
| Styled printing |
+-----------------------------------------------------------------+ *)moduleCodes=structletbold=";1"letunderline=";4"letblink=";5"letreverse=";7"letforeground=30letbackground=40endletfprinttermstr=Lwt_io.fprintterm.oc(encode_stringtermstr)letfprintltermstr=fprintterm(str^"\n")letfprintftermfmt=Printf.ksprintf(funstr->fprinttermstr)fmtletfprintlftermfmt=Printf.ksprintf(funstr->fprintltermstr)fmtletadd_intbufn=letrecloop=function|0->()|n->loop(n/10);Buffer.add_charbuf(Char.unsafe_chr(48+(nmod10)))inifn=0thenBuffer.add_charbuf'0'elseloopnletmap_colortermrgb=letopenLTerm_color_mappingsinletmap=term.color_mapin(* The [String.unsafe_get]s are safe because the private type
[LTerm_style.color] ensure that all components are in the range
[0..255]. *)Char.code(String.unsafe_getmap.map(Char.code(String.unsafe_getmap.index_rr)+map.count_r*(Char.code(String.unsafe_getmap.index_gg)+map.count_g*Char.code(String.unsafe_getmap.index_bb))))letadd_indextermbufbasen=ifn<8thenbeginBuffer.add_charbuf';';add_intbuf(base+n)endelseifn<16&&term.bold_is_brightthenifbase=Codes.foregroundthenbeginBuffer.add_stringbuf";1;";add_intbuf(base+n-8)endelsebeginBuffer.add_charbuf';';add_intbuf(base+n-8)endelsebeginBuffer.add_charbuf';';add_intbuf(base+8);Buffer.add_stringbuf";5;";add_intbufnendletadd_colortermbufbase=function|LTerm_style.Default->()|LTerm_style.Indexn->add_indextermbufbasen|LTerm_style.RGB(r,g,b)->add_indextermbufbase(map_colortermrgb)letadd_styletermbufstyle=letopenLTerm_styleinBuffer.add_stringbuf"\027[0";(matchstyle.boldwithSometrue->Buffer.add_stringbufCodes.bold|_->());(matchstyle.underlinewithSometrue->Buffer.add_stringbufCodes.underline|_->());(matchstyle.blinkwithSometrue->Buffer.add_stringbufCodes.blink|_->());(matchstyle.reversewithSometrue->Buffer.add_stringbufCodes.reverse|_->());(matchstyle.foregroundwithSomecolor->add_colortermbufCodes.foregroundcolor|None->());(matchstyle.backgroundwithSomecolor->add_colortermbufCodes.backgroundcolor|None->());Buffer.add_charbuf'm'letexpandtermtext=ifArray.lengthtext=0then""elsebeginletbuf=Buffer.create256inBuffer.add_stringbuf"\027[0m";letrecloopidxprev_style=ifidx=Array.lengthtextthenbeginBuffer.add_stringbuf"\027[0m";Buffer.contentsbufendelsebeginletch,style=Array.unsafe_gettextidxinifnot(LTerm_style.equalstyleprev_style)thenadd_styletermbufstyle;Buffer.add_stringbuf(Zed_utf8.singletonch);loop(idx+1)styleendinloop0LTerm_style.noneendletwindows_fg_colorterm=function|LTerm_style.Default->7|LTerm_style.Indexn->n|LTerm_style.RGB(r,g,b)->map_colortermrgbletwindows_bg_colorterm=function|LTerm_style.Default->0|LTerm_style.Indexn->n|LTerm_style.RGB(r,g,b)->map_colortermrgbletwindows_default_attributes={LTerm_windows.foreground=7;LTerm_windows.background=0}letwindows_attributes_of_styletermstyle=letopenLTerm_styleinifstyle.reverse=Sometruethen{LTerm_windows.foreground=(matchstyle.backgroundwithSomecolor->windows_bg_colortermcolor|None->0);LTerm_windows.background=(matchstyle.foregroundwithSomecolor->windows_fg_colortermcolor|None->7);}else{LTerm_windows.foreground=(matchstyle.foregroundwithSomecolor->windows_fg_colortermcolor|None->7);LTerm_windows.background=(matchstyle.backgroundwithSomecolor->windows_bg_colortermcolor|None->0);}letfprints_windowstermoctext=letrecloopidxprev_attr=ifidx=Array.lengthtextthenbeginLwt_io.flushoc>>=fun()->LTerm_windows.set_console_text_attributeterm.outgoing_fdwindows_default_attributes;return()endelsebeginletch,style=Array.unsafe_gettextidxinletattr=windows_attributes_of_styletermstyleinbeginifattr<>prev_attrthenLwt_io.flushoc>>=fun()->LTerm_windows.set_console_text_attributeterm.outgoing_fdattr;return()elsereturn()end>>=fun()->Lwt_io.writeoc(encode_chartermch)>>=fun()->loop(idx+1)attrendinLwt_io.flushoc>>=fun()->LTerm_windows.set_console_text_attributeterm.outgoing_fdwindows_default_attributes;loop0windows_default_attributesletfprintstermtext=ifterm.outgoing_is_a_ttythenifterm.windowsthenLwt_io.atomic(funoc->fprints_windowstermoctext)term.ocelsefprintterm(expandtermtext)elsefprintterm(LTerm_text.to_stringtext)letfprintlstermtext=fprintsterm(Array.appendtext(LTerm_text.of_string"\n"))(* +-----------------------------------------------------------------+
| Printing with contexts |
+-----------------------------------------------------------------+ *)typecontext={ctx_term:t;ctx_oc:Lwt_io.output_channel;mutablectx_style:LTerm_style.t;mutablectx_attr:LTerm_windows.text_attributes;}letclear_stylestermoc=ifterm.outgoing_is_a_ttythenifterm.windowsthenLwt_io.flushoc>>=fun()->LTerm_windows.set_console_text_attributeterm.outgoing_fdwindows_default_attributes;return()elseLwt_io.writeoc"\027[0m"elsereturn()letwith_contexttermf=Lwt_io.atomic(funoc->letctx={ctx_term=term;ctx_oc=oc;ctx_style=LTerm_style.none;ctx_attr=windows_default_attributes;}inclear_stylestermoc>>=fun()->Lwt.finalize(fun()->fctx)(fun()->clear_stylestermoc))term.ocletupdate_stylectxstyle=ifctx.ctx_term.outgoing_is_a_ttythenbeginifctx.ctx_term.windowsthenbeginletattr=windows_attributes_of_stylectx.ctx_termstyleinifattr<>ctx.ctx_attrthenLwt_io.flushctx.ctx_oc>>=fun()->LTerm_windows.set_console_text_attributectx.ctx_term.outgoing_fdattr;ctx.ctx_attr<-attr;return()elsereturn()endelsebeginifnot(LTerm_style.equalstylectx.ctx_style)thenbeginletbuf=Buffer.create16inadd_stylectx.ctx_termbufstyle;Lwt_io.writectx.ctx_oc(Buffer.contentsbuf)>>=fun()->ctx.ctx_style<-style;return()endelsereturn()endendelsereturn()letcontext_termctx=ctx.ctx_termletcontext_occtx=ctx.ctx_oc(* +-----------------------------------------------------------------+
| Styles setting |
+-----------------------------------------------------------------+ *)letset_styletermstyle=ifterm.outgoing_is_a_ttythenifterm.windowsthenbeginletattr=windows_attributes_of_styletermstyleinLwt_io.atomic(funoc->Lwt_io.flushoc>>=fun()->LTerm_windows.set_console_text_attributeterm.outgoing_fdattr;return())term.ocendelsebeginletbuf=Buffer.create16inadd_styletermbufstyle;Lwt_io.fprintterm.oc(Buffer.contentsbuf)endelsereturn()(* +-----------------------------------------------------------------+
| Rendering |
+-----------------------------------------------------------------+ *)letsame_stylep1p2=letopenLTerm_drawinp1.bold=p2.bold&&p1.underline=p2.underline&&p1.blink=p2.blink&&p1.reverse=p2.reverse&&p1.foreground=p2.foreground&&p1.background=p2.backgroundletunknown_char=UChar.of_int0xfffdletunknown_utf8=Zed_utf8.singletonunknown_charletrender_styletermbufold_pointnew_point=letopenLTerm_drawinifnot(same_stylenew_pointold_point)thenbegin(* Reset styles if they are different from the previous point. *)Buffer.add_stringbuf"\027[0";ifnew_point.boldthenBuffer.add_stringbufCodes.bold;ifnew_point.underlinethenBuffer.add_stringbufCodes.underline;ifnew_point.blinkthenBuffer.add_stringbufCodes.blink;ifnew_point.reversethenBuffer.add_stringbufCodes.reverse;add_colortermbufCodes.foregroundnew_point.foreground;add_colortermbufCodes.backgroundnew_point.background;Buffer.add_charbuf'm';endletrender_pointtermbufold_pointnew_point=render_styletermbufold_pointnew_point;(* Skip control characters, otherwise output will be messy. *)ifUChar.codenew_point.LTerm_draw.char<32thenBuffer.add_stringbufunknown_utf8elseBuffer.add_stringbuf(Zed_utf8.singletonnew_point.LTerm_draw.char)typerender_kind=Render_screen|Render_boxletrender_update_unixtermkindold_matrixmatrix=letopenLTerm_drawinletbuf=Buffer.create16inBuffer.add_stringbuf(matchkindwith|Render_screen->(* Go the the top-left and reset attributes *)"\027[H\027[0m"|Render_box->(* Go the the beginnig of line and reset attributes *)"\r\027[0m");(* The last displayed point. *)letlast_point=ref{char=uspace;bold=false;underline=false;blink=false;reverse=false;foreground=LTerm_style.default;background=LTerm_style.default;}inletrows=Array.lengthmatrixandold_rows=Array.lengthold_matrixinfory=0torows-1doletline=Array.unsafe_getmatrixyin(* If the current line is equal to the displayed one, skip it *)ify>=old_rows||line<>Array.unsafe_getold_matrixythenbeginforx=0toArray.lengthline-1doletpoint=Array.unsafe_getlinexinrender_pointtermbuf!last_pointpoint;last_point:=pointdoneend;ify<rows-1thenBuffer.add_charbuf'\n'done;Buffer.add_stringbuf"\027[0m";(* Go to the beginning of the line if rendering a box. *)ifkind=Render_boxthenBuffer.add_charbuf'\r';fprintterm(Buffer.contentsbuf)letblank_windows={LTerm_windows.ci_char=uspace;LTerm_windows.ci_foreground=7;LTerm_windows.ci_background=0;}letwindows_char_infotermpointchar=ifpoint.LTerm_draw.reversethen{LTerm_windows.ci_char=char;LTerm_windows.ci_foreground=windows_bg_colortermpoint.LTerm_draw.background;LTerm_windows.ci_background=windows_fg_colortermpoint.LTerm_draw.foreground;}else{LTerm_windows.ci_char=char;LTerm_windows.ci_foreground=windows_fg_colortermpoint.LTerm_draw.foreground;LTerm_windows.ci_background=windows_bg_colortermpoint.LTerm_draw.background;}letrender_windowstermkindhandle_newlinesmatrix=(* Build the matrix of char infos *)letmatrix=Array.map(funline->letlen=Array.lengthline-(ifhandle_newlinesthen1else0)iniflen<0theninvalid_arg"LTerm.print_box_with_newlines";letres=Array.makelenblank_windowsinletrecloopi=ifi=lenthenreselsebeginletpoint=Array.unsafe_getlineiinletcode=UChar.codepoint.LTerm_draw.charinifhandle_newlines&&code=10thenbegin(* Copy styles. *)Array.unsafe_setresi(windows_char_infotermpointuspace);fori=i+1tolen-1doletpoint=Array.unsafe_getlineiinArray.unsafe_setresi(windows_char_infotermpointuspace)done;resendelsebeginletchar=ifcode<32thenunknown_charelsepoint.LTerm_draw.charinArray.unsafe_setresi(windows_char_infotermpointchar);loop(i+1)endendinloop0)matrixinletrows=Array.lengthmatrixinbeginmatchkindwith|Render_screen->return()|Render_box->(* Ensure that there is enough place to display the box. *)fprintterm"\r">>=fun()->fprintterm(String.make(rows-1)'\n')>>=fun()->Lwt_io.flushterm.ocend>>=fun()->letinfo=LTerm_windows.get_console_screen_buffer_infoterm.outgoing_fdinletwindow_rect=info.LTerm_windows.windowinletrect=matchkindwith|Render_screen->window_rect|Render_box->{window_rectwithrow1=info.LTerm_windows.cursor_position.row-(rows-1);row2=info.LTerm_windows.cursor_position.row+1}inignore(LTerm_windows.write_console_outputterm.outgoing_fdmatrix{rows=Array.lengthmatrix;cols=ifmatrix=[||]then0elseArray.lengthmatrix.(0)}{row=0;col=0}rect);return()letrender_updatetermold_matrixmatrix=ifterm.outgoing_is_a_ttythenifterm.windowsthenrender_windowstermRender_screenfalsematrixelserender_update_unixtermRender_screenold_matrixmatrixelseLwt.failNot_a_ttyletrendertermm=render_updateterm[||]mletprint_boxtermmatrix=ifterm.outgoing_is_a_ttythenbeginifArray.lengthmatrix>0thenbeginifterm.windowsthenrender_windowstermRender_boxfalsematrixelserender_update_unixtermRender_box[||]matrixendelsefprintterm"\r"endelseLwt.failNot_a_ttyletprint_box_with_newlines_unixtermmatrix=letopenLTerm_drawinletbuf=Buffer.create16in(* Go the the beginnig of line and reset attributes *)Buffer.add_stringbuf"\r\027[0m";(* The last displayed point. *)letlast_point=ref{char=uspace;bold=false;underline=false;blink=false;reverse=false;foreground=LTerm_style.default;background=LTerm_style.default;}inletrows=Array.lengthmatrixinfory=0torows-1doletline=Array.unsafe_getmatrixyinletcols=Array.lengthline-1inifcols<0theninvalid_arg"LTerm.print_box_with_newlines";letrecloopx=letpoint=Array.unsafe_getlinexinletcode=UChar.codepoint.charinifx=colsthenbeginifcode=10&&y<rows-1thenBuffer.add_charbuf'\n'endelseifcode=10thenbegin(* Use the style of the newline for the rest of the line. *)render_styletermbuf!last_pointpoint;last_point:=point;(* Erase everything until the end of line. *)Buffer.add_stringbuf"\027[K";ify<rows-1thenBuffer.add_charbuf'\n'endelsebeginrender_pointtermbuf!last_pointpoint;last_point:=point;loop(x+1)endinloop0done;Buffer.add_stringbuf"\027[0m\r";fprintterm(Buffer.contentsbuf)letprint_box_with_newlinestermmatrix=ifterm.outgoing_is_a_ttythenbeginifArray.lengthmatrix>0thenbeginifterm.windowsthenrender_windowstermRender_boxtruematrixelseprint_box_with_newlines_unixtermmatrixendelsefprintterm"\r"endelseLwt.failNot_a_tty(* +-----------------------------------------------------------------+
| Misc |
+-----------------------------------------------------------------+ *)letflushterm=Lwt_io.flushterm.ocletget_size_from_fdfd=return(get_size_from_fdfd)letset_size_from_fdfdsize=return(set_size_from_fdfdsize)(* +-----------------------------------------------------------------+
| Standard terminals |
+-----------------------------------------------------------------+ *)letstdout=lazy(createLwt_unix.stdinLwt_io.stdinLwt_unix.stdoutLwt_io.stdout)letstderr=lazy(createLwt_unix.stdinLwt_io.stdinLwt_unix.stderrLwt_io.stderr)letprintstr=Lazy.forcestdout>>=funterm->fprinttermstrletprintlstr=Lazy.forcestdout>>=funterm->fprintltermstrletprintffmt=Printf.ksprintfprintfmtletprintsstr=Lazy.forcestdout>>=funterm->fprintstermstrletprintlffmt=Printf.ksprintfprintlfmtletprintlsstr=Lazy.forcestdout>>=funterm->fprintlstermstrleteprintstr=Lazy.forcestderr>>=funterm->fprinttermstrleteprintlstr=Lazy.forcestderr>>=funterm->fprintltermstrleteprintffmt=Printf.ksprintfeprintfmtleteprintsstr=Lazy.forcestderr>>=funterm->fprintstermstrleteprintlffmt=Printf.ksprintfeprintlfmtleteprintlsstr=Lazy.forcestderr>>=funterm->fprintlstermstr